Merge branch 'main' into Feature/initial-ps4-ime-keyboard

This commit is contained in:
Valdis Bogdāns 2025-07-20 22:06:39 +03:00 committed by GitHub
commit b4e3bf13de
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
46 changed files with 914 additions and 334 deletions

View File

@ -17,7 +17,7 @@ body:
This repository does not provide support for game patches. If you are having issues with patches please refer to [Cheats and Patches Repository](https://github.com/shadps4-emu/ps4_cheats). This repository does not provide support for game patches. If you are having issues with patches please refer to [Cheats and Patches Repository](https://github.com/shadps4-emu/ps4_cheats).
Before submitting an issue please check [Game Compatibility Repository](https://github.com/shadps4-emu/shadps4-game-compatibility) for the information about the status of the game. Before submitting an issue please check [Game Compatibility Repository](https://github.com/shadps4-compatibility/shadps4-game-compatibility) for the information about the status of the game.
Please make an effort to make sure your issue isn't already reported. Please make an effort to make sure your issue isn't already reported.

View File

@ -126,7 +126,7 @@ execute_process(
# If there's no upstream set or the command failed, check remote.pushDefault # If there's no upstream set or the command failed, check remote.pushDefault
if (GIT_REMOTE_RESULT OR GIT_REMOTE_NAME STREQUAL "") if (GIT_REMOTE_RESULT OR GIT_REMOTE_NAME STREQUAL "")
message("check default push") message(STATUS "check default push")
execute_process( execute_process(
COMMAND git config --get remote.pushDefault COMMAND git config --get remote.pushDefault
OUTPUT_VARIABLE GIT_REMOTE_NAME OUTPUT_VARIABLE GIT_REMOTE_NAME
@ -134,30 +134,30 @@ if (GIT_REMOTE_RESULT OR GIT_REMOTE_NAME STREQUAL "")
ERROR_QUIET ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_STRIP_TRAILING_WHITESPACE
) )
message("got remote: ${GIT_REMOTE_NAME}") message(STATUS "got remote: ${GIT_REMOTE_NAME}")
endif() endif()
# If running in GitHub Actions and the above fails # If running in GitHub Actions and the above fails
if (GIT_REMOTE_RESULT OR GIT_REMOTE_NAME STREQUAL "") if (GIT_REMOTE_RESULT OR GIT_REMOTE_NAME STREQUAL "")
message("check github") message(STATUS "check github")
set(GIT_REMOTE_NAME "origin") set(GIT_REMOTE_NAME "origin")
# Retrieve environment variables # Retrieve environment variables
if (DEFINED ENV{GITHUB_HEAD_REF} AND NOT "$ENV{GITHUB_HEAD_REF}" STREQUAL "") if (DEFINED ENV{GITHUB_HEAD_REF} AND NOT "$ENV{GITHUB_HEAD_REF}" STREQUAL "")
message("github head ref: $ENV{GITHUB_HEAD_REF}") message(STATUS "github head ref: $ENV{GITHUB_HEAD_REF}")
set(GITHUB_HEAD_REF "$ENV{GITHUB_HEAD_REF}") set(GITHUB_HEAD_REF "$ENV{GITHUB_HEAD_REF}")
else() else()
set(GITHUB_HEAD_REF "") set(GITHUB_HEAD_REF "")
endif() endif()
if (DEFINED ENV{GITHUB_REF} AND NOT "$ENV{GITHUB_REF}" STREQUAL "") if (DEFINED ENV{GITHUB_REF} AND NOT "$ENV{GITHUB_REF}" STREQUAL "")
message("github ref: $ENV{GITHUB_REF}") message(STATUS "github ref: $ENV{GITHUB_REF}")
string(REGEX REPLACE "^refs/[^/]*/" "" GITHUB_BRANCH "$ENV{GITHUB_REF}") string(REGEX REPLACE "^refs/[^/]*/" "" GITHUB_BRANCH "$ENV{GITHUB_REF}")
string(REGEX MATCH "refs/pull/([0-9]+)/merge" MATCHED_REF "$ENV{GITHUB_REF}") string(REGEX MATCH "refs/pull/([0-9]+)/merge" MATCHED_REF "$ENV{GITHUB_REF}")
if (MATCHED_REF) if (MATCHED_REF)
set(PR_NUMBER "${CMAKE_MATCH_1}") set(PR_NUMBER "${CMAKE_MATCH_1}")
set(GITHUB_BRANCH "") set(GITHUB_BRANCH "")
message("PR number: ${PR_NUMBER}") message(STATUS "PR number: ${PR_NUMBER}")
else() else()
set(PR_NUMBER "") set(PR_NUMBER "")
endif() endif()
@ -179,7 +179,7 @@ if (GIT_REMOTE_RESULT OR GIT_REMOTE_NAME STREQUAL "")
elseif ("${PR_NUMBER}" STREQUAL "" AND NOT "${GITHUB_REF}" STREQUAL "") elseif ("${PR_NUMBER}" STREQUAL "" AND NOT "${GITHUB_REF}" STREQUAL "")
set(GIT_BRANCH "${GITHUB_REF}") set(GIT_BRANCH "${GITHUB_REF}")
elseif("${GIT_BRANCH}" STREQUAL "") elseif("${GIT_BRANCH}" STREQUAL "")
message("couldn't find branch") message(STATUS "couldn't find branch")
set(GIT_BRANCH "detached-head") set(GIT_BRANCH "detached-head")
endif() endif()
else() else()
@ -188,13 +188,13 @@ else()
if (INDEX GREATER -1) if (INDEX GREATER -1)
string(SUBSTRING "${GIT_REMOTE_NAME}" 0 "${INDEX}" GIT_REMOTE_NAME) string(SUBSTRING "${GIT_REMOTE_NAME}" 0 "${INDEX}" GIT_REMOTE_NAME)
elseif("${GIT_REMOTE_NAME}" STREQUAL "") elseif("${GIT_REMOTE_NAME}" STREQUAL "")
message("reset to origin") message(STATUS "reset to origin")
set(GIT_REMOTE_NAME "origin") set(GIT_REMOTE_NAME "origin")
endif() endif()
endif() endif()
# Get remote link # Get remote link
message("getting remote link") message(STATUS "getting remote link")
execute_process( execute_process(
COMMAND git config --get remote.${GIT_REMOTE_NAME}.url COMMAND git config --get remote.${GIT_REMOTE_NAME}.url
OUTPUT_VARIABLE GIT_REMOTE_URL OUTPUT_VARIABLE GIT_REMOTE_URL
@ -212,7 +212,12 @@ set(APP_VERSION "${EMULATOR_VERSION_MAJOR}.${EMULATOR_VERSION_MINOR}.${EMULATOR_
set(APP_IS_RELEASE false) set(APP_IS_RELEASE false)
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/common/scm_rev.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/src/common/scm_rev.cpp" @ONLY) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/common/scm_rev.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/src/common/scm_rev.cpp" @ONLY)
message("end git things, remote: ${GIT_REMOTE_NAME}, branch: ${GIT_BRANCH}") message("-- end git things, remote: ${GIT_REMOTE_NAME}, branch: ${GIT_BRANCH}, link: ${GIT_REMOTE_URL}")
if(NOT GIT_REMOTE_URL MATCHES "shadps4-emu/shadPS4" OR NOT GIT_BRANCH STREQUAL "main")
message(STATUS "not main, disabling auto update")
set(ENABLE_UPDATER OFF)
endif()
if(WIN32 AND ENABLE_QT_GUI AND NOT CMAKE_PREFIX_PATH) if(WIN32 AND ENABLE_QT_GUI AND NOT CMAKE_PREFIX_PATH)
include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/DetectQtInstallation.cmake") include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/DetectQtInstallation.cmake")

View File

@ -37,7 +37,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
**shadPS4** is an early **PlayStation 4** emulator for **Windows**, **Linux** and **macOS** written in C++. **shadPS4** is an early **PlayStation 4** emulator for **Windows**, **Linux** and **macOS** written in C++.
If you encounter problems or have doubts, do not hesitate to look at the [**Quickstart**](https://github.com/shadps4-emu/shadPS4/wiki/I.-Quick-start-%5BUsers%5D).\ If you encounter problems or have doubts, do not hesitate to look at the [**Quickstart**](https://github.com/shadps4-emu/shadPS4/wiki/I.-Quick-start-%5BUsers%5D).\
To verify that a game works, you can look at [**shadPS4 Game Compatibility**](https://github.com/shadps4-emu/shadps4-game-compatibility).\ To verify that a game works, you can look at [**shadPS4 Game Compatibility**](https://github.com/shadps4-compatibility/shadps4-game-compatibility).\
To discuss shadPS4 development, suggest ideas or to ask for help, join our [**Discord server**](https://discord.gg/bFJxfftGW6).\ To discuss shadPS4 development, suggest ideas or to ask for help, join our [**Discord server**](https://discord.gg/bFJxfftGW6).\
To get the latest news, go to our [**X (Twitter)**](https://x.com/shadps4) or our [**website**](https://shadps4.net/).\ To get the latest news, go to our [**X (Twitter)**](https://x.com/shadps4) or our [**website**](https://shadps4.net/).\
For those who'd like to donate to the project, we now have a [**Kofi page**](https://ko-fi.com/shadps4)! For those who'd like to donate to the project, we now have a [**Kofi page**](https://ko-fi.com/shadps4)!

View File

@ -147,7 +147,7 @@ Accurately identifying games will help other developers that own that game recog
- If your issue is small or you aren't sure whether you have properly identified something, [join the Discord server](https://discord.gg/MyZRaBngxA) and use the #development channel - If your issue is small or you aren't sure whether you have properly identified something, [join the Discord server](https://discord.gg/MyZRaBngxA) and use the #development channel
to concisely explain the issue, as well as any findings you currently have. to concisely explain the issue, as well as any findings you currently have.
- It is recommended that you check the [game compatibility issue tracker](https://github.com/shadps4-emu/shadps4-game-compatibility/issues) and post very short summaries of progress changes there, - It is recommended that you check the [game compatibility issue tracker](https://github.com/shadps4-compatibility/shadps4-game-compatibility/issues) and post very short summaries of progress changes there,
(such as the game now booting into the menu or getting in-game) for organizational and status update purposes. (such as the game now booting into the menu or getting in-game) for organizational and status update purposes.
- ⚠ **Do not post theoretical, unproven game-specific issues in the emulator issue tracker that you cannot verify and locate in the emulator source code as being a bug.**\ - ⚠ **Do not post theoretical, unproven game-specific issues in the emulator issue tracker that you cannot verify and locate in the emulator source code as being a bug.**\

View File

@ -32,6 +32,7 @@ std::filesystem::path find_fs_path_or(const basic_value<TC>& v, const K& ky,
namespace Config { namespace Config {
// General // General
static int volumeSlider = 100;
static bool isNeo = false; static bool isNeo = false;
static bool isDevKit = false; static bool isDevKit = false;
static bool isPSNSignedIn = false; static bool isPSNSignedIn = false;
@ -108,6 +109,9 @@ static std::string trophyKey = "";
// Expected number of items in the config file // Expected number of items in the config file
static constexpr u64 total_entries = 54; static constexpr u64 total_entries = 54;
int getVolumeSlider() {
return volumeSlider;
}
bool allowHDR() { bool allowHDR() {
return isHDRAllowed; return isHDRAllowed;
} }
@ -157,6 +161,10 @@ std::filesystem::path GetSaveDataPath() {
return save_data_path; return save_data_path;
} }
void setVolumeSlider(int volumeValue) {
volumeSlider = volumeValue;
}
void setLoadGameSizeEnabled(bool enable) { void setLoadGameSizeEnabled(bool enable) {
load_game_size = enable; load_game_size = enable;
} }
@ -611,6 +619,7 @@ void load(const std::filesystem::path& path) {
if (data.contains("General")) { if (data.contains("General")) {
const toml::value& general = data.at("General"); const toml::value& general = data.at("General");
volumeSlider = toml::find_or<int>(general, "volumeSlider", volumeSlider);
isNeo = toml::find_or<bool>(general, "isPS4Pro", isNeo); isNeo = toml::find_or<bool>(general, "isPS4Pro", isNeo);
isDevKit = toml::find_or<bool>(general, "isDevKit", isDevKit); isDevKit = toml::find_or<bool>(general, "isDevKit", isDevKit);
isPSNSignedIn = toml::find_or<bool>(general, "isPSNSignedIn", isPSNSignedIn); isPSNSignedIn = toml::find_or<bool>(general, "isPSNSignedIn", isPSNSignedIn);
@ -806,6 +815,7 @@ void save(const std::filesystem::path& path) {
fmt::print("Saving new configuration file {}\n", fmt::UTF(path.u8string())); fmt::print("Saving new configuration file {}\n", fmt::UTF(path.u8string()));
} }
data["General"]["volumeSlider"] = volumeSlider;
data["General"]["isPS4Pro"] = isNeo; data["General"]["isPS4Pro"] = isNeo;
data["General"]["isDevKit"] = isDevKit; data["General"]["isDevKit"] = isDevKit;
data["General"]["isPSNSignedIn"] = isPSNSignedIn; data["General"]["isPSNSignedIn"] = isPSNSignedIn;
@ -901,6 +911,7 @@ void save(const std::filesystem::path& path) {
void setDefaultValues() { void setDefaultValues() {
// General // General
volumeSlider = 100;
isNeo = false; isNeo = false;
isDevKit = false; isDevKit = false;
isPSNSignedIn = false; isPSNSignedIn = false;

View File

@ -19,6 +19,8 @@ enum HideCursorState : int { Never, Idle, Always };
void load(const std::filesystem::path& path); void load(const std::filesystem::path& path);
void save(const std::filesystem::path& path); void save(const std::filesystem::path& path);
int getVolumeSlider();
void setVolumeSlider(int volumeValue);
std::string getTrophyKey(); std::string getTrophyKey();
void setTrophyKey(std::string key); void setTrophyKey(std::string key);
bool getIsFullscreen(); bool getIsFullscreen();

View File

@ -14,6 +14,7 @@
#include "core/libraries/audio/audioout.h" #include "core/libraries/audio/audioout.h"
#include "core/libraries/audio/audioout_backend.h" #include "core/libraries/audio/audioout_backend.h"
#include "core/libraries/audio/audioout_error.h" #include "core/libraries/audio/audioout_error.h"
#include "core/libraries/kernel/time.h"
#include "core/libraries/libs.h" #include "core/libraries/libs.h"
namespace Libraries::AudioOut { namespace Libraries::AudioOut {
@ -168,8 +169,19 @@ int PS4_SYSV_ABI sceAudioOutGetInfoOpenNum() {
return ORBIS_OK; return ORBIS_OK;
} }
int PS4_SYSV_ABI sceAudioOutGetLastOutputTime() { int PS4_SYSV_ABI sceAudioOutGetLastOutputTime(s32 handle, u64* output_time) {
LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); LOG_DEBUG(Lib_AudioOut, "called, handle: {}, output time: {}", handle, fmt::ptr(output_time));
if (!output_time) {
return ORBIS_AUDIO_OUT_ERROR_INVALID_POINTER;
}
if (handle >= ports_out.size()) {
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
}
auto& port = ports_out.at(handle - 1);
if (!port.IsOpen()) {
return ORBIS_AUDIO_OUT_ERROR_NOT_OPENED;
}
*output_time = port.last_output_time;
return ORBIS_OK; return ORBIS_OK;
} }
@ -396,6 +408,7 @@ s32 PS4_SYSV_ABI sceAudioOutOutput(s32 handle, void* ptr) {
if (ptr != nullptr && port.IsOpen()) { if (ptr != nullptr && port.IsOpen()) {
std::memcpy(port.output_buffer, ptr, port.BufferSize()); std::memcpy(port.output_buffer, ptr, port.BufferSize());
port.output_ready = true; port.output_ready = true;
port.last_output_time = Kernel::sceKernelGetProcessTime();
} }
} }
port.output_cv.notify_one(); port.output_cv.notify_one();
@ -523,9 +536,24 @@ s32 PS4_SYSV_ABI sceAudioOutSetVolume(s32 handle, s32 flag, s32* vol) {
} }
port.impl->SetVolume(port.volume); port.impl->SetVolume(port.volume);
} }
AdjustVol();
return ORBIS_OK; return ORBIS_OK;
} }
void AdjustVol() {
if (audio == nullptr) {
return;
}
for (int i = 0; i < ports_out.size(); i++) {
std::unique_lock lock{ports_out[i].mutex};
if (!ports_out[i].IsOpen()) {
continue;
}
ports_out[i].impl->SetVolume(ports_out[i].volume);
}
}
int PS4_SYSV_ABI sceAudioOutSetVolumeDown() { int PS4_SYSV_ABI sceAudioOutSetVolumeDown() {
LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
return ORBIS_OK; return ORBIS_OK;

View File

@ -96,6 +96,7 @@ struct PortOut {
AudioFormatInfo format_info; AudioFormatInfo format_info;
u32 sample_rate; u32 sample_rate;
u32 buffer_frames; u32 buffer_frames;
u64 last_output_time;
std::array<s32, 8> volume; std::array<s32, 8> volume;
[[nodiscard]] bool IsOpen() const { [[nodiscard]] bool IsOpen() const {
@ -127,7 +128,7 @@ int PS4_SYSV_ABI sceAudioOutGetFocusEnablePid();
int PS4_SYSV_ABI sceAudioOutGetHandleStatusInfo(); int PS4_SYSV_ABI sceAudioOutGetHandleStatusInfo();
int PS4_SYSV_ABI sceAudioOutGetInfo(); int PS4_SYSV_ABI sceAudioOutGetInfo();
int PS4_SYSV_ABI sceAudioOutGetInfoOpenNum(); int PS4_SYSV_ABI sceAudioOutGetInfoOpenNum();
int PS4_SYSV_ABI sceAudioOutGetLastOutputTime(); int PS4_SYSV_ABI sceAudioOutGetLastOutputTime(s32 handle, u64* output_time);
int PS4_SYSV_ABI sceAudioOutGetPortState(s32 handle, OrbisAudioOutPortState* state); int PS4_SYSV_ABI sceAudioOutGetPortState(s32 handle, OrbisAudioOutPortState* state);
int PS4_SYSV_ABI sceAudioOutGetSimulatedBusUsableStatusByBusType(); int PS4_SYSV_ABI sceAudioOutGetSimulatedBusUsableStatusByBusType();
int PS4_SYSV_ABI sceAudioOutGetSimulatedHandleStatusInfo(); int PS4_SYSV_ABI sceAudioOutGetSimulatedHandleStatusInfo();
@ -181,5 +182,6 @@ int PS4_SYSV_ABI sceAudioOutSystemControlSet();
int PS4_SYSV_ABI sceAudioOutSparkControlSetEqCoef(); int PS4_SYSV_ABI sceAudioOutSparkControlSetEqCoef();
int PS4_SYSV_ABI sceAudioOutSetSystemDebugState(); int PS4_SYSV_ABI sceAudioOutSetSystemDebugState();
void AdjustVol();
void RegisterLib(Core::Loader::SymbolsResolver* sym); void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::AudioOut } // namespace Libraries::AudioOut

View File

@ -4,6 +4,7 @@
#include <thread> #include <thread>
#include <SDL3/SDL_audio.h> #include <SDL3/SDL_audio.h>
#include <SDL3/SDL_hints.h> #include <SDL3/SDL_hints.h>
#include <common/config.h>
#include "common/logging/log.h" #include "common/logging/log.h"
#include "core/libraries/audio/audioout.h" #include "core/libraries/audio/audioout.h"
@ -41,6 +42,7 @@ public:
stream = nullptr; stream = nullptr;
return; return;
} }
SDL_SetAudioStreamGain(stream, Config::getVolumeSlider() / 100.0f);
} }
~SDLPortBackend() override { ~SDLPortBackend() override {
@ -77,7 +79,8 @@ public:
} }
// SDL does not have per-channel volumes, for now just take the maximum of the channels. // SDL does not have per-channel volumes, for now just take the maximum of the channels.
const auto vol = *std::ranges::max_element(ch_volumes); const auto vol = *std::ranges::max_element(ch_volumes);
if (!SDL_SetAudioStreamGain(stream, static_cast<float>(vol) / SCE_AUDIO_OUT_VOLUME_0DB)) { if (!SDL_SetAudioStreamGain(stream, static_cast<float>(vol) / SCE_AUDIO_OUT_VOLUME_0DB *
Config::getVolumeSlider() / 100.0f)) {
LOG_WARNING(Lib_AudioOut, "Failed to change SDL audio stream volume: {}", LOG_WARNING(Lib_AudioOut, "Failed to change SDL audio stream volume: {}",
SDL_GetError()); SDL_GetError());
} }

View File

@ -65,7 +65,7 @@ public:
return Error::OK; return Error::OK;
} }
std::unique_lock lock{g_ime_state.queue_mutex}; std::unique_lock<std::mutex> lock{g_ime_state.queue_mutex};
while (!g_ime_state.event_queue.empty()) { while (!g_ime_state.event_queue.empty()) {
OrbisImeEvent event = g_ime_state.event_queue.front(); OrbisImeEvent event = g_ime_state.event_queue.front();
@ -144,17 +144,17 @@ int PS4_SYSV_ABI sceImeCheckUpdateTextInfo() {
return ORBIS_OK; return ORBIS_OK;
} }
s32 PS4_SYSV_ABI sceImeClose() { Error PS4_SYSV_ABI sceImeClose() {
LOG_INFO(Lib_Ime, "(STUBBED) called"); LOG_INFO(Lib_Ime, "called");
if (!g_ime_handler) { if (!g_ime_handler) {
return ORBIS_IME_ERROR_NOT_OPENED; return Error::NOT_OPENED;
} }
g_ime_handler.release(); g_ime_handler.release();
g_ime_ui = ImeUi(); g_ime_ui = ImeUi();
g_ime_state = ImeState(); g_ime_state = ImeState();
return ORBIS_OK; return Error::OK;
} }
int PS4_SYSV_ABI sceImeConfigGet() { int PS4_SYSV_ABI sceImeConfigGet() {
@ -223,32 +223,68 @@ int PS4_SYSV_ABI sceImeGetPanelPositionAndForm() {
} }
Error PS4_SYSV_ABI sceImeGetPanelSize(const OrbisImeParam* param, u32* width, u32* height) { Error PS4_SYSV_ABI sceImeGetPanelSize(const OrbisImeParam* param, u32* width, u32* height) {
LOG_INFO(Lib_Ime, "called"); LOG_INFO(Lib_Ime, "sceImeGetPanelSize called");
if (!width || !height) { if (!param) {
LOG_ERROR(Lib_Ime, "sceImeGetPanelSize: param is NULL");
return Error::INVALID_ADDRESS; return Error::INVALID_ADDRESS;
} }
if (!width) {
LOG_ERROR(Lib_Ime, "sceImeGetPanelSize: width pointer is NULL");
return Error::INVALID_ADDRESS;
}
if (!height) {
LOG_ERROR(Lib_Ime, "sceImeGetPanelSize: height pointer is NULL");
return Error::INVALID_ADDRESS;
}
if (static_cast<u32>(param->option) & ~0x7BFF) { // Basic check for invalid options
LOG_ERROR(Lib_Ime, "sceImeGetPanelSize: Invalid option 0x{:X}",
static_cast<u32>(param->option));
return Error::INVALID_OPTION;
}
switch (param->type) { switch (param->type) {
case OrbisImeType::Default: case OrbisImeType::Default:
*width = 500; // dummy value
*height = 100; // dummy value
LOG_INFO(Lib_Ime, "sceImeGetPanelSize: IME type Default ({})",
static_cast<u32>(param->type));
break;
case OrbisImeType::BasicLatin: case OrbisImeType::BasicLatin:
*width = 500; // dummy value
*height = 100; // dummy value
LOG_INFO(Lib_Ime, "sceImeGetPanelSize: IME type BasicLatin ({})",
static_cast<u32>(param->type));
break;
case OrbisImeType::Url: case OrbisImeType::Url:
*width = 500; // dummy value
*height = 100; // dummy value
LOG_INFO(Lib_Ime, "sceImeGetPanelSize: IME type Url ({})", static_cast<u32>(param->type));
break;
case OrbisImeType::Mail: case OrbisImeType::Mail:
// We set our custom sizes, commented sizes are the original ones // We set our custom sizes, commented sizes are the original ones
*width = 500; // 793 *width = 500; // 793
*height = 100; // 408 *height = 100; // 408
LOG_INFO(Lib_Ime, "sceImeGetPanelSize: IME type Mail ({})", static_cast<u32>(param->type));
break; break;
case OrbisImeType::Number: case OrbisImeType::Number:
*width = 370; *width = 370;
*height = 402; *height = 402;
LOG_INFO(Lib_Ime, "sceImeGetPanelSize: IME type Number ({})",
static_cast<u32>(param->type));
break; break;
default:
LOG_ERROR(Lib_Ime, "sceImeGetPanelSize: Invalid IME type ({})",
static_cast<u32>(param->type));
return Error::INVALID_TYPE;
} }
return Error::OK; return Error::OK;
} }
Error PS4_SYSV_ABI sceImeKeyboardClose(s32 userId) { Error PS4_SYSV_ABI sceImeKeyboardClose(Libraries::UserService::OrbisUserServiceUserId userId) {
LOG_INFO(Lib_Ime, "(STUBBED) called"); LOG_INFO(Lib_Ime, "called");
if (!g_keyboard_handler) { if (!g_keyboard_handler) {
return Error::NOT_OPENED; return Error::NOT_OPENED;
@ -268,9 +304,12 @@ int PS4_SYSV_ABI sceImeKeyboardGetResourceId() {
return ORBIS_OK; return ORBIS_OK;
} }
Error PS4_SYSV_ABI sceImeKeyboardOpen(s32 userId, const OrbisImeKeyboardParam* param) { Error PS4_SYSV_ABI sceImeKeyboardOpen(Libraries::UserService::OrbisUserServiceUserId userId,
const OrbisImeKeyboardParam* param) {
LOG_INFO(Lib_Ime, "called"); LOG_INFO(Lib_Ime, "called");
LOG_INFO(Lib_Ime, "kValidImeDialogExtOptionMask=0x{:X}", kValidImeDialogExtOptionMask);
if (!param) { if (!param) {
return Error::INVALID_ADDRESS; return Error::INVALID_ADDRESS;
} }
@ -308,13 +347,169 @@ Error PS4_SYSV_ABI sceImeOpen(const OrbisImeParam* param, const OrbisImeParamExt
LOG_INFO(Lib_Ime, "called"); LOG_INFO(Lib_Ime, "called");
if (!param) { if (!param) {
LOG_ERROR(Lib_Ime, "sceImeOpen: param is null");
return Error::INVALID_ADDRESS; return Error::INVALID_ADDRESS;
} else {
// LOG_DEBUG values for debugging purposes
LOG_DEBUG(Lib_Ime, "param: user_id={}", param->user_id);
LOG_DEBUG(Lib_Ime, "param: type={}", static_cast<u32>(param->type));
LOG_DEBUG(Lib_Ime, "param: supported_languages={:064b}",
static_cast<u64>(param->supported_languages));
LOG_DEBUG(Lib_Ime, "param: enter_label={}", static_cast<u32>(param->enter_label));
LOG_DEBUG(Lib_Ime, "param: input_method={}", static_cast<u32>(param->input_method));
LOG_DEBUG(Lib_Ime, "param: filter={:p}", reinterpret_cast<void*>(param->filter));
LOG_DEBUG(Lib_Ime, "param: option={:032b}", static_cast<u32>(param->option));
LOG_DEBUG(Lib_Ime, "param: maxTextLength={}", param->maxTextLength);
LOG_DEBUG(Lib_Ime, "param: inputTextBuffer={:p}",
static_cast<const void*>(param->inputTextBuffer));
LOG_DEBUG(Lib_Ime, "param: posx={}", param->posx);
LOG_DEBUG(Lib_Ime, "param: posy={}", param->posy);
LOG_DEBUG(Lib_Ime, "param: horizontal_alignment={}",
static_cast<u32>(param->horizontal_alignment));
LOG_DEBUG(Lib_Ime, "param: vertical_alignment={}",
static_cast<u32>(param->vertical_alignment));
LOG_DEBUG(Lib_Ime, "param: work={:p}", param->work);
LOG_DEBUG(Lib_Ime, "param: arg={:p}", param->arg);
LOG_DEBUG(Lib_Ime, "param: handler={:p}", reinterpret_cast<void*>(param->handler));
} }
if (!extended) {
LOG_INFO(Lib_Ime, "sceImeOpen: extended is null");
} else {
// LOG_DEBUG values for debugging purposes
LOG_DEBUG(Lib_Ime, "extended: option={:032b}", static_cast<u32>(extended->option));
LOG_DEBUG(Lib_Ime, "extended: color_base={{{},{},{},{}}}", extended->color_base.r,
extended->color_base.g, extended->color_base.b, extended->color_base.a);
LOG_DEBUG(Lib_Ime, "extended: color_line={{{},{},{},{}}}", extended->color_line.r,
extended->color_line.g, extended->color_line.b, extended->color_line.a);
LOG_DEBUG(Lib_Ime, "extended: color_text_field={{{},{},{},{}}}",
extended->color_text_field.r, extended->color_text_field.g,
extended->color_text_field.b, extended->color_text_field.a);
LOG_DEBUG(Lib_Ime, "extended: color_preedit={{{},{},{},{}}}", extended->color_preedit.r,
extended->color_preedit.g, extended->color_preedit.b, extended->color_preedit.a);
LOG_DEBUG(Lib_Ime, "extended: color_button_default={{{},{},{},{}}}",
extended->color_button_default.r, extended->color_button_default.g,
extended->color_button_default.b, extended->color_button_default.a);
LOG_DEBUG(Lib_Ime, "extended: color_button_function={{{},{},{},{}}}",
extended->color_button_function.r, extended->color_button_function.g,
extended->color_button_function.b, extended->color_button_function.a);
LOG_DEBUG(Lib_Ime, "extended: color_button_symbol={{{},{},{},{}}}",
extended->color_button_symbol.r, extended->color_button_symbol.g,
extended->color_button_symbol.b, extended->color_button_symbol.a);
LOG_DEBUG(Lib_Ime, "extended: color_text={{{},{},{},{}}}", extended->color_text.r,
extended->color_text.g, extended->color_text.b, extended->color_text.a);
LOG_DEBUG(Lib_Ime, "extended: color_special={{{},{},{},{}}}", extended->color_special.r,
extended->color_special.g, extended->color_special.b, extended->color_special.a);
LOG_DEBUG(Lib_Ime, "extended: priority={}", static_cast<u32>(extended->priority));
LOG_DEBUG(Lib_Ime, "extended: additional_dictionary_path={:p}",
static_cast<const void*>(extended->additional_dictionary_path));
LOG_DEBUG(Lib_Ime, "extended: ext_keyboard_filter={:p}",
reinterpret_cast<void*>(extended->ext_keyboard_filter));
LOG_DEBUG(Lib_Ime, "extended: disable_device={:032b}",
static_cast<u32>(extended->disable_device));
LOG_DEBUG(Lib_Ime, "extended: ext_keyboard_mode={}", extended->ext_keyboard_mode);
}
if (param->user_id < 1 || param->user_id > 4) {
LOG_ERROR(Lib_Ime, "sceImeOpen: Invalid user_id ({})", static_cast<u32>(param->user_id));
return Error::INVALID_USER_ID;
}
if (!magic_enum::enum_contains(param->type)) {
LOG_ERROR(Lib_Ime, "sceImeOpen: Invalid type ({})", static_cast<u32>(param->type));
return Error::INVALID_TYPE;
}
if (static_cast<u64>(param->supported_languages) & ~kValidOrbisImeLanguageMask) {
LOG_ERROR(Lib_Ime, "sceImeOpen: supported_languages has invalid bits (0x{:016X})",
static_cast<u64>(param->supported_languages));
return Error::INVALID_SUPPORTED_LANGUAGES;
}
if (!magic_enum::enum_contains(param->enter_label)) {
LOG_ERROR(Lib_Ime, "sceImeOpen: Invalid enter_label ({})",
static_cast<u32>(param->enter_label));
return Error::INVALID_ENTER_LABEL;
}
if (!magic_enum::enum_contains(param->input_method)) {
LOG_ERROR(Lib_Ime, "sceImeOpen: Invalid input_method ({})",
static_cast<u32>(param->input_method));
return Error::INVALID_INPUT_METHOD;
}
if (static_cast<u32>(param->option) & ~kValidImeOptionMask) {
LOG_ERROR(Lib_Ime, "sceImeOpen: option has invalid bits set (0x{:X}), mask=(0x{:X})",
static_cast<u32>(param->option), kValidImeOptionMask);
return Error::INVALID_OPTION;
}
if (param->maxTextLength == 0 || param->maxTextLength > ORBIS_IME_DIALOG_MAX_TEXT_LENGTH) {
LOG_ERROR(Lib_Ime, "sceImeOpen: maxTextLength invalid ({})", param->maxTextLength);
return Error::INVALID_MAX_TEXT_LENGTH;
}
if (!param->inputTextBuffer) {
LOG_ERROR(Lib_Ime, "sceImeOpen: inputTextBuffer is NULL");
return Error::INVALID_INPUT_TEXT_BUFFER;
}
bool useHighRes = True(param->option & OrbisImeOption::USE_OVER_2K_COORDINATES);
const float maxWidth = useHighRes ? 3840.0f : 1920.0f;
const float maxHeight = useHighRes ? 2160.0f : 1080.0f;
if (param->posx < 0.0f || param->posx >= maxWidth) {
LOG_ERROR(Lib_Ime, "sceImeOpen: posx out of range (%.2f), max allowed %.0f", param->posx,
maxWidth);
return Error::INVALID_POSX;
}
if (param->posy < 0.0f || param->posy >= maxHeight) {
LOG_ERROR(Lib_Ime, "sceImeOpen: posy out of range (%.2f), max allowed %.0f", param->posy,
maxHeight);
return Error::INVALID_POSY;
}
if (!magic_enum::enum_contains(param->horizontal_alignment)) {
LOG_ERROR(Lib_Ime, "sceImeOpen: Invalid horizontal_alignment ({})",
static_cast<u32>(param->horizontal_alignment));
return Error::INVALID_HORIZONTALIGNMENT;
}
if (!magic_enum::enum_contains(param->vertical_alignment)) {
LOG_ERROR(Lib_Ime, "sceImeOpen: Invalid vertical_alignment ({})",
static_cast<u32>(param->vertical_alignment));
return Error::INVALID_VERTICALALIGNMENT;
}
if (extended) {
u32 ext_option_value = static_cast<u32>(extended->option);
if (ext_option_value & ~kValidImeExtOptionMask) {
LOG_ERROR(Lib_Ime, "sceImeOpen: extended->option has invalid bits set (0x{:X})",
ext_option_value);
return Error::INVALID_EXTENDED;
}
}
if (!param->work) {
LOG_ERROR(Lib_Ime, "sceImeOpen: work buffer is NULL");
return Error::INVALID_WORK;
}
for (unsigned i = 0; i < sizeof(param->reserved); ++i) {
if (param->reserved[i] != 0) {
LOG_ERROR(Lib_Ime, "sceImeOpen: reserved field must be zeroed");
return Error::INVALID_RESERVED;
}
}
// Todo: validate arg and handler
if (g_ime_handler) { if (g_ime_handler) {
LOG_ERROR(Lib_Ime, "sceImeOpen: Error BUSY");
return Error::BUSY; return Error::BUSY;
} }
g_ime_handler = std::make_unique<ImeHandler>(param); g_ime_handler = std::make_unique<ImeHandler>(param);
LOG_INFO(Lib_Ime, "sceImeOpen: OK");
return Error::OK; return Error::OK;
} }
@ -324,7 +519,7 @@ int PS4_SYSV_ABI sceImeOpenInternal() {
} }
void PS4_SYSV_ABI sceImeParamInit(OrbisImeParam* param) { void PS4_SYSV_ABI sceImeParamInit(OrbisImeParam* param) {
LOG_INFO(Lib_Ime, "called"); LOG_INFO(Lib_Ime, "sceImeParamInit called");
if (!param) { if (!param) {
return; return;

View File

@ -18,7 +18,7 @@ int PS4_SYSV_ABI InitializeImeModule();
int PS4_SYSV_ABI sceImeCheckFilterText(); int PS4_SYSV_ABI sceImeCheckFilterText();
int PS4_SYSV_ABI sceImeCheckRemoteEventParam(); int PS4_SYSV_ABI sceImeCheckRemoteEventParam();
int PS4_SYSV_ABI sceImeCheckUpdateTextInfo(); int PS4_SYSV_ABI sceImeCheckUpdateTextInfo();
int PS4_SYSV_ABI sceImeClose(); Error PS4_SYSV_ABI sceImeClose();
int PS4_SYSV_ABI sceImeConfigGet(); int PS4_SYSV_ABI sceImeConfigGet();
int PS4_SYSV_ABI sceImeConfigSet(); int PS4_SYSV_ABI sceImeConfigSet();
int PS4_SYSV_ABI sceImeConfirmCandidate(); int PS4_SYSV_ABI sceImeConfirmCandidate();
@ -33,10 +33,11 @@ int PS4_SYSV_ABI sceImeFilterText();
int PS4_SYSV_ABI sceImeForTestFunction(); int PS4_SYSV_ABI sceImeForTestFunction();
int PS4_SYSV_ABI sceImeGetPanelPositionAndForm(); int PS4_SYSV_ABI sceImeGetPanelPositionAndForm();
Error PS4_SYSV_ABI sceImeGetPanelSize(const OrbisImeParam* param, u32* width, u32* height); Error PS4_SYSV_ABI sceImeGetPanelSize(const OrbisImeParam* param, u32* width, u32* height);
Error PS4_SYSV_ABI sceImeKeyboardClose(s32 userId); Error PS4_SYSV_ABI sceImeKeyboardClose(Libraries::UserService::OrbisUserServiceUserId userId);
int PS4_SYSV_ABI sceImeKeyboardGetInfo(); int PS4_SYSV_ABI sceImeKeyboardGetInfo();
int PS4_SYSV_ABI sceImeKeyboardGetResourceId(); int PS4_SYSV_ABI sceImeKeyboardGetResourceId();
Error PS4_SYSV_ABI sceImeKeyboardOpen(s32 userId, const OrbisImeKeyboardParam* param); Error PS4_SYSV_ABI sceImeKeyboardOpen(Libraries::UserService::OrbisUserServiceUserId userId,
const OrbisImeKeyboardParam* param);
int PS4_SYSV_ABI sceImeKeyboardOpenInternal(); int PS4_SYSV_ABI sceImeKeyboardOpenInternal();
int PS4_SYSV_ABI sceImeKeyboardSetMode(); int PS4_SYSV_ABI sceImeKeyboardSetMode();
int PS4_SYSV_ABI sceImeKeyboardUpdate(); int PS4_SYSV_ABI sceImeKeyboardUpdate();

View File

@ -3,6 +3,8 @@
#pragma once #pragma once
#include <core/libraries/system/userservice.h>
#include <magic_enum/magic_enum.hpp>
#include "common/enum.h" #include "common/enum.h"
#include "common/types.h" #include "common/types.h"
#include "core/libraries/rtc/rtc.h" #include "core/libraries/rtc/rtc.h"
@ -10,8 +12,28 @@
constexpr u32 ORBIS_IME_MAX_TEXT_LENGTH = 2048; constexpr u32 ORBIS_IME_MAX_TEXT_LENGTH = 2048;
constexpr u32 ORBIS_IME_DIALOG_MAX_TEXT_LENGTH = 2048; constexpr u32 ORBIS_IME_DIALOG_MAX_TEXT_LENGTH = 2048;
template <typename E>
const std::underlying_type_t<E> generate_full_mask() {
static_assert(std::is_enum_v<E>, "E must be an enum type.");
static_assert(magic_enum::customize::enum_range<E>::is_flags,
"E must be marked as is_flags = true.");
using U = std::underlying_type_t<E>;
const auto values = magic_enum::enum_values<E>();
U mask = 0;
// Use index-based loop for better constexpr compatibility
for (std::size_t i = 0; i < values.size(); ++i) {
mask |= static_cast<U>(values[i]);
}
return mask;
}
enum class Error : u32 { enum class Error : u32 {
OK = 0x0, OK = 0x0,
// ImeDialog library
BUSY = 0x80bc0001, BUSY = 0x80bc0001,
NOT_OPENED = 0x80bc0002, NOT_OPENED = 0x80bc0002,
NO_MEMORY = 0x80bc0003, NO_MEMORY = 0x80bc0003,
@ -46,6 +68,8 @@ enum class Error : u32 {
INVALID_RESERVED = 0x80bc0032, INVALID_RESERVED = 0x80bc0032,
INVALID_TIMING = 0x80bc0033, INVALID_TIMING = 0x80bc0033,
INTERNAL = 0x80bc00ff, INTERNAL = 0x80bc00ff,
// Ime library
DIALOG_INVALID_TITLE = 0x80bc0101, DIALOG_INVALID_TITLE = 0x80bc0101,
DIALOG_NOT_RUNNING = 0x80bc0105, DIALOG_NOT_RUNNING = 0x80bc0105,
DIALOG_NOT_FINISHED = 0x80bc0106, DIALOG_NOT_FINISHED = 0x80bc0106,
@ -67,9 +91,44 @@ enum class OrbisImeOption : u32 {
DISABLE_POSITION_ADJUSTMENT = 2048, DISABLE_POSITION_ADJUSTMENT = 2048,
EXPANDED_PREEDIT_BUFFER = 4096, EXPANDED_PREEDIT_BUFFER = 4096,
USE_JAPANESE_EISUU_KEY_AS_CAPSLOCK = 8192, USE_JAPANESE_EISUU_KEY_AS_CAPSLOCK = 8192,
USE_2K_COORDINATES = 16384, USE_OVER_2K_COORDINATES = 16384,
}; };
DECLARE_ENUM_FLAG_OPERATORS(OrbisImeOption); DECLARE_ENUM_FLAG_OPERATORS(OrbisImeOption);
template <>
struct magic_enum::customize::enum_range<OrbisImeOption> {
static constexpr bool is_flags = true;
};
const u32 kValidImeOptionMask = generate_full_mask<OrbisImeOption>();
enum class OrbisImeExtOption : u32 {
DEFAULT = 0x00000000,
SET_PRIORITY = 0x00000002,
PRIORITY_FULL_WIDTH = 0x00000008,
PRIORITY_FIXED_PANEL = 0x00000010,
DISABLE_POINTER = 0x00000040,
ENABLE_ADDITIONAL_DICTIONARY = 0x00000080,
DISABLE_STARTUP_SE = 0x00000100,
DISABLE_LIST_FOR_EXT_KEYBOARD = 0x00000200,
HIDE_KEYPANEL_IF_EXT_KEYBOARD = 0x00000400,
INIT_EXT_KEYBOARD_MODE = 0x00000800,
ENABLE_ACCESSIBILITY = 0x00001000, // ImeDialog unly
ADDITIONAL_DICTIONARY_PRIORITY_MODE = 0x00004000, // ImeDialog only
};
DECLARE_ENUM_FLAG_OPERATORS(OrbisImeExtOption);
constexpr u32 kValidImeExtOptionMask = static_cast<u32>(
OrbisImeExtOption::SET_PRIORITY | OrbisImeExtOption::PRIORITY_FULL_WIDTH |
OrbisImeExtOption::PRIORITY_FIXED_PANEL | OrbisImeExtOption::DISABLE_POINTER |
OrbisImeExtOption::ENABLE_ADDITIONAL_DICTIONARY | OrbisImeExtOption::DISABLE_STARTUP_SE |
OrbisImeExtOption::DISABLE_LIST_FOR_EXT_KEYBOARD |
OrbisImeExtOption::HIDE_KEYPANEL_IF_EXT_KEYBOARD | OrbisImeExtOption::INIT_EXT_KEYBOARD_MODE);
template <>
struct magic_enum::customize::enum_range<OrbisImeExtOption> {
static constexpr bool is_flags = true;
};
const u32 kValidImeDialogExtOptionMask = generate_full_mask<OrbisImeExtOption>();
enum class OrbisImeLanguage : u64 { enum class OrbisImeLanguage : u64 {
DANISH = 0x0000000000000001, DANISH = 0x0000000000000001,
@ -105,6 +164,112 @@ enum class OrbisImeLanguage : u64 {
}; };
DECLARE_ENUM_FLAG_OPERATORS(OrbisImeLanguage); DECLARE_ENUM_FLAG_OPERATORS(OrbisImeLanguage);
template <>
struct magic_enum::customize::enum_range<OrbisImeLanguage> {
static constexpr bool is_flags = true;
};
const u64 kValidOrbisImeLanguageMask = generate_full_mask<OrbisImeLanguage>();
enum class OrbisImeDisableDevice : u32 {
DEFAULT = 0x00000000,
CONTROLLER = 0x00000001,
EXT_KEYBOARD = 0x00000002,
REMOTE_OSK = 0x00000004,
};
DECLARE_ENUM_FLAG_OPERATORS(OrbisImeDisableDevice);
template <>
struct magic_enum::customize::enum_range<OrbisImeDisableDevice> {
static constexpr bool is_flags = true;
};
const u32 kValidOrbisImeDisableDeviceMask = generate_full_mask<OrbisImeDisableDevice>();
enum class OrbisImeInputMethodState : u32 {
PREEDIT = 0x01000000,
SELECTED = 0x02000000,
NATIVE = 0x04000000,
NATIVE2 = 0x08000000,
FULL_WIDTH = 0x10000000,
};
DECLARE_ENUM_FLAG_OPERATORS(OrbisImeInputMethodState);
template <>
struct magic_enum::customize::enum_range<OrbisImeInputMethodState> {
static constexpr bool is_flags = true;
};
const u32 kValidOrbisImeInputMethodStateMask = generate_full_mask<OrbisImeInputMethodState>();
enum class OrbisImeInitExtKeyboardMode : u32 {
ISABLE_ARABIC_INDIC_NUMERALS = 0x00000001,
ENABLE_FORMAT_CHARACTERS = 0x00000002,
INPUT_METHOD_STATE_NATIVE = 0x04000000,
INPUT_METHOD_STATE_NATIVE2 = 0x08000000,
INPUT_METHOD_STATE_FULL_WIDTH = 0x10000000,
};
DECLARE_ENUM_FLAG_OPERATORS(OrbisImeInitExtKeyboardMode);
template <>
struct magic_enum::customize::enum_range<OrbisImeInitExtKeyboardMode> {
static constexpr bool is_flags = true;
};
const u32 kValidOrbisImeInitExtKeyboardModeMask = generate_full_mask<OrbisImeInitExtKeyboardMode>();
enum class OrbisImeKeycodeState : u32 {
KEYCODE_VALID = 0x00000001,
CHARACTER_VALID = 0x00000002,
WITH_IME = 0x00000004,
FROM_OSK = 0x00000008,
FROM_OSK_SHORTCUT = 0x00000010,
FROM_IME_OPERATION = 0x00000020,
REPLACE_CHARACTER = 0x00000040,
CONTINUOUS_EVENT = 0x00000080,
MODIFIER_L_CTRL = 0x00000100,
MODIFIER_L_SHIFT = 0x00000200,
MODIFIER_L_ALT = 0x00000400,
MODIFIER_L_GUI = 0x00000800,
MODIFIER_R_CTRL = 0x00001000,
MODIFIER_R_SHIFT = 0x00002000,
MODIFIER_R_ALT = 0x00004000,
MODIFIER_R_GUI = 0x00008000,
LED_NUM_LOCK = 0x00010000,
LED_CAPS_LOCK = 0x00020000,
LED_SCROLL_LOCK = 0x00040000,
RESERVED1 = 0x00080000,
RESERVED2 = 0x00100000,
FROM_IME_INPUT = 0x00200000,
};
DECLARE_ENUM_FLAG_OPERATORS(OrbisImeKeycodeState);
template <>
struct magic_enum::customize::enum_range<OrbisImeKeycodeState> {
static constexpr bool is_flags = true;
};
const u32 kValidOrbisImeKeycodeStateMask = generate_full_mask<OrbisImeKeycodeState>();
enum class OrbisImeKeyboardOption : u32 {
Default = 0,
Repeat = 1,
RepeatEachKey = 2,
AddOsk = 4,
EffectiveWithIme = 8,
DisableResume = 16,
DisableCapslockWithoutShift = 32,
};
DECLARE_ENUM_FLAG_OPERATORS(OrbisImeKeyboardOption)
template <>
struct magic_enum::customize::enum_range<OrbisImeKeyboardOption> {
static constexpr bool is_flags = true;
};
const u32 kValidOrbisImeKeyboardOptionMask = generate_full_mask<OrbisImeKeyboardOption>();
enum class OrbisImeKeyboardMode : u32 {
Auto = 0,
Manual = 1,
Alphabet = 0,
Native = 2,
Part = 4,
Katakana = 8,
Hkana = 16,
ArabicIndicNumerals = 32,
DisableFormatCharacters = 64,
};
enum class OrbisImeType : u32 { enum class OrbisImeType : u32 {
Default = 0, Default = 0,
BasicLatin = 1, BasicLatin = 1,
@ -260,13 +425,13 @@ struct OrbisImeKeycode {
char16_t character; char16_t character;
u32 status; u32 status;
OrbisImeKeyboardType type; OrbisImeKeyboardType type;
s32 user_id; // Todo: switch to OrbisUserServiceUserId Libraries::UserService::OrbisUserServiceUserId user_id;
u32 resource_id; u32 resource_id;
Libraries::Rtc::OrbisRtcTick timestamp; Libraries::Rtc::OrbisRtcTick timestamp;
}; };
struct OrbisImeKeyboardResourceIdArray { struct OrbisImeKeyboardResourceIdArray {
s32 user_id; // Todo: switch to OrbisUserServiceUserId Libraries::UserService::OrbisUserServiceUserId user_id;
u32 resource_id[5]; u32 resource_id[5];
}; };
@ -322,17 +487,6 @@ using OrbisImeTextFilter = PS4_SYSV_ABI int (*)(char16_t* outText, u32* outTextL
using OrbisImeEventHandler = PS4_SYSV_ABI void (*)(void* arg, const OrbisImeEvent* e); using OrbisImeEventHandler = PS4_SYSV_ABI void (*)(void* arg, const OrbisImeEvent* e);
enum class OrbisImeKeyboardOption : u32 {
Default = 0,
Repeat = 1,
RepeatEachKey = 2,
AddOsk = 4,
EffectiveWithIme = 8,
DisableResume = 16,
DisableCapslockWithoutShift = 32,
};
DECLARE_ENUM_FLAG_OPERATORS(OrbisImeKeyboardOption)
struct OrbisImeKeyboardParam { struct OrbisImeKeyboardParam {
OrbisImeKeyboardOption option; OrbisImeKeyboardOption option;
s8 reserved1[4]; s8 reserved1[4];
@ -342,9 +496,9 @@ struct OrbisImeKeyboardParam {
}; };
struct OrbisImeParam { struct OrbisImeParam {
s32 user_id; // Todo: switch to OrbisUserServiceUserId Libraries::UserService::OrbisUserServiceUserId user_id;
OrbisImeType type; OrbisImeType type;
u64 supported_languages; // OrbisImeLanguage flags OrbisImeLanguage supported_languages;
OrbisImeEnterLabel enter_label; OrbisImeEnterLabel enter_label;
OrbisImeInputMethod input_method; OrbisImeInputMethod input_method;
OrbisImeTextFilter filter; OrbisImeTextFilter filter;
@ -369,9 +523,9 @@ struct OrbisImeCaret {
}; };
struct OrbisImeDialogParam { struct OrbisImeDialogParam {
s32 user_id; Libraries::UserService::OrbisUserServiceUserId user_id;
OrbisImeType type; OrbisImeType type;
u64 supported_languages; // OrbisImeLanguage flags OrbisImeLanguage supported_languages;
OrbisImeEnterLabel enter_label; OrbisImeEnterLabel enter_label;
OrbisImeInputMethod input_method; OrbisImeInputMethod input_method;
OrbisImeTextFilter filter; OrbisImeTextFilter filter;
@ -388,7 +542,7 @@ struct OrbisImeDialogParam {
}; };
struct OrbisImeParamExtended { struct OrbisImeParamExtended {
u32 option; // OrbisImeExtOption flags OrbisImeExtOption option;
OrbisImeColor color_base; OrbisImeColor color_base;
OrbisImeColor color_line; OrbisImeColor color_line;
OrbisImeColor color_text_field; OrbisImeColor color_text_field;
@ -401,7 +555,7 @@ struct OrbisImeParamExtended {
OrbisImePanelPriority priority; OrbisImePanelPriority priority;
char* additional_dictionary_path; char* additional_dictionary_path;
OrbisImeExtKeyboardFilter ext_keyboard_filter; OrbisImeExtKeyboardFilter ext_keyboard_filter;
u32 disable_device; OrbisImeDisableDevice disable_device;
u32 ext_keyboard_mode; u32 ext_keyboard_mode;
s8 reserved[60]; s8 reserved[60];
}; };

View File

@ -197,13 +197,15 @@ Error PS4_SYSV_ABI sceImeDialogInit(OrbisImeDialogParam* param, OrbisImeParamExt
// TODO: do correct param->supportedLanguages validation // TODO: do correct param->supportedLanguages validation
if (param->posx < 0.0f || if (param->posx < 0.0f ||
param->posx >= MAX_X_POSITIONS[False(param->option & OrbisImeOption::USE_2K_COORDINATES)]) { param->posx >=
MAX_X_POSITIONS[False(param->option & OrbisImeOption::USE_OVER_2K_COORDINATES)]) {
LOG_ERROR(Lib_ImeDialog, "sceImeDialogInit: invalid posx={}", param->posx); LOG_ERROR(Lib_ImeDialog, "sceImeDialogInit: invalid posx={}", param->posx);
return Error::INVALID_POSX; return Error::INVALID_POSX;
} }
if (param->posy < 0.0f || if (param->posy < 0.0f ||
param->posy >= MAX_Y_POSITIONS[False(param->option & OrbisImeOption::USE_2K_COORDINATES)]) { param->posy >=
MAX_Y_POSITIONS[False(param->option & OrbisImeOption::USE_OVER_2K_COORDINATES)]) {
LOG_ERROR(Lib_ImeDialog, "sceImeDialogInit: invalid posy={}", param->posy); LOG_ERROR(Lib_ImeDialog, "sceImeDialogInit: invalid posy={}", param->posy);
return Error::INVALID_POSY; return Error::INVALID_POSY;
} }
@ -242,7 +244,7 @@ Error PS4_SYSV_ABI sceImeDialogInit(OrbisImeDialogParam* param, OrbisImeParamExt
return Error::INVALID_EXTENDED; return Error::INVALID_EXTENDED;
} }
if (static_cast<u32>(extended->disable_device) & 0x7) { if (static_cast<u32>(extended->disable_device) & ~kValidOrbisImeDisableDeviceMask) {
LOG_ERROR(Lib_ImeDialog, LOG_ERROR(Lib_ImeDialog,
"sceImeDialogInit: disable_device has invalid bits set (0x{:X})", "sceImeDialogInit: disable_device has invalid bits set (0x{:X})",
static_cast<u32>(extended->disable_device)); static_cast<u32>(extended->disable_device));

View File

@ -22,8 +22,8 @@ void CompatibilityInfoClass::UpdateCompatibilityDatabase(QWidget* parent, bool f
if (!forced && LoadCompatibilityFile()) if (!forced && LoadCompatibilityFile())
return; return;
QUrl url("https://github.com/shadps4-emu/shadps4-game-compatibility/releases/latest/download/" QUrl url("https://github.com/shadps4-compatibility/shadps4-game-compatibility/releases/latest/"
"compatibility_data.json"); "download/compatibility_data.json");
QNetworkRequest request(url); QNetworkRequest request(url);
QNetworkReply* reply = m_network_manager->get(request); QNetworkReply* reply = m_network_manager->get(request);

View File

@ -91,7 +91,8 @@ GameListFrame::GameListFrame(std::shared_ptr<gui_settings> gui_settings,
connect(this, &QTableWidget::cellClicked, this, [=, this](int row, int column) { connect(this, &QTableWidget::cellClicked, this, [=, this](int row, int column) {
if (column == 2 && m_game_info->m_games[row].compatibility.issue_number != "") { if (column == 2 && m_game_info->m_games[row].compatibility.issue_number != "") {
auto url_issues = "https://github.com/shadps4-emu/shadps4-game-compatibility/issues/"; auto url_issues =
"https://github.com/shadps4-compatibility/shadps4-game-compatibility/issues/";
QDesktopServices::openUrl( QDesktopServices::openUrl(
QUrl(url_issues + m_game_info->m_games[row].compatibility.issue_number)); QUrl(url_issues + m_game_info->m_games[row].compatibility.issue_number));
} else if (column == 10) { } else if (column == 10) {

View File

@ -581,7 +581,7 @@ public:
if (selected == viewCompatibilityReport) { if (selected == viewCompatibilityReport) {
if (m_games[itemID].compatibility.issue_number != "") { if (m_games[itemID].compatibility.issue_number != "") {
auto url_issues = auto url_issues =
"https://github.com/shadps4-emu/shadps4-game-compatibility/issues/"; "https://github.com/shadps4-compatibility/shadps4-game-compatibility/issues/";
QDesktopServices::openUrl( QDesktopServices::openUrl(
QUrl(url_issues + m_games[itemID].compatibility.issue_number)); QUrl(url_issues + m_games[itemID].compatibility.issue_number));
} }
@ -589,8 +589,8 @@ public:
if (selected == submitCompatibilityReport) { if (selected == submitCompatibilityReport) {
if (m_games[itemID].compatibility.issue_number == "") { if (m_games[itemID].compatibility.issue_number == "") {
QUrl url = QUrl url = QUrl("https://github.com/shadps4-compatibility/"
QUrl("https://github.com/shadps4-emu/shadps4-game-compatibility/issues/new"); "shadps4-game-compatibility/issues/new");
QUrlQuery query; QUrlQuery query;
query.addQueryItem("template", QString("game_compatibility.yml")); query.addQueryItem("template", QString("game_compatibility.yml"));
query.addQueryItem( query.addQueryItem(
@ -605,7 +605,7 @@ public:
QDesktopServices::openUrl(url); QDesktopServices::openUrl(url);
} else { } else {
auto url_issues = auto url_issues =
"https://github.com/shadps4-emu/shadps4-game-compatibility/issues/"; "https://github.com/shadps4-compatibility/shadps4-game-compatibility/issues/";
QDesktopServices::openUrl( QDesktopServices::openUrl(
QUrl(url_issues + m_games[itemID].compatibility.issue_number)); QUrl(url_issues + m_games[itemID].compatibility.issue_number));
} }

View File

@ -37,6 +37,7 @@ const gui_value gl_showBackgroundImage = gui_value(game_list, "showBackgroundIma
const gui_value gl_backgroundImageOpacity = gui_value(game_list, "backgroundImageOpacity", 50); const gui_value gl_backgroundImageOpacity = gui_value(game_list, "backgroundImageOpacity", 50);
const gui_value gl_playBackgroundMusic = gui_value(game_list, "playBackgroundMusic", true); const gui_value gl_playBackgroundMusic = gui_value(game_list, "playBackgroundMusic", true);
const gui_value gl_backgroundMusicVolume = gui_value(game_list, "backgroundMusicVolume", 50); const gui_value gl_backgroundMusicVolume = gui_value(game_list, "backgroundMusicVolume", 50);
const gui_value gl_VolumeSlider = gui_value(game_list, "volumeSlider", 100);
// game grid settings // game grid settings
const gui_value gg_icon_size = gui_value(game_grid, "icon_size", 69); const gui_value gg_icon_size = gui_value(game_grid, "icon_size", 69);

View File

@ -11,6 +11,7 @@
#include "common/config.h" #include "common/config.h"
#include "common/scm_rev.h" #include "common/scm_rev.h"
#include "core/libraries/audio/audioout.h"
#include "qt_gui/compatibility_info.h" #include "qt_gui/compatibility_info.h"
#ifdef ENABLE_DISCORD_RPC #ifdef ENABLE_DISCORD_RPC
#include "common/discord_rpc_handler.h" #include "common/discord_rpc_handler.h"
@ -68,6 +69,7 @@ QMap<QString, QString> chooseHomeTabMap;
int backgroundImageOpacitySlider_backup; int backgroundImageOpacitySlider_backup;
int bgm_volume_backup; int bgm_volume_backup;
int volume_slider_backup;
static std::vector<QString> m_physical_devices; static std::vector<QString> m_physical_devices;
@ -149,9 +151,11 @@ SettingsDialog::SettingsDialog(std::shared_ptr<gui_settings> gui_settings,
} else if (button == ui->buttonBox->button(QDialogButtonBox::Close)) { } else if (button == ui->buttonBox->button(QDialogButtonBox::Close)) {
ui->backgroundImageOpacitySlider->setValue(backgroundImageOpacitySlider_backup); ui->backgroundImageOpacitySlider->setValue(backgroundImageOpacitySlider_backup);
emit BackgroundOpacityChanged(backgroundImageOpacitySlider_backup); emit BackgroundOpacityChanged(backgroundImageOpacitySlider_backup);
ui->horizontalVolumeSlider->setValue(volume_slider_backup);
Config::setVolumeSlider(volume_slider_backup);
ui->BGMVolumeSlider->setValue(bgm_volume_backup); ui->BGMVolumeSlider->setValue(bgm_volume_backup);
BackgroundMusicPlayer::getInstance().setVolume(bgm_volume_backup); BackgroundMusicPlayer::getInstance().setVolume(bgm_volume_backup);
ResetInstallFolders(); SyncRealTimeWidgetstoConfig();
} }
if (Common::Log::IsActive()) { if (Common::Log::IsActive()) {
Common::Log::Filter filter; Common::Log::Filter filter;
@ -170,6 +174,12 @@ SettingsDialog::SettingsDialog(std::shared_ptr<gui_settings> gui_settings,
// GENERAL TAB // GENERAL TAB
{ {
connect(ui->horizontalVolumeSlider, &QSlider::valueChanged, this, [this](int value) {
VolumeSliderChange(value);
Config::setVolumeSlider(value);
Libraries::AudioOut::AdjustVol();
});
#ifdef ENABLE_UPDATER #ifdef ENABLE_UPDATER
#if (QT_VERSION < QT_VERSION_CHECK(6, 7, 0)) #if (QT_VERSION < QT_VERSION_CHECK(6, 7, 0))
connect(ui->updateCheckBox, &QCheckBox::stateChanged, this, [this](int state) { connect(ui->updateCheckBox, &QCheckBox::stateChanged, this, [this](int state) {
@ -398,6 +408,8 @@ void SettingsDialog::closeEvent(QCloseEvent* event) {
if (!is_saving) { if (!is_saving) {
ui->backgroundImageOpacitySlider->setValue(backgroundImageOpacitySlider_backup); ui->backgroundImageOpacitySlider->setValue(backgroundImageOpacitySlider_backup);
emit BackgroundOpacityChanged(backgroundImageOpacitySlider_backup); emit BackgroundOpacityChanged(backgroundImageOpacitySlider_backup);
ui->horizontalVolumeSlider->setValue(volume_slider_backup);
Config::setVolumeSlider(volume_slider_backup);
ui->BGMVolumeSlider->setValue(bgm_volume_backup); ui->BGMVolumeSlider->setValue(bgm_volume_backup);
BackgroundMusicPlayer::getInstance().setVolume(bgm_volume_backup); BackgroundMusicPlayer::getInstance().setVolume(bgm_volume_backup);
} }
@ -463,6 +475,8 @@ void SettingsDialog::LoadValuesFromConfig() {
ui->radioButton_Bottom->setChecked(side == "bottom"); ui->radioButton_Bottom->setChecked(side == "bottom");
ui->BGMVolumeSlider->setValue(m_gui_settings->GetValue(gui::gl_backgroundMusicVolume).toInt()); ui->BGMVolumeSlider->setValue(m_gui_settings->GetValue(gui::gl_backgroundMusicVolume).toInt());
ui->horizontalVolumeSlider->setValue(m_gui_settings->GetValue(gui::gl_VolumeSlider).toInt());
ui->volumeText->setText(QString::number(ui->horizontalVolumeSlider->sliderPosition()) + "%");
ui->discordRPCCheckbox->setChecked( ui->discordRPCCheckbox->setChecked(
toml::find_or<bool>(data, "General", "enableDiscordRPC", true)); toml::find_or<bool>(data, "General", "enableDiscordRPC", true));
QString translatedText_FullscreenMode = QString translatedText_FullscreenMode =
@ -532,7 +546,7 @@ void SettingsDialog::LoadValuesFromConfig() {
toml::find_or<bool>(data, "Input", "isMotionControlsEnabled", true)); toml::find_or<bool>(data, "Input", "isMotionControlsEnabled", true));
ui->removeFolderButton->setEnabled(!ui->gameFoldersListWidget->selectedItems().isEmpty()); ui->removeFolderButton->setEnabled(!ui->gameFoldersListWidget->selectedItems().isEmpty());
ResetInstallFolders(); SyncRealTimeWidgetstoConfig();
ui->backgroundImageOpacitySlider->setValue( ui->backgroundImageOpacitySlider->setValue(
m_gui_settings->GetValue(gui::gl_backgroundImageOpacity).toInt()); m_gui_settings->GetValue(gui::gl_backgroundImageOpacity).toInt());
ui->showBackgroundImageCheckBox->setChecked( ui->showBackgroundImageCheckBox->setChecked(
@ -541,6 +555,7 @@ void SettingsDialog::LoadValuesFromConfig() {
backgroundImageOpacitySlider_backup = backgroundImageOpacitySlider_backup =
m_gui_settings->GetValue(gui::gl_backgroundImageOpacity).toInt(); m_gui_settings->GetValue(gui::gl_backgroundImageOpacity).toInt();
bgm_volume_backup = m_gui_settings->GetValue(gui::gl_backgroundMusicVolume).toInt(); bgm_volume_backup = m_gui_settings->GetValue(gui::gl_backgroundMusicVolume).toInt();
volume_slider_backup = m_gui_settings->GetValue(gui::gl_VolumeSlider).toInt();
} }
void SettingsDialog::InitializeEmulatorLanguages() { void SettingsDialog::InitializeEmulatorLanguages() {
@ -599,6 +614,10 @@ void SettingsDialog::OnCursorStateChanged(s16 index) {
} }
} }
void SettingsDialog::VolumeSliderChange(int value) {
ui->volumeText->setText(QString::number(ui->horizontalVolumeSlider->sliderPosition()) + "%");
}
int SettingsDialog::exec() { int SettingsDialog::exec() {
return QDialog::exec(); return QDialog::exec();
} }
@ -719,7 +738,6 @@ bool SettingsDialog::eventFilter(QObject* obj, QEvent* event) {
if (qobject_cast<QWidget*>(obj)) { if (qobject_cast<QWidget*>(obj)) {
bool hovered = (event->type() == QEvent::Enter); bool hovered = (event->type() == QEvent::Enter);
QString elementName = obj->objectName(); QString elementName = obj->objectName();
if (hovered) { if (hovered) {
updateNoteTextEdit(elementName); updateNoteTextEdit(elementName);
} else { } else {
@ -759,6 +777,7 @@ void SettingsDialog::UpdateSettings() {
Config::setCursorState(ui->hideCursorComboBox->currentIndex()); Config::setCursorState(ui->hideCursorComboBox->currentIndex());
Config::setCursorHideTimeout(ui->idleTimeoutSpinBox->value()); Config::setCursorHideTimeout(ui->idleTimeoutSpinBox->value());
Config::setGpuId(ui->graphicsAdapterBox->currentIndex() - 1); Config::setGpuId(ui->graphicsAdapterBox->currentIndex() - 1);
m_gui_settings->SetValue(gui::gl_VolumeSlider, ui->horizontalVolumeSlider->value());
m_gui_settings->SetValue(gui::gl_backgroundMusicVolume, ui->BGMVolumeSlider->value()); m_gui_settings->SetValue(gui::gl_backgroundMusicVolume, ui->BGMVolumeSlider->value());
Config::setLanguage(languageIndexes[ui->consoleLanguageComboBox->currentIndex()]); Config::setLanguage(languageIndexes[ui->consoleLanguageComboBox->currentIndex()]);
Config::setEnableDiscordRPC(ui->discordRPCCheckbox->isChecked()); Config::setEnableDiscordRPC(ui->discordRPCCheckbox->isChecked());
@ -815,9 +834,10 @@ void SettingsDialog::UpdateSettings() {
#endif #endif
BackgroundMusicPlayer::getInstance().setVolume(ui->BGMVolumeSlider->value()); BackgroundMusicPlayer::getInstance().setVolume(ui->BGMVolumeSlider->value());
Config::setVolumeSlider(ui->horizontalVolumeSlider->value());
} }
void SettingsDialog::ResetInstallFolders() { void SettingsDialog::SyncRealTimeWidgetstoConfig() {
ui->gameFoldersListWidget->clear(); ui->gameFoldersListWidget->clear();
std::filesystem::path userdir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); std::filesystem::path userdir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
@ -865,6 +885,7 @@ void SettingsDialog::setDefaultValues() {
m_gui_settings->SetValue(gui::gl_backgroundImageOpacity, 50); m_gui_settings->SetValue(gui::gl_backgroundImageOpacity, 50);
m_gui_settings->SetValue(gui::gl_playBackgroundMusic, false); m_gui_settings->SetValue(gui::gl_playBackgroundMusic, false);
m_gui_settings->SetValue(gui::gl_backgroundMusicVolume, 50); m_gui_settings->SetValue(gui::gl_backgroundMusicVolume, 50);
m_gui_settings->SetValue(gui::gl_VolumeSlider, 100);
m_gui_settings->SetValue(gui::gen_checkForUpdates, false); m_gui_settings->SetValue(gui::gen_checkForUpdates, false);
m_gui_settings->SetValue(gui::gen_showChangeLog, false); m_gui_settings->SetValue(gui::gen_showChangeLog, false);
if (Common::g_is_release) { if (Common::g_is_release) {

View File

@ -39,12 +39,13 @@ signals:
private: private:
void LoadValuesFromConfig(); void LoadValuesFromConfig();
void UpdateSettings(); void UpdateSettings();
void ResetInstallFolders(); void SyncRealTimeWidgetstoConfig();
void InitializeEmulatorLanguages(); void InitializeEmulatorLanguages();
void OnLanguageChanged(int index); void OnLanguageChanged(int index);
void OnCursorStateChanged(s16 index); void OnCursorStateChanged(s16 index);
void closeEvent(QCloseEvent* event) override; void closeEvent(QCloseEvent* event) override;
void setDefaultValues(); void setDefaultValues();
void VolumeSliderChange(int value);
std::unique_ptr<Ui::SettingsDialog> ui; std::unique_ptr<Ui::SettingsDialog> ui;

View File

@ -59,7 +59,7 @@
</size> </size>
</property> </property>
<property name="currentIndex"> <property name="currentIndex">
<number>5</number> <number>0</number>
</property> </property>
<widget class="QScrollArea" name="generalTab"> <widget class="QScrollArea" name="generalTab">
<property name="widgetResizable"> <property name="widgetResizable">
@ -73,8 +73,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>946</width> <width>944</width>
<height>536</height> <height>537</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="generalTabVLayout" stretch="0"> <layout class="QVBoxLayout" name="generalTabVLayout" stretch="0">
@ -86,148 +86,7 @@
<property name="spacing"> <property name="spacing">
<number>6</number> <number>6</number>
</property> </property>
<item row="0" column="1"> <item row="2" column="0">
<layout class="QVBoxLayout" name="emulatorTabLayoutMiddle">
<property name="leftMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="emulatorSettingsGroupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Emulator</string>
</property>
<property name="flat">
<bool>false</bool>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="additionalSettingsVLayout" stretch="0">
<property name="spacing">
<number>6</number>
</property>
<property name="leftMargin">
<number>9</number>
</property>
<property name="topMargin">
<number>9</number>
</property>
<property name="rightMargin">
<number>9</number>
</property>
<property name="bottomMargin">
<number>9</number>
</property>
<item>
<layout class="QVBoxLayout" name="emulatorverticalLayout">
<property name="spacing">
<number>10</number>
</property>
<item>
<widget class="QCheckBox" name="showSplashCheckBox">
<property name="text">
<string>Show Splash</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="discordRPCCheckbox">
<property name="text">
<string>Enable Discord Rich Presence</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item row="0" column="0">
<layout class="QVBoxLayout" name="systemTabLayoutLeft">
<property name="spacing">
<number>6</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="SystemSettings">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="sizeIncrement">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="title">
<string>System</string>
</property>
<layout class="QVBoxLayout" name="emuSettingsLayout">
<property name="bottomMargin">
<number>70</number>
</property>
<item>
<widget class="QGroupBox" name="consoleLanguageGroupBox">
<property name="title">
<string>Console Language</string>
</property>
<layout class="QVBoxLayout" name="settingsLayout">
<item>
<widget class="QComboBox" name="consoleLanguageComboBox"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="emulatorLanguageGroupBox">
<property name="title">
<string>Emulator Language</string>
</property>
<layout class="QVBoxLayout" name="langSettingsLayout">
<item>
<widget class="QComboBox" name="emulatorLanguageComboBox"/>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item row="1" column="1">
<spacer name="verticalSpacer_4">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="0">
<spacer name="verticalSpacer_3"> <spacer name="verticalSpacer_3">
<property name="orientation"> <property name="orientation">
<enum>Qt::Orientation::Vertical</enum> <enum>Qt::Orientation::Vertical</enum>
@ -240,8 +99,8 @@
</property> </property>
</spacer> </spacer>
</item> </item>
<item row="1" column="2"> <item row="2" column="2">
<spacer name="verticalSpacer_5"> <spacer name="verticalSpacer_4">
<property name="orientation"> <property name="orientation">
<enum>Qt::Orientation::Vertical</enum> <enum>Qt::Orientation::Vertical</enum>
</property> </property>
@ -253,7 +112,7 @@
</property> </property>
</spacer> </spacer>
</item> </item>
<item row="0" column="2"> <item row="0" column="3">
<layout class="QVBoxLayout" name="updaterTabLayoutLeft"> <layout class="QVBoxLayout" name="updaterTabLayoutLeft">
<property name="spacing"> <property name="spacing">
<number>6</number> <number>6</number>
@ -427,6 +286,228 @@
</item> </item>
</layout> </layout>
</item> </item>
<item row="0" column="0">
<layout class="QVBoxLayout" name="systemTabLayoutLeft">
<property name="spacing">
<number>6</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="SystemSettings">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="sizeIncrement">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="title">
<string>System</string>
</property>
<layout class="QVBoxLayout" name="emuSettingsLayout">
<property name="bottomMargin">
<number>70</number>
</property>
<item>
<widget class="QGroupBox" name="consoleLanguageGroupBox">
<property name="title">
<string>Console Language</string>
</property>
<layout class="QVBoxLayout" name="settingsLayout">
<item>
<widget class="QComboBox" name="consoleLanguageComboBox"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="emulatorLanguageGroupBox">
<property name="title">
<string>Emulator Language</string>
</property>
<layout class="QVBoxLayout" name="langSettingsLayout">
<item>
<widget class="QComboBox" name="emulatorLanguageComboBox"/>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QGroupBox" name="volumeSliderElement">
<property name="title">
<string>Volume</string>
</property>
<layout class="QVBoxLayout" name="volumeLayout">
<item>
<widget class="QScrollArea" name="scrollArea">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string/>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents_2">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>414</width>
<height>69</height>
</rect>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QLineEdit" name="volumeText">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>60</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>100%</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="horizontalVolumeSlider">
<property name="focusPolicy">
<enum>Qt::FocusPolicy::StrongFocus</enum>
</property>
<property name="maximum">
<number>500</number>
</property>
<property name="value">
<number>100</number>
</property>
<property name="sliderPosition">
<number>100</number>
</property>
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="2">
<layout class="QVBoxLayout" name="emulatorTabLayoutMiddle">
<property name="leftMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="emulatorSettingsGroupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Emulator</string>
</property>
<property name="flat">
<bool>false</bool>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="additionalSettingsVLayout" stretch="0">
<property name="spacing">
<number>6</number>
</property>
<property name="leftMargin">
<number>9</number>
</property>
<property name="topMargin">
<number>9</number>
</property>
<property name="rightMargin">
<number>9</number>
</property>
<property name="bottomMargin">
<number>9</number>
</property>
<item>
<layout class="QVBoxLayout" name="emulatorverticalLayout">
<property name="spacing">
<number>10</number>
</property>
<item>
<widget class="QCheckBox" name="showSplashCheckBox">
<property name="text">
<string>Show Splash</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="discordRPCCheckbox">
<property name="text">
<string>Enable Discord Rich Presence</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item row="2" column="3">
<spacer name="verticalSpacer_5">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout> </layout>
</item> </item>
</layout> </layout>
@ -444,8 +525,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>946</width> <width>944</width>
<height>536</height> <height>537</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="guiTabVLayout" stretch="0"> <layout class="QVBoxLayout" name="guiTabVLayout" stretch="0">
@ -700,7 +781,7 @@
<item> <item>
<widget class="QLabel" name="label_Volume"> <widget class="QLabel" name="label_Volume">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed"> <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
@ -893,8 +974,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>946</width> <width>944</width>
<height>536</height> <height>537</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="graphicsTabVLayout" stretch="0,0"> <layout class="QVBoxLayout" name="graphicsTabVLayout" stretch="0,0">
@ -1188,8 +1269,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>946</width> <width>944</width>
<height>536</height> <height>537</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="userTabVLayout" stretch="0,0,1"> <layout class="QVBoxLayout" name="userTabVLayout" stretch="0,0,1">
@ -1430,8 +1511,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>946</width> <width>944</width>
<height>536</height> <height>537</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="inputTabVLayout" stretch="0,0"> <layout class="QVBoxLayout" name="inputTabVLayout" stretch="0,0">
@ -1684,8 +1765,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>946</width> <width>944</width>
<height>536</height> <height>537</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="pathsTabLayout"> <layout class="QVBoxLayout" name="pathsTabLayout">
@ -1826,8 +1907,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>946</width> <width>944</width>
<height>536</height> <height>537</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="debugTabVLayout" stretch="0,0"> <layout class="QVBoxLayout" name="debugTabVLayout" stretch="0,0">

View File

@ -268,6 +268,7 @@ Id EmitBufferAtomicFMin32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addre
const auto sign_bit_set = const auto sign_bit_set =
ctx.OpBitFieldUExtract(ctx.U32[1], u32_value, ctx.ConstU32(31u), ctx.ConstU32(1u)); ctx.OpBitFieldUExtract(ctx.U32[1], u32_value, ctx.ConstU32(31u), ctx.ConstU32(1u));
// FIXME this needs control flow because it currently executes both atomics
const auto result = ctx.OpSelect( const auto result = ctx.OpSelect(
ctx.F32[1], sign_bit_set, ctx.F32[1], sign_bit_set,
EmitBitCastF32U32(ctx, EmitBufferAtomicUMax32(ctx, inst, handle, address, u32_value)), EmitBitCastF32U32(ctx, EmitBufferAtomicUMax32(ctx, inst, handle, address, u32_value)),
@ -302,6 +303,7 @@ Id EmitBufferAtomicFMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addre
const auto sign_bit_set = const auto sign_bit_set =
ctx.OpBitFieldUExtract(ctx.U32[1], u32_value, ctx.ConstU32(31u), ctx.ConstU32(1u)); ctx.OpBitFieldUExtract(ctx.U32[1], u32_value, ctx.ConstU32(31u), ctx.ConstU32(1u));
// FIXME this needs control flow because it currently executes both atomics
const auto result = ctx.OpSelect( const auto result = ctx.OpSelect(
ctx.F32[1], sign_bit_set, ctx.F32[1], sign_bit_set,
EmitBitCastF32U32(ctx, EmitBufferAtomicUMin32(ctx, inst, handle, address, u32_value)), EmitBitCastF32U32(ctx, EmitBufferAtomicUMin32(ctx, inst, handle, address, u32_value)),

View File

@ -7,60 +7,32 @@
namespace Shader::Backend::SPIRV { namespace Shader::Backend::SPIRV {
namespace { namespace {
Id ExtractU16(EmitContext& ctx, Id value) { Id ExtractU16(EmitContext& ctx, Id value) {
if (ctx.profile.support_int16) { return ctx.OpUConvert(ctx.U16, value);
return ctx.OpUConvert(ctx.U16, value);
} else {
return ctx.OpBitFieldUExtract(ctx.U32[1], value, ctx.u32_zero_value, ctx.ConstU32(16u));
}
} }
Id ExtractS16(EmitContext& ctx, Id value) { Id ExtractS16(EmitContext& ctx, Id value) {
if (ctx.profile.support_int16) { return ctx.OpSConvert(ctx.S16, value);
return ctx.OpSConvert(ctx.S16, value);
} else {
return ctx.OpBitFieldSExtract(ctx.U32[1], value, ctx.u32_zero_value, ctx.ConstU32(16u));
}
} }
Id ExtractU8(EmitContext& ctx, Id value) { Id ExtractU8(EmitContext& ctx, Id value) {
if (ctx.profile.support_int8) { return ctx.OpUConvert(ctx.U8, value);
return ctx.OpUConvert(ctx.U8, value);
} else {
return ctx.OpBitFieldUExtract(ctx.U32[1], value, ctx.u32_zero_value, ctx.ConstU32(8u));
}
} }
Id ExtractS8(EmitContext& ctx, Id value) { Id ExtractS8(EmitContext& ctx, Id value) {
if (ctx.profile.support_int8) { return ctx.OpSConvert(ctx.S8, value);
return ctx.OpSConvert(ctx.S8, value);
} else {
return ctx.OpBitFieldSExtract(ctx.U32[1], value, ctx.u32_zero_value, ctx.ConstU32(8u));
}
} }
} // Anonymous namespace } // Anonymous namespace
Id EmitConvertS16F16(EmitContext& ctx, Id value) { Id EmitConvertS16F16(EmitContext& ctx, Id value) {
if (ctx.profile.support_int16) { return ctx.OpSConvert(ctx.U32[1], ctx.OpConvertFToS(ctx.U16, value));
return ctx.OpSConvert(ctx.U32[1], ctx.OpConvertFToS(ctx.U16, value));
} else {
return ExtractS16(ctx, ctx.OpConvertFToS(ctx.U32[1], value));
}
} }
Id EmitConvertS16F32(EmitContext& ctx, Id value) { Id EmitConvertS16F32(EmitContext& ctx, Id value) {
if (ctx.profile.support_int16) { return ctx.OpSConvert(ctx.U32[1], ctx.OpConvertFToS(ctx.U16, value));
return ctx.OpSConvert(ctx.U32[1], ctx.OpConvertFToS(ctx.U16, value));
} else {
return ExtractS16(ctx, ctx.OpConvertFToS(ctx.U32[1], value));
}
} }
Id EmitConvertS16F64(EmitContext& ctx, Id value) { Id EmitConvertS16F64(EmitContext& ctx, Id value) {
if (ctx.profile.support_int16) { return ctx.OpSConvert(ctx.U32[1], ctx.OpConvertFToS(ctx.U16, value));
return ctx.OpSConvert(ctx.U32[1], ctx.OpConvertFToS(ctx.U16, value));
} else {
return ExtractS16(ctx, ctx.OpConvertFToS(ctx.U32[1], value));
}
} }
Id EmitConvertS32F16(EmitContext& ctx, Id value) { Id EmitConvertS32F16(EmitContext& ctx, Id value) {
@ -88,27 +60,15 @@ Id EmitConvertS64F64(EmitContext& ctx, Id value) {
} }
Id EmitConvertU16F16(EmitContext& ctx, Id value) { Id EmitConvertU16F16(EmitContext& ctx, Id value) {
if (ctx.profile.support_int16) { return ctx.OpUConvert(ctx.U32[1], ctx.OpConvertFToU(ctx.U16, value));
return ctx.OpUConvert(ctx.U32[1], ctx.OpConvertFToU(ctx.U16, value));
} else {
return ExtractU16(ctx, ctx.OpConvertFToU(ctx.U32[1], value));
}
} }
Id EmitConvertU16F32(EmitContext& ctx, Id value) { Id EmitConvertU16F32(EmitContext& ctx, Id value) {
if (ctx.profile.support_int16) { return ctx.OpUConvert(ctx.U32[1], ctx.OpConvertFToU(ctx.U16, value));
return ctx.OpUConvert(ctx.U32[1], ctx.OpConvertFToU(ctx.U16, value));
} else {
return ExtractU16(ctx, ctx.OpConvertFToU(ctx.U32[1], value));
}
} }
Id EmitConvertU16F64(EmitContext& ctx, Id value) { Id EmitConvertU16F64(EmitContext& ctx, Id value) {
if (ctx.profile.support_int16) { return ctx.OpUConvert(ctx.U32[1], ctx.OpConvertFToU(ctx.U16, value));
return ctx.OpUConvert(ctx.U32[1], ctx.OpConvertFToU(ctx.U16, value));
} else {
return ExtractU16(ctx, ctx.OpConvertFToU(ctx.U32[1], value));
}
} }
Id EmitConvertU32F16(EmitContext& ctx, Id value) { Id EmitConvertU32F16(EmitContext& ctx, Id value) {
@ -271,4 +231,12 @@ Id EmitConvertU32U8(EmitContext& ctx, Id value) {
return ctx.OpUConvert(ctx.U32[1], value); return ctx.OpUConvert(ctx.U32[1], value);
} }
Id EmitConvertS32S8(EmitContext& ctx, Id value) {
return ctx.OpSConvert(ctx.U32[1], value);
}
Id EmitConvertS32S16(EmitContext& ctx, Id value) {
return ctx.OpSConvert(ctx.U32[1], value);
}
} // namespace Shader::Backend::SPIRV } // namespace Shader::Backend::SPIRV

View File

@ -488,6 +488,8 @@ Id EmitConvertU16U32(EmitContext& ctx, Id value);
Id EmitConvertU32U16(EmitContext& ctx, Id value); Id EmitConvertU32U16(EmitContext& ctx, Id value);
Id EmitConvertU8U32(EmitContext& ctx, Id value); Id EmitConvertU8U32(EmitContext& ctx, Id value);
Id EmitConvertU32U8(EmitContext& ctx, Id value); Id EmitConvertU32U8(EmitContext& ctx, Id value);
Id EmitConvertS32S8(EmitContext& ctx, Id value);
Id EmitConvertS32S16(EmitContext& ctx, Id value);
Id EmitImageSampleRaw(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address1, Id address2, Id EmitImageSampleRaw(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address1, Id address2,
Id address3, Id address4); Id address3, Id address4);

View File

@ -117,7 +117,9 @@ void EmitContext::DefineArithmeticTypes() {
void_id = Name(TypeVoid(), "void_id"); void_id = Name(TypeVoid(), "void_id");
U1[1] = Name(TypeBool(), "bool_id"); U1[1] = Name(TypeBool(), "bool_id");
U8 = Name(TypeUInt(8), "u8_id"); U8 = Name(TypeUInt(8), "u8_id");
S8 = Name(TypeSInt(8), "i8_id");
U16 = Name(TypeUInt(16), "u16_id"); U16 = Name(TypeUInt(16), "u16_id");
S16 = Name(TypeSInt(16), "i16_id");
if (info.uses_fp16) { if (info.uses_fp16) {
F16[1] = Name(TypeFloat(16), "f16_id"); F16[1] = Name(TypeFloat(16), "f16_id");
U16 = Name(TypeUInt(16), "u16_id"); U16 = Name(TypeUInt(16), "u16_id");

View File

@ -14,7 +14,7 @@ CopyShaderData ParseCopyShader(std::span<const u32> code) {
constexpr u32 token_mov_vcchi = 0xBEEB03FF; constexpr u32 token_mov_vcchi = 0xBEEB03FF;
ASSERT_MSG(code[0] == token_mov_vcchi, "First instruction is not s_mov_b32 vcc_hi, #imm"); ASSERT_MSG(code[0] == token_mov_vcchi, "First instruction is not s_mov_b32 vcc_hi, #imm");
std::array<s32, 32> offsets{}; std::array<s32, 64> offsets{};
offsets.fill(-1); offsets.fill(-1);
std::array<s32, 256> sources{}; std::array<s32, 256> sources{};
@ -52,6 +52,8 @@ CopyShaderData ParseCopyShader(std::span<const u32> code) {
break; break;
} }
case Gcn::Opcode::BUFFER_LOAD_DWORD: { case Gcn::Opcode::BUFFER_LOAD_DWORD: {
ASSERT_MSG(inst.src[1].code < offsets.size(),
"offsets array for geometry shaders is too short");
offsets[inst.src[1].code] = inst.control.mubuf.offset; offsets[inst.src[1].code] = inst.control.mubuf.offset;
if (inst.src[3].field != Gcn::OperandField::ConstZero) { if (inst.src[3].field != Gcn::OperandField::ConstZero) {
const u32 index = inst.src[3].code; const u32 index = inst.src[3].code;
@ -65,7 +67,7 @@ CopyShaderData ParseCopyShader(std::span<const u32> code) {
} }
} }
if (last_attr != IR::Attribute::Position0) { if (!IsPosition(last_attr)) {
data.num_attrs = static_cast<u32>(last_attr) - static_cast<u32>(IR::Attribute::Param0) + 1; data.num_attrs = static_cast<u32>(last_attr) - static_cast<u32>(IR::Attribute::Param0) + 1;
const auto it = data.attr_map.begin(); const auto it = data.attr_map.begin();
const u32 comp_stride = std::next(it)->first - it->first; const u32 comp_stride = std::next(it)->first - it->first;

View File

@ -281,9 +281,10 @@ public:
// Buffer Memory // Buffer Memory
// MUBUF / MTBUF // MUBUF / MTBUF
void BUFFER_LOAD(u32 num_dwords, bool is_inst_typed, bool is_buffer_typed, const GcnInst& inst); void BUFFER_LOAD(u32 num_dwords, bool is_inst_typed, bool is_buffer_typed, const GcnInst& inst,
void BUFFER_STORE(u32 num_dwords, bool is_inst_typed, bool is_buffer_typed, u32 scalar_width = 32, bool is_signed = false);
const GcnInst& inst); void BUFFER_STORE(u32 num_dwords, bool is_inst_typed, bool is_buffer_typed, const GcnInst& inst,
u32 scalar_width = 32);
template <typename T = IR::U32> template <typename T = IR::U32>
void BUFFER_ATOMIC(AtomicOp op, const GcnInst& inst); void BUFFER_ATOMIC(AtomicOp op, const GcnInst& inst);

View File

@ -28,6 +28,15 @@ void Translator::EmitVectorMemory(const GcnInst& inst) {
case Opcode::BUFFER_LOAD_FORMAT_XYZW: case Opcode::BUFFER_LOAD_FORMAT_XYZW:
return BUFFER_LOAD(4, false, true, inst); return BUFFER_LOAD(4, false, true, inst);
case Opcode::BUFFER_LOAD_UBYTE:
return BUFFER_LOAD(1, false, false, inst, 8, false);
case Opcode::BUFFER_LOAD_SBYTE:
return BUFFER_LOAD(1, false, false, inst, 8, true);
case Opcode::BUFFER_LOAD_USHORT:
return BUFFER_LOAD(1, false, false, inst, 16, false);
case Opcode::BUFFER_LOAD_SSHORT:
return BUFFER_LOAD(1, false, false, inst, 16, true);
case Opcode::BUFFER_LOAD_DWORD: case Opcode::BUFFER_LOAD_DWORD:
return BUFFER_LOAD(1, false, false, inst); return BUFFER_LOAD(1, false, false, inst);
case Opcode::BUFFER_LOAD_DWORDX2: case Opcode::BUFFER_LOAD_DWORDX2:
@ -56,6 +65,11 @@ void Translator::EmitVectorMemory(const GcnInst& inst) {
case Opcode::TBUFFER_STORE_FORMAT_XYZW: case Opcode::TBUFFER_STORE_FORMAT_XYZW:
return BUFFER_STORE(4, true, false, inst); return BUFFER_STORE(4, true, false, inst);
case Opcode::BUFFER_STORE_BYTE:
return BUFFER_STORE(1, false, false, inst, 8);
case Opcode::BUFFER_STORE_SHORT:
return BUFFER_STORE(1, false, false, inst, 16);
case Opcode::BUFFER_STORE_DWORD: case Opcode::BUFFER_STORE_DWORD:
return BUFFER_STORE(1, false, false, inst); return BUFFER_STORE(1, false, false, inst);
case Opcode::BUFFER_STORE_DWORDX2: case Opcode::BUFFER_STORE_DWORDX2:
@ -186,7 +200,7 @@ void Translator::EmitVectorMemory(const GcnInst& inst) {
} }
void Translator::BUFFER_LOAD(u32 num_dwords, bool is_inst_typed, bool is_buffer_typed, void Translator::BUFFER_LOAD(u32 num_dwords, bool is_inst_typed, bool is_buffer_typed,
const GcnInst& inst) { const GcnInst& inst, u32 scalar_width, bool is_signed) {
const auto& mubuf = inst.control.mubuf; const auto& mubuf = inst.control.mubuf;
const bool is_ring = mubuf.glc && mubuf.slc; const bool is_ring = mubuf.glc && mubuf.slc;
const IR::VectorReg vaddr{inst.src[0].code}; const IR::VectorReg vaddr{inst.src[0].code};
@ -242,7 +256,26 @@ void Translator::BUFFER_LOAD(u32 num_dwords, bool is_inst_typed, bool is_buffer_
ir.SetVectorReg(dst_reg + i, IR::F32{ir.CompositeExtract(value, i)}); ir.SetVectorReg(dst_reg + i, IR::F32{ir.CompositeExtract(value, i)});
} }
} else { } else {
const IR::Value value = ir.LoadBufferU32(num_dwords, handle, address, buffer_info); IR::Value value;
switch (scalar_width) {
case 8: {
IR::U8 byte_val = ir.LoadBufferU8(handle, address, buffer_info);
value = is_signed ? ir.SConvert(32, byte_val) : ir.UConvert(32, byte_val);
break;
}
case 16: {
IR::U16 short_val = ir.LoadBufferU16(handle, address, buffer_info);
value = is_signed ? ir.SConvert(32, short_val) : ir.UConvert(32, short_val);
break;
}
case 32:
value = ir.LoadBufferU32(num_dwords, handle, address, buffer_info);
break;
default:
UNREACHABLE();
}
if (num_dwords == 1) { if (num_dwords == 1) {
ir.SetVectorReg(dst_reg, IR::U32{value}); ir.SetVectorReg(dst_reg, IR::U32{value});
return; return;
@ -254,7 +287,7 @@ void Translator::BUFFER_LOAD(u32 num_dwords, bool is_inst_typed, bool is_buffer_
} }
void Translator::BUFFER_STORE(u32 num_dwords, bool is_inst_typed, bool is_buffer_typed, void Translator::BUFFER_STORE(u32 num_dwords, bool is_inst_typed, bool is_buffer_typed,
const GcnInst& inst) { const GcnInst& inst, u32 scalar_width) {
const auto& mubuf = inst.control.mubuf; const auto& mubuf = inst.control.mubuf;
const bool is_ring = mubuf.glc && mubuf.slc; const bool is_ring = mubuf.glc && mubuf.slc;
const IR::VectorReg vaddr{inst.src[0].code}; const IR::VectorReg vaddr{inst.src[0].code};
@ -314,8 +347,23 @@ void Translator::BUFFER_STORE(u32 num_dwords, bool is_inst_typed, bool is_buffer
} }
ir.StoreBufferFormat(handle, address, ir.CompositeConstruct(comps), buffer_info); ir.StoreBufferFormat(handle, address, ir.CompositeConstruct(comps), buffer_info);
} else { } else {
const auto value = num_dwords == 1 ? comps[0] : ir.CompositeConstruct(comps); IR::Value value = num_dwords == 1 ? comps[0] : ir.CompositeConstruct(comps);
ir.StoreBufferU32(num_dwords, handle, address, value, buffer_info); if (scalar_width != 32) {
value = ir.UConvert(scalar_width, IR::U32{value});
}
switch (scalar_width) {
case 8:
ir.StoreBufferU8(handle, address, IR::U8{value}, buffer_info);
break;
case 16:
ir.StoreBufferU16(handle, address, IR::U16{value}, buffer_info);
break;
case 32:
ir.StoreBufferU32(num_dwords, handle, address, value, buffer_info);
break;
default:
UNREACHABLE();
}
} }
} }

View File

@ -1979,6 +1979,24 @@ U8U16U32U64 IREmitter::UConvert(size_t result_bitsize, const U8U16U32U64& value)
throw NotImplementedException("Conversion from {} to {} bits", value.Type(), result_bitsize); throw NotImplementedException("Conversion from {} to {} bits", value.Type(), result_bitsize);
} }
U8U16U32U64 IR::IREmitter::SConvert(size_t result_bitsize, const U8U16U32U64& value) {
switch (result_bitsize) {
case 32:
switch (value.Type()) {
case Type::U8:
return Inst<U32>(Opcode::ConvertS32S8, value);
case Type::U16:
return Inst<U32>(Opcode::ConvertS32S16, value);
default:
break;
}
default:
break;
}
throw NotImplementedException("Signed Conversion from {} to {} bits", value.Type(),
result_bitsize);
}
F16F32F64 IREmitter::FPConvert(size_t result_bitsize, const F16F32F64& value) { F16F32F64 IREmitter::FPConvert(size_t result_bitsize, const F16F32F64& value) {
switch (result_bitsize) { switch (result_bitsize) {
case 16: case 16:

View File

@ -325,6 +325,7 @@ public:
const Value& value); const Value& value);
[[nodiscard]] U8U16U32U64 UConvert(size_t result_bitsize, const U8U16U32U64& value); [[nodiscard]] U8U16U32U64 UConvert(size_t result_bitsize, const U8U16U32U64& value);
[[nodiscard]] U8U16U32U64 SConvert(size_t result_bitsize, const U8U16U32U64& value);
[[nodiscard]] F16F32F64 FPConvert(size_t result_bitsize, const F16F32F64& value); [[nodiscard]] F16F32F64 FPConvert(size_t result_bitsize, const F16F32F64& value);
[[nodiscard]] Value ImageAtomicIAdd(const Value& handle, const Value& coords, [[nodiscard]] Value ImageAtomicIAdd(const Value& handle, const Value& coords,

View File

@ -432,6 +432,8 @@ OPCODE(ConvertU16U32, U16, U32,
OPCODE(ConvertU32U16, U32, U16, ) OPCODE(ConvertU32U16, U32, U16, )
OPCODE(ConvertU8U32, U8, U32, ) OPCODE(ConvertU8U32, U8, U32, )
OPCODE(ConvertU32U8, U32, U8, ) OPCODE(ConvertU32U8, U32, U8, )
OPCODE(ConvertS32S8, U32, U8, )
OPCODE(ConvertS32S16, U32, U16, )
// Image operations // Image operations
OPCODE(ImageSampleRaw, F32x4, Opaque, F32x4, F32x4, F32x4, F32, Opaque, ) OPCODE(ImageSampleRaw, F32x4, Opaque, F32x4, F32x4, F32x4, F32, Opaque, )

View File

@ -171,6 +171,7 @@ enum class MrtSwizzle : u8 {
static constexpr u32 MaxColorBuffers = 8; static constexpr u32 MaxColorBuffers = 8;
struct PsColorBuffer { struct PsColorBuffer {
AmdGpu::DataFormat data_format : 6;
AmdGpu::NumberFormat num_format : 4; AmdGpu::NumberFormat num_format : 4;
AmdGpu::NumberConversion num_conversion : 3; AmdGpu::NumberConversion num_conversion : 3;
AmdGpu::Liverpool::ShaderExportFormat export_format : 4; AmdGpu::Liverpool::ShaderExportFormat export_format : 4;

View File

@ -248,6 +248,15 @@ constexpr CompMapping RemapSwizzle(const DataFormat format, const CompMapping sw
result.a = swizzle.r; result.a = swizzle.r;
return result; return result;
} }
case DataFormat::Format5_6_5: {
// Remap to a more supported component order.
CompMapping result;
result.r = swizzle.b;
result.g = swizzle.g;
result.b = swizzle.r;
result.a = swizzle.a;
return result;
}
default: default:
return swizzle; return swizzle;
} }

View File

@ -699,7 +699,7 @@ struct PM4CmdWaitRegMem {
struct PM4CmdWriteData { struct PM4CmdWriteData {
PM4Type3Header header; PM4Type3Header header;
union { union {
BitField<8, 11, u32> dst_sel; BitField<8, 4, u32> dst_sel;
BitField<16, 1, u32> wr_one_addr; BitField<16, 1, u32> wr_one_addr;
BitField<20, 1, u32> wr_confirm; BitField<20, 1, u32> wr_confirm;
BitField<30, 1, u32> engine_sel; BitField<30, 1, u32> engine_sel;

View File

@ -961,15 +961,15 @@ bool BufferCache::SynchronizeBufferFromImage(Buffer& buffer, VAddr device_addr,
const u32 height = std::max(image.info.size.height >> m, 1u); const u32 height = std::max(image.info.size.height >> m, 1u);
const u32 depth = const u32 depth =
image.info.props.is_volume ? std::max(image.info.size.depth >> m, 1u) : 1u; image.info.props.is_volume ? std::max(image.info.size.depth >> m, 1u) : 1u;
const auto& [mip_size, mip_pitch, mip_height, mip_ofs] = image.info.mips_layout[m]; const auto [mip_size, mip_pitch, mip_height, mip_ofs] = image.info.mips_layout[m];
offset += mip_ofs; offset += mip_ofs;
if (offset + mip_size > max_offset) { if (offset + mip_size > max_offset) {
break; break;
} }
copies.push_back({ copies.push_back({
.bufferOffset = offset, .bufferOffset = offset,
.bufferRowLength = static_cast<u32>(mip_pitch), .bufferRowLength = mip_pitch,
.bufferImageHeight = static_cast<u32>(mip_height), .bufferImageHeight = mip_height,
.imageSubresource{ .imageSubresource{
.aspectMask = image.aspect_mask & ~vk::ImageAspectFlagBits::eStencil, .aspectMask = image.aspect_mask & ~vk::ImageAspectFlagBits::eStencil,
.mipLevel = m, .mipLevel = m,

View File

@ -671,7 +671,7 @@ std::span<const SurfaceFormatInfo> SurfaceFormats() {
vk::Format::eR32G32B32A32Sfloat), vk::Format::eR32G32B32A32Sfloat),
// 5_6_5 // 5_6_5
CreateSurfaceFormatInfo(AmdGpu::DataFormat::Format5_6_5, AmdGpu::NumberFormat::Unorm, CreateSurfaceFormatInfo(AmdGpu::DataFormat::Format5_6_5, AmdGpu::NumberFormat::Unorm,
vk::Format::eB5G6R5UnormPack16), vk::Format::eR5G6B5UnormPack16),
// 1_5_5_5 // 1_5_5_5
CreateSurfaceFormatInfo(AmdGpu::DataFormat::Format1_5_5_5, AmdGpu::NumberFormat::Unorm, CreateSurfaceFormatInfo(AmdGpu::DataFormat::Format1_5_5_5, AmdGpu::NumberFormat::Unorm,
vk::Format::eA1R5G5B5UnormPack16), vk::Format::eA1R5G5B5UnormPack16),

View File

@ -244,9 +244,24 @@ GraphicsPipeline::GraphicsPipeline(
const auto depth_format = const auto depth_format =
instance.GetSupportedFormat(LiverpoolToVK::DepthFormat(key.z_format, key.stencil_format), instance.GetSupportedFormat(LiverpoolToVK::DepthFormat(key.z_format, key.stencil_format),
vk::FormatFeatureFlagBits2::eDepthStencilAttachment); vk::FormatFeatureFlagBits2::eDepthStencilAttachment);
std::array<vk::Format, Shader::IR::NumRenderTargets> color_formats;
for (s32 i = 0; i < key.num_color_attachments; ++i) {
const auto& col_buf = key.color_buffers[i];
const auto format = LiverpoolToVK::SurfaceFormat(col_buf.data_format, col_buf.num_format);
const auto color_format =
instance.GetSupportedFormat(format, vk::FormatFeatureFlagBits2::eColorAttachment);
if (!instance.IsFormatSupported(color_format,
vk::FormatFeatureFlagBits2::eColorAttachment)) {
LOG_WARNING(Render_Vulkan,
"color buffer format {} does not support COLOR_ATTACHMENT_BIT",
vk::to_string(color_format));
}
color_formats[i] = color_format;
}
const vk::PipelineRenderingCreateInfo pipeline_rendering_ci = { const vk::PipelineRenderingCreateInfo pipeline_rendering_ci = {
.colorAttachmentCount = key.num_color_attachments, .colorAttachmentCount = key.num_color_attachments,
.pColorAttachmentFormats = key.color_formats.data(), .pColorAttachmentFormats = color_formats.data(),
.depthAttachmentFormat = key.z_format != Liverpool::DepthBuffer::ZFormat::Invalid .depthAttachmentFormat = key.z_format != Liverpool::DepthBuffer::ZFormat::Invalid
? depth_format ? depth_format
: vk::Format::eUndefined, : vk::Format::eUndefined,

View File

@ -36,7 +36,6 @@ struct GraphicsPipelineKey {
std::array<vk::Format, MaxVertexBufferCount> vertex_buffer_formats; std::array<vk::Format, MaxVertexBufferCount> vertex_buffer_formats;
u32 patch_control_points; u32 patch_control_points;
u32 num_color_attachments; u32 num_color_attachments;
std::array<vk::Format, Liverpool::NumColorBuffers> color_formats;
std::array<Shader::PsColorBuffer, Liverpool::NumColorBuffers> color_buffers; std::array<Shader::PsColorBuffer, Liverpool::NumColorBuffers> color_buffers;
std::array<Liverpool::BlendControl, Liverpool::NumColorBuffers> blend_controls; std::array<Liverpool::BlendControl, Liverpool::NumColorBuffers> blend_controls;
std::array<vk::ColorComponentFlags, Liverpool::NumColorBuffers> write_masks; std::array<vk::ColorComponentFlags, Liverpool::NumColorBuffers> write_masks;

View File

@ -669,6 +669,12 @@ vk::Format Instance::GetSupportedFormat(const vk::Format format,
if (IsFormatSupported(vk::Format::eD32SfloatS8Uint, flags)) { if (IsFormatSupported(vk::Format::eD32SfloatS8Uint, flags)) {
return vk::Format::eD32SfloatS8Uint; return vk::Format::eD32SfloatS8Uint;
} }
break;
case vk::Format::eR8Srgb:
if (IsFormatSupported(vk::Format::eR8Unorm, flags)) {
return vk::Format::eR8Unorm;
}
break;
default: default:
break; break;
} }

View File

@ -94,6 +94,21 @@ public:
return features.shaderFloat64; return features.shaderFloat64;
} }
/// Returns true if 64-bit ints are supported in shaders
bool IsShaderInt64Supported() const {
return features.shaderInt64;
}
/// Returns true if 16-bit ints are supported in shaders
bool IsShaderInt16Supported() const {
return features.shaderInt16;
}
/// Returns true if 8-bit ints are supported in shaders
bool IsShaderInt8Supported() const {
return vk12_features.shaderInt8;
}
/// Returns true when VK_EXT_custom_border_color is supported /// Returns true when VK_EXT_custom_border_color is supported
bool IsCustomBorderColorSupported() const { bool IsCustomBorderColorSupported() const {
return custom_border_color; return custom_border_color;

View File

@ -203,6 +203,9 @@ PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_,
profile = Shader::Profile{ profile = Shader::Profile{
.supported_spirv = SpirvVersion1_6, .supported_spirv = SpirvVersion1_6,
.subgroup_size = instance.SubgroupSize(), .subgroup_size = instance.SubgroupSize(),
.support_int8 = instance.IsShaderInt8Supported(),
.support_int16 = instance.IsShaderInt16Supported(),
.support_int64 = instance.IsShaderInt64Supported(),
.support_float64 = instance.IsShaderFloat64Supported(), .support_float64 = instance.IsShaderFloat64Supported(),
.support_fp32_denorm_preserve = bool(vk12_props.shaderDenormPreserveFloat32), .support_fp32_denorm_preserve = bool(vk12_props.shaderDenormPreserveFloat32),
.support_fp32_denorm_flush = bool(vk12_props.shaderDenormFlushToZeroFloat32), .support_fp32_denorm_flush = bool(vk12_props.shaderDenormFlushToZeroFloat32),
@ -312,7 +315,6 @@ bool PipelineCache::RefreshGraphicsKey() {
// attachments. This might be not a case as HW color buffers can be bound in an arbitrary // attachments. This might be not a case as HW color buffers can be bound in an arbitrary
// order. We need to do some arrays compaction at this stage // order. We need to do some arrays compaction at this stage
key.num_color_attachments = 0; key.num_color_attachments = 0;
key.color_formats.fill(vk::Format::eUndefined);
key.color_buffers.fill({}); key.color_buffers.fill({});
key.blend_controls.fill({}); key.blend_controls.fill({});
key.write_masks.fill({}); key.write_masks.fill({});
@ -348,16 +350,8 @@ bool PipelineCache::RefreshGraphicsKey() {
col_buf.GetDataFmt() == AmdGpu::DataFormat::Format8_8 || col_buf.GetDataFmt() == AmdGpu::DataFormat::Format8_8 ||
col_buf.GetDataFmt() == AmdGpu::DataFormat::Format8_8_8_8); col_buf.GetDataFmt() == AmdGpu::DataFormat::Format8_8_8_8);
const auto format =
LiverpoolToVK::SurfaceFormat(col_buf.GetDataFmt(), col_buf.GetNumberFmt());
key.color_formats[remapped_cb] = format;
if (!instance.IsFormatSupported(format, vk::FormatFeatureFlagBits2::eColorAttachment)) {
LOG_WARNING(Render_Vulkan,
"color buffer format {} does not support COLOR_ATTACHMENT_BIT",
vk::to_string(format));
}
key.color_buffers[remapped_cb] = Shader::PsColorBuffer{ key.color_buffers[remapped_cb] = Shader::PsColorBuffer{
.data_format = col_buf.GetDataFmt(),
.num_format = col_buf.GetNumberFmt(), .num_format = col_buf.GetNumberFmt(),
.num_conversion = col_buf.GetNumberConversion(), .num_conversion = col_buf.GetNumberConversion(),
.export_format = regs.color_export_format.GetFormat(cb), .export_format = regs.color_export_format.GetFormat(cb),
@ -476,9 +470,7 @@ bool PipelineCache::RefreshGraphicsKey() {
// Attachment is masked out by either color_target_mask or shader mrt_mask. In the case // Attachment is masked out by either color_target_mask or shader mrt_mask. In the case
// of the latter we need to change format to undefined, and either way we need to // of the latter we need to change format to undefined, and either way we need to
// increment the index for the null attachment binding. // increment the index for the null attachment binding.
key.color_formats[remapped_cb] = vk::Format::eUndefined; key.color_buffers[remapped_cb++] = {};
key.color_buffers[remapped_cb] = {};
++remapped_cb;
continue; continue;
} }

View File

@ -1211,11 +1211,13 @@ void Rasterizer::UpdateDepthStencilState() const {
} : front_ops; } : front_ops;
dynamic_state.SetStencilOps(front_ops, back_ops); dynamic_state.SetStencilOps(front_ops, back_ops);
const bool stencil_clear = regs.depth_render_control.stencil_clear_enable;
const auto front = regs.stencil_ref_front; const auto front = regs.stencil_ref_front;
const auto back = const auto back =
regs.depth_control.backface_enable ? regs.stencil_ref_back : regs.stencil_ref_front; regs.depth_control.backface_enable ? regs.stencil_ref_back : regs.stencil_ref_front;
dynamic_state.SetStencilReferences(front.stencil_test_val, back.stencil_test_val); dynamic_state.SetStencilReferences(front.stencil_test_val, back.stencil_test_val);
dynamic_state.SetStencilWriteMasks(front.stencil_write_mask, back.stencil_write_mask); dynamic_state.SetStencilWriteMasks(!stencil_clear ? front.stencil_write_mask.Value() : 0U,
!stencil_clear ? back.stencil_write_mask.Value() : 0U);
dynamic_state.SetStencilCompareMasks(front.stencil_mask, back.stencil_mask); dynamic_state.SetStencilCompareMasks(front.stencil_mask, back.stencil_mask);
} }
} }

View File

@ -126,13 +126,13 @@ void BlitHelper::BlitColorToMsDepth(Image& source, Image& dest) {
.minDepth = 0.f, .minDepth = 0.f,
.maxDepth = 1.f, .maxDepth = 1.f,
}; };
cmdbuf.setViewport(0, viewport); cmdbuf.setViewportWithCount(viewport);
const vk::Rect2D scissor = { const vk::Rect2D scissor = {
.offset = {0, 0}, .offset = {0, 0},
.extent = {state.width, state.height}, .extent = {state.width, state.height},
}; };
cmdbuf.setScissor(0, scissor); cmdbuf.setScissorWithCount(scissor);
cmdbuf.draw(3, 1, 0, 0); cmdbuf.draw(3, 1, 0, 0);

View File

@ -21,7 +21,7 @@ static vk::ImageUsageFlags ImageUsageFlags(const ImageInfo& info) {
if (info.IsDepthStencil()) { if (info.IsDepthStencil()) {
usage |= vk::ImageUsageFlagBits::eDepthStencilAttachment; usage |= vk::ImageUsageFlagBits::eDepthStencilAttachment;
} else { } else {
if (!info.IsBlockCoded() && !info.IsPacked()) { if (!info.IsBlockCoded()) {
usage |= vk::ImageUsageFlagBits::eColorAttachment; usage |= vk::ImageUsageFlagBits::eColorAttachment;
} }
// In cases where an image is created as a render/depth target and cleared with compute, // In cases where an image is created as a render/depth target and cleared with compute,

View File

@ -176,17 +176,6 @@ bool ImageInfo::IsBlockCoded() const {
} }
} }
bool ImageInfo::IsPacked() const {
switch (pixel_format) {
case vk::Format::eB5G5R5A1UnormPack16:
[[fallthrough]];
case vk::Format::eB5G6R5UnormPack16:
return true;
default:
return false;
}
}
bool ImageInfo::IsDepthStencil() const { bool ImageInfo::IsDepthStencil() const {
switch (pixel_format) { switch (pixel_format) {
case vk::Format::eD16Unorm: case vk::Format::eD16Unorm:

View File

@ -31,7 +31,6 @@ struct ImageInfo {
} }
bool IsBlockCoded() const; bool IsBlockCoded() const;
bool IsPacked() const;
bool IsDepthStencil() const; bool IsDepthStencil() const;
bool HasStencil() const; bool HasStencil() const;

View File

@ -605,28 +605,27 @@ void TextureCache::RefreshImage(Image& image, Vulkan::Scheduler* custom_schedule
const u32 height = std::max(image.info.size.height >> m, 1u); const u32 height = std::max(image.info.size.height >> m, 1u);
const u32 depth = const u32 depth =
image.info.props.is_volume ? std::max(image.info.size.depth >> m, 1u) : 1u; image.info.props.is_volume ? std::max(image.info.size.depth >> m, 1u) : 1u;
const auto& mip = image.info.mips_layout[m]; const auto [mip_size, mip_pitch, mip_height, mip_offset] = image.info.mips_layout[m];
// Protect GPU modified resources from accidental CPU reuploads. // Protect GPU modified resources from accidental CPU reuploads.
if (is_gpu_modified && !is_gpu_dirty) { if (is_gpu_modified && !is_gpu_dirty) {
const u8* addr = std::bit_cast<u8*>(image.info.guest_address); const u8* addr = std::bit_cast<u8*>(image.info.guest_address);
const u64 hash = XXH3_64bits(addr + mip.offset, mip.size); const u64 hash = XXH3_64bits(addr + mip_offset, mip_size);
if (image.mip_hashes[m] == hash) { if (image.mip_hashes[m] == hash) {
continue; continue;
} }
image.mip_hashes[m] = hash; image.mip_hashes[m] = hash;
} }
auto mip_pitch = static_cast<u32>(mip.pitch); const u32 extent_width = mip_pitch ? std::min(mip_pitch, width) : width;
auto mip_height = static_cast<u32>(mip.height); const u32 extent_height = mip_height ? std::min(mip_height, height) : height;
const u32 height_aligned =
auto image_extent_width = mip_pitch ? std::min(mip_pitch, width) : width; mip_height && image.info.IsTiled() ? std::max(mip_height, 8U) : mip_height;
auto image_extent_height = mip_height ? std::min(mip_height, height) : height;
image_copy.push_back({ image_copy.push_back({
.bufferOffset = mip.offset, .bufferOffset = mip_offset,
.bufferRowLength = mip_pitch, .bufferRowLength = mip_pitch,
.bufferImageHeight = mip_height, .bufferImageHeight = height_aligned,
.imageSubresource{ .imageSubresource{
.aspectMask = image.aspect_mask & ~vk::ImageAspectFlagBits::eStencil, .aspectMask = image.aspect_mask & ~vk::ImageAspectFlagBits::eStencil,
.mipLevel = m, .mipLevel = m,
@ -634,7 +633,7 @@ void TextureCache::RefreshImage(Image& image, Vulkan::Scheduler* custom_schedule
.layerCount = num_layers, .layerCount = num_layers,
}, },
.imageOffset = {0, 0, 0}, .imageOffset = {0, 0, 0},
.imageExtent = {image_extent_width, image_extent_height, depth}, .imageExtent = {extent_width, extent_height, depth},
}); });
} }