diff --git a/.github/linux-appimage-sdl.sh b/.github/linux-appimage-sdl.sh index 7961f5312..d85aa6c4c 100755 --- a/.github/linux-appimage-sdl.sh +++ b/.github/linux-appimage-sdl.sh @@ -8,8 +8,8 @@ if [[ -z $GITHUB_WORKSPACE ]]; then fi # Prepare Tools for building the AppImage -wget -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage -wget -q https://github.com/linuxdeploy/linuxdeploy-plugin-checkrt/releases/download/continuous/linuxdeploy-plugin-checkrt-x86_64.sh +wget --waitretry=3 --read-timeout=20 --timeout=15 --tries=5 -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage +wget --waitretry=3 --read-timeout=20 --timeout=15 --tries=5 -q https://github.com/linuxdeploy/linuxdeploy-plugin-checkrt/releases/download/continuous/linuxdeploy-plugin-checkrt-x86_64.sh chmod a+x linuxdeploy-x86_64.AppImage chmod a+x linuxdeploy-plugin-checkrt-x86_64.sh diff --git a/src/common/config.cpp b/src/common/config.cpp index ee9dd0337..863ae0f59 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -174,7 +174,7 @@ static ConfigEntry internalScreenWidth(1280); static ConfigEntry internalScreenHeight(720); static ConfigEntry isNullGpu(false); static ConfigEntry shouldCopyGPUBuffers(false); -static ConfigEntry readbacksEnabled(false); +static ConfigEntry readbacksMode(GpuReadbacksMode::Disabled); static ConfigEntry readbackLinearImagesEnabled(false); static ConfigEntry directMemoryAccessEnabled(false); static ConfigEntry shouldDumpShaders(false); @@ -442,8 +442,8 @@ bool copyGPUCmdBuffers() { return shouldCopyGPUBuffers.get(); } -bool readbacks() { - return readbacksEnabled.get(); +int getReadbacksMode() { + return readbacksMode.get(); } bool readbackLinearImages() { @@ -593,8 +593,8 @@ void setCopyGPUCmdBuffers(bool enable, bool is_game_specific) { shouldCopyGPUBuffers.set(enable, is_game_specific); } -void setReadbacks(bool enable, bool is_game_specific) { - readbacksEnabled.set(enable, is_game_specific); +void setReadbacksMode(int mode, bool is_game_specific) { + readbacksMode.set(mode, is_game_specific); } void setReadbackLinearImages(bool enable, bool is_game_specific) { @@ -963,7 +963,7 @@ void load(const std::filesystem::path& path, bool is_game_specific) { internalScreenHeight.setFromToml(gpu, "internalScreenHeight", is_game_specific); isNullGpu.setFromToml(gpu, "nullGpu", is_game_specific); shouldCopyGPUBuffers.setFromToml(gpu, "copyGPUBuffers", is_game_specific); - readbacksEnabled.setFromToml(gpu, "readbacks", is_game_specific); + readbacksMode.setFromToml(gpu, "readbacksMode", is_game_specific); readbackLinearImagesEnabled.setFromToml(gpu, "readbackLinearImages", is_game_specific); directMemoryAccessEnabled.setFromToml(gpu, "directMemoryAccess", is_game_specific); shouldDumpShaders.setFromToml(gpu, "dumpShaders", is_game_specific); @@ -1142,7 +1142,7 @@ void save(const std::filesystem::path& path, bool is_game_specific) { windowHeight.setTomlValue(data, "GPU", "screenHeight", is_game_specific); isNullGpu.setTomlValue(data, "GPU", "nullGpu", is_game_specific); shouldCopyGPUBuffers.setTomlValue(data, "GPU", "copyGPUBuffers", is_game_specific); - readbacksEnabled.setTomlValue(data, "GPU", "readbacks", is_game_specific); + readbacksMode.setTomlValue(data, "GPU", "readbacksMode", is_game_specific); readbackLinearImagesEnabled.setTomlValue(data, "GPU", "readbackLinearImages", is_game_specific); shouldDumpShaders.setTomlValue(data, "GPU", "dumpShaders", is_game_specific); vblankFrequency.setTomlValue(data, "GPU", "vblankFrequency", is_game_specific); @@ -1241,7 +1241,7 @@ void setDefaultValues(bool is_game_specific) { // Entries with game-specific settings that are in the game-specific setings GUI but not in // the global settings GUI if (is_game_specific) { - readbacksEnabled.set(false, is_game_specific); + readbacksMode.set(GpuReadbacksMode::Disabled, is_game_specific); readbackLinearImagesEnabled.set(false, is_game_specific); isNeo.set(false, is_game_specific); isDevKit.set(false, is_game_specific); diff --git a/src/common/config.h b/src/common/config.h index d2fa7390c..07145785e 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -23,6 +23,12 @@ struct GameInstallDir { enum HideCursorState : int { Never, Idle, Always }; +enum GpuReadbacksMode : int { + Disabled, + Relaxed, + Precise, +}; + void load(const std::filesystem::path& path, bool is_game_specific = false); void save(const std::filesystem::path& path, bool is_game_specific = false); void resetGameSpecificValue(std::string entry); @@ -63,8 +69,8 @@ bool nullGpu(); void setNullGpu(bool enable, bool is_game_specific = false); bool copyGPUCmdBuffers(); void setCopyGPUCmdBuffers(bool enable, bool is_game_specific = false); -bool readbacks(); -void setReadbacks(bool enable, bool is_game_specific = false); +int getReadbacksMode(); +void setReadbacksMode(int mode, bool is_game_specific = false); bool readbackLinearImages(); void setReadbackLinearImages(bool enable, bool is_game_specific = false); bool directMemoryAccess(); diff --git a/src/core/libraries/audio3d/audio3d.cpp b/src/core/libraries/audio3d/audio3d.cpp index 3f5fdcf78..989679107 100644 --- a/src/core/libraries/audio3d/audio3d.cpp +++ b/src/core/libraries/audio3d/audio3d.cpp @@ -1,7 +1,8 @@ -// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include +#include +#include #include #include "common/assert.h" @@ -20,12 +21,21 @@ static constexpr u32 AUDIO3D_SAMPLE_RATE = 48000; static constexpr AudioOut::OrbisAudioOutParamFormat AUDIO3D_OUTPUT_FORMAT = AudioOut::OrbisAudioOutParamFormat::S16Stereo; static constexpr u32 AUDIO3D_OUTPUT_NUM_CHANNELS = 2; -static constexpr u32 AUDIO3D_OUTPUT_BUFFER_FRAMES = 0x100; static std::unique_ptr state; s32 PS4_SYSV_ABI sceAudio3dAudioOutClose(const s32 handle) { LOG_INFO(Lib_Audio3d, "called, handle = {}", handle); + + // Remove from any port that was tracking this handle. + if (state) { + for (auto& [port_id, port] : state->ports) { + std::scoped_lock lock{port.mutex}; + auto& handles = port.audioout_handles; + handles.erase(std::remove(handles.begin(), handles.end(), handle), handles.end()); + } + } + return AudioOut::sceAudioOutClose(handle); } @@ -42,13 +52,21 @@ s32 PS4_SYSV_ABI sceAudio3dAudioOutOpen( return ORBIS_AUDIO3D_ERROR_INVALID_PORT; } + std::scoped_lock lock{state->ports[port_id].mutex}; if (len != state->ports[port_id].parameters.granularity) { LOG_ERROR(Lib_Audio3d, "len != state->ports[port_id].parameters.granularity"); return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; } - return sceAudioOutOpen(user_id, static_cast(type), index, len, - freq, param); + const s32 handle = sceAudioOutOpen(user_id, static_cast(type), + index, len, freq, param); + if (handle < 0) { + return handle; + } + + // Track this handle in the port so sceAudio3dPortFlush can use it for sync. + state->ports[port_id].audioout_handles.push_back(handle); + return handle; } s32 PS4_SYSV_ABI sceAudio3dAudioOutOutput(const s32 handle, void* ptr) { @@ -79,34 +97,31 @@ s32 PS4_SYSV_ABI sceAudio3dAudioOutOutputs(AudioOut::OrbisAudioOutOutputParam* p return AudioOut::sceAudioOutOutputs(param, num); } -static s32 PortQueueAudio(Port& port, const OrbisAudio3dPcm& pcm, const u32 num_channels) { - // Audio3d output is configured for stereo signed 16-bit PCM. Convert the data to match. - const SDL_AudioSpec src_spec = { - .format = pcm.format == OrbisAudio3dFormat::ORBIS_AUDIO3D_FORMAT_S16 ? SDL_AUDIO_S16LE - : SDL_AUDIO_F32LE, - .channels = static_cast(num_channels), - .freq = AUDIO3D_SAMPLE_RATE, - }; - constexpr SDL_AudioSpec dst_spec = { - .format = SDL_AUDIO_S16LE, - .channels = AUDIO3D_OUTPUT_NUM_CHANNELS, - .freq = AUDIO3D_SAMPLE_RATE, - }; - const auto src_size = pcm.num_samples * - (pcm.format == OrbisAudio3dFormat::ORBIS_AUDIO3D_FORMAT_S16 ? 2 : 4) * - num_channels; +static s32 ConvertAndEnqueue(std::deque& queue, const OrbisAudio3dPcm& pcm, + const u32 num_channels, const u32 granularity) { + if (!pcm.sample_buffer || !pcm.num_samples) { + return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; + } - u8* dst_data; - int dst_len; - if (!SDL_ConvertAudioSamples(&src_spec, static_cast(pcm.sample_buffer), - static_cast(src_size), &dst_spec, &dst_data, &dst_len)) { - LOG_ERROR(Lib_Audio3d, "SDL_ConvertAudioSamples failed: {}", SDL_GetError()); + const u32 bytes_per_sample = + (pcm.format == OrbisAudio3dFormat::ORBIS_AUDIO3D_FORMAT_S16) ? sizeof(s16) : sizeof(float); + + // Always allocate exactly granularity samples (zeroed = silence for padding). + const u32 dst_bytes = granularity * num_channels * bytes_per_sample; + u8* copy = static_cast(std::calloc(1, dst_bytes)); + if (!copy) { return ORBIS_AUDIO3D_ERROR_OUT_OF_MEMORY; } - port.queue.emplace_back(AudioData{ - .sample_buffer = dst_data, - .num_samples = pcm.num_samples, + // Copy min(provided, granularity) samples — extra are dropped, shortage stays zero. + const u32 samples_to_copy = std::min(pcm.num_samples, granularity); + std::memcpy(copy, pcm.sample_buffer, samples_to_copy * num_channels * bytes_per_sample); + + queue.emplace_back(AudioData{ + .sample_buffer = copy, + .num_samples = granularity, + .num_channels = num_channels, + .format = pcm.format, }); return ORBIS_OK; } @@ -145,8 +160,8 @@ s32 PS4_SYSV_ABI sceAudio3dBedWrite2(const OrbisAudio3dPortId port_id, const u32 return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; } - if (num_channels != 2 && num_channels != 8) { - LOG_ERROR(Lib_Audio3d, "num_channels != 2 && num_channels != 8"); + if (num_channels != 2 && num_channels != 6 && num_channels != 8) { + LOG_ERROR(Lib_Audio3d, "num_channels must be 2, 6, or 8"); return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; } @@ -167,13 +182,14 @@ s32 PS4_SYSV_ABI sceAudio3dBedWrite2(const OrbisAudio3dPortId port_id, const u32 } } - return PortQueueAudio(state->ports[port_id], - OrbisAudio3dPcm{ - .format = format, - .sample_buffer = buffer, - .num_samples = num_samples, - }, - num_channels); + std::scoped_lock lock{state->ports[port_id].mutex}; + return ConvertAndEnqueue(state->ports[port_id].bed_queue, + OrbisAudio3dPcm{ + .format = format, + .sample_buffer = buffer, + .num_samples = num_samples, + }, + num_channels, state->ports[port_id].parameters.granularity); } s32 PS4_SYSV_ABI sceAudio3dCreateSpeakerArray() { @@ -237,15 +253,6 @@ s32 PS4_SYSV_ABI sceAudio3dInitialize(const s64 reserved) { return init_ret; } - AudioOut::OrbisAudioOutParamExtendedInformation ext_info{}; - ext_info.data_format.Assign(AUDIO3D_OUTPUT_FORMAT); - state->audio_out_handle = - AudioOut::sceAudioOutOpen(0xFF, AudioOut::OrbisAudioOutPort::Audio3d, 0, - AUDIO3D_OUTPUT_BUFFER_FRAMES, AUDIO3D_SAMPLE_RATE, ext_info); - if (state->audio_out_handle < 0) { - return state->audio_out_handle; - } - return ORBIS_OK; } @@ -254,18 +261,84 @@ s32 PS4_SYSV_ABI sceAudio3dObjectReserve(const OrbisAudio3dPortId port_id, LOG_INFO(Lib_Audio3d, "called, port_id = {}, object_id = {}", port_id, static_cast(object_id)); - if (!state->ports.contains(port_id)) { - LOG_ERROR(Lib_Audio3d, "!state->ports.contains(port_id)"); - return ORBIS_AUDIO3D_ERROR_INVALID_PORT; - } - if (!object_id) { LOG_ERROR(Lib_Audio3d, "!object_id"); return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; } - static int last_id = 0; - *object_id = ++last_id; + *object_id = ORBIS_AUDIO3D_OBJECT_INVALID; + + if (!state->ports.contains(port_id)) { + LOG_ERROR(Lib_Audio3d, "!state->ports.contains(port_id)"); + return ORBIS_AUDIO3D_ERROR_INVALID_PORT; + } + + auto& port = state->ports[port_id]; + std::scoped_lock lock{port.mutex}; + + // Enforce the max_objects limit set at PortOpen time. + if (port.objects.size() >= port.parameters.max_objects) { + LOG_ERROR(Lib_Audio3d, "port has no available objects (max_objects = {})", + port.parameters.max_objects); + return ORBIS_AUDIO3D_ERROR_OUT_OF_RESOURCES; + } + + // counter lives in the Port so it resets when the port is closed and reopened, + do { + ++port.next_object_id; + } while (port.next_object_id == 0 || port.next_object_id == ORBIS_AUDIO3D_OBJECT_INVALID || + port.objects.contains(port.next_object_id)); + + *object_id = port.next_object_id; + port.objects.emplace(*object_id, ObjectState{}); + LOG_INFO(Lib_Audio3d, "reserved object_id = {}", *object_id); + + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceAudio3dObjectSetAttribute(const OrbisAudio3dPortId port_id, + const OrbisAudio3dObjectId object_id, + const OrbisAudio3dAttributeId attribute_id, + const void* attribute, const u64 attribute_size) { + LOG_DEBUG(Lib_Audio3d, "called, port_id = {}, object_id = {}, attribute_id = {:#x}, size = {}", + port_id, object_id, static_cast(attribute_id), attribute_size); + + if (!state->ports.contains(port_id)) { + LOG_ERROR(Lib_Audio3d, "!state->ports.contains(port_id)"); + return ORBIS_AUDIO3D_ERROR_INVALID_PORT; + } + + auto& port = state->ports[port_id]; + std::scoped_lock lock{port.mutex}; + if (!port.objects.contains(object_id)) { + LOG_DEBUG(Lib_Audio3d, "object_id {} not reserved (race with Unreserve?), no-op", + object_id); + return ORBIS_OK; + } + + if (!attribute_size && + attribute_id != OrbisAudio3dAttributeId::ORBIS_AUDIO3D_ATTRIBUTE_RESET_STATE) { + LOG_ERROR(Lib_Audio3d, "!attribute_size for non-reset attribute"); + return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; + } + + auto& obj = port.objects[object_id]; + + // RESET_STATE clears all attributes and queued PCM; it takes no value. + if (attribute_id == OrbisAudio3dAttributeId::ORBIS_AUDIO3D_ATTRIBUTE_RESET_STATE) { + for (auto& data : obj.pcm_queue) { + std::free(data.sample_buffer); + } + obj.pcm_queue.clear(); + obj.persistent_attributes.clear(); + LOG_DEBUG(Lib_Audio3d, "RESET_STATE for object {}", object_id); + return ORBIS_OK; + } + + // we don't handle any attributes yet, but store them in the ObjectState so they're available + // when we do + const auto* src = static_cast(attribute); + obj.persistent_attributes[static_cast(attribute_id)].assign(src, src + attribute_size); return ORBIS_OK; } @@ -283,32 +356,95 @@ s32 PS4_SYSV_ABI sceAudio3dObjectSetAttributes(const OrbisAudio3dPortId port_id, return ORBIS_AUDIO3D_ERROR_INVALID_PORT; } - auto& port = state->ports[port_id]; + if (!num_attributes || !attribute_array) { + LOG_ERROR(Lib_Audio3d, "!num_attributes || !attribute_array"); + return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; + } + auto& port = state->ports[port_id]; + std::scoped_lock lock{port.mutex}; + if (!port.objects.contains(object_id)) { + LOG_DEBUG(Lib_Audio3d, "object_id {} not reserved", object_id); + return ORBIS_OK; + } + + auto& obj = port.objects[object_id]; + + for (u64 i = 0; i < num_attributes; i++) { + if (attribute_array[i].attribute_id == + OrbisAudio3dAttributeId::ORBIS_AUDIO3D_ATTRIBUTE_RESET_STATE) { + for (auto& data : obj.pcm_queue) { + std::free(data.sample_buffer); + } + obj.pcm_queue.clear(); + obj.persistent_attributes.clear(); + LOG_DEBUG(Lib_Audio3d, "RESET_STATE for object {}", object_id); + break; // Only one reset is needed even if listed multiple times. + } + } + + // apply all other attributes. for (u64 i = 0; i < num_attributes; i++) { const auto& attribute = attribute_array[i]; switch (attribute.attribute_id) { + case OrbisAudio3dAttributeId::ORBIS_AUDIO3D_ATTRIBUTE_RESET_STATE: + break; // Already applied in first pass above. case OrbisAudio3dAttributeId::ORBIS_AUDIO3D_ATTRIBUTE_PCM: { + if (attribute.value_size < sizeof(OrbisAudio3dPcm)) { + LOG_ERROR(Lib_Audio3d, "PCM attribute value_size too small"); + continue; + } const auto pcm = static_cast(attribute.value); - // Object audio has 1 channel. - if (const auto ret = PortQueueAudio(port, *pcm, 1); ret != ORBIS_OK) { + // Object audio is always mono (1 channel). + if (const auto ret = + ConvertAndEnqueue(obj.pcm_queue, *pcm, 1, port.parameters.granularity); + ret != ORBIS_OK) { return ret; } break; } - default: - LOG_ERROR(Lib_Audio3d, "Unsupported attribute ID: {:#x}", - static_cast(attribute.attribute_id)); + default: { + // store the other attributes in the ObjectState so they're available when we implement + // them + if (attribute.value && attribute.value_size > 0) { + const auto* src = static_cast(attribute.value); + obj.persistent_attributes[static_cast(attribute.attribute_id)].assign( + src, src + attribute.value_size); + } + LOG_DEBUG(Lib_Audio3d, "Stored attribute {:#x} for object {}", + static_cast(attribute.attribute_id), object_id); break; } + } } return ORBIS_OK; } -s32 PS4_SYSV_ABI sceAudio3dObjectUnreserve() { - LOG_ERROR(Lib_Audio3d, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceAudio3dObjectUnreserve(const OrbisAudio3dPortId port_id, + const OrbisAudio3dObjectId object_id) { + LOG_DEBUG(Lib_Audio3d, "called, port_id = {}, object_id = {}", port_id, object_id); + + if (!state->ports.contains(port_id)) { + LOG_ERROR(Lib_Audio3d, "!state->ports.contains(port_id)"); + return ORBIS_AUDIO3D_ERROR_INVALID_PORT; + } + + auto& port = state->ports[port_id]; + std::scoped_lock lock{port.mutex}; + + if (!port.objects.contains(object_id)) { + LOG_ERROR(Lib_Audio3d, "object_id not reserved"); + return ORBIS_AUDIO3D_ERROR_INVALID_OBJECT; + } + + // Free any queued PCM audio for this object. + for (auto& data : port.objects[object_id].pcm_queue) { + std::free(data.sample_buffer); + } + + port.objects.erase(object_id); return ORBIS_OK; } @@ -320,32 +456,164 @@ s32 PS4_SYSV_ABI sceAudio3dPortAdvance(const OrbisAudio3dPortId port_id) { return ORBIS_AUDIO3D_ERROR_INVALID_PORT; } - if (state->ports[port_id].parameters.buffer_mode == - OrbisAudio3dBufferMode::ORBIS_AUDIO3D_BUFFER_NO_ADVANCE) { + auto& port = state->ports[port_id]; + + if (port.parameters.buffer_mode == OrbisAudio3dBufferMode::ORBIS_AUDIO3D_BUFFER_NO_ADVANCE) { LOG_ERROR(Lib_Audio3d, "port doesn't have advance capability"); return ORBIS_AUDIO3D_ERROR_NOT_SUPPORTED; } - auto& port = state->ports[port_id]; - if (port.current_buffer.has_value()) { - // Free existing buffer before replacing. - SDL_free(port.current_buffer->sample_buffer); + if (port.mixed_queue.size() >= port.parameters.queue_depth) { + LOG_WARNING(Lib_Audio3d, "mixed queue full (depth={}), dropping advance", + port.parameters.queue_depth); + return ORBIS_AUDIO3D_ERROR_NOT_READY; } - if (!port.queue.empty()) { - port.current_buffer = port.queue.front(); - port.queue.pop_front(); - } else { - // Nothing to advance to. - LOG_DEBUG(Lib_Audio3d, "Port advance with no buffer queued"); - port.current_buffer = std::nullopt; + const u32 granularity = port.parameters.granularity; + const u32 out_samples = granularity * AUDIO3D_OUTPUT_NUM_CHANNELS; + + // ---- FLOAT MIX BUFFER ---- + float* mix_float = static_cast(std::calloc(out_samples, sizeof(float))); + + if (!mix_float) + return ORBIS_AUDIO3D_ERROR_OUT_OF_MEMORY; + + auto mix_in = [&](std::deque& queue, const float gain) { + if (queue.empty()) + return; + + // default gain is 0.0 — objects with no GAIN set are silent. + if (gain == 0.0f) { + AudioData data = queue.front(); + queue.pop_front(); + std::free(data.sample_buffer); + return; + } + + AudioData data = queue.front(); + queue.pop_front(); + + const u32 frames = std::min(granularity, data.num_samples); + const u32 channels = data.num_channels; + + if (data.format == OrbisAudio3dFormat::ORBIS_AUDIO3D_FORMAT_S16) { + const s16* src = reinterpret_cast(data.sample_buffer); + + for (u32 i = 0; i < frames; i++) { + float left = 0.0f; + float right = 0.0f; + + if (channels == 1) { + float v = src[i] / 32768.0f; + left = v; + right = v; + } else { + left = src[i * channels + 0] / 32768.0f; + right = src[i * channels + 1] / 32768.0f; + } + + mix_float[i * 2 + 0] += left * gain; + mix_float[i * 2 + 1] += right * gain; + } + } else { // FLOAT input + const float* src = reinterpret_cast(data.sample_buffer); + + for (u32 i = 0; i < frames; i++) { + float left = 0.0f; + float right = 0.0f; + + if (channels == 1) { + left = src[i]; + right = src[i]; + } else { + left = src[i * channels + 0]; + right = src[i * channels + 1]; + } + + mix_float[i * 2 + 0] += left * gain; + mix_float[i * 2 + 1] += right * gain; + } + } + + std::free(data.sample_buffer); + }; + + // Bed is mixed at full gain (1.0) + mix_in(port.bed_queue, 1.0f); + + // Mix all object PCM queues, applying each object's GAIN persistent attribute. + for (auto& [obj_id, obj] : port.objects) { + float gain = 0.0f; + const auto gain_key = + static_cast(OrbisAudio3dAttributeId::ORBIS_AUDIO3D_ATTRIBUTE_GAIN); + if (obj.persistent_attributes.contains(gain_key)) { + const auto& blob = obj.persistent_attributes.at(gain_key); + if (blob.size() >= sizeof(float)) { + std::memcpy(&gain, blob.data(), sizeof(float)); + } + } + mix_in(obj.pcm_queue, gain); } + s16* mix_s16 = static_cast(std::malloc(out_samples * sizeof(s16))); + + if (!mix_s16) { + std::free(mix_float); + return ORBIS_AUDIO3D_ERROR_OUT_OF_MEMORY; + } + + for (u32 i = 0; i < out_samples; i++) { + float v = std::clamp(mix_float[i], -1.0f, 1.0f); + mix_s16[i] = static_cast(v * 32767.0f); + } + + std::free(mix_float); + + port.mixed_queue.push_back(AudioData{.sample_buffer = reinterpret_cast(mix_s16), + .num_samples = granularity, + .num_channels = AUDIO3D_OUTPUT_NUM_CHANNELS, + .format = OrbisAudio3dFormat::ORBIS_AUDIO3D_FORMAT_S16}); + return ORBIS_OK; } +s32 PS4_SYSV_ABI sceAudio3dPortClose(const OrbisAudio3dPortId port_id) { + LOG_INFO(Lib_Audio3d, "called, port_id = {}", port_id); -s32 PS4_SYSV_ABI sceAudio3dPortClose() { - LOG_ERROR(Lib_Audio3d, "(STUBBED) called"); + if (!state->ports.contains(port_id)) { + LOG_ERROR(Lib_Audio3d, "!state->ports.contains(port_id)"); + return ORBIS_AUDIO3D_ERROR_INVALID_PORT; + } + + auto& port = state->ports[port_id]; + { + std::scoped_lock lock{port.mutex}; + + if (port.audio_out_handle >= 0) { + AudioOut::sceAudioOutClose(port.audio_out_handle); + port.audio_out_handle = -1; + } + + for (const s32 handle : port.audioout_handles) { + AudioOut::sceAudioOutClose(handle); + } + port.audioout_handles.clear(); + + for (auto& data : port.mixed_queue) { + std::free(data.sample_buffer); + } + + for (auto& data : port.bed_queue) { + std::free(data.sample_buffer); + } + + for (auto& [obj_id, obj] : port.objects) { + for (auto& data : obj.pcm_queue) { + std::free(data.sample_buffer); + } + } + } + + state->ports.erase(port_id); return ORBIS_OK; } @@ -359,8 +627,65 @@ s32 PS4_SYSV_ABI sceAudio3dPortDestroy() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceAudio3dPortFlush() { - LOG_ERROR(Lib_Audio3d, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceAudio3dPortFlush(const OrbisAudio3dPortId port_id) { + LOG_DEBUG(Lib_Audio3d, "called, port_id = {}", port_id); + + if (!state->ports.contains(port_id)) { + LOG_ERROR(Lib_Audio3d, "!state->ports.contains(port_id)"); + return ORBIS_AUDIO3D_ERROR_INVALID_PORT; + } + + auto& port = state->ports[port_id]; + std::scoped_lock lock{port.mutex}; + + if (!port.audioout_handles.empty()) { + for (const s32 handle : port.audioout_handles) { + const s32 ret = AudioOut::sceAudioOutOutput(handle, nullptr); + if (ret < 0) { + return ret; + } + } + return ORBIS_OK; + } + + if (port.mixed_queue.empty()) { + // Only mix if there's actually something to mix. + if (!port.bed_queue.empty() || + std::any_of(port.objects.begin(), port.objects.end(), + [](const auto& kv) { return !kv.second.pcm_queue.empty(); })) { + const s32 ret = sceAudio3dPortAdvance(port_id); + if (ret != ORBIS_OK && ret != ORBIS_AUDIO3D_ERROR_NOT_READY) { + return ret; + } + } + } + + if (port.mixed_queue.empty()) { + return ORBIS_OK; + } + + if (port.audio_out_handle < 0) { + AudioOut::OrbisAudioOutParamExtendedInformation ext_info{}; + ext_info.data_format.Assign(AUDIO3D_OUTPUT_FORMAT); + port.audio_out_handle = + AudioOut::sceAudioOutOpen(0xFF, AudioOut::OrbisAudioOutPort::Audio3d, 0, + port.parameters.granularity, AUDIO3D_SAMPLE_RATE, ext_info); + if (port.audio_out_handle < 0) { + return port.audio_out_handle; + } + } + + // Drain all queued mixed frames, blocking on each until consumed. + while (!port.mixed_queue.empty()) { + AudioData frame = port.mixed_queue.front(); + port.mixed_queue.pop_front(); + const s32 ret = AudioOut::sceAudioOutOutput(port.audio_out_handle, frame.sample_buffer); + std::free(frame.sample_buffer); + if (ret < 0) { + return ret; + } + } + return ORBIS_OK; } @@ -398,15 +723,17 @@ s32 PS4_SYSV_ABI sceAudio3dPortGetQueueLevel(const OrbisAudio3dPortId port_id, u return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER; } - const auto port = state->ports[port_id]; - const size_t size = port.queue.size(); + const auto& port = state->ports[port_id]; + std::scoped_lock lock{port.mutex}; + const size_t size = port.mixed_queue.size(); if (queue_level) { - *queue_level = size; + *queue_level = static_cast(size); } if (queue_available) { - *queue_available = port.parameters.queue_depth - size; + const u32 depth = port.parameters.queue_depth; + *queue_available = (size < depth) ? static_cast(depth - size) : 0u; } return ORBIS_OK; @@ -446,7 +773,10 @@ s32 PS4_SYSV_ABI sceAudio3dPortOpen(const Libraries::UserService::OrbisUserServi } *port_id = id; - std::memcpy(&state->ports[id].parameters, parameters, parameters->size_this); + auto& port = state->ports[id]; + std::memcpy( + &port.parameters, parameters, + std::min(parameters->size_this, static_cast(sizeof(OrbisAudio3dOpenParameters)))); return ORBIS_OK; } @@ -461,24 +791,96 @@ s32 PS4_SYSV_ABI sceAudio3dPortPush(const OrbisAudio3dPortId port_id, return ORBIS_AUDIO3D_ERROR_INVALID_PORT; } - const auto& port = state->ports[port_id]; + auto& port = state->ports[port_id]; + if (port.parameters.buffer_mode != OrbisAudio3dBufferMode::ORBIS_AUDIO3D_BUFFER_ADVANCE_AND_PUSH) { LOG_ERROR(Lib_Audio3d, "port doesn't have push capability"); return ORBIS_AUDIO3D_ERROR_NOT_SUPPORTED; } - if (!port.current_buffer.has_value()) { - // Nothing to push. - LOG_DEBUG(Lib_Audio3d, "Port push with no buffer ready"); + const u32 depth = port.parameters.queue_depth; + + if (port.audio_out_handle < 0) { + AudioOut::OrbisAudioOutParamExtendedInformation ext_info{}; + ext_info.data_format.Assign(AUDIO3D_OUTPUT_FORMAT); + + port.audio_out_handle = + AudioOut::sceAudioOutOpen(0xFF, AudioOut::OrbisAudioOutPort::Audio3d, 0, + port.parameters.granularity, AUDIO3D_SAMPLE_RATE, ext_info); + + if (port.audio_out_handle < 0) + return port.audio_out_handle; + } + + // Function that submits exactly one frame (if available) + auto submit_one_frame = [&](bool& submitted) -> s32 { + AudioData frame; + { + std::scoped_lock lock{port.mutex}; + + if (port.mixed_queue.empty()) { + submitted = false; + return ORBIS_OK; + } + + frame = port.mixed_queue.front(); + port.mixed_queue.pop_front(); + } + + const s32 ret = AudioOut::sceAudioOutOutput(port.audio_out_handle, frame.sample_buffer); + + std::free(frame.sample_buffer); + + if (ret < 0) + return ret; + + submitted = true; + return ORBIS_OK; + }; + + // if not full, return immediately + { + std::scoped_lock lock{port.mutex}; + if (port.mixed_queue.size() < depth) { + return ORBIS_OK; + } + } + + // Submit one frame to free space + bool submitted = false; + s32 ret = submit_one_frame(submitted); + if (ret < 0) + return ret; + + if (!submitted) + return ORBIS_OK; + + // ASYNC: free exactly one slot and return + if (blocking == OrbisAudio3dBlocking::ORBIS_AUDIO3D_BLOCKING_ASYNC) { return ORBIS_OK; } - // TODO: Implement asynchronous blocking mode. - const auto& [sample_buffer, num_samples] = port.current_buffer.value(); - return AudioOut::sceAudioOutOutput(state->audio_out_handle, sample_buffer); -} + // SYNC: ensure at least one slot is free + // (drain until size < depth) + while (true) { + { + std::scoped_lock lock{port.mutex}; + if (port.mixed_queue.size() < depth) + break; + } + bool drained = false; + ret = submit_one_frame(drained); + if (ret < 0) + return ret; + + if (!drained) + break; + } + + return ORBIS_OK; +} s32 PS4_SYSV_ABI sceAudio3dPortQueryDebug() { LOG_ERROR(Lib_Audio3d, "(STUBBED) called"); return ORBIS_OK; @@ -532,9 +934,15 @@ s32 PS4_SYSV_ABI sceAudio3dTerminate() { return ORBIS_AUDIO3D_ERROR_NOT_READY; } - AudioOut::sceAudioOutOutput(state->audio_out_handle, nullptr); - AudioOut::sceAudioOutClose(state->audio_out_handle); - state.release(); + std::vector port_ids; + for (const auto& [id, _] : state->ports) { + port_ids.push_back(id); + } + for (const auto id : port_ids) { + sceAudio3dPortClose(id); + } + + state.reset(); return ORBIS_OK; } @@ -557,6 +965,7 @@ void RegisterLib(Core::Loader::SymbolsResolver* sym) { sceAudio3dGetSpeakerArrayMixCoefficients2); LIB_FUNCTION("UmCvjSmuZIw", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dInitialize); LIB_FUNCTION("jO2tec4dJ2M", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dObjectReserve); + LIB_FUNCTION("V1FBFpNIAzk", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dObjectSetAttribute); LIB_FUNCTION("4uyHN9q4ZeU", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dObjectSetAttributes); LIB_FUNCTION("1HXxo-+1qCw", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dObjectUnreserve); LIB_FUNCTION("lw0qrdSjZt8", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dPortAdvance); diff --git a/src/core/libraries/audio3d/audio3d.h b/src/core/libraries/audio3d/audio3d.h index ae20e39a8..0db7fa83b 100644 --- a/src/core/libraries/audio3d/audio3d.h +++ b/src/core/libraries/audio3d/audio3d.h @@ -3,7 +3,10 @@ #pragma once +#include +#include #include +#include #include #include "common/types.h" @@ -15,6 +18,8 @@ class SymbolsResolver; namespace Libraries::Audio3d { +constexpr int ORBIS_AUDIO3D_OBJECT_INVALID = 0xFFFFFFFF; + enum class OrbisAudio3dRate : u32 { ORBIS_AUDIO3D_RATE_48000 = 0, }; @@ -60,10 +65,21 @@ struct OrbisAudio3dPcm { enum class OrbisAudio3dAttributeId : u32 { ORBIS_AUDIO3D_ATTRIBUTE_PCM = 1, + ORBIS_AUDIO3D_ATTRIBUTE_POSITION = 2, + ORBIS_AUDIO3D_ATTRIBUTE_GAIN = 3, + ORBIS_AUDIO3D_ATTRIBUTE_SPREAD = 4, + ORBIS_AUDIO3D_ATTRIBUTE_PRIORITY = 5, + ORBIS_AUDIO3D_ATTRIBUTE_PASSTHROUGH = 6, + ORBIS_AUDIO3D_ATTRIBUTE_AMBISONICS = 7, + ORBIS_AUDIO3D_ATTRIBUTE_APPLICATION_SPECIFIC = 8, + ORBIS_AUDIO3D_ATTRIBUTE_RESET_STATE = 9, + ORBIS_AUDIO3D_ATTRIBUTE_RESTRICTED = 10, + ORBIS_AUDIO3D_ATTRIBUTE_OUTPUT_ROUTE = 11, }; using OrbisAudio3dPortId = u32; using OrbisAudio3dObjectId = u32; +using OrbisAudio3dAmbisonics = u32; struct OrbisAudio3dAttribute { OrbisAudio3dAttributeId attribute_id; @@ -75,17 +91,35 @@ struct OrbisAudio3dAttribute { struct AudioData { u8* sample_buffer; u32 num_samples; + u32 num_channels{1}; // channels in sample_buffer + OrbisAudio3dFormat format{ + OrbisAudio3dFormat::ORBIS_AUDIO3D_FORMAT_S16}; // format of sample_buffer +}; + +struct ObjectState { + std::deque pcm_queue; + std::unordered_map> persistent_attributes; }; struct Port { + mutable std::recursive_mutex mutex; OrbisAudio3dOpenParameters parameters{}; - std::deque queue; // Only stores PCM buffers for now - std::optional current_buffer{}; + // Opened lazily on the first sceAudio3dPortPush call. + s32 audio_out_handle{-1}; + // Handles explicitly opened by the game via sceAudio3dAudioOutOpen. + std::vector audioout_handles; + // Reserved objects and their state. + std::unordered_map objects; + // increasing counter for generating unique object IDs within this port. + OrbisAudio3dObjectId next_object_id{0}; + // Bed audio queue + std::deque bed_queue; + // Mixed stereo frames ready to be consumed by sceAudio3dPortPush. + std::deque mixed_queue; }; struct Audio3dState { std::unordered_map ports; - s32 audio_out_handle; }; s32 PS4_SYSV_ABI sceAudio3dAudioOutClose(s32 handle); @@ -109,15 +143,20 @@ s32 PS4_SYSV_ABI sceAudio3dGetSpeakerArrayMixCoefficients2(); s32 PS4_SYSV_ABI sceAudio3dInitialize(s64 reserved); s32 PS4_SYSV_ABI sceAudio3dObjectReserve(OrbisAudio3dPortId port_id, OrbisAudio3dObjectId* object_id); +s32 PS4_SYSV_ABI sceAudio3dObjectSetAttribute(OrbisAudio3dPortId port_id, + OrbisAudio3dObjectId object_id, + OrbisAudio3dAttributeId attribute_id, + const void* attribute, u64 attribute_size); s32 PS4_SYSV_ABI sceAudio3dObjectSetAttributes(OrbisAudio3dPortId port_id, OrbisAudio3dObjectId object_id, u64 num_attributes, const OrbisAudio3dAttribute* attribute_array); -s32 PS4_SYSV_ABI sceAudio3dObjectUnreserve(); +s32 PS4_SYSV_ABI sceAudio3dObjectUnreserve(OrbisAudio3dPortId port_id, + OrbisAudio3dObjectId object_id); s32 PS4_SYSV_ABI sceAudio3dPortAdvance(OrbisAudio3dPortId port_id); -s32 PS4_SYSV_ABI sceAudio3dPortClose(); +s32 PS4_SYSV_ABI sceAudio3dPortClose(OrbisAudio3dPortId port_id); s32 PS4_SYSV_ABI sceAudio3dPortCreate(); s32 PS4_SYSV_ABI sceAudio3dPortDestroy(); -s32 PS4_SYSV_ABI sceAudio3dPortFlush(); +s32 PS4_SYSV_ABI sceAudio3dPortFlush(OrbisAudio3dPortId port_id); s32 PS4_SYSV_ABI sceAudio3dPortFreeState(); s32 PS4_SYSV_ABI sceAudio3dPortGetAttributesSupported(); s32 PS4_SYSV_ABI sceAudio3dPortGetList(); diff --git a/src/core/libraries/avplayer/avplayer_impl.cpp b/src/core/libraries/avplayer/avplayer_impl.cpp index 138747da4..db32862ad 100644 --- a/src/core/libraries/avplayer/avplayer_impl.cpp +++ b/src/core/libraries/avplayer/avplayer_impl.cpp @@ -12,28 +12,28 @@ void* PS4_SYSV_ABI AvPlayer::Allocate(void* handle, u32 alignment, u32 size) { const auto* const self = reinterpret_cast(handle); const auto allocate = self->m_init_data_original.memory_replacement.allocate; const auto ptr = self->m_init_data_original.memory_replacement.object_ptr; - return Core::ExecuteGuest(allocate, ptr, alignment, size); + return allocate(ptr, alignment, size); } void PS4_SYSV_ABI AvPlayer::Deallocate(void* handle, void* memory) { const auto* const self = reinterpret_cast(handle); const auto deallocate = self->m_init_data_original.memory_replacement.deallocate; const auto ptr = self->m_init_data_original.memory_replacement.object_ptr; - return Core::ExecuteGuest(deallocate, ptr, memory); + return deallocate(ptr, memory); } void* PS4_SYSV_ABI AvPlayer::AllocateTexture(void* handle, u32 alignment, u32 size) { const auto* const self = reinterpret_cast(handle); const auto allocate = self->m_init_data_original.memory_replacement.allocate_texture; const auto ptr = self->m_init_data_original.memory_replacement.object_ptr; - return Core::ExecuteGuest(allocate, ptr, alignment, size); + return allocate(ptr, alignment, size); } void PS4_SYSV_ABI AvPlayer::DeallocateTexture(void* handle, void* memory) { const auto* const self = reinterpret_cast(handle); const auto deallocate = self->m_init_data_original.memory_replacement.deallocate_texture; const auto ptr = self->m_init_data_original.memory_replacement.object_ptr; - return Core::ExecuteGuest(deallocate, ptr, memory); + return deallocate(ptr, memory); } int PS4_SYSV_ABI AvPlayer::OpenFile(void* handle, const char* filename) { @@ -42,7 +42,7 @@ int PS4_SYSV_ABI AvPlayer::OpenFile(void* handle, const char* filename) { const auto open = self->m_init_data_original.file_replacement.open; const auto ptr = self->m_init_data_original.file_replacement.object_ptr; - return Core::ExecuteGuest(open, ptr, filename); + return open(ptr, filename); } int PS4_SYSV_ABI AvPlayer::CloseFile(void* handle) { @@ -51,7 +51,7 @@ int PS4_SYSV_ABI AvPlayer::CloseFile(void* handle) { const auto close = self->m_init_data_original.file_replacement.close; const auto ptr = self->m_init_data_original.file_replacement.object_ptr; - return Core::ExecuteGuest(close, ptr); + return close(ptr); } int PS4_SYSV_ABI AvPlayer::ReadOffsetFile(void* handle, u8* buffer, u64 position, u32 length) { @@ -60,7 +60,7 @@ int PS4_SYSV_ABI AvPlayer::ReadOffsetFile(void* handle, u8* buffer, u64 position const auto read_offset = self->m_init_data_original.file_replacement.read_offset; const auto ptr = self->m_init_data_original.file_replacement.object_ptr; - return Core::ExecuteGuest(read_offset, ptr, buffer, position, length); + return read_offset(ptr, buffer, position, length); } u64 PS4_SYSV_ABI AvPlayer::SizeFile(void* handle) { @@ -69,7 +69,7 @@ u64 PS4_SYSV_ABI AvPlayer::SizeFile(void* handle) { const auto size = self->m_init_data_original.file_replacement.size; const auto ptr = self->m_init_data_original.file_replacement.object_ptr; - return Core::ExecuteGuest(size, ptr); + return size(ptr); } AvPlayerInitData AvPlayer::StubInitData(const AvPlayerInitData& data) { diff --git a/src/core/libraries/avplayer/avplayer_state.cpp b/src/core/libraries/avplayer/avplayer_state.cpp index e1b11840e..dbaa36d18 100644 --- a/src/core/libraries/avplayer/avplayer_state.cpp +++ b/src/core/libraries/avplayer/avplayer_state.cpp @@ -92,7 +92,7 @@ void AvPlayerState::DefaultEventCallback(void* opaque, AvPlayerEvents event_id, const auto callback = self->m_event_replacement.event_callback; const auto ptr = self->m_event_replacement.object_ptr; if (callback != nullptr) { - Core::ExecuteGuest(callback, ptr, event_id, 0, event_data); + callback(ptr, event_id, 0, event_data); } } diff --git a/src/core/libraries/gnmdriver/gnmdriver.cpp b/src/core/libraries/gnmdriver/gnmdriver.cpp index 326dc2418..1993d8cd7 100644 --- a/src/core/libraries/gnmdriver/gnmdriver.cpp +++ b/src/core/libraries/gnmdriver/gnmdriver.cpp @@ -627,10 +627,30 @@ int PS4_SYSV_ABI sceGnmDrawIndirectCountMulti() { return ORBIS_OK; } -int PS4_SYSV_ABI sceGnmDrawIndirectMulti() { - LOG_ERROR(Lib_GnmDriver, "(STUBBED) called"); - UNREACHABLE(); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceGnmDrawIndirectMulti(u32* cmdbuf, u32 size, u32 data_offset, u32 max_count, + u32 shader_stage, u32 vertex_sgpr_offset, + u32 instance_sgpr_offset, u32 flags) { + LOG_TRACE(Lib_GnmDriver, "called"); + + if (cmdbuf && size == 11 && shader_stage < ShaderStages::Max && vertex_sgpr_offset < 0x10 && + instance_sgpr_offset < 0x10) { + const auto predicate = flags & 1 ? PM4Predicate::PredEnable : PM4Predicate::PredDisable; + cmdbuf = WriteHeader( + cmdbuf, 4, PM4ShaderType::ShaderGraphics, predicate); + + const auto sgpr_offset = indirect_sgpr_offsets[shader_stage]; + cmdbuf[0] = data_offset; + cmdbuf[1] = vertex_sgpr_offset == 0 ? 0 : (vertex_sgpr_offset & 0xffffu) + sgpr_offset; + cmdbuf[2] = instance_sgpr_offset == 0 ? 0 : (instance_sgpr_offset & 0xffffu) + sgpr_offset; + cmdbuf[3] = max_count; + cmdbuf[4] = sizeof(DrawIndirectArgs); + cmdbuf[5] = sceKernelIsNeoMode() ? flags & 0xe0000000u | 2u : 2u; // auto index + + cmdbuf += 6; + WriteTrailingNop<3>(cmdbuf); + return ORBIS_OK; + } + return -1; } u32 PS4_SYSV_ABI sceGnmDrawInitDefaultHardwareState(u32* cmdbuf, u32 size) { diff --git a/src/core/libraries/gnmdriver/gnmdriver.h b/src/core/libraries/gnmdriver/gnmdriver.h index 9f5fde628..4ece58ebd 100644 --- a/src/core/libraries/gnmdriver/gnmdriver.h +++ b/src/core/libraries/gnmdriver/gnmdriver.h @@ -60,7 +60,9 @@ s32 PS4_SYSV_ABI sceGnmDrawIndexOffset(u32* cmdbuf, u32 size, u32 index_offset, s32 PS4_SYSV_ABI sceGnmDrawIndirect(u32* cmdbuf, u32 size, u32 data_offset, u32 shader_stage, u32 vertex_sgpr_offset, u32 instance_sgpr_offset, u32 flags); int PS4_SYSV_ABI sceGnmDrawIndirectCountMulti(); -int PS4_SYSV_ABI sceGnmDrawIndirectMulti(); +s32 PS4_SYSV_ABI sceGnmDrawIndirectMulti(u32* cmdbuf, u32 size, u32 data_offset, u32 max_count, + u32 shader_stage, u32 vertex_sgpr_offset, + u32 instance_sgpr_offset, u32 flags); u32 PS4_SYSV_ABI sceGnmDrawInitDefaultHardwareState(u32* cmdbuf, u32 size); u32 PS4_SYSV_ABI sceGnmDrawInitDefaultHardwareState175(u32* cmdbuf, u32 size); u32 PS4_SYSV_ABI sceGnmDrawInitDefaultHardwareState200(u32* cmdbuf, u32 size); diff --git a/src/core/libraries/ime/ime.cpp b/src/core/libraries/ime/ime.cpp index d4f9789d1..c72dc4f28 100644 --- a/src/core/libraries/ime/ime.cpp +++ b/src/core/libraries/ime/ime.cpp @@ -100,16 +100,16 @@ public: if (m_ime_mode) { OrbisImeParam param = m_param.ime; if (use_param_handler) { - Core::ExecuteGuest(param.handler, param.arg, event); + param.handler(param.arg, event); } else { - Core::ExecuteGuest(handler, param.arg, event); + handler(param.arg, event); } } else { OrbisImeKeyboardParam param = m_param.key; if (use_param_handler) { - Core::ExecuteGuest(param.handler, param.arg, event); + param.handler(param.arg, event); } else { - Core::ExecuteGuest(handler, param.arg, event); + handler(param.arg, event); } } } diff --git a/src/core/libraries/ime/ime_dialog_ui.cpp b/src/core/libraries/ime/ime_dialog_ui.cpp index 6fc1b639d..f306a803b 100644 --- a/src/core/libraries/ime/ime_dialog_ui.cpp +++ b/src/core/libraries/ime/ime_dialog_ui.cpp @@ -348,8 +348,7 @@ bool ImeDialogState::CallTextFilter() { src_text_length = static_cast( BoundedUtf16Length(src_text, static_cast(ORBIS_IME_DIALOG_MAX_TEXT_LENGTH))); - int ret = - Core::ExecuteGuest(text_filter, out_text, &out_text_length, src_text, src_text_length); + int ret = text_filter(out_text, &out_text_length, src_text, src_text_length); if (ret != 0) { return false; @@ -381,7 +380,7 @@ bool ImeDialogState::CallKeyboardFilter(const OrbisImeKeycode* src_keycode, u16* return true; } - int ret = Core::ExecuteGuest(keyboard_filter, src_keycode, out_keycode, out_status, nullptr); + int ret = keyboard_filter(src_keycode, out_keycode, out_status, nullptr); return ret == 0; } diff --git a/src/core/libraries/kernel/threads.cpp b/src/core/libraries/kernel/threads.cpp index 082a52b67..083dc8ee1 100644 --- a/src/core/libraries/kernel/threads.cpp +++ b/src/core/libraries/kernel/threads.cpp @@ -7,6 +7,17 @@ namespace Libraries::Kernel { +void PS4_SYSV_ABI ClearStack() { + void* const stackaddr_attr = Libraries::Kernel::g_curthread->attr.stackaddr_attr; + void* volatile sp; + asm("mov %%rsp, %0" : "=rm"(sp)); + // leave a safety net of 64 bytes for memset + const size_t size = ((uintptr_t)sp - (uintptr_t)stackaddr_attr) - 64; + void* volatile buf = alloca(size); + memset(buf, 0, size); + sp = nullptr; +} + void RegisterThreads(Core::Loader::SymbolsResolver* sym) { RegisterMutex(sym); RegisterCond(sym); diff --git a/src/core/libraries/kernel/threads.h b/src/core/libraries/kernel/threads.h index 42ab0b13f..81535352c 100644 --- a/src/core/libraries/kernel/threads.h +++ b/src/core/libraries/kernel/threads.h @@ -27,6 +27,7 @@ int PS4_SYSV_ABI posix_pthread_create(PthreadT* thread, const PthreadAttrT* attr PthreadEntryFunc start_routine, void* arg); int PS4_SYSV_ABI posix_pthread_join(PthreadT pthread, void** thread_return); +int PS4_SYSV_ABI posix_pthread_detach(PthreadT pthread); int PS4_SYSV_ABI posix_pthread_mutexattr_init(PthreadMutexAttrT* attr); int PS4_SYSV_ABI posix_pthread_mutexattr_settype(PthreadMutexAttrT* attr, PthreadMutexType type); @@ -40,6 +41,8 @@ int PS4_SYSV_ABI posix_pthread_mutex_destroy(PthreadMutexT* mutex); void RegisterThreads(Core::Loader::SymbolsResolver* sym); +void PS4_SYSV_ABI ClearStack(); + class Thread { public: explicit Thread() = default; diff --git a/src/core/libraries/kernel/threads/exception.cpp b/src/core/libraries/kernel/threads/exception.cpp index 0b27f2bd8..cf36da0cc 100644 --- a/src/core/libraries/kernel/threads/exception.cpp +++ b/src/core/libraries/kernel/threads/exception.cpp @@ -317,7 +317,7 @@ int PS4_SYSV_ABI sceKernelRaiseException(PthreadT thread, int signum) { u64 res = NtQueueApcThreadEx(reinterpret_cast(thread->native_thr.GetHandle()), option, ExceptionHandler, (void*)thread->name.c_str(), - (void*)native_signum, nullptr); + (void*)(s64)native_signum, nullptr); ASSERT(res == 0); #endif return ORBIS_OK; diff --git a/src/core/libraries/kernel/threads/pthread.cpp b/src/core/libraries/kernel/threads/pthread.cpp index 20bd20f4b..da9e1600f 100644 --- a/src/core/libraries/kernel/threads/pthread.cpp +++ b/src/core/libraries/kernel/threads/pthread.cpp @@ -199,10 +199,17 @@ static void RunThread(void* arg) { g_curthread = curthread; Common::SetCurrentThreadName(curthread->name.c_str()); DebugState.AddCurrentThreadToGuestList(); + Core::InitializeTLS(); + + curthread->native_thr.Initialize(); + + // Clear the stack before running the guest thread + if (False(g_curthread->attr.flags & PthreadAttrFlags::StackUser)) { + ClearStack(); + } /* Run the current thread's start routine with argument: */ - curthread->native_thr.Initialize(); - void* ret = Core::ExecuteGuest(curthread->start_routine, curthread->arg); + void* ret = curthread->start_routine(curthread->arg); /* Remove thread from tracking */ DebugState.RemoveCurrentThreadFromGuestList(); diff --git a/src/core/libraries/kernel/threads/pthread_spec.cpp b/src/core/libraries/kernel/threads/pthread_spec.cpp index 094866a5a..38032f174 100644 --- a/src/core/libraries/kernel/threads/pthread_spec.cpp +++ b/src/core/libraries/kernel/threads/pthread_spec.cpp @@ -84,7 +84,7 @@ void _thread_cleanupspecific() { * destructor: */ lk.unlock(); - Core::ExecuteGuest(destructor, data); + destructor(data); lk.lock(); } } diff --git a/src/core/libraries/network/http.cpp b/src/core/libraries/network/http.cpp index 8bc9b51f0..4d6886908 100644 --- a/src/core/libraries/network/http.cpp +++ b/src/core/libraries/network/http.cpp @@ -935,18 +935,24 @@ int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, v pathLength++; } - // Ensure the path starts with '/' - if (pathLength > 0 && pathStart[0] != '/') { + if (pathLength > 0) { // Prepend '/' to the path requiredSize += pathLength + 2; // Include '/' and null terminator if (pool && prepare < requiredSize) { - LOG_ERROR(Lib_Http, "out of memory"); + LOG_ERROR(Lib_Http, "out of memory, provided size: {}, required size: {}", + prepare, requiredSize); return ORBIS_HTTP_ERROR_OUT_OF_MEMORY; } if (out && pool) { out->path = (char*)pool + (requiredSize - pathLength - 2); + out->username = (char*)pool + (requiredSize - pathLength - 3); + out->password = (char*)pool + (requiredSize - pathLength - 3); + out->hostname = (char*)pool + (requiredSize - pathLength - 3); + out->query = (char*)pool + (requiredSize - pathLength - 3); + out->fragment = (char*)pool + (requiredSize - pathLength - 3); + out->username[0] = '\0'; out->path[0] = '/'; // Add leading '/' memcpy(out->path + 1, pathStart, pathLength); out->path[pathLength + 1] = '\0'; @@ -969,6 +975,19 @@ int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, v // Move past the path offset += pathLength; + } else { + // Parse the path (everything after the slashes) + char* pathStart = (char*)srcUri + offset; + u64 pathLength = 0; + while (pathStart[pathLength] && pathStart[pathLength] != '?' && + pathStart[pathLength] != '#') { + pathLength++; + } + + if (pathLength > 0) { + requiredSize += pathLength + 3; // Add '/' and null terminator, and the dummy + // null character for the other fields + } } } diff --git a/src/core/libraries/network/net.cpp b/src/core/libraries/network/net.cpp index 102447952..ca75ad394 100644 --- a/src/core/libraries/network/net.cpp +++ b/src/core/libraries/network/net.cpp @@ -887,6 +887,10 @@ int PS4_SYSV_ABI sceNetEpollWait(OrbisNetId epollid, OrbisNetEpollEvent* events, } file->resolver->Resolve(); + if (file->resolver->resolution_error != ORBIS_OK) { + // Resolution failed, shouldn't appear. + continue; + } const auto it = std::ranges::find_if(epoll->events, [&](auto& el) { return el.first == rid; }); @@ -1402,8 +1406,21 @@ int PS4_SYSV_ABI sceNetResolverDestroy(OrbisNetId resolverid) { } int PS4_SYSV_ABI sceNetResolverGetError(OrbisNetId resolverid, s32* status) { - LOG_ERROR(Lib_Net, "(STUBBED) called rid = {}", resolverid); - *status = 0; + if (!status) { + LOG_ERROR(Lib_Net, "status == nullptr"); + *sceNetErrnoLoc() = ORBIS_NET_EINVAL; + return ORBIS_NET_ERROR_EINVAL; + } + + auto file = FDTable::Instance()->GetResolver(resolverid); + if (!file) { + LOG_ERROR(Lib_Net, "invalid resolverid {}", resolverid); + *sceNetErrnoLoc() = ORBIS_NET_EBADF; + return ORBIS_NET_ERROR_EBADF; + } + + *status = file->resolver->resolution_error; + LOG_INFO(Lib_Net, "called rid = {}, error = {:#x}", resolverid, static_cast(*status)); return ORBIS_OK; } @@ -1425,10 +1442,17 @@ int PS4_SYSV_ABI sceNetResolverStartNtoa(OrbisNetId resolverid, const char* host auto file = FDTable::Instance()->GetResolver(resolverid); if (!file) { + LOG_ERROR(Lib_Net, "invalid resolverid {}", resolverid); *sceNetErrnoLoc() = ORBIS_NET_EBADF; return ORBIS_NET_ERROR_EBADF; } + if (!Config::getIsConnectedToNetwork()) { + *sceNetErrnoLoc() = ORBIS_NET_RESOLVER_ENODNS; + file->resolver->resolution_error = ORBIS_NET_ERROR_RESOLVER_ENODNS; + return ORBIS_NET_ERROR_RESOLVER_ENODNS; + } + if ((flags & ORBIS_NET_RESOLVER_ASYNC) != 0) { return file->resolver->ResolveAsync(hostname, addr, timeout, retry, flags); } diff --git a/src/core/libraries/network/net_ctl_obj.cpp b/src/core/libraries/network/net_ctl_obj.cpp index a295477b6..a4081cd11 100644 --- a/src/core/libraries/network/net_ctl_obj.cpp +++ b/src/core/libraries/network/net_ctl_obj.cpp @@ -50,7 +50,7 @@ void NetCtlInternal::CheckCallback() { : ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED; for (const auto [func, arg] : callbacks) { if (func != nullptr) { - Core::ExecuteGuest(func, event, arg); + func(event, arg); } } } @@ -61,7 +61,7 @@ void NetCtlInternal::CheckNpToolkitCallback() { : ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED; for (const auto [func, arg] : nptool_callbacks) { if (func != nullptr) { - Core::ExecuteGuest(func, event, arg); + func(event, arg); } } } diff --git a/src/core/libraries/network/net_resolver.cpp b/src/core/libraries/network/net_resolver.cpp index 6571176df..7eb4c4001 100644 --- a/src/core/libraries/network/net_resolver.cpp +++ b/src/core/libraries/network/net_resolver.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "common/assert.h" +#include "common/config.h" #include "common/singleton.h" #include "common/types.h" #include "core/libraries/error_codes.h" @@ -26,11 +27,19 @@ int Resolver::ResolveAsync(const char* hostname, OrbisNetInAddr* addr, int timeo } void Resolver::Resolve() { + if (!Config::getIsConnectedToNetwork()) { + resolution_error = ORBIS_NET_ERROR_RESOLVER_ENODNS; + return; + } + if (async_resolution) { auto* netinfo = Common::Singleton::Instance(); auto ret = netinfo->ResolveHostname(async_resolution->hostname, async_resolution->addr); - resolution_error = ret; + if (ret != ORBIS_OK) { + // Resolver errors are stored as ORBIS_NET_ERROR values. + resolution_error = -ret | ORBIS_NET_ERROR_BASE; + } } else { LOG_ERROR(Lib_Net, "async resolution has not been set-up"); } diff --git a/src/core/libraries/network/net_resolver.h b/src/core/libraries/network/net_resolver.h index 34d7dc591..4c5c2ece8 100644 --- a/src/core/libraries/network/net_resolver.h +++ b/src/core/libraries/network/net_resolver.h @@ -18,6 +18,8 @@ public: int ResolveAsync(const char* hostname, OrbisNetInAddr* addr, int timeout, int retry, int flags); void Resolve(); + int resolution_error = ORBIS_OK; + private: struct AsyncResolution { const char* hostname; @@ -31,7 +33,6 @@ private: int poolid; int flags; std::optional async_resolution{}; - int resolution_error = ORBIS_OK; std::mutex m_mutex; }; diff --git a/src/core/libraries/network/posix_sockets.cpp b/src/core/libraries/network/posix_sockets.cpp index f2adccf50..164d85896 100644 --- a/src/core/libraries/network/posix_sockets.cpp +++ b/src/core/libraries/network/posix_sockets.cpp @@ -430,6 +430,15 @@ int PosixSocket::Connect(const OrbisNetSockaddr* addr, u32 namelen) { sockaddr addr2; convertOrbisNetSockaddrToPosix(addr, &addr2); int result = ::connect(sock, &addr2, sizeof(sockaddr_in)); +#ifdef _WIN32 + // Winsock returns EWOULDBLOCK where real hardware returns EINPROGRESS + // Step in here on errors to address this. + if (result == -1) { + if (WSAGetLastError() == WSAEWOULDBLOCK) { + WSASetLastError(WSAEINPROGRESS); + } + } +#endif LOG_DEBUG(Lib_Net, "raw connect result = {}, errno = {}", result, result == -1 ? Common::GetLastErrorMsg() : "none"); return ConvertReturnErrorCode(result); diff --git a/src/core/libraries/network/sys_net.cpp b/src/core/libraries/network/sys_net.cpp index 76107d323..a0fae3a58 100644 --- a/src/core/libraries/network/sys_net.cpp +++ b/src/core/libraries/network/sys_net.cpp @@ -28,8 +28,11 @@ int PS4_SYSV_ABI sys_connect(OrbisNetId s, const OrbisNetSockaddr* addr, u32 add if (returncode >= 0) { return returncode; } - LOG_ERROR(Lib_Net, "s = {} ({}) returned error code: {}", s, file->m_guest_name, - (u32)*Libraries::Kernel::__Error()); + u32 error = *Libraries::Kernel::__Error(); + // Don't log EINPROGRESS or EISCONN, these are normal to see from non-blocking communication. + if (error != ORBIS_NET_EINPROGRESS && error != ORBIS_NET_EISCONN) { + LOG_ERROR(Lib_Net, "s = {} ({}) returned error code: {}", s, file->m_guest_name, error); + } return -1; } @@ -59,8 +62,13 @@ int PS4_SYSV_ABI sys_accept(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen) LOG_DEBUG(Lib_Net, "s = {} ({})", s, file->m_guest_name); auto new_sock = file->socket->Accept(addr, paddrlen); if (!new_sock) { - LOG_ERROR(Lib_Net, "s = {} ({}) returned error code creating new socket for accepting: {}", - s, file->m_guest_name, (u32)*Libraries::Kernel::__Error()); + u32 error = *Libraries::Kernel::__Error(); + // Don't log EWOULDBLOCK, this is normal to see from non-blocking communication. + if (error != ORBIS_NET_EWOULDBLOCK) { + LOG_ERROR(Lib_Net, + "s = {} ({}) returned error code creating new socket for accepting: {}", s, + file->m_guest_name, error); + } return -1; } auto fd = FDTable::Instance()->CreateHandle(); @@ -396,8 +404,11 @@ s64 PS4_SYSV_ABI sys_recvfrom(OrbisNetId s, void* buf, u64 len, int flags, Orbis if (returncode >= 0) { return returncode; } - LOG_ERROR(Lib_Net, "s = {} ({}) returned error code: {}", s, file->m_guest_name, - (u32)*Libraries::Kernel::__Error()); + // Don't log EWOULDBLOCK, this is normal to see from non-blocking communication. + u32 error = *Libraries::Kernel::__Error(); + if (error != ORBIS_NET_EWOULDBLOCK) { + LOG_ERROR(Lib_Net, "s = {} ({}) returned error code: {}", s, file->m_guest_name, error); + } return -1; } diff --git a/src/core/libraries/ngs2/ngs2.cpp b/src/core/libraries/ngs2/ngs2.cpp index 2f785f9a0..97d19c352 100644 --- a/src/core/libraries/ngs2/ngs2.cpp +++ b/src/core/libraries/ngs2/ngs2.cpp @@ -160,13 +160,13 @@ s32 PS4_SYSV_ABI sceNgs2SystemCreateWithAllocator(const OrbisNgs2SystemOption* o result = SystemSetup(option, &bufferInfo, 0, 0); if (result >= 0) { uintptr_t sysUserData = allocator->userData; - result = Core::ExecuteGuest(hostAlloc, &bufferInfo); + result = hostAlloc(&bufferInfo); if (result >= 0) { OrbisNgs2Handle* handleCopy = outHandle; result = SystemSetup(option, &bufferInfo, hostFree, handleCopy); if (result < 0) { if (hostFree) { - Core::ExecuteGuest(hostFree, &bufferInfo); + hostFree(&bufferInfo); } } } diff --git a/src/core/libraries/save_data/savedata.cpp b/src/core/libraries/save_data/savedata.cpp index a5199c297..48b086457 100644 --- a/src/core/libraries/save_data/savedata.cpp +++ b/src/core/libraries/save_data/savedata.cpp @@ -333,6 +333,9 @@ static bool match(std::string_view str, std::string_view pattern) { for (auto str_wild_it = str_it; str_wild_it <= str.end(); ++str_wild_it) { if (match({str_wild_it, str.end()}, {pat_it + 1, pattern.end()})) { return true; + } else if (str_wild_it == str.end()) { + // Avoid incrementing str_wild_it past str.end(). + break; } } return false; diff --git a/src/core/libraries/usbd/emulated/dimensions.cpp b/src/core/libraries/usbd/emulated/dimensions.cpp index 272f2f649..452968840 100644 --- a/src/core/libraries/usbd/emulated/dimensions.cpp +++ b/src/core/libraries/usbd/emulated/dimensions.cpp @@ -3,6 +3,9 @@ #include "dimensions.h" +#include "core/libraries/kernel/threads.h" +#include "core/tls.h" + #include #include @@ -619,22 +622,46 @@ libusb_transfer_status DimensionsBackend::HandleAsyncTransfer(libusb_transfer* t return LIBUSB_TRANSFER_COMPLETED; } +struct WriteThreadArgs { + DimensionsBackend* self; + libusb_transfer* transfer; +}; + +void* PS4_SYSV_ABI DimensionsBackend::WriteThread(void* arg) { + auto* args = reinterpret_cast(arg); + + auto* self = args->self; + auto* transfer = args->transfer; + + self->HandleAsyncTransfer(transfer); + + const u8 flags = transfer->flags; + transfer->status = LIBUSB_TRANSFER_COMPLETED; + transfer->actual_length = transfer->length; + if (transfer->callback) { + transfer->callback(transfer); + } + if (flags & LIBUSB_TRANSFER_FREE_TRANSFER) { + libusb_free_transfer(transfer); + } + delete args; + return nullptr; +} + s32 DimensionsBackend::SubmitTransfer(libusb_transfer* transfer) { if (transfer->endpoint == 0x01) { - std::thread write_thread([this, transfer] { - HandleAsyncTransfer(transfer); + using namespace Libraries::Kernel; + + PthreadAttrT attr{}; + posix_pthread_attr_init(&attr); + PthreadT thread{}; + auto* args = new WriteThreadArgs(); + args->self = this; + args->transfer = transfer; + posix_pthread_create(&thread, &attr, HOST_CALL(DimensionsBackend::WriteThread), args); + posix_pthread_attr_destroy(&attr); + posix_pthread_detach(thread); - const u8 flags = transfer->flags; - transfer->status = LIBUSB_TRANSFER_COMPLETED; - transfer->actual_length = transfer->length; - if (transfer->callback) { - transfer->callback(transfer); - } - if (flags & LIBUSB_TRANSFER_FREE_TRANSFER) { - libusb_free_transfer(transfer); - } - }); - write_thread.detach(); return LIBUSB_SUCCESS; } diff --git a/src/core/libraries/usbd/emulated/dimensions.h b/src/core/libraries/usbd/emulated/dimensions.h index d9573b5f4..bc409f7c3 100644 --- a/src/core/libraries/usbd/emulated/dimensions.h +++ b/src/core/libraries/usbd/emulated/dimensions.h @@ -103,6 +103,8 @@ protected: std::queue> m_queries; private: + static void* PS4_SYSV_ABI WriteThread(void* arg); + std::shared_ptr m_dimensions_toypad = std::make_shared(); std::array m_endpoint_out_extra = {0x09, 0x21, 0x11, 0x01, 0x00, 0x01, 0x22, 0x1d, 0x00}; diff --git a/src/core/linker.cpp b/src/core/linker.cpp index 7a0653e9f..0b80ecacc 100644 --- a/src/core/linker.cpp +++ b/src/core/linker.cpp @@ -135,7 +135,8 @@ void Linker::Execute(const std::vector& args) { } } params.entry_addr = module->GetEntryAddress(); - ExecuteGuest(RunMainEntry, ¶ms); + Libraries::Kernel::ClearStack(); + RunMainEntry(¶ms); }); } @@ -379,8 +380,7 @@ void* Linker::TlsGetAddr(u64 module_index, u64 offset) { if (!addr) { // Module was just loaded by above code. Allocate TLS block for it. const u32 init_image_size = module->tls.init_image_size; - u8* dest = reinterpret_cast( - Core::ExecuteGuest(heap_api->heap_malloc, module->tls.image_size)); + u8* dest = reinterpret_cast(heap_api->heap_malloc(module->tls.image_size)); const u8* src = reinterpret_cast(module->tls.image_virtual_addr); std::memcpy(dest, src, init_image_size); std::memset(dest + init_image_size, 0, module->tls.image_size - init_image_size); @@ -412,7 +412,7 @@ void* Linker::AllocateTlsForThread(bool is_primary) { ASSERT_MSG(ret == 0, "Unable to allocate TLS+TCB for the primary thread"); } else { if (heap_api) { - addr_out = Core::ExecuteGuest(heap_api->heap_malloc, total_tls_size); + addr_out = heap_api->heap_malloc(total_tls_size); } else { addr_out = std::malloc(total_tls_size); } @@ -422,7 +422,7 @@ void* Linker::AllocateTlsForThread(bool is_primary) { void Linker::FreeTlsForNonPrimaryThread(void* pointer) { if (heap_api) { - Core::ExecuteGuest(heap_api->heap_free, pointer); + heap_api->heap_free(pointer); } else { std::free(pointer); } diff --git a/src/core/module.cpp b/src/core/module.cpp index 127e74293..7e9d74a09 100644 --- a/src/core/module.cpp +++ b/src/core/module.cpp @@ -97,7 +97,7 @@ Module::~Module() = default; s32 Module::Start(u64 args, const void* argp, void* param) { LOG_INFO(Core_Linker, "Module started : {}", name); const VAddr addr = dynamic_info.init_virtual_addr + GetBaseAddress(); - return ExecuteGuest(reinterpret_cast(addr), args, argp, param); + return reinterpret_cast(addr)(args, argp, param); } void Module::LoadModuleToMemory(u32& max_tls_index) { diff --git a/src/core/tls.cpp b/src/core/tls.cpp index 57ed20f38..8b926cb39 100644 --- a/src/core/tls.cpp +++ b/src/core/tls.cpp @@ -198,7 +198,7 @@ Tcb* GetTcbBase() { thread_local std::once_flag init_tls_flag; -void EnsureThreadInitialized() { +void InitializeTLS() { std::call_once(init_tls_flag, [] { SetTcbBase(Libraries::Kernel::g_curthread->tcb); }); } diff --git a/src/core/tls.h b/src/core/tls.h index 27de518ea..2d94488f7 100644 --- a/src/core/tls.h +++ b/src/core/tls.h @@ -43,30 +43,7 @@ void SetTcbBase(void* image_address); Tcb* GetTcbBase(); /// Makes sure TLS is initialized for the thread before entering guest. -void EnsureThreadInitialized(); - -template -#ifdef __clang__ -__attribute__((optnone)) -#else -__attribute__((optimize("O0"))) -#endif -void ClearStack() { - volatile void* buf = alloca(size); - memset(const_cast(buf), 0, size); - buf = nullptr; -} - -template -ReturnType ExecuteGuest(PS4_SYSV_ABI ReturnType (*func)(FuncArgs...), CallArgs&&... args) { - EnsureThreadInitialized(); - // clear stack to avoid trash from EnsureThreadInitialized - auto* tcb = GetTcbBase(); - if (tcb != nullptr && tcb->tcb_fiber == nullptr) { - ClearStack<12_KB>(); - } - return func(std::forward(args)...); -} +void InitializeTLS(); template struct HostCallWrapperImpl; diff --git a/src/emulator.cpp b/src/emulator.cpp index 27def3565..18d3024dc 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -245,7 +245,7 @@ void Emulator::Run(std::filesystem::path file, std::vector args, LOG_INFO(Config, "General isConnectedToNetwork: {}", Config::getIsConnectedToNetwork()); LOG_INFO(Config, "General isPsnSignedIn: {}", Config::getPSNSignedIn()); LOG_INFO(Config, "GPU isNullGpu: {}", Config::nullGpu()); - LOG_INFO(Config, "GPU readbacks: {}", Config::readbacks()); + LOG_INFO(Config, "GPU readbacksMode: {}", Config::getReadbacksMode()); LOG_INFO(Config, "GPU readbackLinearImages: {}", Config::readbackLinearImages()); LOG_INFO(Config, "GPU directMemoryAccess: {}", Config::directMemoryAccess()); LOG_INFO(Config, "GPU shouldDumpShaders: {}", Config::dumpShaders()); diff --git a/src/video_core/amdgpu/liverpool.cpp b/src/video_core/amdgpu/liverpool.cpp index b2a4d7a61..c4f1d6695 100644 --- a/src/video_core/amdgpu/liverpool.cpp +++ b/src/video_core/amdgpu/liverpool.cpp @@ -229,6 +229,8 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(dcb.data()); while (!dcb.empty()) { @@ -267,27 +269,27 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::spanheader.count.Value() * 2; - const std::string_view label{reinterpret_cast(&nop->data_block[1]), - marker_sz}; - if (rasterizer) { + if (guest_markers_enabled) { + const auto marker_sz = nop->header.count.Value() * 2; + const std::string_view label{ + reinterpret_cast(&nop->data_block[1]), marker_sz}; rasterizer->ScopeMarkerBegin(label, true); } break; } case PM4CmdNop::PayloadType::DebugColorMarkerPush: { - const auto marker_sz = nop->header.count.Value() * 2; - const std::string_view label{reinterpret_cast(&nop->data_block[1]), - marker_sz}; - const u32 color = *reinterpret_cast( - reinterpret_cast(&nop->data_block[1]) + marker_sz); - if (rasterizer) { + if (guest_markers_enabled) { + const auto marker_sz = nop->header.count.Value() * 2; + const std::string_view label{ + reinterpret_cast(&nop->data_block[1]), marker_sz}; + const u32 color = *reinterpret_cast( + reinterpret_cast(&nop->data_block[1]) + marker_sz); rasterizer->ScopedMarkerInsertColor(label, color, true); } break; } case PM4CmdNop::PayloadType::DebugMarkerPop: { - if (rasterizer) { + if (guest_markers_enabled) { rasterizer->ScopeMarkerEnd(true); } break; @@ -427,9 +429,13 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); - rasterizer->ScopeMarkerBegin(fmt::format("gfx:{}:DrawIndex2", cmd_address)); - rasterizer->Draw(true); - rasterizer->ScopeMarkerEnd(); + if (host_markers_enabled) { + rasterizer->ScopeMarkerBegin(fmt::format("gfx:{}:DrawIndex2", cmd_address)); + rasterizer->Draw(true); + rasterizer->ScopeMarkerEnd(); + } else { + rasterizer->Draw(true); + } } break; } @@ -444,10 +450,14 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); - rasterizer->ScopeMarkerBegin( - fmt::format("gfx:{}:DrawIndexOffset2", cmd_address)); - rasterizer->Draw(true, draw_index_off->index_offset); - rasterizer->ScopeMarkerEnd(); + if (host_markers_enabled) { + rasterizer->ScopeMarkerBegin( + fmt::format("gfx:{}:DrawIndexOffset2", cmd_address)); + rasterizer->Draw(true, draw_index_off->index_offset); + rasterizer->ScopeMarkerEnd(); + } else { + rasterizer->Draw(true, draw_index_off->index_offset); + } } break; } @@ -460,9 +470,14 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); - rasterizer->ScopeMarkerBegin(fmt::format("gfx:{}:DrawIndexAuto", cmd_address)); - rasterizer->Draw(false); - rasterizer->ScopeMarkerEnd(); + if (host_markers_enabled) { + rasterizer->ScopeMarkerBegin( + fmt::format("gfx:{}:DrawIndexAuto", cmd_address)); + rasterizer->Draw(false); + rasterizer->ScopeMarkerEnd(); + } else { + rasterizer->Draw(false); + } } break; } @@ -475,9 +490,36 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); - rasterizer->ScopeMarkerBegin(fmt::format("gfx:{}:DrawIndirect", cmd_address)); - rasterizer->DrawIndirect(false, indirect_args_addr, offset, stride, 1, 0); - rasterizer->ScopeMarkerEnd(); + if (host_markers_enabled) { + rasterizer->ScopeMarkerBegin( + fmt::format("gfx:{}:DrawIndirect", cmd_address)); + rasterizer->DrawIndirect(false, indirect_args_addr, offset, stride, 1, 0); + rasterizer->ScopeMarkerEnd(); + } else { + rasterizer->DrawIndirect(false, indirect_args_addr, offset, stride, 1, 0); + } + } + break; + } + case PM4ItOpcode::DrawIndirectMulti: { + const auto* draw_indirect = + reinterpret_cast(header); + const auto offset = draw_indirect->data_offset; + if (DebugState.DumpingCurrentReg()) { + DebugState.PushRegsDump(base_addr, reinterpret_cast(header), regs); + } + if (rasterizer) { + const auto cmd_address = reinterpret_cast(header); + if (host_markers_enabled) { + rasterizer->ScopeMarkerBegin( + fmt::format("gfx:{}:DrawIndirectMulti", cmd_address)); + rasterizer->DrawIndirect(false, indirect_args_addr, offset, + draw_indirect->stride, draw_indirect->count, 0); + rasterizer->ScopeMarkerEnd(); + } else { + rasterizer->DrawIndirect(false, indirect_args_addr, offset, + draw_indirect->stride, draw_indirect->count, 0); + } } break; } @@ -491,10 +533,14 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); - rasterizer->ScopeMarkerBegin( - fmt::format("gfx:{}:DrawIndexIndirect", cmd_address)); - rasterizer->DrawIndirect(true, indirect_args_addr, offset, stride, 1, 0); - rasterizer->ScopeMarkerEnd(); + if (host_markers_enabled) { + rasterizer->ScopeMarkerBegin( + fmt::format("gfx:{}:DrawIndexIndirect", cmd_address)); + rasterizer->DrawIndirect(true, indirect_args_addr, offset, stride, 1, 0); + rasterizer->ScopeMarkerEnd(); + } else { + rasterizer->DrawIndirect(true, indirect_args_addr, offset, stride, 1, 0); + } } break; } @@ -507,12 +553,18 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); - rasterizer->ScopeMarkerBegin( - fmt::format("gfx:{}:DrawIndexIndirectMulti", cmd_address)); - rasterizer->DrawIndirect(true, indirect_args_addr, offset, - draw_index_indirect->stride, - draw_index_indirect->count, 0); - rasterizer->ScopeMarkerEnd(); + if (host_markers_enabled) { + rasterizer->ScopeMarkerBegin( + fmt::format("gfx:{}:DrawIndexIndirectMulti", cmd_address)); + rasterizer->DrawIndirect(true, indirect_args_addr, offset, + draw_index_indirect->stride, + draw_index_indirect->count, 0); + rasterizer->ScopeMarkerEnd(); + } else { + rasterizer->DrawIndirect(true, indirect_args_addr, offset, + draw_index_indirect->stride, + draw_index_indirect->count, 0); + } } break; } @@ -525,15 +577,24 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); - rasterizer->ScopeMarkerBegin( - fmt::format("gfx:{}:DrawIndexIndirectCountMulti", cmd_address)); - rasterizer->DrawIndirect(true, indirect_args_addr, offset, - draw_index_indirect->stride, - draw_index_indirect->count, - draw_index_indirect->count_indirect_enable.Value() - ? draw_index_indirect->count_addr - : 0); - rasterizer->ScopeMarkerEnd(); + if (host_markers_enabled) { + rasterizer->ScopeMarkerBegin( + fmt::format("gfx:{}:DrawIndexIndirectCountMulti", cmd_address)); + rasterizer->DrawIndirect(true, indirect_args_addr, offset, + draw_index_indirect->stride, + draw_index_indirect->count, + draw_index_indirect->count_indirect_enable.Value() + ? draw_index_indirect->count_addr + : 0); + rasterizer->ScopeMarkerEnd(); + } else { + rasterizer->DrawIndirect(true, indirect_args_addr, offset, + draw_index_indirect->stride, + draw_index_indirect->count, + draw_index_indirect->count_indirect_enable.Value() + ? draw_index_indirect->count_addr + : 0); + } } break; } @@ -550,9 +611,14 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); - rasterizer->ScopeMarkerBegin(fmt::format("gfx:{}:DispatchDirect", cmd_address)); - rasterizer->DispatchDirect(); - rasterizer->ScopeMarkerEnd(); + if (host_markers_enabled) { + rasterizer->ScopeMarkerBegin( + fmt::format("gfx:{}:DispatchDirect", cmd_address)); + rasterizer->DispatchDirect(); + rasterizer->ScopeMarkerEnd(); + } else { + rasterizer->DispatchDirect(); + } } break; } @@ -568,10 +634,14 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); - rasterizer->ScopeMarkerBegin( - fmt::format("gfx:{}:DispatchIndirect", cmd_address)); - rasterizer->DispatchIndirect(indirect_args_addr, offset, size); - rasterizer->ScopeMarkerEnd(); + if (host_markers_enabled) { + rasterizer->ScopeMarkerBegin( + fmt::format("gfx:{}:DispatchIndirect", cmd_address)); + rasterizer->DispatchIndirect(indirect_args_addr, offset, size); + rasterizer->ScopeMarkerEnd(); + } else { + rasterizer->DispatchIndirect(indirect_args_addr, offset, size); + } } break; } @@ -829,6 +899,7 @@ template Liverpool::Task Liverpool::ProcessCompute(std::span acb, u32 vqid) { FIBER_ENTER(acb_task_name[vqid]); auto& queue = asc_queues[{vqid}]; + const bool host_markers_enabled = rasterizer && Config::getVkHostMarkersEnabled(); struct IndirectPatch { const PM4Header* header; @@ -881,6 +952,7 @@ Liverpool::Task Liverpool::ProcessCompute(std::span acb, u32 vqid) { } const PM4ItOpcode opcode = header->type3.opcode; + const auto* it_body = reinterpret_cast(header) + 1; switch (opcode) { case PM4ItOpcode::Nop: { @@ -998,10 +1070,14 @@ Liverpool::Task Liverpool::ProcessCompute(std::span acb, u32 vqid) { } if (rasterizer && (cs_program.dispatch_initiator & 1)) { const auto cmd_address = reinterpret_cast(header); - rasterizer->ScopeMarkerBegin( - fmt::format("asc[{}]:{}:DispatchDirect", vqid, cmd_address)); - rasterizer->DispatchDirect(); - rasterizer->ScopeMarkerEnd(); + if (host_markers_enabled) { + rasterizer->ScopeMarkerBegin( + fmt::format("asc[{}]:{}:DispatchDirect", vqid, cmd_address)); + rasterizer->DispatchDirect(); + rasterizer->ScopeMarkerEnd(); + } else { + rasterizer->DispatchDirect(); + } } break; } @@ -1017,10 +1093,14 @@ Liverpool::Task Liverpool::ProcessCompute(std::span acb, u32 vqid) { } if (rasterizer && (cs_program.dispatch_initiator & 1)) { const auto cmd_address = reinterpret_cast(header); - rasterizer->ScopeMarkerBegin( - fmt::format("asc[{}]:{}:DispatchIndirect", vqid, cmd_address)); - rasterizer->DispatchIndirect(ib_address, 0, size); - rasterizer->ScopeMarkerEnd(); + if (host_markers_enabled) { + rasterizer->ScopeMarkerBegin( + fmt::format("asc[{}]:{}:DispatchIndirect", vqid, cmd_address)); + rasterizer->DispatchIndirect(ib_address, 0, size); + rasterizer->ScopeMarkerEnd(); + } else { + rasterizer->DispatchIndirect(ib_address, 0, size); + } } break; } diff --git a/src/video_core/amdgpu/pm4_cmds.h b/src/video_core/amdgpu/pm4_cmds.h index 17511d0a2..abf58ad89 100644 --- a/src/video_core/amdgpu/pm4_cmds.h +++ b/src/video_core/amdgpu/pm4_cmds.h @@ -1051,6 +1051,24 @@ struct PM4CmdDrawIndirect { u32 draw_initiator; ///< Draw Initiator Register }; +struct PM4CmdDrawIndirectMulti { + PM4Type3Header header; ///< header + u32 data_offset; ///< Byte aligned offset where the required data structure starts + union { + u32 dw2; + BitField<0, 16, u32> base_vtx_loc; ///< Offset where the CP will write the + ///< BaseVertexLocation it fetched from memory + }; + union { + u32 dw3; + BitField<0, 16, u32> start_inst_loc; ///< Offset where the CP will write the + ///< StartInstanceLocation it fetched from memory + }; + u32 count; ///< Count of data structures to loop through before going to next packet + u32 stride; ///< Stride in memory from one data structure to the next + u32 draw_initiator; ///< Draw Initiator Register +}; + struct DrawIndexedIndirectArgs { u32 index_count_per_instance; u32 instance_count; diff --git a/src/video_core/buffer_cache/memory_tracker.h b/src/video_core/buffer_cache/memory_tracker.h index ec0878c3b..2ec86de35 100644 --- a/src/video_core/buffer_cache/memory_tracker.h +++ b/src/video_core/buffer_cache/memory_tracker.h @@ -5,8 +5,10 @@ #include #include +#include #include #include + #include "common/debug.h" #include "common/types.h" #include "video_core/buffer_cache/region_manager.h" @@ -71,7 +73,7 @@ public: // modified. If we need to flush the flush function is going to perform CPU // state change. std::scoped_lock lk{manager->lock}; - if (Config::readbacks() && + if (Config::getReadbacksMode() != Config::GpuReadbacksMode::Disabled && manager->template IsRegionModified(offset, size)) { return true; } diff --git a/src/video_core/buffer_cache/region_manager.h b/src/video_core/buffer_cache/region_manager.h index 608b16fb3..ecf9406af 100644 --- a/src/video_core/buffer_cache/region_manager.h +++ b/src/video_core/buffer_cache/region_manager.h @@ -95,7 +95,7 @@ public: } if constexpr (type == Type::CPU) { UpdateProtection(); - } else if (Config::readbacks()) { + } else if (Config::getReadbacksMode() == Config::GpuReadbacksMode::Precise) { UpdateProtection(); } } @@ -126,7 +126,7 @@ public: bits.UnsetRange(start_page, end_page); if constexpr (type == Type::CPU) { UpdateProtection(); - } else if (Config::readbacks()) { + } else if (Config::getReadbacksMode() != Config::GpuReadbacksMode::Disabled) { UpdateProtection(); } }