From 5bf8c8bf1959a7a8ca8569df4b48b7d87e628839 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 8 May 2026 07:54:21 +0000
Subject: [PATCH 1/7] chore: outline AVIF-for-JPEG optimization plan
Agent-Logs-Url: https://github.com/winnerspiros/osu/sessions/308e53dd-8e58-4c88-bce2-a507f3bc571e
Co-authored-by: winnerspiros <1675249+winnerspiros@users.noreply.github.com>
---
.github/resource-optimizer/config.json | 9 +++++----
.github/resource-optimizer/osu-resources-config.json | 9 +++++----
scripts/optimize_resource_overrides.py | 4 +++-
3 files changed, 13 insertions(+), 9 deletions(-)
diff --git a/.github/resource-optimizer/config.json b/.github/resource-optimizer/config.json
index 3a47baea13e4..2363e98af3dc 100644
--- a/.github/resource-optimizer/config.json
+++ b/.github/resource-optimizer/config.json
@@ -11,16 +11,17 @@
"enable_video_conversion": true,
"keep_original_files": true,
"measure_image_ssim": true,
- "image_lossy_min_ssim": 0.995,
+ "image_lossy_min_ssim": 0.990,
"png_webp_try_lossless": true,
- "png_webp_lossless_compression_level": 6,
- "png_webp_lossy_quality": 92,
+ "png_webp_lossless_compression_level": 9,
+ "png_webp_lossy_quality": 90,
"png_webp_alpha_lossy_quality": 95,
"png_webp_lossy_method": 6,
"jpeg_webp_quality": 88,
"jpeg_webp_method": 6,
"audio_codec": "libopus",
"audio_ogg_bitrate": "96k",
+ "audio_opus_mono_bitrate": "64k",
"audio_target_sample_rate_hz": 48000,
"audio_target_channels": 2,
"audio_force_mono_globs": [
@@ -29,7 +30,7 @@
],
"audio_opus_application": "audio",
"audio_opus_frame_duration_ms": 20,
- "video_webm_crf": 34,
+ "video_webm_crf": 36,
"video_webm_audio_bitrate": "96k",
"video_webm_deadline": "good",
"video_webm_cpu_used": 4,
diff --git a/.github/resource-optimizer/osu-resources-config.json b/.github/resource-optimizer/osu-resources-config.json
index be0c9fa9c817..d4716dfd739b 100644
--- a/.github/resource-optimizer/osu-resources-config.json
+++ b/.github/resource-optimizer/osu-resources-config.json
@@ -11,16 +11,17 @@
"enable_video_conversion": true,
"keep_original_files": false,
"measure_image_ssim": true,
- "image_lossy_min_ssim": 0.995,
+ "image_lossy_min_ssim": 0.990,
"png_webp_try_lossless": true,
- "png_webp_lossless_compression_level": 6,
- "png_webp_lossy_quality": 92,
+ "png_webp_lossless_compression_level": 9,
+ "png_webp_lossy_quality": 90,
"png_webp_alpha_lossy_quality": 95,
"png_webp_lossy_method": 6,
"jpeg_webp_quality": 88,
"jpeg_webp_method": 6,
"audio_codec": "libopus",
"audio_ogg_bitrate": "96k",
+ "audio_opus_mono_bitrate": "64k",
"audio_target_sample_rate_hz": 48000,
"audio_target_channels": 2,
"audio_force_mono_globs": [
@@ -29,7 +30,7 @@
],
"audio_opus_application": "audio",
"audio_opus_frame_duration_ms": 20,
- "video_webm_crf": 34,
+ "video_webm_crf": 36,
"video_webm_audio_bitrate": "96k",
"video_webm_deadline": "good",
"video_webm_cpu_used": 4,
diff --git a/scripts/optimize_resource_overrides.py b/scripts/optimize_resource_overrides.py
index 2d1c1c91bbc5..2534e33e654e 100644
--- a/scripts/optimize_resource_overrides.py
+++ b/scripts/optimize_resource_overrides.py
@@ -174,7 +174,9 @@ def convert_audio_to_ogg(source: Path, output: Path, config: dict, relative_path
base_args = ["-i", str(source), "-ar", str(target_sample_rate), "-ac", str(channel_count)]
if audio_codec == "libopus":
- bitrate = str(config.get("audio_opus_bitrate", config.get("audio_ogg_bitrate", "96k")))
+ stereo_bitrate = str(config.get("audio_opus_bitrate", config.get("audio_ogg_bitrate", "96k")))
+ mono_bitrate = str(config.get("audio_opus_mono_bitrate", stereo_bitrate))
+ bitrate = mono_bitrate if channel_count == 1 else stereo_bitrate
opus_application = str(config.get("audio_opus_application", "audio"))
frame_duration = str(config.get("audio_opus_frame_duration_ms", 20))
run_ffmpeg(
From 1d46415d41b5137134838c40ce6d11f487c9fe80 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 8 May 2026 07:56:59 +0000
Subject: [PATCH 2/7] perf(optimizer): AVIF for JPEG, mono 64k Opus, tighter
WebP/video settings
Agent-Logs-Url: https://github.com/winnerspiros/osu/sessions/308e53dd-8e58-4c88-bce2-a507f3bc571e
Co-authored-by: winnerspiros <1675249+winnerspiros@users.noreply.github.com>
---
.github/resource-optimizer/config.json | 3 +
.../osu-resources-config.json | 3 +
scripts/optimize_resource_overrides.py | 139 ++++++++++++------
3 files changed, 96 insertions(+), 49 deletions(-)
diff --git a/.github/resource-optimizer/config.json b/.github/resource-optimizer/config.json
index 2363e98af3dc..35923aa8996b 100644
--- a/.github/resource-optimizer/config.json
+++ b/.github/resource-optimizer/config.json
@@ -19,6 +19,9 @@
"png_webp_lossy_method": 6,
"jpeg_webp_quality": 88,
"jpeg_webp_method": 6,
+ "jpeg_avif_enabled": true,
+ "jpeg_avif_crf": 30,
+ "jpeg_avif_preset": 4,
"audio_codec": "libopus",
"audio_ogg_bitrate": "96k",
"audio_opus_mono_bitrate": "64k",
diff --git a/.github/resource-optimizer/osu-resources-config.json b/.github/resource-optimizer/osu-resources-config.json
index d4716dfd739b..6451f8310cb4 100644
--- a/.github/resource-optimizer/osu-resources-config.json
+++ b/.github/resource-optimizer/osu-resources-config.json
@@ -19,6 +19,9 @@
"png_webp_lossy_method": 6,
"jpeg_webp_quality": 88,
"jpeg_webp_method": 6,
+ "jpeg_avif_enabled": true,
+ "jpeg_avif_crf": 30,
+ "jpeg_avif_preset": 4,
"audio_codec": "libopus",
"audio_ogg_bitrate": "96k",
"audio_opus_mono_bitrate": "64k",
diff --git a/scripts/optimize_resource_overrides.py b/scripts/optimize_resource_overrides.py
index 2534e33e654e..22cedfc6328e 100644
--- a/scripts/optimize_resource_overrides.py
+++ b/scripts/optimize_resource_overrides.py
@@ -89,80 +89,118 @@ def matches_any_glob(relative_path: str, patterns: list[str]) -> bool:
return any(fnmatch.fnmatch(relative_path, pattern) for pattern in patterns)
-def convert_image_to_webp(source: Path, output: Path, config: dict, relative_path: str) -> str:
+def convert_image(source: Path, config: dict, relative_path: str) -> tuple[Path, str]:
+ """
+ Convert a source image to the best compressed format available.
+
+ For PNG: tries lossless WebP and lossy WebP; picks the smallest that meets the SSIM
+ threshold. AVIF is explicitly excluded for PNGs because libsvtav1 only supports
+ yuv420p (no alpha channel), which would silently strip transparency.
+
+ For JPEG/JPG: tries lossy WebP and, when ``jpeg_avif_enabled`` is true in config,
+ lossy AVIF via libsvtav1 (yuv420p — safe for JPEG sources which have no alpha).
+ Picks the smallest candidate that meets the SSIM threshold.
+
+ Returns ``(temp_output_path, strategy)`` where ``temp_output_path.suffix`` is the
+ winning format extension (``.webp`` or ``.avif``). The caller is responsible for
+ moving the file to its final destination.
+ """
ext = source.suffix.lower()
- temp_candidates: list[tuple[Path, str]] = []
- has_alpha = ext == ".png" and ffprobe_has_alpha_channel(source)
+ # List of (temp_path, strategy_name, is_lossless_flag, format_extension) tuples
+ # representing conversion candidates to compare.
+ temp_candidates: list[tuple[Path, str, bool, str]] = []
+ # ── WebP lossless (PNG only) ─────────────────────────────────────────────
if ext == ".png" and bool(config.get("png_webp_try_lossless", True)):
- lossless_path = source.parent / f".{output.name}.lossless.tmp"
+ p = source.parent / f".{source.stem}.lossless.webp.tmp"
run_ffmpeg(
[
- "-i",
- str(source),
- "-c:v",
- "libwebp",
- "-lossless",
- "1",
- "-compression_level",
- str(config.get("png_webp_lossless_compression_level", 6)),
- "-f",
- "webp",
- str(lossless_path),
+ "-i", str(source),
+ "-c:v", "libwebp",
+ "-lossless", "1",
+ "-compression_level", str(config.get("png_webp_lossless_compression_level", 9)),
+ "-f", "webp", str(p),
]
)
- temp_candidates.append((lossless_path, "png-lossless-webp"))
+ temp_candidates.append((p, "png-lossless-webp", True, ".webp"))
+ # ── WebP lossy ───────────────────────────────────────────────────────────
+ has_alpha = ext == ".png" and ffprobe_has_alpha_channel(source)
lossy_quality = (
- config.get("png_webp_alpha_lossy_quality", 95) if has_alpha else config.get("png_webp_lossy_quality", 92)
+ config.get("png_webp_alpha_lossy_quality", 95) if has_alpha else config.get("png_webp_lossy_quality", 90)
) if ext == ".png" else config.get("jpeg_webp_quality", 88)
lossy_method = config.get("png_webp_lossy_method", 6) if ext == ".png" else config.get("jpeg_webp_method", 6)
- lossy_path = source.parent / f".{output.name}.lossy.tmp"
+ p_webp = source.parent / f".{source.stem}.lossy.webp.tmp"
run_ffmpeg(
[
- "-i",
- str(source),
- "-c:v",
- "libwebp",
- "-q:v",
- str(lossy_quality),
- "-compression_level",
- str(lossy_method),
- "-f",
- "webp",
- str(lossy_path),
+ "-i", str(source),
+ "-c:v", "libwebp",
+ "-q:v", str(lossy_quality),
+ "-compression_level", str(lossy_method),
+ "-f", "webp", str(p_webp),
]
)
- lossy_strategy = f"{ext.lstrip('.')}-lossy-webp-q{lossy_quality}"
- temp_candidates.append((lossy_path, lossy_strategy))
-
- minimum_ssim = float(config.get("image_lossy_min_ssim", 0.995))
+ temp_candidates.append((p_webp, f"{ext.lstrip('.')}-lossy-webp-q{lossy_quality}", False, ".webp"))
+
+ # ── AVIF via libsvtav1 (JPEG/JPG only, opt-in via jpeg_avif_enabled) ─────
+ # PNG files must NOT use AVIF: libsvtav1 encodes yuv420p only and silently
+ # strips alpha channels, producing a tiny but completely transparent output.
+ # JPEG sources have no alpha, so yuv420p is safe.
+ if ext in {".jpg", ".jpeg"} and bool(config.get("jpeg_avif_enabled", False)):
+ avif_crf = int(config.get("jpeg_avif_crf", 30))
+ avif_preset = int(config.get("jpeg_avif_preset", 4))
+ p_avif = source.parent / f".{source.stem}.lossy.avif.tmp"
+ try:
+ run_ffmpeg(
+ [
+ "-i", str(source),
+ "-c:v", "libsvtav1",
+ "-crf", str(avif_crf),
+ "-preset", str(avif_preset),
+ "-pix_fmt", "yuv420p",
+ "-f", "avif", # explicit muxer — ffmpeg cannot infer AVIF from .tmp extension
+ str(p_avif),
+ ]
+ )
+ temp_candidates.append((p_avif, f"jpg-avif-svtav1-crf{avif_crf}-p{avif_preset}", False, ".avif"))
+ except subprocess.CalledProcessError:
+ # Log a warning so the operator knows AVIF was requested but unavailable.
+ print(f"::warning::AVIF requested for {relative_path} but libsvtav1 encode failed; falling back to WebP.")
+ p_avif.unlink(missing_ok=True)
+
+ # ── Pick the smallest candidate that meets the SSIM threshold ────────────
+ minimum_ssim = float(config.get("image_lossy_min_ssim", 0.990))
compare_ssim = bool(config.get("measure_image_ssim", True))
- best_candidate: Optional[Tuple[Path, str]] = None
+ best_candidate: Optional[Tuple[Path, str, str]] = None # (path, strategy, fmt_ext)
- for candidate_path, strategy in temp_candidates:
- if strategy == lossy_strategy and compare_ssim:
- ssim = measure_image_ssim(source, candidate_path)
+ for cand_path, strategy, is_lossless, fmt_ext in temp_candidates:
+ if not is_lossless and compare_ssim:
+ ssim = measure_image_ssim(source, cand_path)
if ssim is not None and ssim < minimum_ssim:
- candidate_path.unlink(missing_ok=True)
+ cand_path.unlink(missing_ok=True)
continue
- if best_candidate is None or candidate_path.stat().st_size < best_candidate[0].stat().st_size:
- best_candidate = (candidate_path, strategy)
+ if best_candidate is None or cand_path.stat().st_size < best_candidate[0].stat().st_size:
+ best_candidate = (cand_path, strategy, fmt_ext)
if best_candidate is None:
- for candidate_path, _ in temp_candidates:
- candidate_path.unlink(missing_ok=True)
+ for cand_path, _, _, _ in temp_candidates:
+ cand_path.unlink(missing_ok=True)
raise RuntimeError(
f"No image candidate met quality threshold for {relative_path}. "
f"Consider lowering image_lossy_min_ssim in config."
)
- best_path, best_strategy = best_candidate
- shutil.move(str(best_path), str(output))
- for candidate_path, _ in temp_candidates:
- if candidate_path != best_path:
- candidate_path.unlink(missing_ok=True)
- return best_strategy
+ best_path, best_strategy, winning_ext = best_candidate
+ # Clean up all losing candidates (those that passed SSIM but weren't the smallest).
+ for cand_path, _, _, _ in temp_candidates:
+ if cand_path != best_path:
+ cand_path.unlink(missing_ok=True)
+
+ # Rename to a clean temp path whose .suffix is the winning format extension
+ # (.webp or .avif) so the caller can derive the final output path via Path.with_suffix.
+ clean_tmp = source.parent / f".{source.stem}.win{winning_ext}"
+ shutil.move(str(best_path), str(clean_tmp))
+ return clean_tmp, best_strategy
def convert_audio_to_ogg(source: Path, output: Path, config: dict, relative_path: str) -> str:
@@ -278,7 +316,10 @@ def optimize(root: Path, config: dict, dry_run: bool) -> dict:
try:
if ext in {".png", ".jpg", ".jpeg"}:
- strategy = convert_image_to_webp(source, temp_output, config=config, relative_path=relative_path)
+ # convert_image manages its own temp files and returns a clean temp
+ # whose suffix is the winning format extension (.webp or .avif).
+ temp_output, strategy = convert_image(source, config=config, relative_path=relative_path)
+ output = source.with_suffix(temp_output.suffix)
elif ext in {".wav", ".mp3"}:
strategy = convert_audio_to_ogg(source, temp_output, config=config, relative_path=relative_path)
elif ext == ".mp4":
From 79b506d96dade373ffbdedd10e5661ab42f735e4 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 8 May 2026 08:33:18 +0000
Subject: [PATCH 3/7] perf: ADPF setPreferPowerEfficiency, GC no-region,
nursery-size=8m, OEM game modes, VP9 deadline=best
Agent-Logs-Url: https://github.com/winnerspiros/osu/sessions/2523e170-1d43-41d8-af53-47db9142dcaa
Co-authored-by: winnerspiros <1675249+winnerspiros@users.noreply.github.com>
---
.github/resource-optimizer/config.json | 4 +-
.../osu-resources-config.json | 4 +-
osu.Android/AndroidManifest.xml | 28 ++++++++++
osu.Android/Native/OboeAudioBridge.cs | 1 +
osu.Android/Native/oboe_bridge.cpp | 26 +++++++++
osu.Android/OsuGameAndroid.cs | 6 +++
.../AndroidHighPerformanceSessionManager.cs | 54 +++++++++++++++++++
osu.Android/mono.env | 6 +++
8 files changed, 125 insertions(+), 4 deletions(-)
diff --git a/.github/resource-optimizer/config.json b/.github/resource-optimizer/config.json
index 35923aa8996b..364fda56bb9e 100644
--- a/.github/resource-optimizer/config.json
+++ b/.github/resource-optimizer/config.json
@@ -35,7 +35,7 @@
"audio_opus_frame_duration_ms": 20,
"video_webm_crf": 36,
"video_webm_audio_bitrate": "96k",
- "video_webm_deadline": "good",
- "video_webm_cpu_used": 4,
+ "video_webm_deadline": "best",
+ "video_webm_cpu_used": 2,
"video_webm_tile_columns": 2
}
diff --git a/.github/resource-optimizer/osu-resources-config.json b/.github/resource-optimizer/osu-resources-config.json
index 6451f8310cb4..c3887fbfa2d2 100644
--- a/.github/resource-optimizer/osu-resources-config.json
+++ b/.github/resource-optimizer/osu-resources-config.json
@@ -35,7 +35,7 @@
"audio_opus_frame_duration_ms": 20,
"video_webm_crf": 36,
"video_webm_audio_bitrate": "96k",
- "video_webm_deadline": "good",
- "video_webm_cpu_used": 4,
+ "video_webm_deadline": "best",
+ "video_webm_cpu_used": 2,
"video_webm_tile_columns": 2
}
diff --git a/osu.Android/AndroidManifest.xml b/osu.Android/AndroidManifest.xml
index 3f2b76ec5f82..15ab78b1a3e6 100644
--- a/osu.Android/AndroidManifest.xml
+++ b/osu.Android/AndroidManifest.xml
@@ -84,6 +84,34 @@
mappings on Legion Phone hardware. -->
+
+
+
+
+
+
+
+
+
+