From 0bcb99f2c46cf08e0c05e1cd27a0662a4a1e375b Mon Sep 17 00:00:00 2001 From: Salman Ahmed <frenzibyte@gmail.com> Date: Sat, 30 Sep 2023 01:16:56 +0300 Subject: [PATCH 1/3] Crop oversized gameplay textures instead of downscaling them --- osu.Game/Skinning/LegacySkinExtensions.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/LegacySkinExtensions.cs b/osu.Game/Skinning/LegacySkinExtensions.cs index dde6c1fa29..868f36fb34 100644 --- a/osu.Game/Skinning/LegacySkinExtensions.cs +++ b/osu.Game/Skinning/LegacySkinExtensions.cs @@ -9,6 +9,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; +using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osuTK; @@ -112,9 +113,11 @@ namespace osu.Game.Skinning if (texture.DisplayWidth <= maxSize.X && texture.DisplayHeight <= maxSize.Y) return texture; - // use scale adjust property for downscaling the texture in order to meet the specified maximum dimensions. - texture.ScaleAdjust *= Math.Max(texture.DisplayWidth / maxSize.X, texture.DisplayHeight / maxSize.Y); - return texture; + maxSize *= texture.ScaleAdjust; + + var croppedTexture = texture.Crop(new RectangleF(texture.Width / 2f - maxSize.X / 2f, texture.Height / 2f - maxSize.Y / 2f, maxSize.X, maxSize.Y)); + croppedTexture.ScaleAdjust = texture.ScaleAdjust; + return croppedTexture; } public static bool HasFont(this ISkin source, LegacyFont font) From 314ecec65b34c73a293e7f8fbf14183c5dcfa6b4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed <frenzibyte@gmail.com> Date: Sat, 30 Sep 2023 01:17:59 +0300 Subject: [PATCH 2/3] Refactor player max dimensions test scene to actually upscale textures --- .../Gameplay/TestScenePlayerMaxDimensions.cs | 77 ++++++++++++++++--- osu.Game/Skinning/Skin.cs | 5 +- 2 files changed, 71 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerMaxDimensions.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerMaxDimensions.cs index a8ed44c7f8..68443b234b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerMaxDimensions.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerMaxDimensions.cs @@ -3,14 +3,20 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Graphics.Textures; +using osu.Framework.IO.Stores; using osu.Framework.Testing; using osu.Game.IO; using osu.Game.Rulesets; using osu.Game.Screens.Play; using osu.Game.Skinning; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Processing; namespace osu.Game.Tests.Visual.Gameplay { @@ -23,6 +29,9 @@ namespace osu.Game.Tests.Visual.Gameplay /// </remarks> public partial class TestScenePlayerMaxDimensions : TestSceneAllRulesetPlayers { + // scale textures to 4 times their size. + private const int scale_factor = 4; + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); @@ -63,18 +72,66 @@ namespace osu.Game.Tests.Visual.Gameplay remove { } } - public override Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) - { - var texture = base.GetTexture(componentName, wrapModeS, wrapModeT); - - if (texture != null) - texture.ScaleAdjust /= 8f; - - return texture; - } - public ISkin FindProvider(Func<ISkin, bool> lookupFunction) => this; public IEnumerable<ISkin> AllSources => new[] { this }; + + protected override IResourceStore<TextureUpload> CreateTextureLoaderStore(IStorageResourceProvider resources, IResourceStore<byte[]> storage) + => new UpscaledTextureLoaderStore(base.CreateTextureLoaderStore(resources, storage)); + + private class UpscaledTextureLoaderStore : IResourceStore<TextureUpload> + { + private readonly IResourceStore<TextureUpload>? textureStore; + + public UpscaledTextureLoaderStore(IResourceStore<TextureUpload>? textureStore) + { + this.textureStore = textureStore; + } + + public void Dispose() + { + textureStore?.Dispose(); + } + + public TextureUpload Get(string name) + { + var textureUpload = textureStore?.Get(name); + + // NRT not enabled on framework side classes (IResourceStore / TextureLoaderStore), welp. + if (textureUpload == null) + return null!; + + return upscale(textureUpload); + } + + public async Task<TextureUpload> GetAsync(string name, CancellationToken cancellationToken = new CancellationToken()) + { + // NRT not enabled on framework side classes (IResourceStore / TextureLoaderStore), welp. + if (textureStore == null) + return null!; + + var textureUpload = await textureStore.GetAsync(name, cancellationToken).ConfigureAwait(false); + + if (textureUpload == null) + return null!; + + return await Task.Run(() => upscale(textureUpload), cancellationToken).ConfigureAwait(false); + } + + private TextureUpload upscale(TextureUpload textureUpload) + { + var image = Image.LoadPixelData(textureUpload.Data.ToArray(), textureUpload.Width, textureUpload.Height); + + // The original texture upload will no longer be returned or used. + textureUpload.Dispose(); + + image.Mutate(i => i.Resize(new Size(textureUpload.Width, textureUpload.Height) * scale_factor)); + return new TextureUpload(image); + } + + public Stream? GetStream(string name) => textureStore?.GetStream(name); + + public IEnumerable<string> GetAvailableResources() => textureStore?.GetAvailableResources() ?? Array.Empty<string>(); + } } } } diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index ccf49d722f..1e312142d7 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -88,7 +88,7 @@ namespace osu.Game.Skinning } Samples = samples; - Textures = new TextureStore(resources.Renderer, new MaxDimensionLimitedTextureLoaderStore(resources.CreateTextureLoaderStore(storage))); + Textures = new TextureStore(resources.Renderer, CreateTextureLoaderStore(resources, storage)); } else { @@ -171,6 +171,9 @@ namespace osu.Game.Skinning } } + protected virtual IResourceStore<TextureUpload> CreateTextureLoaderStore(IStorageResourceProvider resources, IResourceStore<byte[]> storage) + => new MaxDimensionLimitedTextureLoaderStore(resources.CreateTextureLoaderStore(storage)); + protected virtual void ParseConfigurationStream(Stream stream) { using (LineBufferedReader reader = new LineBufferedReader(stream, true)) From 5a0faaa0b118e4935c05a78e00ec4214c69a6b5f Mon Sep 17 00:00:00 2001 From: Dean Herbert <pe@ppy.sh> Date: Thu, 5 Oct 2023 14:30:04 +0900 Subject: [PATCH 3/3] Fix `TestReplayExport` intermittent failure The previous fix was not working as it was checking the path for the prefix `_`, not the filename. See https://github.com/ppy/osu/runs/17415814653#r0s2 which clearly shows this. --- .../Visual/Gameplay/TestScenePlayerLocalScoreImport.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs index feda251744..6a7fab86d6 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.IO; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -173,7 +174,7 @@ namespace osu.Game.Tests.Visual.Gameplay string? filePath = null; // Files starting with _ are temporary, created by CreateFileSafely call. - AddUntilStep("wait for export file", () => filePath = LocalStorage.GetFiles("exports").SingleOrDefault(f => !f.StartsWith("_", StringComparison.Ordinal)), () => Is.Not.Null); + AddUntilStep("wait for export file", () => filePath = LocalStorage.GetFiles("exports").SingleOrDefault(f => !Path.GetFileName(f).StartsWith("_", StringComparison.Ordinal)), () => Is.Not.Null); AddAssert("filesize is non-zero", () => { using (var stream = LocalStorage.GetStream(filePath))