diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index d1fab6354..8cf5efbf0 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -160,7 +160,7 @@ jobs:
sudo add-apt-repository 'deb http://apt.llvm.org/noble/ llvm-toolchain-noble-19 main'
- name: Install dependencies
- run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 clang-19 mold build-essential libasound2-dev libpulse-dev libopenal-dev libudev-dev
+ run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 clang-19 mold build-essential libasound2-dev libpulse-dev libopenal-dev libudev-dev libxcursor-dev libxi-dev libxss-dev libxtst-dev
- name: Cache CMake Configuration
uses: actions/cache@v4
@@ -216,7 +216,7 @@ jobs:
submodules: recursive
- name: Install dependencies
- run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 gcc-14 mold build-essential libasound2-dev libpulse-dev libopenal-dev libudev-dev
+ run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 gcc-14 mold build-essential libasound2-dev libpulse-dev libopenal-dev libudev-dev libxcursor-dev libxi-dev libxss-dev libxtst-dev
- name: Cache CMake Configuration
uses: actions/cache@v4
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ccd5a4175..d5b63d6fe 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -202,8 +202,8 @@ execute_process(
# Set Version
set(EMULATOR_VERSION_MAJOR "0")
-set(EMULATOR_VERSION_MINOR "12")
-set(EMULATOR_VERSION_PATCH "6")
+set(EMULATOR_VERSION_MINOR "13")
+set(EMULATOR_VERSION_PATCH "1")
set_source_files_properties(src/shadps4.rc PROPERTIES COMPILE_DEFINITIONS "EMULATOR_VERSION_MAJOR=${EMULATOR_VERSION_MAJOR};EMULATOR_VERSION_MINOR=${EMULATOR_VERSION_MINOR};EMULATOR_VERSION_PATCH=${EMULATOR_VERSION_PATCH}")
diff --git a/dist/net.shadps4.shadPS4.metainfo.xml b/dist/net.shadps4.shadPS4.metainfo.xml
index c85fcf003..f42840e9b 100644
--- a/dist/net.shadps4.shadPS4.metainfo.xml
+++ b/dist/net.shadps4.shadPS4.metainfo.xml
@@ -37,6 +37,9 @@
Game
+
+ https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.13.0
+
https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.12.5
diff --git a/externals/ffmpeg-core b/externals/ffmpeg-core
index b0de1dcca..94dde08c8 160000
--- a/externals/ffmpeg-core
+++ b/externals/ffmpeg-core
@@ -1 +1 @@
-Subproject commit b0de1dcca26c0ebfb8011b8e59dd17fc399db0ff
+Subproject commit 94dde08c8a9e4271a93a2a7e4159e9fb05d30c0a
diff --git a/externals/fmt b/externals/fmt
index 64db979e3..ec73fb724 160000
--- a/externals/fmt
+++ b/externals/fmt
@@ -1 +1 @@
-Subproject commit 64db979e38ec644b1798e41610b28c8d2c8a2739
+Subproject commit ec73fb72477d80926c758894a3ab2cb3994fd051
diff --git a/externals/sdl3 b/externals/sdl3
index e9c2e9bfc..bdb72bb3f 160000
--- a/externals/sdl3
+++ b/externals/sdl3
@@ -1 +1 @@
-Subproject commit e9c2e9bfc3a6e1e70596f743fa9e1fc5fadabef7
+Subproject commit bdb72bb3f051de32c91f5deb439a50bfd51499dc
diff --git a/src/core/cpu_patches.cpp b/src/core/cpu_patches.cpp
index 2788cfe58..e303417c3 100644
--- a/src/core/cpu_patches.cpp
+++ b/src/core/cpu_patches.cpp
@@ -790,11 +790,12 @@ static bool PatchesIllegalInstructionHandler(void* context) {
Common::Decoder::Instance()->decodeInstruction(instruction, operands, code_address);
if (ZYAN_SUCCESS(status) && instruction.mnemonic == ZydisMnemonic::ZYDIS_MNEMONIC_UD2)
[[unlikely]] {
- UNREACHABLE_MSG("ud2 at code address {:#x}", (u64)code_address);
+ UNREACHABLE_MSG("ud2 at code address {:#x}", reinterpret_cast(code_address));
}
- LOG_ERROR(Core, "Failed to patch address {:x} -- mnemonic: {}", (u64)code_address,
- ZYAN_SUCCESS(status) ? ZydisMnemonicGetString(instruction.mnemonic)
- : "Failed to decode");
+ UNREACHABLE_MSG("Failed to patch address {:x} -- mnemonic: {}",
+ reinterpret_cast(code_address),
+ ZYAN_SUCCESS(status) ? ZydisMnemonicGetString(instruction.mnemonic)
+ : "Failed to decode");
}
}
diff --git a/src/core/file_sys/fs.cpp b/src/core/file_sys/fs.cpp
index 2a0fa43dd..f6c34ae94 100644
--- a/src/core/file_sys/fs.cpp
+++ b/src/core/file_sys/fs.cpp
@@ -52,6 +52,9 @@ std::filesystem::path MntPoints::GetHostPath(std::string_view path, bool* is_rea
pos = corrected_path.find("//", pos + 1);
}
+ if (path.length() > 255)
+ return "";
+
const MntPair* mount = GetMount(corrected_path);
if (!mount) {
return "";
diff --git a/src/core/libraries/ajm/ajm.h b/src/core/libraries/ajm/ajm.h
index 2c529cd4b..d68a4c0f4 100644
--- a/src/core/libraries/ajm/ajm.h
+++ b/src/core/libraries/ajm/ajm.h
@@ -133,7 +133,7 @@ struct AjmSidebandGaplessDecode {
struct AjmSidebandResampleParameters {
float ratio;
- uint32_t flags;
+ u32 flags;
};
struct AjmDecAt9InitializeParameters {
diff --git a/src/core/libraries/ajm/ajm_at9.cpp b/src/core/libraries/ajm/ajm_at9.cpp
index 014d1a4e5..ea7add4f3 100644
--- a/src/core/libraries/ajm/ajm_at9.cpp
+++ b/src/core/libraries/ajm/ajm_at9.cpp
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include "ajm_result.h"
#include "common/assert.h"
#include "core/libraries/ajm/ajm_at9.h"
#include "error_codes.h"
@@ -85,8 +86,8 @@ void AjmAt9Decoder::GetInfo(void* out_info) const {
auto* info = reinterpret_cast(out_info);
info->super_frame_size = m_codec_info.superframeSize;
info->frames_in_super_frame = m_codec_info.framesInSuperframe;
+ info->next_frame_size = m_superframe_bytes_remain;
info->frame_samples = m_codec_info.frameSamples;
- info->next_frame_size = static_cast(m_handle)->Config.FrameBytes;
}
u8 g_at9_guid[] = {0xD2, 0x42, 0xE1, 0x47, 0xBA, 0x36, 0x8D, 0x4D,
@@ -133,18 +134,22 @@ void AjmAt9Decoder::ParseRIFFHeader(std::span& in_buf, AjmInstanceGapless& g
}
}
-std::tuple AjmAt9Decoder::ProcessData(std::span& in_buf,
- SparseOutputBuffer& output,
- AjmInstanceGapless& gapless) {
- bool is_reset = false;
+u32 AjmAt9Decoder::GetMinimumInputSize() const {
+ return m_superframe_bytes_remain;
+}
+
+DecoderResult AjmAt9Decoder::ProcessData(std::span& in_buf, SparseOutputBuffer& output,
+ AjmInstanceGapless& gapless) {
+ DecoderResult result{};
if (True(m_flags & AjmAt9CodecFlags::ParseRiffHeader) &&
*reinterpret_cast(in_buf.data()) == 'FFIR') {
ParseRIFFHeader(in_buf, gapless);
- is_reset = true;
+ result.is_reset = true;
}
if (!m_is_initialized) {
- return {0, 0, is_reset};
+ result.result = ORBIS_AJM_RESULT_NOT_INITIALIZED;
+ return result;
}
int ret = 0;
@@ -166,7 +171,14 @@ std::tuple AjmAt9Decoder::ProcessData(std::span& in_buf,
default:
UNREACHABLE();
}
- ASSERT_MSG(ret == At9Status::ERR_SUCCESS, "Atrac9Decode failed ret = {:#x}", ret);
+ if (ret != At9Status::ERR_SUCCESS) {
+ LOG_ERROR(Lib_Ajm, "Atrac9Decode failed ret = {:#x}", ret);
+ result.result = ORBIS_AJM_RESULT_CODEC_ERROR | ORBIS_AJM_RESULT_FATAL;
+ result.internal_result = ret;
+ return result;
+ }
+
+ result.frames_decoded += 1;
in_buf = in_buf.subspan(bytes_used);
m_superframe_bytes_remain -= bytes_used;
@@ -196,10 +208,10 @@ std::tuple AjmAt9Decoder::ProcessData(std::span& in_buf,
UNREACHABLE();
}
- const auto samples_written = pcm_written / m_codec_info.channels;
- gapless.current.skipped_samples += m_codec_info.frameSamples - samples_written;
+ result.samples_written = pcm_written / m_codec_info.channels;
+ gapless.current.skipped_samples += m_codec_info.frameSamples - result.samples_written;
if (gapless.init.total_samples != 0) {
- gapless.current.total_samples -= samples_written;
+ gapless.current.total_samples -= result.samples_written;
}
m_num_frames += 1;
@@ -209,9 +221,23 @@ std::tuple AjmAt9Decoder::ProcessData(std::span& in_buf,
}
m_superframe_bytes_remain = m_codec_info.superframeSize;
m_num_frames = 0;
+ } else if (gapless.IsEnd()) {
+ // Drain the remaining superframe
+ std::vector buf(m_codec_info.frameSamples * m_codec_info.channels, 0);
+ while ((m_num_frames % m_codec_info.framesInSuperframe) != 0) {
+ ret = Atrac9Decode(m_handle, in_buf.data(), buf.data(), &bytes_used,
+ True(m_flags & AjmAt9CodecFlags::NonInterleavedOutput));
+ in_buf = in_buf.subspan(bytes_used);
+ m_superframe_bytes_remain -= bytes_used;
+ result.frames_decoded += 1;
+ m_num_frames += 1;
+ }
+ in_buf = in_buf.subspan(m_superframe_bytes_remain);
+ m_superframe_bytes_remain = m_codec_info.superframeSize;
+ m_num_frames = 0;
}
- return {1, m_codec_info.frameSamples, is_reset};
+ return result;
}
AjmSidebandFormat AjmAt9Decoder::GetFormat() const {
@@ -232,7 +258,7 @@ u32 AjmAt9Decoder::GetNextFrameSize(const AjmInstanceGapless& gapless) const {
const auto samples =
gapless.init.total_samples != 0
? std::min(gapless.current.total_samples, m_codec_info.frameSamples - skip_samples)
- : m_codec_info.frameSamples;
+ : m_codec_info.frameSamples - skip_samples;
return samples * m_codec_info.channels * GetPCMSize(m_format);
}
diff --git a/src/core/libraries/ajm/ajm_at9.h b/src/core/libraries/ajm/ajm_at9.h
index 3262f1aa0..94a718824 100644
--- a/src/core/libraries/ajm/ajm_at9.h
+++ b/src/core/libraries/ajm/ajm_at9.h
@@ -36,9 +36,10 @@ struct AjmAt9Decoder final : AjmCodec {
void Initialize(const void* buffer, u32 buffer_size) override;
void GetInfo(void* out_info) const override;
AjmSidebandFormat GetFormat() const override;
+ u32 GetMinimumInputSize() const override;
u32 GetNextFrameSize(const AjmInstanceGapless& gapless) const override;
- std::tuple ProcessData(std::span& input, SparseOutputBuffer& output,
- AjmInstanceGapless& gapless) override;
+ DecoderResult ProcessData(std::span& input, SparseOutputBuffer& output,
+ AjmInstanceGapless& gapless) override;
private:
template
diff --git a/src/core/libraries/ajm/ajm_batch.h b/src/core/libraries/ajm/ajm_batch.h
index 09daa630d..7f8890898 100644
--- a/src/core/libraries/ajm/ajm_batch.h
+++ b/src/core/libraries/ajm/ajm_batch.h
@@ -52,6 +52,7 @@ struct AjmBatch {
u32 id{};
std::atomic_bool waiting{};
std::atomic_bool canceled{};
+ std::atomic_bool processed{};
std::binary_semaphore finished{0};
boost::container::small_vector jobs;
diff --git a/src/core/libraries/ajm/ajm_context.cpp b/src/core/libraries/ajm/ajm_context.cpp
index 0e2915f32..83d38c5b5 100644
--- a/src/core/libraries/ajm/ajm_context.cpp
+++ b/src/core/libraries/ajm/ajm_context.cpp
@@ -39,7 +39,12 @@ s32 AjmContext::BatchCancel(const u32 batch_id) {
batch = *p_batch;
}
- batch->canceled = true;
+ if (batch->processed) {
+ return ORBIS_OK;
+ }
+
+ bool expected = false;
+ batch->canceled.compare_exchange_strong(expected, true);
return ORBIS_OK;
}
@@ -58,7 +63,9 @@ void AjmContext::WorkerThread(std::stop_token stop) {
Common::SetCurrentThreadName("shadPS4:AjmWorker");
while (!stop.stop_requested()) {
auto batch = batch_queue.PopWait(stop);
- if (batch != nullptr) {
+ if (batch != nullptr && !batch->canceled) {
+ bool expected = false;
+ batch->processed.compare_exchange_strong(expected, true);
ProcessBatch(batch->id, batch->jobs);
batch->finished.release();
}
diff --git a/src/core/libraries/ajm/ajm_instance.cpp b/src/core/libraries/ajm/ajm_instance.cpp
index c4ea395b9..35685e6a4 100644
--- a/src/core/libraries/ajm/ajm_instance.cpp
+++ b/src/core/libraries/ajm/ajm_instance.cpp
@@ -1,27 +1,15 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-#include "core/libraries/ajm/ajm_at9.h"
-#include "core/libraries/ajm/ajm_instance.h"
-#include "core/libraries/ajm/ajm_mp3.h"
+#include "ajm_at9.h"
+#include "ajm_instance.h"
+#include "ajm_mp3.h"
+#include "ajm_result.h"
#include
namespace Libraries::Ajm {
-constexpr int ORBIS_AJM_RESULT_NOT_INITIALIZED = 0x00000001;
-constexpr int ORBIS_AJM_RESULT_INVALID_DATA = 0x00000002;
-constexpr int ORBIS_AJM_RESULT_INVALID_PARAMETER = 0x00000004;
-constexpr int ORBIS_AJM_RESULT_PARTIAL_INPUT = 0x00000008;
-constexpr int ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM = 0x00000010;
-constexpr int ORBIS_AJM_RESULT_STREAM_CHANGE = 0x00000020;
-constexpr int ORBIS_AJM_RESULT_TOO_MANY_CHANNELS = 0x00000040;
-constexpr int ORBIS_AJM_RESULT_UNSUPPORTED_FLAG = 0x00000080;
-constexpr int ORBIS_AJM_RESULT_SIDEBAND_TRUNCATED = 0x00000100;
-constexpr int ORBIS_AJM_RESULT_PRIORITY_PASSED = 0x00000200;
-constexpr int ORBIS_AJM_RESULT_CODEC_ERROR = 0x40000000;
-constexpr int ORBIS_AJM_RESULT_FATAL = 0x80000000;
-
u8 GetPCMSize(AjmFormatEncoding format) {
switch (format) {
case AjmFormatEncoding::S16:
@@ -60,6 +48,7 @@ void AjmInstance::Reset() {
void AjmInstance::ExecuteJob(AjmJob& job) {
const auto control_flags = job.flags.control_flags;
+ job.output.p_result->result = 0;
if (True(control_flags & AjmJobControlFlags::Reset)) {
LOG_TRACE(Lib_Ajm, "Resetting instance {}", job.instance_id);
Reset();
@@ -91,8 +80,7 @@ void AjmInstance::ExecuteJob(AjmJob& job) {
m_gapless.current.total_samples -= sample_difference;
} else {
LOG_WARNING(Lib_Ajm, "ORBIS_AJM_RESULT_INVALID_PARAMETER");
- job.output.p_result->result = ORBIS_AJM_RESULT_INVALID_PARAMETER;
- return;
+ job.output.p_result->result |= ORBIS_AJM_RESULT_INVALID_PARAMETER;
}
}
@@ -106,61 +94,59 @@ void AjmInstance::ExecuteJob(AjmJob& job) {
m_gapless.current.skip_samples -= sample_difference;
} else {
LOG_WARNING(Lib_Ajm, "ORBIS_AJM_RESULT_INVALID_PARAMETER");
- job.output.p_result->result = ORBIS_AJM_RESULT_INVALID_PARAMETER;
- return;
+ job.output.p_result->result |= ORBIS_AJM_RESULT_INVALID_PARAMETER;
}
}
}
- if (!job.input.buffer.empty() && !job.output.buffers.empty()) {
- std::span in_buf(job.input.buffer);
- SparseOutputBuffer out_buf(job.output.buffers);
+ std::span in_buf(job.input.buffer);
+ SparseOutputBuffer out_buf(job.output.buffers);
+ auto in_size = in_buf.size();
+ auto out_size = out_buf.Size();
+ u32 frames_decoded = 0;
- u32 frames_decoded = 0;
- auto in_size = in_buf.size();
- auto out_size = out_buf.Size();
- while (!in_buf.empty() && !out_buf.IsEmpty() && !m_gapless.IsEnd()) {
- if (!HasEnoughSpace(out_buf)) {
- if (job.output.p_mframe == nullptr || frames_decoded == 0) {
- LOG_WARNING(Lib_Ajm, "ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM ({} < {})",
- out_buf.Size(), m_codec->GetNextFrameSize(m_gapless));
- job.output.p_result->result = ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM;
- break;
- }
- }
-
- const auto [nframes, nsamples, reset] =
- m_codec->ProcessData(in_buf, out_buf, m_gapless);
- if (reset) {
+ if (!job.input.buffer.empty()) {
+ for (;;) {
+ if (m_flags.gapless_loop && m_gapless.IsEnd()) {
+ m_gapless.Reset();
m_total_samples = 0;
}
- if (!nframes) {
- LOG_WARNING(Lib_Ajm, "ORBIS_AJM_RESULT_NOT_INITIALIZED");
- job.output.p_result->result = ORBIS_AJM_RESULT_NOT_INITIALIZED;
+ if (!HasEnoughSpace(out_buf)) {
+ LOG_TRACE(Lib_Ajm, "ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM ({} < {})", out_buf.Size(),
+ m_codec->GetNextFrameSize(m_gapless));
+ job.output.p_result->result |= ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM;
+ }
+ if (in_buf.size() < m_codec->GetMinimumInputSize()) {
+ job.output.p_result->result |= ORBIS_AJM_RESULT_PARTIAL_INPUT;
+ }
+ if (job.output.p_result->result != 0) {
+ break;
+ }
+ const auto result = m_codec->ProcessData(in_buf, out_buf, m_gapless);
+ if (result.is_reset) {
+ m_total_samples = 0;
+ } else {
+ m_total_samples += result.samples_written;
+ }
+ frames_decoded += result.frames_decoded;
+ if (result.result != 0) {
+ job.output.p_result->result |= result.result;
+ job.output.p_result->internal_result = result.internal_result;
break;
}
- frames_decoded += nframes;
- m_total_samples += nsamples;
-
if (False(job.flags.run_flags & AjmJobRunFlags::MultipleFrames)) {
break;
}
}
+ }
- const auto total_decoded_samples = m_total_samples;
- if (m_flags.gapless_loop && m_gapless.IsEnd()) {
- in_buf = in_buf.subspan(in_buf.size());
- m_gapless.Reset();
- m_codec->Reset();
- }
- if (job.output.p_mframe) {
- job.output.p_mframe->num_frames = frames_decoded;
- }
- if (job.output.p_stream) {
- job.output.p_stream->input_consumed = in_size - in_buf.size();
- job.output.p_stream->output_written = out_size - out_buf.Size();
- job.output.p_stream->total_decoded_samples = total_decoded_samples;
- }
+ if (job.output.p_mframe) {
+ job.output.p_mframe->num_frames = frames_decoded;
+ }
+ if (job.output.p_stream) {
+ job.output.p_stream->input_consumed = in_size - in_buf.size();
+ job.output.p_stream->output_written = out_size - out_buf.Size();
+ job.output.p_stream->total_decoded_samples = m_total_samples;
}
if (job.output.p_format != nullptr) {
@@ -175,6 +161,9 @@ void AjmInstance::ExecuteJob(AjmJob& job) {
}
bool AjmInstance::HasEnoughSpace(const SparseOutputBuffer& output) const {
+ if (m_gapless.IsEnd()) {
+ return true;
+ }
return output.Size() >= m_codec->GetNextFrameSize(m_gapless);
}
diff --git a/src/core/libraries/ajm/ajm_instance.h b/src/core/libraries/ajm/ajm_instance.h
index ad0a82f29..db53add4d 100644
--- a/src/core/libraries/ajm/ajm_instance.h
+++ b/src/core/libraries/ajm/ajm_instance.h
@@ -73,6 +73,14 @@ struct AjmInstanceGapless {
}
};
+struct DecoderResult {
+ s32 result = 0;
+ s32 internal_result = 0;
+ u32 frames_decoded = 0;
+ u32 samples_written = 0;
+ bool is_reset = false;
+};
+
class AjmCodec {
public:
virtual ~AjmCodec() = default;
@@ -81,9 +89,10 @@ public:
virtual void Reset() = 0;
virtual void GetInfo(void* out_info) const = 0;
virtual AjmSidebandFormat GetFormat() const = 0;
+ virtual u32 GetMinimumInputSize() const = 0;
virtual u32 GetNextFrameSize(const AjmInstanceGapless& gapless) const = 0;
- virtual std::tuple ProcessData(std::span& input, SparseOutputBuffer& output,
- AjmInstanceGapless& gapless) = 0;
+ virtual DecoderResult ProcessData(std::span& input, SparseOutputBuffer& output,
+ AjmInstanceGapless& gapless) = 0;
};
class AjmInstance {
@@ -94,7 +103,6 @@ public:
private:
bool HasEnoughSpace(const SparseOutputBuffer& output) const;
- std::optional GetNumRemainingSamples() const;
void Reset();
AjmInstanceFlags m_flags{};
diff --git a/src/core/libraries/ajm/ajm_instance_statistics.cpp b/src/core/libraries/ajm/ajm_instance_statistics.cpp
index c0c1af8bb..2e4a65914 100644
--- a/src/core/libraries/ajm/ajm_instance_statistics.cpp
+++ b/src/core/libraries/ajm/ajm_instance_statistics.cpp
@@ -8,7 +8,7 @@ namespace Libraries::Ajm {
void AjmInstanceStatistics::ExecuteJob(AjmJob& job) {
if (job.output.p_engine) {
- job.output.p_engine->usage_batch = 0.01;
+ job.output.p_engine->usage_batch = 0.05;
const auto ic = job.input.statistics_engine_parameters->interval_count;
for (u32 idx = 0; idx < ic; ++idx) {
job.output.p_engine->usage_interval[idx] = 0.01;
@@ -25,10 +25,12 @@ void AjmInstanceStatistics::ExecuteJob(AjmJob& job) {
job.output.p_memory->batch_size = 0x4200;
job.output.p_memory->input_size = 0x2000;
job.output.p_memory->output_size = 0x2000;
- job.output.p_memory->small_size = 0x200;
+ job.output.p_memory->small_size = 0x400;
}
}
+void AjmInstanceStatistics::Reset() {}
+
AjmInstanceStatistics& AjmInstanceStatistics::Getinstance() {
static AjmInstanceStatistics instance;
return instance;
diff --git a/src/core/libraries/ajm/ajm_instance_statistics.h b/src/core/libraries/ajm/ajm_instance_statistics.h
index ea70c9d56..0ec79aeac 100644
--- a/src/core/libraries/ajm/ajm_instance_statistics.h
+++ b/src/core/libraries/ajm/ajm_instance_statistics.h
@@ -10,6 +10,7 @@ namespace Libraries::Ajm {
class AjmInstanceStatistics {
public:
void ExecuteJob(AjmJob& job);
+ void Reset();
static AjmInstanceStatistics& Getinstance();
};
diff --git a/src/core/libraries/ajm/ajm_mp3.cpp b/src/core/libraries/ajm/ajm_mp3.cpp
index f17f53d51..f8d77f031 100644
--- a/src/core/libraries/ajm/ajm_mp3.cpp
+++ b/src/core/libraries/ajm/ajm_mp3.cpp
@@ -122,6 +122,7 @@ void AjmMp3Decoder::Reset() {
avcodec_flush_buffers(m_codec_context);
m_header.reset();
m_frame_samples = 0;
+ m_frame_size = 0;
}
void AjmMp3Decoder::GetInfo(void* out_info) const {
@@ -138,16 +139,28 @@ void AjmMp3Decoder::GetInfo(void* out_info) const {
}
}
-std::tuple AjmMp3Decoder::ProcessData(std::span& in_buf,
- SparseOutputBuffer& output,
- AjmInstanceGapless& gapless) {
+u32 AjmMp3Decoder::GetMinimumInputSize() const {
+ // 4 bytes is for mp3 header that contains frame_size
+ return std::max(m_frame_size, 4);
+}
+
+DecoderResult AjmMp3Decoder::ProcessData(std::span& in_buf, SparseOutputBuffer& output,
+ AjmInstanceGapless& gapless) {
+ DecoderResult result{};
AVPacket* pkt = av_packet_alloc();
if ((!m_header.has_value() || m_frame_samples == 0) && in_buf.size() >= 4) {
m_header = std::byteswap(*reinterpret_cast(in_buf.data()));
AjmDecMp3ParseFrame info{};
- ParseMp3Header(in_buf.data(), in_buf.size(), false, &info);
+ ParseMp3Header(in_buf.data(), in_buf.size(), true, &info);
m_frame_samples = info.samples_per_channel;
+ m_frame_size = info.frame_size;
+ gapless.init = {
+ .total_samples = info.total_samples,
+ .skip_samples = static_cast(info.encoder_delay),
+ .skipped_samples = 0,
+ };
+ gapless.current = gapless.init;
}
int ret = av_parser_parse2(m_parser, m_codec_context, &pkt->data, &pkt->size, in_buf.data(),
@@ -155,9 +168,6 @@ std::tuple AjmMp3Decoder::ProcessData(std::span& in_buf,
ASSERT_MSG(ret >= 0, "Error while parsing {}", ret);
in_buf = in_buf.subspan(ret);
- u32 frames_decoded = 0;
- u32 samples_decoded = 0;
-
if (pkt->size) {
// Send the packet with the compressed data to the decoder
pkt->pts = m_parser->pts;
@@ -177,9 +187,8 @@ std::tuple AjmMp3Decoder::ProcessData(std::span& in_buf,
UNREACHABLE_MSG("Error during decoding");
}
frame = ConvertAudioFrame(frame);
- samples_decoded += u32(frame->nb_samples);
- frames_decoded += 1;
+ result.frames_decoded += 1;
u32 skip_samples = 0;
if (gapless.current.skip_samples > 0) {
skip_samples = std::min(u16(frame->nb_samples), gapless.current.skip_samples);
@@ -211,6 +220,7 @@ std::tuple AjmMp3Decoder::ProcessData(std::span& in_buf,
if (gapless.init.total_samples != 0) {
gapless.current.total_samples -= samples;
}
+ result.samples_written += samples;
av_frame_free(&frame);
}
@@ -218,16 +228,16 @@ std::tuple AjmMp3Decoder::ProcessData(std::span& in_buf,
av_packet_free(&pkt);
- return {frames_decoded, samples_decoded, false};
+ return result;
}
u32 AjmMp3Decoder::GetNextFrameSize(const AjmInstanceGapless& gapless) const {
- const auto max_samples = gapless.init.total_samples != 0
- ? std::min(gapless.current.total_samples, m_frame_samples)
- : m_frame_samples;
- const auto skip_samples = std::min(u32(gapless.current.skip_samples), max_samples);
- return (max_samples - skip_samples) * m_codec_context->ch_layout.nb_channels *
- GetPCMSize(m_format);
+ const auto skip_samples = std::min(gapless.current.skip_samples, m_frame_samples);
+ const auto samples =
+ gapless.init.total_samples != 0
+ ? std::min(gapless.current.total_samples, m_frame_samples - skip_samples)
+ : m_frame_samples - skip_samples;
+ return samples * m_codec_context->ch_layout.nb_channels * GetPCMSize(m_format);
}
class BitReader {
@@ -264,7 +274,7 @@ private:
int AjmMp3Decoder::ParseMp3Header(const u8* p_begin, u32 stream_size, int parse_ofl,
AjmDecMp3ParseFrame* frame) {
- LOG_INFO(Lib_Ajm, "called stream_size = {} parse_ofl = {}", stream_size, parse_ofl);
+ LOG_TRACE(Lib_Ajm, "called stream_size = {} parse_ofl = {}", stream_size, parse_ofl);
if (p_begin == nullptr || stream_size < 4 || frame == nullptr) {
return ORBIS_AJM_ERROR_INVALID_PARAMETER;
diff --git a/src/core/libraries/ajm/ajm_mp3.h b/src/core/libraries/ajm/ajm_mp3.h
index 7ac65fdba..c03d5ba15 100644
--- a/src/core/libraries/ajm/ajm_mp3.h
+++ b/src/core/libraries/ajm/ajm_mp3.h
@@ -70,9 +70,10 @@ public:
void Initialize(const void* buffer, u32 buffer_size) override {}
void GetInfo(void* out_info) const override;
AjmSidebandFormat GetFormat() const override;
+ u32 GetMinimumInputSize() const override;
u32 GetNextFrameSize(const AjmInstanceGapless& gapless) const override;
- std::tuple ProcessData(std::span& input, SparseOutputBuffer& output,
- AjmInstanceGapless& gapless) override;
+ DecoderResult ProcessData(std::span& input, SparseOutputBuffer& output,
+ AjmInstanceGapless& gapless) override;
static int ParseMp3Header(const u8* buf, u32 stream_size, int parse_ofl,
AjmDecMp3ParseFrame* frame);
@@ -97,6 +98,7 @@ private:
SwrContext* m_swr_context = nullptr;
std::optional m_header;
u32 m_frame_samples = 0;
+ u32 m_frame_size = 0;
};
} // namespace Libraries::Ajm
diff --git a/src/core/libraries/ajm/ajm_result.h b/src/core/libraries/ajm/ajm_result.h
new file mode 100644
index 000000000..d4e6d1147
--- /dev/null
+++ b/src/core/libraries/ajm/ajm_result.h
@@ -0,0 +1,17 @@
+// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+constexpr int ORBIS_AJM_RESULT_NOT_INITIALIZED = 0x00000001;
+constexpr int ORBIS_AJM_RESULT_INVALID_DATA = 0x00000002;
+constexpr int ORBIS_AJM_RESULT_INVALID_PARAMETER = 0x00000004;
+constexpr int ORBIS_AJM_RESULT_PARTIAL_INPUT = 0x00000008;
+constexpr int ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM = 0x00000010;
+constexpr int ORBIS_AJM_RESULT_STREAM_CHANGE = 0x00000020;
+constexpr int ORBIS_AJM_RESULT_TOO_MANY_CHANNELS = 0x00000040;
+constexpr int ORBIS_AJM_RESULT_UNSUPPORTED_FLAG = 0x00000080;
+constexpr int ORBIS_AJM_RESULT_SIDEBAND_TRUNCATED = 0x00000100;
+constexpr int ORBIS_AJM_RESULT_PRIORITY_PASSED = 0x00000200;
+constexpr int ORBIS_AJM_RESULT_CODEC_ERROR = 0x40000000;
+constexpr int ORBIS_AJM_RESULT_FATAL = 0x80000000;
diff --git a/src/core/libraries/kernel/file_system.cpp b/src/core/libraries/kernel/file_system.cpp
index b4c342f18..5330b90fd 100644
--- a/src/core/libraries/kernel/file_system.cpp
+++ b/src/core/libraries/kernel/file_system.cpp
@@ -37,6 +37,7 @@
#endif
namespace D = Core::Devices;
+namespace fs = std::filesystem;
using FactoryDevice = std::function(u32, const char*, int, u16)>;
#define GET_DEVICE_FD(fd) \
@@ -74,6 +75,7 @@ namespace Libraries::Kernel {
s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) {
LOG_INFO(Kernel_Fs, "path = {} flags = {:#x} mode = {:#o}", raw_path, flags, mode);
+
auto* h = Common::Singleton::Instance();
auto* mnt = Common::Singleton::Instance();
@@ -87,6 +89,11 @@ s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) {
return -1;
}
+ if (strlen(raw_path) > 255) {
+ *__Error() = POSIX_ENAMETOOLONG;
+ return -1;
+ }
+
bool nonblock = (flags & ORBIS_KERNEL_O_NONBLOCK) != 0;
bool append = (flags & ORBIS_KERNEL_O_APPEND) != 0;
// Flags fsync and sync behave the same
@@ -121,7 +128,7 @@ s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) {
bool read_only = false;
file->m_guest_name = path;
file->m_host_name = mnt->GetHostPath(file->m_guest_name, &read_only);
- bool exists = std::filesystem::exists(file->m_host_name);
+ bool exists = fs::exists(file->m_host_name);
s32 e = 0;
if (create) {
@@ -149,14 +156,14 @@ s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) {
return -1;
}
- if (std::filesystem::is_directory(file->m_host_name) || directory) {
+ if (fs::is_directory(file->m_host_name) || directory) {
// Directories can be opened even if the directory flag isn't set.
// In these cases, error behavior is identical to the directory code path.
directory = true;
}
if (directory) {
- if (!std::filesystem::is_directory(file->m_host_name)) {
+ if (!fs::is_directory(file->m_host_name)) {
// If the opened file is not a directory, return ENOTDIR.
// This will trigger when create & directory is specified, this is expected.
h->DeleteHandle(handle);
@@ -554,6 +561,10 @@ s64 PS4_SYSV_ABI sceKernelRead(s32 fd, void* buf, u64 nbytes) {
s32 PS4_SYSV_ABI posix_mkdir(const char* path, u16 mode) {
LOG_INFO(Kernel_Fs, "path = {} mode = {:#o}", path, mode);
+ if (strlen(path) > 255) {
+ *__Error() = POSIX_ENAMETOOLONG;
+ return -1;
+ }
if (path == nullptr) {
*__Error() = POSIX_ENOTDIR;
return -1;
@@ -563,7 +574,7 @@ s32 PS4_SYSV_ABI posix_mkdir(const char* path, u16 mode) {
bool ro = false;
const auto dir_name = mnt->GetHostPath(path, &ro);
- if (std::filesystem::exists(dir_name)) {
+ if (fs::exists(dir_name)) {
*__Error() = POSIX_EEXIST;
return -1;
}
@@ -575,12 +586,12 @@ s32 PS4_SYSV_ABI posix_mkdir(const char* path, u16 mode) {
// CUSA02456: path = /aotl after sceSaveDataMount(mode = 1)
std::error_code ec;
- if (dir_name.empty() || !std::filesystem::create_directory(dir_name, ec)) {
+ if (dir_name.empty() || !fs::create_directory(dir_name, ec)) {
*__Error() = POSIX_EIO;
return -1;
}
- if (!std::filesystem::exists(dir_name)) {
+ if (!fs::exists(dir_name)) {
*__Error() = POSIX_ENOENT;
return -1;
}
@@ -597,28 +608,32 @@ s32 PS4_SYSV_ABI sceKernelMkdir(const char* path, u16 mode) {
}
s32 PS4_SYSV_ABI posix_rmdir(const char* path) {
+ if (strlen(path) > 255) {
+ *__Error() = POSIX_ENAMETOOLONG;
+ return -1;
+ }
auto* mnt = Common::Singleton::Instance();
bool ro = false;
- const std::filesystem::path dir_name = mnt->GetHostPath(path, &ro);
+ const fs::path dir_name = mnt->GetHostPath(path, &ro);
if (ro) {
*__Error() = POSIX_EROFS;
return -1;
}
- if (dir_name.empty() || !std::filesystem::is_directory(dir_name)) {
+ if (dir_name.empty() || !fs::is_directory(dir_name)) {
*__Error() = POSIX_ENOTDIR;
return -1;
}
- if (!std::filesystem::exists(dir_name)) {
+ if (!fs::exists(dir_name)) {
*__Error() = POSIX_ENOENT;
return -1;
}
std::error_code ec;
- s32 result = std::filesystem::remove_all(dir_name, ec);
+ s32 result = fs::remove_all(dir_name, ec);
if (ec) {
*__Error() = POSIX_EIO;
@@ -638,11 +653,15 @@ s32 PS4_SYSV_ABI sceKernelRmdir(const char* path) {
s32 PS4_SYSV_ABI posix_stat(const char* path, OrbisKernelStat* sb) {
LOG_DEBUG(Kernel_Fs, "(PARTIAL) path = {}", path);
+ if (strlen(path) > 255) {
+ *__Error() = POSIX_ENAMETOOLONG;
+ return -1;
+ }
auto* mnt = Common::Singleton::Instance();
const auto path_name = mnt->GetHostPath(path);
std::memset(sb, 0, sizeof(OrbisKernelStat));
- const bool is_dir = std::filesystem::is_directory(path_name);
- const bool is_file = std::filesystem::is_regular_file(path_name);
+ const bool is_dir = fs::is_directory(path_name);
+ const bool is_file = fs::is_regular_file(path_name);
if (!is_dir && !is_file) {
*__Error() = POSIX_ENOENT;
return -1;
@@ -650,12 +669,12 @@ s32 PS4_SYSV_ABI posix_stat(const char* path, OrbisKernelStat* sb) {
// get the difference between file clock and system clock
const auto now_sys = std::chrono::system_clock::now();
- const auto now_file = std::filesystem::file_time_type::clock::now();
+ const auto now_file = fs::file_time_type::clock::now();
// calculate the file modified time
- const auto mtime = std::filesystem::last_write_time(path_name);
+ const auto mtime = fs::last_write_time(path_name);
const auto mtimestamp = now_sys + (mtime - now_file);
- if (std::filesystem::is_directory(path_name)) {
+ if (fs::is_directory(path_name)) {
sb->st_mode = 0000777u | 0040000u;
sb->st_size = 65536;
sb->st_blksize = 65536;
@@ -665,7 +684,7 @@ s32 PS4_SYSV_ABI posix_stat(const char* path, OrbisKernelStat* sb) {
// TODO incomplete
} else {
sb->st_mode = 0000777u | 0100000u;
- sb->st_size = static_cast(std::filesystem::file_size(path_name));
+ sb->st_size = static_cast(fs::file_size(path_name));
sb->st_blksize = 512;
sb->st_blocks = (sb->st_size + 511) / 512;
sb->st_mtim.tv_sec =
@@ -686,6 +705,10 @@ s32 PS4_SYSV_ABI sceKernelStat(const char* path, OrbisKernelStat* sb) {
}
s32 PS4_SYSV_ABI sceKernelCheckReachability(const char* path) {
+ if (strlen(path) > 255) {
+ return ORBIS_KERNEL_ERROR_ENAMETOOLONG;
+ }
+
auto* mnt = Common::Singleton::Instance();
std::string_view guest_path{path};
for (const auto& prefix : available_device | std::views::keys) {
@@ -694,7 +717,7 @@ s32 PS4_SYSV_ABI sceKernelCheckReachability(const char* path) {
}
}
const auto path_name = mnt->GetHostPath(guest_path);
- if (!std::filesystem::exists(path_name)) {
+ if (!fs::exists(path_name)) {
return ORBIS_KERNEL_ERROR_ENOENT;
}
return ORBIS_OK;
@@ -807,7 +830,15 @@ s32 PS4_SYSV_ABI posix_rename(const char* from, const char* to) {
auto* mnt = Common::Singleton::Instance();
bool ro = false;
const auto src_path = mnt->GetHostPath(from, &ro);
- if (!std::filesystem::exists(src_path)) {
+ if (strlen(from) > 255) {
+ *__Error() = POSIX_ENAMETOOLONG;
+ return -1;
+ }
+ if (strlen(to) > 255) {
+ *__Error() = POSIX_ENAMETOOLONG;
+ return -1;
+ }
+ if (!fs::exists(src_path)) {
*__Error() = POSIX_ENOENT;
return -1;
}
@@ -820,32 +851,36 @@ s32 PS4_SYSV_ABI posix_rename(const char* from, const char* to) {
*__Error() = POSIX_EROFS;
return -1;
}
- const bool src_is_dir = std::filesystem::is_directory(src_path);
- const bool dst_is_dir = std::filesystem::is_directory(dst_path);
- if (src_is_dir && !dst_is_dir) {
- *__Error() = POSIX_ENOTDIR;
- return -1;
- }
- if (!src_is_dir && dst_is_dir) {
- *__Error() = POSIX_EISDIR;
- return -1;
- }
- if (dst_is_dir && !std::filesystem::is_empty(dst_path)) {
- *__Error() = POSIX_ENOTEMPTY;
- return -1;
+ const bool src_is_dir = fs::is_directory(src_path);
+ const bool dst_is_dir = fs::is_directory(dst_path);
+
+ if (fs::exists(dst_path)) {
+ if (src_is_dir && !dst_is_dir) {
+ *__Error() = POSIX_ENOTDIR;
+ return -1;
+ }
+ if (!src_is_dir && dst_is_dir) {
+ *__Error() = POSIX_EISDIR;
+ return -1;
+ }
+ if (dst_is_dir && !fs::is_empty(dst_path)) {
+ *__Error() = POSIX_ENOTEMPTY;
+ return -1;
+ }
}
- // On Windows, std::filesystem::rename will error if the file has been opened before.
- std::filesystem::copy(src_path, dst_path, std::filesystem::copy_options::overwrite_existing);
+ // On Windows, fs::rename will error if the file has been opened before.
+ fs::copy(src_path, dst_path,
+ fs::copy_options::overwrite_existing | fs::copy_options::recursive);
auto* h = Common::Singleton::Instance();
auto file = h->GetFile(src_path);
if (file) {
auto access_mode = file->f.GetAccessMode();
file->f.Close();
- std::filesystem::remove(src_path);
+ fs::remove(src_path);
file->f.Open(dst_path, access_mode);
} else {
- std::filesystem::remove(src_path);
+ fs::remove_all(src_path);
}
return ORBIS_OK;
@@ -1098,6 +1133,10 @@ s64 PS4_SYSV_ABI sceKernelPwritev(s32 fd, const OrbisKernelIovec* iov, s32 iovcn
}
s32 PS4_SYSV_ABI posix_unlink(const char* path) {
+ if (strlen(path) > 255) {
+ *__Error() = POSIX_ENAMETOOLONG;
+ return -1;
+ }
if (path == nullptr) {
*__Error() = POSIX_EINVAL;
return -1;
@@ -1118,7 +1157,7 @@ s32 PS4_SYSV_ABI posix_unlink(const char* path) {
return -1;
}
- if (std::filesystem::is_directory(host_path)) {
+ if (fs::is_directory(host_path)) {
*__Error() = POSIX_EPERM;
return -1;
}
@@ -1491,6 +1530,7 @@ void RegisterFileSystem(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("FCcmRZhWtOk", "libkernel", 1, "libkernel", posix_pwritev);
LIB_FUNCTION("nKWi-N2HBV4", "libkernel", 1, "libkernel", sceKernelPwrite);
LIB_FUNCTION("mBd4AfLP+u8", "libkernel", 1, "libkernel", sceKernelPwritev);
+ LIB_FUNCTION("VAzswvTOCzI", "libkernel", 1, "libkernel", posix_unlink);
LIB_FUNCTION("AUXVxWeJU-A", "libkernel", 1, "libkernel", sceKernelUnlink);
LIB_FUNCTION("T8fER+tIGgk", "libScePosix", 1, "libkernel", posix_select);
LIB_FUNCTION("T8fER+tIGgk", "libkernel", 1, "libkernel", posix_select);
diff --git a/src/core/libraries/network/net.cpp b/src/core/libraries/network/net.cpp
index 97005813b..a365d407b 100644
--- a/src/core/libraries/network/net.cpp
+++ b/src/core/libraries/network/net.cpp
@@ -1357,7 +1357,8 @@ int PS4_SYSV_ABI sceNetResolverConnectDestroy() {
}
int PS4_SYSV_ABI sceNetResolverCreate(const char* name, int poolid, int flags) {
- LOG_INFO(Lib_Net, "name = {}, poolid = {}, flags = {}", name, poolid, flags);
+ const char* safe_name = name ? name : "";
+ LOG_INFO(Lib_Net, "name = {}, poolid = {}, flags = {}", safe_name, poolid, flags);
if (flags != 0) {
*sceNetErrnoLoc() = ORBIS_NET_EINVAL;
@@ -1368,8 +1369,8 @@ int PS4_SYSV_ABI sceNetResolverCreate(const char* name, int poolid, int flags) {
auto* resolver = FDTable::Instance()->GetFile(fd);
resolver->is_opened = true;
resolver->type = Core::FileSys::FileType::Resolver;
- resolver->resolver = std::make_shared(name, poolid, flags);
- resolver->m_guest_name = name;
+ resolver->resolver = std::make_shared(safe_name, poolid, flags);
+ resolver->m_guest_name = safe_name;
return fd;
}
diff --git a/src/core/linker.cpp b/src/core/linker.cpp
index 5587b151c..644f9c374 100644
--- a/src/core/linker.cpp
+++ b/src/core/linker.cpp
@@ -105,17 +105,6 @@ void Linker::Execute(const std::vector& args) {
memory->SetupMemoryRegions(fmem_size, use_extended_mem1, use_extended_mem2);
- // Simulate sceKernelInternalMemory mapping, a mapping usually performed during libkernel init.
- // Due to the large size of this mapping, failing to emulate it causes issues in some titles.
- // This mapping belongs in the system reserved area, which starts at address 0x880000000.
- static constexpr VAddr KernelAllocBase = 0x880000000ULL;
- static constexpr s64 InternalMemorySize = 0x1000000;
- void* addr_out{reinterpret_cast(KernelAllocBase)};
-
- s32 ret = Libraries::Kernel::sceKernelMapNamedFlexibleMemory(&addr_out, InternalMemorySize, 3,
- 0, "SceKernelInternalMemory");
- ASSERT_MSG(ret == 0, "Unable to perform sceKernelInternalMemory mapping");
-
main_thread.Run([this, module, &args](std::stop_token) {
Common::SetCurrentThreadName("GAME_MainThread");
if (auto& ipc = IPC::Instance()) {
diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp
index ca7d09c52..44aa79d98 100644
--- a/src/video_core/renderer_vulkan/vk_instance.cpp
+++ b/src/video_core/renderer_vulkan/vk_instance.cpp
@@ -299,6 +299,7 @@ bool Instance::CreateDevice() {
amd_shader_trinary_minmax = add_extension(VK_AMD_SHADER_TRINARY_MINMAX_EXTENSION_NAME);
nv_framebuffer_mixed_samples = add_extension(VK_NV_FRAMEBUFFER_MIXED_SAMPLES_EXTENSION_NAME);
amd_mixed_attachment_samples = add_extension(VK_AMD_MIXED_ATTACHMENT_SAMPLES_EXTENSION_NAME);
+ shader_atomic_float = add_extension(VK_EXT_SHADER_ATOMIC_FLOAT_EXTENSION_NAME);
shader_atomic_float2 = add_extension(VK_EXT_SHADER_ATOMIC_FLOAT_2_EXTENSION_NAME);
if (shader_atomic_float2) {
shader_atomic_float2_features =
diff --git a/src/video_core/renderer_vulkan/vk_instance.h b/src/video_core/renderer_vulkan/vk_instance.h
index bbefdc1b3..8975669bb 100644
--- a/src/video_core/renderer_vulkan/vk_instance.h
+++ b/src/video_core/renderer_vulkan/vk_instance.h
@@ -493,6 +493,7 @@ private:
bool amd_shader_trinary_minmax{};
bool nv_framebuffer_mixed_samples{};
bool amd_mixed_attachment_samples{};
+ bool shader_atomic_float{};
bool shader_atomic_float2{};
bool workgroup_memory_explicit_layout{};
bool portability_subset{};