Compare commits

...
Author SHA1 Message Date
nmzik ba55ba5dc5 Allow storage access to PS5 cubemap faces 2026-09-21 00:49:54 +04:00
nmzik c997cdb837 Skip zero-valued indirect CX 0x24a 2026-09-21 00:49:54 +04:00
Kody Schram fe4f9423e3 Significantly Reduce Linux Release Size (#738)
Added symbol stripping for linux build, drops fs from 120 mb to 20mb
2026-09-20 21:26:24 +04:00
nmzik 81af130147 Avoid game list rescans on settings changes/Remove Space pause shortcut 2026-09-20 21:07:36 +04:00
Saadiandnmzik ada0514084 launcher: add a game list refresh action (#683)
* launcher: add a game list refresh action

Dropping new games into an already-configured game folder was only
picked up after restarting the launcher or re-saving the global
settings dialog. Add a refresh toolbar button, an F5 shortcut, and a
"Refresh game list" context-menu entry, all triggering a rescan of the
game folders.

* launcher: do not rescan the game list while a game is running

Clearing cfgs_list destroys the running ConfigurationItem: the running
state is lost and the replacement items allow editing or deleting a
game whose process is still active. Skip the rescan while any item is
running (CodeRabbit review finding for the refresh action).

* launcher: preserve game state when refreshing the list

Share one refresh action across the toolbar, keyboard shortcut, and context menu. Retain running items during rescans, update their settings for the next launch, and preserve selection, filtering, and sort order.

Commit active inline editors before rebuilding rows to avoid losing comments or accessing destroyed configuration data. Tie editor callbacks to item lifetime and guard the context-menu item reference.

Validation: launcher build and startup check passed; 32 Qt interaction checks passed.

* launcher: remove refresh from the game context menu

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-20 20:44:40 +04:00
14 changed files with 279 additions and 70 deletions

No files matched your search

+6
View File
@@ -681,6 +681,12 @@ elseif(WIN32 OR LINUX)
set_target_properties(kyty_emulator PROPERTIES LINK_FLAGS "${KYTY_LD_OPTIONS} -Wl,-Map=${KYTY_EMULATOR_MAP_LINK_PATH}")
endif()
if(LINUX)
target_link_options(kyty_emulator PRIVATE
$<$<CONFIG:Release>:-Wl,--strip-all>
)
endif()
if(APPLE)
# The emulator writes x86-64 trampolines/PLT stubs into memory and executes them, and
# runs guest code under Rosetta. That requires the JIT / unsigned-executable-memory
@@ -2017,6 +2017,14 @@ KYTY_CP_OP_PARSER(CpOpIndirectCxRegs) {
auto pfunc = g_hw_ctx_indirect_func[cmd_offset & (Pm4::CX_NUM - 1)];
if (pfunc == nullptr) {
if (raw_cmd_offset == 0x24au && value == 0u) {
static std::atomic_flag logged = ATOMIC_FLAG_INIT;
if (!logged.test_and_set(std::memory_order_relaxed)) {
Log::WriteToConsoleAndLog(
"\t diagnostic: ignoring indirect CX {0x24a, 0}; hardware effect unresolved\n");
}
continue;
}
EXIT("unknown cx reg at %05" PRIx32 ": 0x%" PRIx32 "\n", num_dw - dw, cmd_offset);
}
@@ -252,10 +252,10 @@ static bool IsSupportedStorageTextureDescriptor(const ShaderRecompiler::IR::Imag
(is_color_2d_array && descriptor.BaseArray5() <= descriptor.Depth());
const bool is_2d =
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D && valid_2d_slice;
// Storage cube coordinates address individual faces, including partial cube views.
const bool is_cube = resource.cube && descriptor.Type() == Prospero::ImageType::kCube &&
descriptor.Width5() == descriptor.Height5() &&
descriptor.BaseArray5() <= descriptor.Depth() &&
(descriptor.Depth() - descriptor.BaseArray5() + 1u) % 6u == 0;
descriptor.BaseArray5() <= descriptor.Depth();
const bool is_2d_array =
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray &&
((!resource.cube && is_color_2d_array && descriptor.BaseArray5() <= descriptor.Depth()) ||
@@ -141,9 +141,8 @@ public:
}
}
const bool reserved = binding.key == SDLK_ESCAPE || binding.key == SDLK_SPACE ||
binding.key == SDLK_F1 || binding.key == SDLK_F7 ||
binding.key == SDLK_F11;
const bool reserved = binding.key == SDLK_ESCAPE || binding.key == SDLK_F1 ||
binding.key == SDLK_F7 || binding.key == SDLK_F11;
if (binding.control == INVALID_CONTROL || reserved ||
(binding.key == SDLK_UNKNOWN && binding.mouse_button == 0)) {
EXIT("Invalid input mapping: %s\n", value.c_str());
+2 -3
View File
@@ -222,7 +222,7 @@ static void ToggleDesktopFullscreen() {
}
}
static void GameEventKeyboard(WindowLoopState& game, const EventKeyboard& key) {
static void GameEventKeyboard(const EventKeyboard& key) {
static SDL_Keycode fullscreen_key = SDLK_UNKNOWN;
#ifdef KYTY_DBG_INPUT
@@ -235,7 +235,6 @@ static void GameEventKeyboard(WindowLoopState& game, const EventKeyboard& key) {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS || KYTY_PLATFORM == KYTY_PLATFORM_LINUX
if (key.down) {
switch (key.key_code) {
case SDLK_SPACE: SetPause(game, !game.paused.load(std::memory_order_acquire)); break;
case SDLK_F1:
if (!key.repeat) {
RenderDocRequestCapture();
@@ -539,7 +538,7 @@ void WindowContext::ProcessEvent(double time_s) {
key.mod = event->key.keysym.mod;
key.timestamp_seconds = time_s;
GameEventKeyboard(game, key);
GameEventKeyboard(key);
break;
}
@@ -44,6 +44,28 @@
<property name="spacing">
<number>4</number>
</property>
<item>
<widget class="QToolButton" name="refresh_button">
<property name="minimumSize">
<size>
<width>36</width>
<height>36</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="iconSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="global_settings_button">
<property name="minimumSize">
@@ -245,6 +267,14 @@
</layout>
</item>
</layout>
<action name="refresh_action">
<property name="text">
<string>Refresh game list</string>
</property>
<property name="toolTip">
<string>Rescan game folders for new games</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="#000" d="M17.65 6.35A7.96 7.96 0 0 0 12 4a8 8 0 1 0 7.73 10h-2.08A6 6 0 1 1 12 6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/></svg>

After

Width:  |  Height:  |  Size: 232 B

+1 -3
View File
@@ -144,7 +144,7 @@ public:
host_input_mapping = other.host_input_mapping;
}
void CopyFrom(const Configuration& other) {
void CopyGameInfoFrom(const Configuration& other) {
name = other.name;
title_id = other.title_id;
gameVersion = other.gameVersion;
@@ -154,8 +154,6 @@ public:
custom_settings = other.custom_settings;
game_status = other.game_status;
game_comment = other.game_comment;
CopyEmulatorSettingsFrom(other);
elf = other.elf;
}
void WriteSettings(QSettings* s) const {
@@ -8,6 +8,8 @@
#include <QStringList>
#include <QWidget>
#include <memory>
class ConfigurationItem;
class CompatibilityDatabase;
class QEvent;
@@ -30,10 +32,9 @@ public:
[[nodiscard]] const ConfigurationItem* GetSelectedItem() const { return m_selected_item; }
ConfigurationItem* GetSelectedItem() { return m_selected_item; }
[[nodiscard]] const QString& GetSettingsFile() const { return m_settings_file; }
[[nodiscard]] const QStringList& GetHostInputMapping() const {
return m_global_info.host_input_mapping;
}
[[nodiscard]] const QString& GetSettingsFile() const { return m_settings_file; }
[[nodiscard]] std::unique_ptr<Configuration>
CreateConfiguration(const ConfigurationItem& item) const;
bool EnsureGameDirectory();
void ScanGameDirectory();
@@ -64,7 +65,6 @@ protected slots:
void filter_configurations(const QString& text);
private:
void ClearCustomSettings(ConfigurationItem* item);
void SelectItem(QTreeWidgetItem* witem);
void ApplyCompatibility();
void UpdateToolbarIcons();
+1
View File
@@ -3,6 +3,7 @@
<file alias="edit-configuration.svg">icons/edit-configuration.svg</file>
<file alias="global-settings.svg">icons/global-settings.svg</file>
<file alias="input-mapping.svg">icons/input-mapping.svg</file>
<file alias="refresh.svg">icons/refresh.svg</file>
<file alias="remove-configuration.svg">icons/remove-configuration.svg</file>
</qresource>
<qresource prefix="/styles">
+102 -42
View File
@@ -13,6 +13,7 @@
#include <QAbstractItemModel>
#include <QAbstractItemView>
#include <QAction>
#include <QApplication>
#include <QComboBox>
#include <QCoreApplication>
#include <QCursor>
@@ -26,11 +27,13 @@
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonParseError>
#include <QKeySequence>
#include <QLineEdit>
#include <QMenu>
#include <QMessageBox>
#include <QPainter>
#include <QPalette>
#include <QPointer>
#include <QPushButton>
#include <QRegularExpression>
#include <QSet>
@@ -201,6 +204,10 @@ ConfigurationListWidget::ConfigurationListWidget(QWidget* parent)
m_ui->setupUi(this);
ConfigureGameList(m_ui);
m_ui->refresh_action->setShortcuts(QKeySequence::Refresh);
m_ui->refresh_button->setDefaultAction(m_ui->refresh_action);
addAction(m_ui->refresh_action);
UpdateToolbarIcons();
m_ui->global_settings_button->setToolTip(tr("Edit global settings and game folders"));
m_ui->input_mapping_button->setToolTip(tr("Edit global input mapping"));
@@ -221,7 +228,10 @@ ConfigurationListWidget::ConfigurationListWidget(QWidget* parent)
m_ui->cfgs_list->setColumnWidth(GAME_PATH_COLUMN, 320);
m_ui->cfgs_list->setColumnWidth(GAME_STATUS_COLUMN, 150);
m_ui->cfgs_list->setColumnWidth(GAME_COMMENT_COLUMN, 240);
m_ui->cfgs_list->sortItems(GAME_NAME_COLUMN, Qt::AscendingOrder);
connect(m_ui->refresh_action, &QAction::triggered, this,
&ConfigurationListWidget::ScanGameDirectory);
connect(m_ui->global_settings_button, &QToolButton::clicked, this,
&ConfigurationListWidget::edit_global_settings);
connect(m_ui->input_mapping_button, &QToolButton::clicked, this,
@@ -276,6 +286,8 @@ void ConfigurationListWidget::UpdateToolbarIcons() {
button->setIcon(QIcon(pixmap));
};
set_icon(m_ui->refresh_button, QStringLiteral(":/icons/refresh.svg"));
m_ui->refresh_action->setIcon(m_ui->refresh_button->icon());
set_icon(m_ui->global_settings_button, QStringLiteral(":/icons/global-settings.svg"));
set_icon(m_ui->input_mapping_button, QStringLiteral(":/icons/input-mapping.svg"));
set_icon(m_ui->edit_button, QStringLiteral(":/icons/edit-configuration.svg"));
@@ -398,10 +410,17 @@ void ConfigurationListWidget::ApplyCompatibility() {
}
}
static Configuration* CloneConfiguration(const Configuration& source) {
auto* ret = new Configuration;
ret->CopyFrom(source);
return ret;
std::unique_ptr<Configuration>
ConfigurationListWidget::CreateConfiguration(const ConfigurationItem& item) const {
auto info = std::make_unique<Configuration>();
const auto* custom = m_custom_infos.value(item.GetInfo().game_path);
info->CopyGameInfoFrom(item.GetInfo());
info->CopyEmulatorSettingsFrom(custom != nullptr ? *custom : m_global_info);
if (custom != nullptr && !custom->elf.isEmpty()) {
info->elf = custom->elf;
}
info->host_input_mapping = m_global_info.host_input_mapping;
return info;
}
struct GameMetadata {
@@ -516,9 +535,6 @@ static void SetGameFiles(Configuration& info, const QString& game_dir, const QSt
if (info.name.isEmpty()) {
info.name = game.dirName();
}
if (info.elf.isEmpty()) {
info.elf = QStringLiteral("eboot.bin");
}
}
static Configuration* FindCustomInfo(QMap<QString, Configuration*>* custom_infos,
@@ -564,11 +580,32 @@ bool ConfigurationListWidget::HasValidGameDirectory() const {
}
void ConfigurationListWidget::ScanGameDirectory() {
// Commit an active inline editor before destroying its row or replacing its data.
if (auto* focused = QApplication::focusWidget();
focused != nullptr && m_ui->cfgs_list->isAncestorOf(focused)) {
m_ui->cfgs_list->setFocus();
}
const auto selected_path =
m_selected_item != nullptr ? m_selected_item->GetInfo().game_path : QString();
const bool sorting_enabled = m_ui->cfgs_list->isSortingEnabled();
const int sort_column = m_ui->cfgs_list->sortColumn();
const auto sort_order = m_ui->cfgs_list->header()->sortIndicatorOrder();
const QSignalBlocker blocker(m_ui->cfgs_list);
m_ui->cfgs_list->setSortingEnabled(false);
m_selected_item = nullptr;
m_ui->cfgs_list->clear();
m_ui->cfgs_list->SetBackgroundImage({});
m_ui->edit_button->setEnabled(false);
m_ui->delete_button->setEnabled(false);
// MainDialog tracks the running item, so keep its identity even if its
// directory is no longer present. Other rows can be rebuilt from disk.
QMap<QString, ConfigurationItem*> running_items;
for (int index = m_ui->cfgs_list->topLevelItemCount() - 1; index >= 0; index--) {
auto* item = static_cast<ConfigurationItem*>(m_ui->cfgs_list->topLevelItem(index));
if (item->IsRunning()) {
running_items.insert(PathKey(item->GetInfo().game_path), item);
} else {
delete item;
}
}
const QString eboot_name = QStringLiteral("eboot.bin");
QSet<QString> found_games;
@@ -601,16 +638,9 @@ void ConfigurationListWidget::ScanGameDirectory() {
auto metadata = GetGameMetadata(
game_dir.filePath(QStringLiteral("sce_sys/param.json")), game_dir.dirName());
auto info = std::make_unique<Configuration>();
auto* custom = FindCustomInfo(&m_custom_infos, game_path, legacy_game_path);
if (custom != nullptr) {
info->CopyFrom(*custom);
info->custom_settings = true;
} else {
info->CopyEmulatorSettingsFrom(m_global_info);
info->custom_settings = false;
}
auto info = std::make_unique<Configuration>();
info->custom_settings =
FindCustomInfo(&m_custom_infos, game_path, legacy_game_path) != nullptr;
SetGameFiles(*info, game_dir.absolutePath(), game_path, metadata);
const auto* compatibility = m_compatibility->Find(info->title_id);
if (compatibility != nullptr) {
@@ -618,10 +648,20 @@ void ConfigurationListWidget::ScanGameDirectory() {
info->game_comment = compatibility->comment;
}
if (auto* item = running_items.value(game_key); item != nullptr) {
// Refresh metadata without losing the running row's identity.
const QSignalBlocker status_blocker(item->GetStatusCombo());
item->GetInfo().CopyGameInfoFrom(*info);
item->Update();
item->SetCompatibilityEditable(m_compatibility->IsLocal() &&
!item->GetInfo().title_id.trimmed().isEmpty());
continue;
}
auto* item = new ConfigurationItem(std::move(info), m_ui->cfgs_list);
item->SetCompatibilityEditable(m_compatibility->IsLocal() &&
!item->GetInfo().title_id.trimmed().isEmpty());
connect(item->GetStatusCombo(), &QComboBox::currentIndexChanged, this,
connect(item->GetStatusCombo(), &QComboBox::currentIndexChanged, item,
[this, item](int /*index*/) {
if (!m_compatibility->IsLocal()) {
return;
@@ -638,7 +678,7 @@ void ConfigurationListWidget::ScanGameDirectory() {
m_ui->cfgs_list->setCurrentItem(item);
SelectItem(item);
});
connect(item->GetCommentEdit(), &QLineEdit::editingFinished, this, [this, item]() {
connect(item->GetCommentEdit(), &QLineEdit::editingFinished, item, [this, item]() {
if (!m_compatibility->IsLocal()) {
return;
}
@@ -664,8 +704,22 @@ void ConfigurationListWidget::ScanGameDirectory() {
}
}
m_ui->cfgs_list->sortItems(GAME_NAME_COLUMN, Qt::AscendingOrder);
m_ui->cfgs_list->setSortingEnabled(sorting_enabled);
if (sorting_enabled) {
m_ui->cfgs_list->sortItems(sort_column, sort_order);
}
filter_configurations(m_ui->search_line_edit->text());
ConfigurationItem* selected_item = nullptr;
for (int index = 0; index < m_ui->cfgs_list->topLevelItemCount(); index++) {
auto* item = static_cast<ConfigurationItem*>(m_ui->cfgs_list->topLevelItem(index));
if (!item->isHidden() && item->GetInfo().game_path == selected_path) {
selected_item = item;
break;
}
}
m_ui->cfgs_list->setCurrentItem(selected_item);
SelectItem(selected_item);
}
void ConfigurationListWidget::edit_configuration() {
@@ -674,16 +728,19 @@ void ConfigurationListWidget::edit_configuration() {
return;
}
ConfigurationEditDialog dlg(item->GetInfo(), this);
auto info = CreateConfiguration(*item);
ConfigurationEditDialog dlg(*info, this);
dlg.setWindowTitle(tr("Edit game settings"));
if (dlg.exec() == QDialog::Accepted) {
info->custom_settings = true;
item->GetInfo().custom_settings = true;
auto game_path = item->GetInfo().game_path;
auto game_path = info->game_path;
delete m_custom_infos.take(game_path);
m_custom_infos.insert(game_path, CloneConfiguration(item->GetInfo()));
m_custom_infos.insert(game_path, info.release());
WriteSettings();
ScanGameDirectory();
item->Update();
SelectItem(item);
}
}
@@ -695,9 +752,11 @@ void ConfigurationListWidget::delete_configuartion() {
if (QMessageBox::Yes == QMessageBox::question(this, tr("Clear custom settings"),
tr("Do you want to clear custom settings?"))) {
ClearCustomSettings(item);
delete m_custom_infos.take(item->GetInfo().game_path);
item->GetInfo().custom_settings = false;
WriteSettings();
ScanGameDirectory();
item->Update();
SelectItem(item);
}
}
@@ -712,9 +771,13 @@ void ConfigurationListWidget::edit_global_settings() {
if (dlg.exec() == QDialog::Accepted) {
m_global_info.CopyEmulatorSettingsFrom(info);
m_game_dirs = NormalizeGameDirectories(dlg.GetGameDirectories());
const auto game_dirs = NormalizeGameDirectories(dlg.GetGameDirectories());
const bool game_dirs_changed = game_dirs != m_game_dirs;
m_game_dirs = game_dirs;
WriteSettings();
ScanGameDirectory();
if (game_dirs_changed) {
ScanGameDirectory();
}
}
}
@@ -726,10 +789,6 @@ void ConfigurationListWidget::edit_input_mapping() {
}
}
void ConfigurationListWidget::ClearCustomSettings(ConfigurationItem* item) {
delete m_custom_infos.take(item->GetInfo().game_path);
}
void ConfigurationListWidget::ViewTrophies() {
auto* current = static_cast<ConfigurationItem*>(m_ui->cfgs_list->currentItem());
auto* item = current != nullptr ? current : m_selected_item;
@@ -871,12 +930,13 @@ void ConfigurationListWidget::show_context_menu(const QPoint& pos) {
connect(action_view_trophies, &QAction::triggered, this,
&ConfigurationListWidget::ViewTrophies);
QAction* action_patches = menu.addAction(tr("Cheats (experimental)..."));
connect(action_patches, &QAction::triggered, this, [this, item]() {
if (item != nullptr) {
auto* dialog = new PatchesDialog(item->GetInfo(), this);
dialog->show();
}
});
connect(action_patches, &QAction::triggered, this,
[this, item = QPointer<ConfigurationItem>(item)]() {
if (item != nullptr) {
auto* dialog = new PatchesDialog(item->GetInfo(), this);
dialog->show();
}
});
action_patches->setVisible(item != nullptr &&
PatchesDialog::IsSupportedTitleId(item->GetInfo().title_id));
QAction* action_remove_save_data =
+4 -4
View File
@@ -90,6 +90,7 @@ QString KeyName(const QKeyEvent& event) {
}
switch (key) {
case Qt::Key_Space: return QStringLiteral("Space");
case Qt::Key_Return:
case Qt::Key_Enter: return QStringLiteral("Return");
case Qt::Key_Backspace: return QStringLiteral("Backspace");
@@ -132,8 +133,7 @@ public:
auto* layout = new QVBoxLayout(this);
m_label = new QLabel(
tr("Press a key or mouse button.\nSpace, F1, F7, and F11 are reserved; Esc cancels."),
this);
tr("Press a key or mouse button.\nF1, F7, and F11 are reserved; Esc cancels."), this);
m_label->setAlignment(Qt::AlignCenter);
layout->addWidget(m_label);
}
@@ -149,8 +149,8 @@ protected:
reject();
return;
}
if (event->key() == Qt::Key_Space || event->key() == Qt::Key_F1 ||
event->key() == Qt::Key_F7 || event->key() == Qt::Key_F11) {
if (event->key() == Qt::Key_F1 || event->key() == Qt::Key_F7 ||
event->key() == Qt::Key_F11) {
m_label->setText(tr("That key is reserved by the emulator."));
return;
}
+2 -4
View File
@@ -493,10 +493,8 @@ void MainDialogPrivate::Run() {
m_running_item->SetRunning(true);
Configuration info;
info.CopyFrom(m_running_item->GetInfo());
info.host_input_mapping = m_ui->widget->GetHostInputMapping();
m_main_dialog->RunInterpreter(&m_process, info);
auto info = m_ui->widget->CreateConfiguration(*m_running_item);
m_main_dialog->RunInterpreter(&m_process, *info);
Update();
}
+113 -4
View File
@@ -9405,6 +9405,114 @@ public:
}
void CheckCubeFaceStorageExpansion() {
constexpr const char *name = "CubeFaceStorageExpansion";
constexpr uintptr_t base = 0x0000000204700000ull;
constexpr uint64_t allocation_size = 0x10000;
constexpr std::array<uint32_t, 6> half_red{0x3c00, 0x4000, 0x4200,
0x4400, 0x4500, 0x4600};
EnsureRuntimeContext();
int64_t direct_offset = -1;
Require(name, "direct allocation",
Libs::LibKernel::Memory::KernelAllocateDirectMemory(
0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(),
allocation_size, allocation_size, 0, &direct_offset) == 0,
"cube-face allocation failed");
void *mapped = reinterpret_cast<void *>(base);
Require(name, "direct mapping",
Libs::LibKernel::Memory::KernelMapDirectMemory(
&mapped, allocation_size, 0x3, 0x10, direct_offset,
allocation_size) == 0 && mapped == reinterpret_cast<void *>(base),
"cube-face mapping failed");
std::memset(mapped, 0, allocation_size);
{
RenderContext context(m_runtime_context);
auto &scheduler = context.GetCommandScheduler();
HW::Context registers{};
HW::UserConfig user_config{};
HW::Shader shaders{};
scheduler.Begin(registers, user_config, shaders);
context.MapMemory(base, allocation_size);
auto &cache = context.GetTextureCache();
auto &executor = context.GetRenderExecutor();
for (uint32_t face = 0; face < half_red.size(); ++face) {
// PPSA25380 clears six single-face cube views at one guest address.
ShaderTextureResource descriptor{{static_cast<uint32_t>(base >> 8u),
0xc4700000u, 0x0001c001u, 0xb0000facu,
face | (face << 16u), 0x00700000u, 0, 0}};
TestCase test;
test.name = name;
test.has_user_data = true;
std::copy_n(descriptor.fields, 8, test.user_data.begin());
test.has_compute_info = true;
test.compute_info.threads_num[0] = test.compute_info.threads_num[1] = 8;
test.compute_info.threads_num[2] = 1;
test.compute_info.thread_ids_num = 2;
test.opcodes = {ShaderOpcode::V_MOV_B32, ShaderOpcode::IMAGE_STORE,
ShaderOpcode::S_ENDPGM};
test.code = {EncodeVop1(0x01, 20, 256), EncodeVop1(0x01, 21, 257)};
AppendVMovU32(&test.code, 22, 0);
AppendVMovLiteral(&test.code, 0,
std::bit_cast<uint32_t>(static_cast<float>(face + 1)));
AppendVMovLiteral(&test.code, 1, std::bit_cast<uint32_t>(0.5f));
AppendVMovLiteral(&test.code, 2, std::bit_cast<uint32_t>(0.25f));
AppendVMovLiteral(&test.code, 3, std::bit_cast<uint32_t>(1.0f));
test.code.push_back(EncodeMimg0(0x08, 0xf, 0, false, 5));
test.code.push_back(EncodeMimg1(0, 20));
AppendEnd(&test.code);
const auto compiled = CompileCase(test, SubgroupSize());
const auto &resource = compiled.program.info.images.at(0);
Require(name, "cube storage specialization", resource.cube &&
resource.written && resource.dimension ==
ShaderRecompiler::Decoder::ImageDimension::Dim2DArray,
"single-face cube store lost its array coordinates");
ShaderRecompiler::IR::DescriptorValue value{};
value.dword_count = 8;
std::copy_n(descriptor.fields, 8, value.dwords.begin());
const auto binding = RenderExecutorTestAccess::ResolveTexture(executor, resource, value);
Image storage;
storage.view = cache.FindTexture(binding.image_id, binding.desc);
auto &image = cache.GetImage(binding.image_id);
Require(name, "face view and backing", storage.view != nullptr &&
image.info.pixel_format == vk::Format::eR16G16B16A16Sfloat &&
image.info.bytes_per_block == 8 &&
image.info.resources.layers == face + 1 &&
image.backing.layers == face + 1 &&
binding.desc.view_info.type == vk::ImageViewType::e2DArray &&
binding.desc.view_info.base_layer == face &&
binding.desc.view_info.layer_count == 1,
"cube face did not expand the array or select its own layer");
image.Transit(vk::ImageLayout::eGeneral, vk::AccessFlagBits2::eShaderWrite,
{}, scheduler.Current().Handle());
storage.layout = image.backing.state.layout;
scheduler.Finish();
Dispatch(test, compiled, {}, nullptr, nullptr, &storage);
cache.MarkGpuWritten(binding.image_id);
for (uint32_t prior = 0; prior <= face; ++prior) {
const auto pixels = ReadCachedTexel(name, context, binding.image_id,
{}, {8, 8, 1}, prior);
for (uint32_t pixel = 0; pixel < 64; ++pixel) {
Require(name, "stored and preserved faces",
pixels[pixel * 2] == (0x38000000u | half_red[prior]) &&
pixels[pixel * 2 + 1] == 0x3c003400u,
"face " + std::to_string(prior) + " pixel " +
std::to_string(pixel) + " changed after writing face " +
std::to_string(face));
}
}
}
RenderExecutorTestAccess::ResetBindings(executor);
context.UnmapMemory(base, allocation_size);
scheduler.Finish();
}
Require(name, "release",
Libs::LibKernel::Memory::KernelMunmap(base, allocation_size) == 0 &&
Libs::LibKernel::Memory::KernelReleaseDirectMemory(
direct_offset, allocation_size) == 0,
"cube-face backing release failed");
std::printf("[gpu] %-32s ok\n", name);
}
void CheckRenderExecutorColorStandardTileDiscovery() {
constexpr const char *name = "RenderExecutorColorStandardTile";
constexpr uintptr_t base = 0x0000000203b00000ull;
@@ -12389,15 +12497,14 @@ public:
const auto &image =
compiled.program.info.images.at(binding.resources.front());
bool supported =
image.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D;
image.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D ||
image.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray;
if (resource_class == ShaderRecompiler::IR::ImageResourceClass::Sampled) {
supported = supported ||
image.dimension ==
ShaderRecompiler::Decoder::ImageDimension::Dim1D ||
image.dimension ==
ShaderRecompiler::Decoder::ImageDimension::Dim1DArray ||
image.dimension ==
ShaderRecompiler::Decoder::ImageDimension::Dim2DArray;
ShaderRecompiler::Decoder::ImageDimension::Dim1DArray;
}
Require(test.name, "dispatch", supported,
"unsupported image dimension needs a matching Vulkan test view");
@@ -32429,6 +32536,7 @@ int main(int argc, char **argv) {
if (argc == 2 && std::strcmp(argv[1], "--layered-image-only") == 0) {
VulkanHarness vulkan;
vulkan.CheckUnifiedImageViewCache();
vulkan.CheckCubeFaceStorageExpansion();
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--image-view-cache-only") == 0) {
@@ -32619,6 +32727,7 @@ int main(int argc, char **argv) {
#endif
vulkan.CheckUnifiedImageViewCache();
vulkan.CheckPackedTextureComponents();
vulkan.CheckCubeFaceStorageExpansion();
const auto tests = MakeCases();
const auto graphics_tests = MakeGraphicsCases();
CheckOpcodeCoverage(tests, graphics_tests);