Merge branch 'main' into fontfixes

This commit is contained in:
Valdis Bogdāns 2026-01-16 16:33:59 +02:00 committed by GitHub
commit 692c30e62c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 87 additions and 60 deletions

View File

@ -150,7 +150,7 @@ The following firmware modules are supported and must be placed in shadPS4's `sy
| libSceCesCs.sprx | libSceFont.sprx | libSceFontFt.sprx | libSceFreeTypeOt.sprx |
| libSceJpegDec.sprx | libSceJpegEnc.sprx | libSceJson.sprx | libSceJson2.sprx |
| libSceLibcInternal.sprx | libSceNgs2.sprx | libScePngEnc.sprx | libSceRtc.sprx |
| libSceUlt.sprx | | | |
| libSceUlt.sprx | libSceAudiodec.sprx | | |
</div>
> [!Caution]

View File

@ -59,7 +59,7 @@ static void hexToBytes(const char* hex, unsigned char* dst) {
bool TRP::Extract(const std::filesystem::path& trophyPath, const std::string titleId) {
std::filesystem::path gameSysDir = trophyPath / "sce_sys/trophy/";
if (!std::filesystem::exists(gameSysDir)) {
LOG_CRITICAL(Common_Filesystem, "Game sce_sys directory doesn't exist");
LOG_WARNING(Common_Filesystem, "Game trophy directory doesn't exist");
return false;
}

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
@ -34,7 +34,7 @@ u32 GetChannelMask(u32 num_channels) {
case 8:
return ORBIS_AJM_CHANNELMASK_7POINT1;
default:
UNREACHABLE();
UNREACHABLE_MSG("Unexpected number of channels: {}", num_channels);
}
}

View File

@ -5,20 +5,45 @@
#include "ajm_aac.h"
#include "ajm_result.h"
#include <aacdecoder_lib.h>
// using this internal header to manually configure the decoder in RAW mode
#include "externals/aacdec/fdk-aac/libAACdec/src/aacdecoder.h"
#include <aacdecoder_lib.h>
#include <magic_enum/magic_enum.hpp>
#include <algorithm> // std::transform
#include <iterator> // std::back_inserter
#include <limits>
namespace Libraries::Ajm {
std::span<const s16> AjmAacDecoder::GetOuputPcm(u32 skipped_pcm, u32 max_pcm) const {
const auto pcm_data = std::span(m_pcm_buffer).subspan(skipped_pcm);
return pcm_data.subspan(0, std::min<u32>(pcm_data.size(), max_pcm));
}
template <>
size_t AjmAacDecoder::WriteOutputSamples<float>(SparseOutputBuffer& out, std::span<const s16> pcm) {
if (pcm.empty()) {
return 0;
}
m_resample_buffer.clear();
constexpr float inv_scale = 1.0f / std::numeric_limits<s16>::max();
std::transform(pcm.begin(), pcm.end(), std::back_inserter(m_resample_buffer),
[](auto sample) { return float(sample) * inv_scale; });
return out.Write(std::span(m_resample_buffer));
}
AjmAacDecoder::AjmAacDecoder(AjmFormatEncoding format, AjmAacCodecFlags flags, u32 channels)
: m_format(format), m_flags(flags), m_channels(channels), m_pcm_buffer(2048 * 8),
m_skip_frames(True(flags & AjmAacCodecFlags::EnableNondelayOutput) ? 0 : 2) {}
: m_format(format), m_flags(flags), m_channels(channels), m_pcm_buffer(1024 * 8),
m_skip_frames(True(flags & AjmAacCodecFlags::EnableNondelayOutput) ? 0 : 2) {
m_resample_buffer.reserve(m_pcm_buffer.size());
}
AjmAacDecoder::~AjmAacDecoder() {
aacDecoder_Close(m_decoder);
if (m_decoder) {
aacDecoder_Close(m_decoder);
}
}
TRANSPORT_TYPE TransportTypeFromConfigType(ConfigType config_type) {
@ -98,7 +123,7 @@ AjmSidebandFormat AjmAacDecoder::GetFormat() const {
.num_channels = static_cast<u32>(info->numChannels),
.channel_mask = GetChannelMask(info->numChannels),
.sampl_freq = static_cast<u32>(info->sampleRate),
.sample_encoding = m_format, // AjmFormatEncoding
.sample_encoding = m_format,
.bitrate = static_cast<u32>(info->bitRate),
};
}
@ -130,8 +155,7 @@ DecoderResult AjmAacDecoder::ProcessData(std::span<u8>& input, SparseOutputBuffe
const UINT sizes[] = {static_cast<UINT>(input.size())};
UINT valid = sizes[0];
aacDecoder_Fill(m_decoder, buffers, sizes, &valid);
auto ret = aacDecoder_DecodeFrame(m_decoder, reinterpret_cast<s16*>(m_pcm_buffer.data()),
m_pcm_buffer.size() / 2, 0);
auto ret = aacDecoder_DecodeFrame(m_decoder, m_pcm_buffer.data(), m_pcm_buffer.size(), 0);
switch (ret) {
case AAC_DEC_OK:
@ -167,16 +191,16 @@ DecoderResult AjmAacDecoder::ProcessData(std::span<u8>& input, SparseOutputBuffe
gapless.init.total_samples != 0 ? gapless.current.total_samples : info->aacSamplesPerFrame;
size_t pcm_written = 0;
auto pcm = GetOuputPcm(skip_samples * info->numChannels, max_samples * info->numChannels);
switch (m_format) {
case AjmFormatEncoding::S16:
pcm_written = WriteOutputSamples<s16>(output, skip_samples * info->numChannels,
max_samples * info->numChannels);
pcm_written = output.Write(pcm);
break;
case AjmFormatEncoding::S32:
UNREACHABLE_MSG("NOT IMPLEMENTED");
break;
case AjmFormatEncoding::Float:
UNREACHABLE_MSG("NOT IMPLEMENTED");
pcm_written = WriteOutputSamples<float>(output, pcm);
break;
default:
UNREACHABLE();
@ -191,4 +215,4 @@ DecoderResult AjmAacDecoder::ProcessData(std::span<u8>& input, SparseOutputBuffe
return result;
}
} // namespace Libraries::Ajm
} // namespace Libraries::Ajm

View File

@ -52,22 +52,18 @@ private:
};
template <class T>
size_t WriteOutputSamples(SparseOutputBuffer& output, u32 skipped_pcm, u32 max_pcm) {
std::span<T> pcm_data{reinterpret_cast<T*>(m_pcm_buffer.data()),
m_pcm_buffer.size() / sizeof(T)};
pcm_data = pcm_data.subspan(skipped_pcm);
const auto pcm_size = std::min(u32(pcm_data.size()), max_pcm);
return output.Write(pcm_data.subspan(0, pcm_size));
}
size_t WriteOutputSamples(SparseOutputBuffer& output, std::span<const s16> pcm);
std::span<const s16> GetOuputPcm(u32 skipped_pcm, u32 max_pcm) const;
const AjmFormatEncoding m_format;
const AjmAacCodecFlags m_flags;
const u32 m_channels;
std::vector<u8> m_pcm_buffer;
std::vector<s16> m_pcm_buffer;
std::vector<float> m_resample_buffer;
u32 m_skip_frames = 0;
InitializeParameters m_init_params = {};
AAC_DECODER_INSTANCE* m_decoder = nullptr;
};
} // namespace Libraries::Ajm
} // namespace Libraries::Ajm

View File

@ -280,9 +280,7 @@ AjmJob AjmJobFromBatchBuffer(u32 instance_id, AjmBatchBuffer batch_buffer) {
job.input.resample_parameters = input_batch.Consume<AjmSidebandResampleParameters>();
}
if (True(control_flags & AjmJobControlFlags::Initialize)) {
job.input.init_params = AjmDecAt9InitializeParameters{};
std::memcpy(&job.input.init_params.value(), input_batch.GetCurrent(),
input_batch.BytesRemaining());
job.input.init_params = input_batch.Consume<AjmSidebandInitParameters>();
}
}

View File

@ -21,7 +21,7 @@ namespace Libraries::Ajm {
struct AjmJob {
struct Input {
std::optional<AjmDecAt9InitializeParameters> init_params;
std::optional<AjmSidebandInitParameters> init_params;
std::optional<AjmSidebandResampleParameters> resample_parameters;
std::optional<AjmSidebandStatisticsEngineParameters> statistics_engine_parameters;
std::optional<AjmSidebandFormat> format;

View File

@ -189,7 +189,7 @@ s32 PS4_SYSV_ABI sceAudio3dDeleteSpeakerArray() {
s32 PS4_SYSV_ABI sceAudio3dGetDefaultOpenParameters(OrbisAudio3dOpenParameters* params) {
LOG_DEBUG(Lib_Audio3d, "called");
if (params) {
*params = OrbisAudio3dOpenParameters{
auto default_params = OrbisAudio3dOpenParameters{
.size_this = 0x20,
.granularity = 0x100,
.rate = OrbisAudio3dRate::ORBIS_AUDIO3D_RATE_48000,
@ -197,6 +197,7 @@ s32 PS4_SYSV_ABI sceAudio3dGetDefaultOpenParameters(OrbisAudio3dOpenParameters*
.queue_depth = 2,
.buffer_mode = OrbisAudio3dBufferMode::ORBIS_AUDIO3D_BUFFER_ADVANCE_AND_PUSH,
};
memcpy(params, &default_params, 0x20);
}
return ORBIS_OK;
}
@ -445,7 +446,7 @@ s32 PS4_SYSV_ABI sceAudio3dPortOpen(const OrbisUserServiceUserId user_id,
}
*port_id = id;
std::memcpy(&state->ports[id].parameters, parameters, sizeof(OrbisAudio3dOpenParameters));
std::memcpy(&state->ports[id].parameters, parameters, parameters->size_this);
return ORBIS_OK;
}

View File

@ -153,9 +153,9 @@ std::string NameOf(Attribute attribute) {
case Attribute::TessellationEvaluationPointV:
return "TessellationEvaluationPointV";
case Attribute::PackedHullInvocationInfo:
return "OffChipLdsBase";
case Attribute::OffChipLdsBase:
return "PackedHullInvocationInfo";
case Attribute::OffChipLdsBase:
return "OffChipLdsBase";
case Attribute::TessFactorsBufferBase:
return "TessFactorsBufferBase";
case Attribute::PointSize:

View File

@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <unordered_map>
#include "common/assert.h"
#include "shader_recompiler/info.h"
#include "shader_recompiler/ir/attribute.h"
@ -189,7 +190,7 @@ std::optional<TessSharpLocation> FindTessConstantSharp(IR::Inst* read_const_buff
// Walker that helps deduce what type of attribute a DS instruction is reading
// or writing, which could be an input control point, output control point,
// or per-patch constant (PatchConst).
// For certain ReadConstBuffer instructions using the tess constants V#,, we visit the users
// For certain ReadConstBuffer instructions using the tess constants V#, we visit the users
// recursively and increment a counter on the Load/WriteShared users.
// Namely NumPatch (from m_hsNumPatch), HsOutputBase (m_hsOutputBase),
// and PatchConstBase (m_patchConstBase).
@ -200,9 +201,11 @@ std::optional<TessSharpLocation> FindTessConstantSharp(IR::Inst* read_const_buff
//
// TODO: this will break if AMD compiler used distributive property like
// TcsNumPatches * (ls_stride * #input_cp_in_patch + hs_cp_stride * #output_cp_in_patch)
//
// Assert if the region is ambiguous due to phi nodes in the addr calculation for a DS instruction,
class TessConstantUseWalker {
public:
void MarkTessAttributeUsers(IR::Inst* read_const_buffer, TessConstantAttribute attr) {
void WalkUsersOfTessConstant(IR::Inst* read_const_buffer, TessConstantAttribute attr) {
u32 inc;
switch (attr) {
case TessConstantAttribute::HsNumPatch:
@ -217,14 +220,19 @@ public:
}
for (IR::Use use : read_const_buffer->Uses()) {
MarkTessAttributeUsersHelper(use, inc);
WalkUsersOfTessConstantHelper(use, inc, false);
}
++seq_num;
}
private:
void MarkTessAttributeUsersHelper(IR::Use use, u32 inc) {
struct PhiInfo {
u32 seq_num;
u32 unique_edge;
};
void WalkUsersOfTessConstantHelper(IR::Use use, u32 inc, bool propagateError) {
IR::Inst* inst = use.user;
switch (use.user->GetOpcode()) {
@ -232,38 +240,37 @@ private:
case IR::Opcode::LoadSharedU64:
case IR::Opcode::WriteSharedU32:
case IR::Opcode::WriteSharedU64: {
u32 counter = inst->Flags<u32>();
inst->SetFlags<u32>(counter + inc);
// Stop here
return;
bool is_addr_operand = use.operand == 0;
if (is_addr_operand) {
u32 counter = inst->Flags<u32>();
inst->SetFlags<u32>(counter + inc);
ASSERT_MSG(!propagateError, "LDS instruction {} accesses ambiguous attribute type",
fmt::ptr(use.user));
// Stop here
return;
}
}
case IR::Opcode::Phi: {
struct PhiCounter {
u16 seq_num;
u16 unique_edge;
};
PhiCounter count = inst->Flags<PhiCounter>();
ASSERT_MSG(count.seq_num == 0 || count.unique_edge == use.operand);
auto it = phi_infos.find(use.user);
// the point of seq_num is to tell us if we've already traversed this
// phi on the current walk. Alternatively we could keep a set of phi's
// seen on the current walk. This is to handle phi cycles
if (count.seq_num == 0) {
// phi on the current walk to handle phi cycles
if (it == phi_infos.end()) {
// First time we've encountered this phi
count.seq_num = seq_num;
// Mark the phi as having been traversed originally through this edge
count.unique_edge = use.operand;
} else if (count.seq_num < seq_num) {
count.seq_num = seq_num;
phi_infos[inst] = {.seq_num = seq_num,
.unique_edge = static_cast<u16>(use.operand)};
} else if (it->second.seq_num < seq_num) {
it->second.seq_num = seq_num;
// For now, assume we are visiting this phi via the same edge
// as on other walks. If not, some dataflow analysis might be necessary
ASSERT(count.unique_edge == use.operand);
if (it->second.unique_edge != use.operand) {
propagateError = true;
}
} else {
// count.seq_num == seq_num
ASSERT(it->second.seq_num == seq_num);
// there's a cycle, and we've already been here on this walk
return;
}
inst->SetFlags<PhiCounter>(count);
break;
}
default:
@ -271,10 +278,11 @@ private:
}
for (IR::Use use : inst->Uses()) {
MarkTessAttributeUsersHelper(use, inc);
WalkUsersOfTessConstantHelper(use, inc, propagateError);
}
}
std::unordered_map<const IR::Inst*, PhiInfo> phi_infos;
u32 seq_num{1u};
};
@ -690,7 +698,7 @@ void TessellationPreprocess(IR::Program& program, RuntimeInfo& runtime_info) {
case TessConstantAttribute::HsNumPatch:
case TessConstantAttribute::HsOutputBase:
case TessConstantAttribute::PatchConstBase:
walker.MarkTessAttributeUsers(&inst, tess_const_attr);
walker.WalkUsersOfTessConstant(&inst, tess_const_attr);
// We should be able to safely set these to 0 so that indexing happens only
// within the local patch in the recompiled Vulkan shader. This assumes
// these values only contribute to address calculations for in/out