Fontlib implemment stubs for knack2 (#4564)

* Improve spec-correct V_CMP_U64 semantics

* Clang-format

* - implement libSceFont text-source, string, and writing object support
- add OrbisFontTextSource, OrbisFontCreateStringDetail, OrbisFontString, and writing-related ABI structures and function declarations
- implement font string lifecycle and parsing flow, including default-font handling, terminate state, and character storage management
- implement writing-step and writing-metrics APIs, including render-step accessors and invisible-character masking
- implement additional glyph and character query APIs for text/font code, glyph form, metrics form, scale, and metric references
- implement library font open mode setter and string metadata getters
- consolidate duplicated font helper math and system-font alias resolution into shared internal utilities
- update font rendering metric snapping logic to use consistent ceil/floor handling for bearings, advance, and stride computation
- clean up font internals by removing duplicate local helpers and redundant declarations
- apply clang-format and line-ending cleanup to modified font sources

---------

Co-authored-by: w1naenator <valdis.bogdans@hotmail.com>
This commit is contained in:
Valdis Bogdāns 2026-06-12 23:02:22 +03:00 committed by GitHub
parent b5b7cb5459
commit 843bec6f4f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 2235 additions and 881 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -58,7 +58,6 @@ std::string FormatNamedParams(std::initializer_list<NamedParam> params) {
}
bool HasSfntTables(const std::vector<unsigned char>& bytes);
FontState* TryGetState(Libraries::Font::OrbisFontHandle h);
std::string ReportSystemFaceRequest(FontState& st, Libraries::Font::OrbisFontHandle handle,
bool& attached_out);
@ -83,34 +82,6 @@ static constexpr FT_Int32 kFtLoadFlagsBase =
static constexpr FT_Int32 kFtLoadFlagsRender =
static_cast<FT_Int32>(kFtLoadFlagsBase | FT_LOAD_RENDER);
static s32 RoundFixedMul16x16ToS32(long fixed_16_16, s32 value) {
const long long prod = static_cast<long long>(fixed_16_16) * static_cast<long long>(value);
const long long sign_adj =
(static_cast<long long>(~static_cast<long long>(value)) >> 63) * -0x10000LL;
const long long base = sign_adj + prod;
long long tmp = base - 0x8000LL;
if (tmp < 0) {
tmp = base + 0x7FFFLL;
}
return static_cast<s32>(static_cast<u64>(tmp) >> 16);
}
static int FloorIntCompat(float v) {
int i = static_cast<int>(std::trunc(v));
if (static_cast<float>(i) > v) {
--i;
}
return i;
}
static int CeilIntCompat(float v) {
int i = static_cast<int>(std::trunc(v));
if (static_cast<float>(i) < v) {
++i;
}
return i;
}
} // namespace
FT_Face CreateFreeTypeFaceFromBytes(const unsigned char* data, std::size_t size,
@ -1398,25 +1369,27 @@ s32 RenderCodepointToSurface(FontState& st, Libraries::Font::OrbisFontHandle han
const float bottom_f = top_f - metrics->height;
const int left_i = FloorIntCompat(left_f);
const int top_i = FloorIntCompat(top_f);
const int top_i = CeilIntCompat(top_f);
const int right_i = CeilIntCompat(right_f);
const int bottom_i = CeilIntCompat(bottom_f);
const int bottom_i = FloorIntCompat(bottom_f);
const float adv_f = x + metrics->Horizontal.advance;
const float adv_snapped = static_cast<float>(FloorIntCompat(adv_f)) - x;
const int adv_i = CeilIntCompat(adv_f);
const float adv_snapped = static_cast<float>(adv_i) - x;
result->ImageMetrics.bearingX = static_cast<float>(left_i) - x;
result->ImageMetrics.bearingY = static_cast<float>(top_i) - y;
result->ImageMetrics.advance = adv_snapped;
int stride_i = right_i + 1;
const float adjust = static_cast<float>(right_i) - right_f;
const int tmp_i = FloorIntCompat(adv_f + adjust);
const int adv_trunc_i = static_cast<int>(std::trunc(adv_f));
if (adv_trunc_i == 0) {
stride_i = tmp_i;
}
if (stride_i < tmp_i) {
stride_i = tmp_i;
int stride_i = adv_i;
if (adv_f != static_cast<float>(adv_i)) {
const int tmp_i = static_cast<int>((static_cast<float>(right_i) - right_f) + adv_f);
stride_i = right_i + 1;
if (adv_f <= right_f) {
stride_i = tmp_i;
}
if (stride_i < tmp_i) {
stride_i = tmp_i;
}
}
result->ImageMetrics.stride = static_cast<float>(stride_i) - x;
result->ImageMetrics.width = static_cast<u32>(std::max(0, right_i - left_i));
@ -1629,25 +1602,27 @@ s32 RenderCodepointToSurfaceWithScale(FontState& st, Libraries::Font::OrbisFontH
const float bottom_f = top_f - m->height;
const int left_i = FloorIntCompat(left_f);
const int top_i = FloorIntCompat(top_f);
const int top_i = CeilIntCompat(top_f);
const int right_i = CeilIntCompat(right_f);
const int bottom_i = CeilIntCompat(bottom_f);
const int bottom_i = FloorIntCompat(bottom_f);
const float adv_f = x + m->Horizontal.advance;
const float adv_snapped = static_cast<float>(FloorIntCompat(adv_f)) - x;
const int adv_i = CeilIntCompat(adv_f);
const float adv_snapped = static_cast<float>(adv_i) - x;
result->ImageMetrics.bearingX = static_cast<float>(left_i) - x;
result->ImageMetrics.bearingY = static_cast<float>(top_i) - y;
result->ImageMetrics.advance = adv_snapped;
int stride_i = right_i + 1;
const float adjust = static_cast<float>(right_i) - right_f;
const int tmp_i = FloorIntCompat(adv_f + adjust);
const int adv_trunc_i = static_cast<int>(std::trunc(adv_f));
if (adv_trunc_i == 0) {
stride_i = tmp_i;
}
if (stride_i < tmp_i) {
stride_i = tmp_i;
int stride_i = adv_i;
if (adv_f != static_cast<float>(adv_i)) {
const int tmp_i = static_cast<int>((static_cast<float>(right_i) - right_f) + adv_f);
stride_i = right_i + 1;
if (adv_f <= right_f) {
stride_i = tmp_i;
}
if (stride_i < tmp_i) {
stride_i = tmp_i;
}
}
result->ImageMetrics.stride = static_cast<float>(stride_i) - x;
result->ImageMetrics.width = static_cast<u32>(std::max(0, right_i - left_i));

View File

@ -197,6 +197,34 @@ inline void log_debug(std::string_view message) {
LOG_DEBUG(Lib_Font, "{}", message);
}
inline s32 RoundFixedMul16x16ToS32(long fixed_16_16, s32 value) {
const long long prod = static_cast<long long>(fixed_16_16) * static_cast<long long>(value);
const long long sign_adj =
(static_cast<long long>(~static_cast<long long>(value)) >> 63) * -0x10000LL;
const long long base = sign_adj + prod;
long long tmp = base - 0x8000LL;
if (tmp < 0) {
tmp = base + 0x7FFFLL;
}
return static_cast<s32>(static_cast<u64>(tmp) >> 16);
}
inline int FloorIntCompat(float v) {
int i = static_cast<int>(std::trunc(v));
if (static_cast<float>(i) > v) {
--i;
}
return i;
}
inline int CeilIntCompat(float v) {
int i = static_cast<int>(std::trunc(v));
if (static_cast<float>(i) < v) {
++i;
}
return i;
}
#define formatParams(...) formatParamsImpl(#__VA_ARGS__ __VA_OPT__(, ) __VA_ARGS__)
struct FontSetSelector;

View File

@ -225,34 +225,6 @@ static float MaxSsCompat(float a, float b) {
return a;
}
static s32 RoundFixedMul16x16ToS32(long fixed_16_16, s32 value) {
const long long prod = static_cast<long long>(fixed_16_16) * static_cast<long long>(value);
const long long sign_adj =
(static_cast<long long>(~static_cast<long long>(value)) >> 63) * -0x10000LL;
const long long base = sign_adj + prod;
long long tmp = base - 0x8000LL;
if (tmp < 0) {
tmp = base + 0x7FFFLL;
}
return static_cast<s32>(static_cast<u64>(tmp) >> 16);
}
static int FloorIntCompat(float v) {
int i = static_cast<int>(std::trunc(v));
if (static_cast<float>(i) > v) {
--i;
}
return i;
}
static int CeilIntCompat(float v) {
int i = static_cast<int>(std::trunc(v));
if (static_cast<float>(i) < v) {
++i;
}
return i;
}
float FixedMulUnitsToF26Dot6(long fixed_16_16, u16 units_per_em) {
constexpr float kOneOver64Local = 1.0f / 64.0f;
const long prod = static_cast<long>(static_cast<long long>(fixed_16_16) *
@ -1944,25 +1916,27 @@ static s32 RenderGlyphIndexToSurface(FontObj& font_obj, u32 glyph_index,
const float bottom_f = top_f - metrics->height;
const int left_i = FloorIntCompat(left_f);
const int top_i = FloorIntCompat(top_f);
const int top_i = CeilIntCompat(top_f);
const int right_i = CeilIntCompat(right_f);
const int bottom_i = FloorIntCompat(bottom_f);
const float adv_f = x + metrics->Horizontal.advance;
const float adv_snapped = static_cast<float>(FloorIntCompat(adv_f)) - x;
const int adv_i = CeilIntCompat(adv_f);
const float adv_snapped = static_cast<float>(adv_i) - x;
result->ImageMetrics.bearingX = static_cast<float>(left_i) - x;
result->ImageMetrics.bearingY = static_cast<float>(top_i) - y;
result->ImageMetrics.advance = adv_snapped;
int stride_i = right_i + 1;
const float adjust = static_cast<float>(right_i) - right_f;
const int tmp_i = FloorIntCompat(adv_f + adjust);
const int adv_trunc_i = static_cast<int>(std::trunc(adv_f));
if (adv_trunc_i == 0) {
stride_i = tmp_i;
}
if (stride_i < tmp_i) {
stride_i = tmp_i;
int stride_i = adv_i;
if (adv_f != static_cast<float>(adv_i)) {
const int tmp_i = static_cast<int>((static_cast<float>(right_i) - right_f) + adv_f);
stride_i = right_i + 1;
if (adv_f <= right_f) {
stride_i = tmp_i;
}
if (stride_i < tmp_i) {
stride_i = tmp_i;
}
}
result->ImageMetrics.stride = static_cast<float>(stride_i) - x;
result->ImageMetrics.width = static_cast<u32>(std::max(0, right_i - left_i));
@ -2583,90 +2557,6 @@ static float to_f26dot6_s64(s64 value) {
return static_cast<float>(value) * kOneOver64;
}
static std::optional<std::filesystem::path> ResolveKnownSysFontAlias(
const std::filesystem::path& sysfonts_dir, std::string_view ps4_filename) {
const auto resolve_existing =
[&](std::string_view filename) -> std::optional<std::filesystem::path> {
const std::filesystem::path file_path{std::string(filename)};
std::error_code ec;
{
const auto candidate = sysfonts_dir / file_path;
if (std::filesystem::exists(candidate, ec)) {
return candidate;
}
}
{
const auto candidate = sysfonts_dir / "font" / file_path;
if (std::filesystem::exists(candidate, ec)) {
return candidate;
}
}
{
const auto candidate = sysfonts_dir / "font2" / file_path;
if (std::filesystem::exists(candidate, ec)) {
return candidate;
}
}
return std::nullopt;
};
static constexpr std::array<std::pair<std::string_view, std::string_view>, 41> kAliases = {{
{"SST-EU-ROMAN-L.OTF", "SST-Light.otf"},
{"SST-EU-ROMAN.OTF", "SST-Roman.otf"},
{"SST-EU-ROMAN-M.OTF", "SST-Medium.otf"},
{"SST-EU-ROMAN-R.OTF", "SST-Roman.otf"},
{"SST-EU-ROMAN-I.OTF", "SST-Italic.otf"},
{"SST-EU-ROMAN-B.OTF", "SST-Bold.otf"},
{"SST-EU-ROMAN-BI.OTF", "SST-BoldItalic.otf"},
{"SST-ITALIC-L.OTF", "SST-LightItalic.otf"},
{"SST-ITALIC-R.OTF", "SST-Italic.otf"},
{"SST-ITALIC-M.OTF", "SST-MediumItalic.otf"},
{"SST-ITALIC-B.OTF", "SST-BoldItalic.otf"},
{"SST-TYPEWRITER-R.OTF", "SSTTypewriter-Roman.otf"},
{"SST-TYPEWRITER-B.OTF", "SSTTypewriter-Bd.otf"},
{"SST-JPPRO-R.OTF", "SSTJpPro-Regular.otf"},
{"SST-JPPRO-B.OTF", "SSTJpPro-Bold.otf"},
{"SST-CNGB-HEI-R.TTF", "DFHEI5-SONY.ttf"},
{"SST-ARIB-STD-B24-R.TTF", "SSTAribStdB24-Regular.ttf"},
{"SST-ARABIC-R.OTF", "SSTArabic-Roman.otf"},
{"SST-ARABIC-L.OTF", "SSTArabic-Light.otf"},
{"SST-ARABIC-M.OTF", "SSTArabic-Medium.otf"},
{"SST-ARABIC-B.OTF", "SSTArabic-Bold.otf"},
{"SCE-EXT-HANGUL-L.OTF", "SCEPS4Yoongd-Light.otf"},
{"SCE-EXT-HANGUL-R.OTF", "SCEPS4Yoongd-Medium.otf"},
{"SCE-EXT-HANGUL-B.OTF", "SCEPS4Yoongd-Bold.otf"},
{"SSTCC-SERIF-MONO.OTF", "e046323ms.ttf"},
{"SSTCC-SERIF.OTF", "e046323ts.ttf"},
{"SSTCC-SANSSERIF-MONO.OTF", "n023055ms.ttf"},
{"SSTCC-SANSSERIF.OTF", "n023055ts.ttf"},
{"SSTCC-CUSUAL.OTF", "d013013ds.ttf"},
{"SSTCC-CURSIVE.OTF", "k006004ds.ttf"},
{"SSTCC-SMALLCAPITAL.OTF", "c041056ts.ttf"},
{"SCE-JP-CATTLEYA-L.OTF", "SCE-RDC-R-JPN.otf"},
{"SCE-JP-CATTLEYA-B.OTF", "SCE-RDC-B-JPN.otf"},
{"SST-THAI-L.OTF", "SSTThai-Light.otf"},
{"SST-THAI-R.OTF", "SSTThai-Roman.otf"},
{"SST-THAI-M.OTF", "SSTThai-Medium.otf"},
{"SST-THAI-B.OTF", "SSTThai-Bold.otf"},
{"SST-VIETNAMESE-L.OTF", "SSTVietnamese-Light.otf"},
{"SST-VIETNAMESE-R.OTF", "SSTVietnamese-Roman.otf"},
{"SST-VIETNAMESE-M.OTF", "SSTVietnamese-Medium.otf"},
{"SST-VIETNAMESE-B.OTF", "SSTVietnamese-Bold.otf"},
}};
for (const auto& [from, to] : kAliases) {
if (ps4_filename == from) {
return resolve_existing(to);
}
if (ps4_filename == to) {
if (auto reverse = resolve_existing(from)) {
return reverse;
}
}
}
return std::nullopt;
}
static constexpr u32 MakeTag(char a, char b, char c, char d) {
return (static_cast<u32>(static_cast<u8>(a)) << 24) |
(static_cast<u32>(static_cast<u8>(b)) << 16) |
@ -3137,7 +3027,8 @@ s32 PS4_SYSV_ABI LibraryOpenFontMemoryStub(void* library, u32 mode, const void*
const std::filesystem::path file_path{ps4_name};
candidates.emplace_back((sysfonts_dir / "font" / file_path).string());
candidates.emplace_back((sysfonts_dir / "font2" / file_path).string());
if (const auto alias = ResolveKnownSysFontAlias(sysfonts_dir, ps4_name)) {
if (const auto alias =
Libraries::Font::Internal::ResolveKnownSysFontAlias(sysfonts_dir, ps4_name)) {
candidates.emplace_back(alias->string());
}
}

View File

@ -960,7 +960,7 @@ void Translator::V_CVT_FLR_I32_F32(const GcnInst& inst) {
void Translator::V_CVT_OFF_F32_I4(const GcnInst& inst) {
const IR::U32 src0{GetSrc(inst.src[0])};
ASSERT(src0.IsImmediate());
ASSERT_MSG(src0.IsImmediate(), "V_CVT_OFF_F32_I4 expects an immediate source operand");
static constexpr std::array IntToFloat = {
0.0f, 0.0625f, 0.1250f, 0.1875f, 0.2500f, 0.3125f, 0.3750f, 0.4375f,
-0.5000f, -0.4375f, -0.3750f, -0.3125f, -0.2500f, -0.1875f, -0.1250f, -0.0625f};
@ -1252,45 +1252,57 @@ void Translator::V_CMP_U32(ConditionOp op, bool is_signed, bool set_exec, const
}
void Translator::V_CMP_U64(ConditionOp op, bool is_signed, bool set_exec, const GcnInst& inst) {
const bool is_zero = inst.src[1].field == OperandField::ConstZero;
const bool is_neg_one = inst.src[1].field == OperandField::SignedConstIntNeg;
ASSERT(is_zero || is_neg_one);
if (is_neg_one) {
ASSERT_MSG(-s32(inst.src[1].code) + SignedConstIntNegMin - 1 == -1,
"SignedConstIntNeg must be -1");
}
// AMD VOPC semantics:
// V_CMP_* : D/VCC[threadId] = compare(S0, S1)
// V_CMPX_* : EXEC[threadId] = D/VCC[threadId] = compare(S0, S1)
//
// Do not use subgroup reductions here. Result must stay per-lane.
const IR::U1 src0 = [&] {
switch (inst.src[0].field) {
case OperandField::ScalarGPR:
return ir.GetThreadBitScalarReg(IR::ScalarReg(inst.src[0].code));
case OperandField::VccLo:
return ir.GetVcc();
default:
UNREACHABLE_MSG("src0 = {}", u32(inst.src[0].field));
const auto set_result = [&](IR::U1 result) {
if (set_exec) {
ir.SetExec(result);
}
}();
const IR::U1 result = [&] {
SetDst1(inst.dst[1], result);
};
const auto make_compare = [&](IR::U64 src0, IR::U64 src1) -> IR::U1 {
switch (op) {
case ConditionOp::F:
return ir.Imm1(false);
case ConditionOp::TRU:
return ir.Imm1(true);
case ConditionOp::EQ:
return is_zero ? ir.LogicalNot(src0) : src0;
return ir.IEqual(src0, src1);
case ConditionOp::LG: // NE
return is_zero ? src0 : ir.LogicalNot(src0);
return ir.INotEqual(src0, src1);
case ConditionOp::LT:
return ir.ILessThan(src0, src1, is_signed);
case ConditionOp::LE:
return ir.ILessThanEqual(src0, src1, is_signed);
case ConditionOp::GT:
ASSERT(is_zero);
return ir.GroupAny(ir.GetThreadBitScalarReg(IR::ScalarReg(inst.src[0].code)));
return ir.IGreaterThan(src0, src1, is_signed);
case ConditionOp::GE:
return ir.IGreaterThanEqual(src0, src1, is_signed);
default:
UNREACHABLE_MSG("Unsupported V_CMP_U64 condition operation: {}", u32(op));
UNREACHABLE_MSG("Unsupported V_CMP_{}64 condition: {}", is_signed ? "I" : "U", u32(op));
}
}();
};
if (is_signed) {
UNREACHABLE_MSG("V_CMP_U64 with signed integers is not supported");
// Cheap constants first. These are always spec-correct.
if (op == ConditionOp::F) {
set_result(ir.Imm1(false));
return;
}
if (set_exec) {
UNREACHABLE_MSG("Exec setting for V_CMP_U64 is not supported");
if (op == ConditionOp::TRU) {
set_result(ir.Imm1(true));
return;
}
SetDst1(inst.dst[1], result);
const IR::U64 src0 = GetSrc64(inst.src[0]);
const IR::U64 src1 = GetSrc64(inst.src[1]);
set_result(make_compare(src0, src1));
}
void Translator::V_CMP_CLASS_F32(const GcnInst& inst) {

View File

@ -239,9 +239,13 @@ bool Instance::CreateDevice() {
};
// Required
ASSERT(add_extension(VK_KHR_SWAPCHAIN_EXTENSION_NAME));
ASSERT(add_extension(VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME));
ASSERT(add_extension(VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME));
ASSERT_MSG(add_extension(VK_KHR_SWAPCHAIN_EXTENSION_NAME),
"Required Vulkan extension unavailable: {}", VK_KHR_SWAPCHAIN_EXTENSION_NAME);
ASSERT_MSG(add_extension(VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME),
"Required Vulkan extension unavailable: {}", VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
ASSERT_MSG(add_extension(VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME),
"Required Vulkan extension unavailable: {}",
VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME);
// Optional
maintenance_8 = add_extension(VK_KHR_MAINTENANCE_8_EXTENSION_NAME);

View File

@ -310,7 +310,6 @@ PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_,
.needs_clip_distance_emulation = instance.GetDriverID() == vk::DriverId::eNvidiaProprietary,
.supports_shader_stencil_export = instance_.IsShaderStencilExportSupported(),
};
WarmUp();
auto [cache_result, cache] = instance.GetDevice().createPipelineCacheUnique({});
@ -589,7 +588,9 @@ bool PipelineCache::RefreshGraphicsStages() {
u32 vertex_binding = 0;
for (const auto& attrib : fetch_shader->attributes) {
const auto& buffer = attrib.GetSharp(*vs_info);
ASSERT(vertex_binding < MaxVertexBufferCount);
ASSERT_MSG(vertex_binding < MaxVertexBufferCount,
"Vertex attribute binding count exceeded limit: {} >= {}", vertex_binding,
MaxVertexBufferCount);
key.vertex_buffer_formats[vertex_binding++] =
Vulkan::LiverpoolToVK::SurfaceFormat(buffer.GetDataFmt(), buffer.GetNumberFmt());
}