A game framework written with osu! in mind.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge branch 'master' into sdl2

# Conflicts:
# osu.Framework/Platform/Linux/LinuxGameHost.cs
# osu.Framework/osu.Framework.csproj

+1236 -582
+1 -1
SampleGame.Android/Properties/AndroidManifest.xml
··· 1 1 <?xml version="1.0" encoding="utf-8"?> 2 2 <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="SampleGame.Android" android:installLocation="auto"> 3 - <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="27" /> 3 + <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29" /> 4 4 <application /> 5 5 </manifest>
+1 -1
SampleGame.Android/SampleGame.Android.csproj
··· 7 7 <ProjectTypeGuids>{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> 8 8 <RootNamespace>SampleGame.Android</RootNamespace> 9 9 <AssemblyName>SampleGame.Android</AssemblyName> 10 - <TargetFrameworkVersion>v8.0</TargetFrameworkVersion> 11 10 <AndroidApplication>True</AndroidApplication> 12 11 <AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest> 13 12 <AndroidSupportedAbis>armeabi-v7a;x86;arm64-v8a</AndroidSupportedAbis> 13 + <TargetFrameworkVersion>v10.0</TargetFrameworkVersion> 14 14 </PropertyGroup> 15 15 <ItemGroup> 16 16 <Compile Include="SampleGameActivity.cs" />
+1 -1
SampleGame/SampleGame.csproj
··· 1 1 <Project Sdk="Microsoft.NET.Sdk"> 2 2 <PropertyGroup> 3 - <TargetFramework>netstandard2.0</TargetFramework> 3 + <TargetFramework>netstandard2.1</TargetFramework> 4 4 </PropertyGroup> 5 5 <ItemGroup Label="Project References"> 6 6 <ProjectReference Include="..\osu.Framework\osu.Framework.csproj" />
+1 -1
global.json
··· 1 1 { 2 2 "msbuild-sdks": { 3 3 "MSBuild.Sdk.Extras": "2.0.54", 4 - "Microsoft.Build.Traversal": "2.0.19" 4 + "Microsoft.Build.Traversal": "2.0.24" 5 5 } 6 6 }
+1 -1
osu-framework.iOS.slnf
··· 6 6 "SampleGame\\SampleGame.csproj", 7 7 "osu.Framework.iOS\\osu.Framework.iOS.csproj", 8 8 "osu.Framework.NativeLibs\\osu.Framework.NativeLibs.csproj", 9 - "osu.Framework.Tests.Android\\osu.Framework.Tests.Android.csproj", 9 + "osu.Framework.Tests.iOS\\osu.Framework.Tests.iOS.csproj", 10 10 "osu.Framework\\osu.Framework.csproj" 11 11 ] 12 12 }
+2 -2
osu.Framework.Android.props
··· 11 11 <GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies> 12 12 <AndroidApplication>True</AndroidApplication> 13 13 <AndroidHttpClientHandlerType>Xamarin.Android.Net.AndroidClientHandler</AndroidHttpClientHandlerType> 14 - <TargetFrameworkVersion>v8.1</TargetFrameworkVersion> 14 + <!-- This does not take effect, unlike osu game repo. --> 15 + <!--<TargetFrameworkVersion>v10.0</TargetFrameworkVersion>--> 15 16 <AndroidUseLatestPlatformSdk>false</AndroidUseLatestPlatformSdk> 16 17 <AllowUnsafeBlocks>true</AllowUnsafeBlocks> 17 18 <AndroidSupportedAbis>armeabi-v7a;x86;arm64-v8a</AndroidSupportedAbis> ··· 58 59 <ItemGroup Label="Package References"> 59 60 <PackageReference Include="ManagedBass" Version="2.0.4" /> 60 61 <PackageReference Include="ManagedBass.Fx" Version="2.0.1" /> 61 - <PackageReference Include="System.ValueTuple" Version="4.5.0" /> 62 62 </ItemGroup> 63 63 </Project>
+11
osu.Framework.Android/AndroidGameView.cs
··· 114 114 public void RenderGame() 115 115 { 116 116 Host = new AndroidGameHost(this); 117 + Host.ExceptionThrown += handleException; 117 118 Host.Run(game); 119 + } 120 + 121 + private bool handleException(Exception ex) 122 + { 123 + // suppress exceptions related to MobileAuthenticatedStream disposal 124 + // (see: https://github.com/ppy/osu/issues/6264 and linked related mono/xamarin issues) 125 + // to be removed when upstream fixes come in 126 + return ex is AggregateException ae 127 + && ae.InnerException is ObjectDisposedException ode 128 + && ode.ObjectName == "MobileAuthenticatedStream"; 118 129 } 119 130 120 131 public override bool OnCheckIsTextEditor() => true;
+1 -1
osu.Framework.Android/osu.Framework.Android.csproj
··· 1 1 <Project Sdk="MSBuild.Sdk.Extras"> 2 2 <PropertyGroup Label="Project"> 3 - <TargetFramework>monoandroid80</TargetFramework> 3 + <TargetFramework>monoandroid10.0</TargetFramework> 4 4 <OutputType>Library</OutputType> 5 5 <AllowUnsafeBlocks>true</AllowUnsafeBlocks> 6 6 <AssemblyTitle>osu!framework Android</AssemblyTitle>
+1 -1
osu.Framework.Tests.Android/Properties/AndroidManifest.xml
··· 1 1 <?xml version="1.0" encoding="utf-8"?> 2 2 <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="osu.Framework.Tests.Android" android:installLocation="auto"> 3 - <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="27" /> 3 + <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29" /> 4 4 <application android:label="osu!framework test" /> 5 5 </manifest>
+1
osu.Framework.Tests.Android/osu.Framework.Tests.Android.csproj
··· 10 10 <AssemblyName>osu.Framework.Tests.Android</AssemblyName> 11 11 <AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest> 12 12 <AndroidSupportedAbis>armeabi-v7a;x86;arm64-v8a</AndroidSupportedAbis> 13 + <TargetFrameworkVersion>v10.0</TargetFrameworkVersion> 13 14 </PropertyGroup> 14 15 <ItemGroup> 15 16 <Compile Include="TestGameActivity.cs" />
+2 -1
osu.Framework.Tests/Audio/AudioComponentTest.cs
··· 1 1 // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 2 // See the LICENCE file in the repository root for full licence text. 3 3 4 + using System.Reflection; 4 5 using System.Threading; 5 6 using System.Threading.Tasks; 6 7 using NUnit.Framework; ··· 22 23 public void SetUp() 23 24 { 24 25 thread = new AudioThread(); 25 - store = new NamespacedResourceStore<byte[]>(new DllResourceStore(@"osu.Framework.dll"), @"Resources"); 26 + store = new NamespacedResourceStore<byte[]>(new DllResourceStore(new AssemblyName("osu.Framework")), @"Resources"); 26 27 27 28 manager = new AudioManager(thread, store, store); 28 29
+91
osu.Framework.Tests/Audio/AudioManagerWithDeviceLoss.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + using System.Collections.Generic; 5 + using System.Linq; 6 + using ManagedBass; 7 + using osu.Framework.Audio; 8 + using osu.Framework.IO.Stores; 9 + using osu.Framework.Threading; 10 + 11 + namespace osu.Framework.Tests.Audio 12 + { 13 + /// <summary> 14 + /// <see cref="AudioManager"/> that can simulate the loss of a device. 15 + /// This will NOT work without a physical audio device! 16 + /// </summary> 17 + internal class AudioManagerWithDeviceLoss : AudioManager 18 + { 19 + public AudioManagerWithDeviceLoss(AudioThread audioThread, ResourceStore<byte[]> trackStore, ResourceStore<byte[]> sampleStore) 20 + : base(audioThread, trackStore, sampleStore) 21 + { 22 + } 23 + 24 + public volatile int CurrentDevice = Bass.DefaultDevice; 25 + 26 + private volatile bool simulateLoss; 27 + 28 + protected override bool InitBass(int device) 29 + { 30 + if (simulateLoss) 31 + { 32 + if (device != Bass.NoSoundDevice || !base.InitBass(device)) 33 + return false; 34 + 35 + CurrentDevice = device; 36 + return true; 37 + } 38 + 39 + if (!base.InitBass(device)) 40 + return false; 41 + 42 + CurrentDevice = device; 43 + return true; 44 + } 45 + 46 + protected override IEnumerable<DeviceInfo> EnumerateAllDevices() 47 + { 48 + var devices = base.EnumerateAllDevices(); 49 + 50 + if (simulateLoss) 51 + devices = devices.Take(1); 52 + 53 + return devices; 54 + } 55 + 56 + protected override bool IsCurrentDeviceValid() 57 + { 58 + if (simulateLoss) 59 + return CurrentDevice == Bass.NoSoundDevice && base.IsCurrentDeviceValid(); 60 + 61 + return CurrentDevice != Bass.NoSoundDevice && base.IsCurrentDeviceValid(); 62 + } 63 + 64 + public void SimulateDeviceLoss() 65 + { 66 + var current = CurrentDevice; 67 + 68 + simulateLoss = true; 69 + 70 + if (current != Bass.NoSoundDevice) 71 + WaitForDeviceChange(current); 72 + } 73 + 74 + public void SimulateDeviceRestore() 75 + { 76 + var current = CurrentDevice; 77 + 78 + simulateLoss = false; 79 + 80 + if (current == Bass.NoSoundDevice) 81 + WaitForDeviceChange(current); 82 + } 83 + 84 + public void WaitForDeviceChange(int? current = null, int timeoutMs = 5000) 85 + { 86 + current ??= CurrentDevice; 87 + 88 + AudioThreadTest.WaitForOrAssert(() => CurrentDevice != current, $"Timed out while waiting for the device to change from {current}.", timeoutMs); 89 + } 90 + } 91 + }
+65
osu.Framework.Tests/Audio/AudioThreadTest.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + using System; 5 + using System.Threading; 6 + using System.Threading.Tasks; 7 + using NUnit.Framework; 8 + using osu.Framework.Audio.Track; 9 + using osu.Framework.IO.Stores; 10 + using osu.Framework.Threading; 11 + 12 + namespace osu.Framework.Tests.Audio 13 + { 14 + [TestFixture] 15 + public abstract class AudioThreadTest 16 + { 17 + private AudioThread thread; 18 + internal AudioManagerWithDeviceLoss Manager; 19 + 20 + [SetUp] 21 + public virtual void SetUp() 22 + { 23 + thread = new AudioThread(); 24 + 25 + var store = new NamespacedResourceStore<byte[]>(new DllResourceStore(@"osu.Framework.Tests.dll"), @"Resources"); 26 + 27 + Manager = new AudioManagerWithDeviceLoss(thread, store, store); 28 + 29 + thread.Start(); 30 + } 31 + 32 + [TearDown] 33 + public void TearDown() 34 + { 35 + Assert.IsFalse(thread.Exited); 36 + 37 + thread.Exit(); 38 + 39 + WaitForOrAssert(() => thread.Exited, "Audio thread did not exit in time"); 40 + } 41 + 42 + public void CheckTrackIsProgressing(Track track) 43 + { 44 + // playback should be continuing after device change 45 + for (int i = 0; i < 2; i++) 46 + { 47 + var checkAfter = track.CurrentTime; 48 + WaitForOrAssert(() => track.CurrentTime > checkAfter, "Track time did not increase", 1000); 49 + Assert.IsTrue(track.IsRunning); 50 + } 51 + } 52 + 53 + /// <summary> 54 + /// Waits for a specified condition to become true, or timeout reached. 55 + /// </summary> 56 + /// <param name="condition">The condition which should become true.</param> 57 + /// <param name="message">A message to display on timeout.</param> 58 + /// <param name="timeout">Timeout in milliseconds.</param> 59 + public static void WaitForOrAssert(Func<bool> condition, string message, int timeout = 60000) => 60 + Assert.IsTrue(Task.Run(() => 61 + { 62 + while (!condition()) Thread.Sleep(50); 63 + }).Wait(timeout), message); 64 + } 65 + }
+67
osu.Framework.Tests/Audio/DeviceLosingAudioTest.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + using System; 5 + using NUnit.Framework; 6 + 7 + namespace osu.Framework.Tests.Audio 8 + { 9 + /// <remarks> 10 + /// This unit will ALWAYS SKIP if the system does not have a physical audio device!!! 11 + /// A physical audio device is required to simulate the "loss" of it during playback. 12 + /// </remarks> 13 + [TestFixture] 14 + public class DeviceLosingAudioTest : AudioThreadTest 15 + { 16 + public override void SetUp() 17 + { 18 + base.SetUp(); 19 + 20 + // wait for any device to be initialized 21 + Manager.WaitForDeviceChange(-1); 22 + 23 + // if the initialized device is "No sound", it indicates that no other physical devices are available, so this unit should be ignored 24 + if (Manager.CurrentDevice == 0) 25 + Assert.Ignore("Physical audio devices are required for this unit."); 26 + 27 + // we don't want music playing in unit tests :) 28 + Manager.Volume.Value = 0; 29 + } 30 + 31 + [Test] 32 + public void TestPlaybackWithDeviceLoss() => testPlayback(Manager.SimulateDeviceRestore, Manager.SimulateDeviceLoss); 33 + 34 + [Test] 35 + public void TestPlaybackWithDeviceRestore() => testPlayback(Manager.SimulateDeviceLoss, Manager.SimulateDeviceRestore); 36 + 37 + private void testPlayback(Action preparation, Action simulate) 38 + { 39 + preparation(); 40 + 41 + var track = Manager.Tracks.Get("Tracks.sample-track.mp3"); 42 + 43 + // start track 44 + track.Restart(); 45 + 46 + WaitForOrAssert(() => track.IsRunning, "Track did not start running"); 47 + 48 + WaitForOrAssert(() => track.CurrentTime > 0, "Track did not start running"); 49 + 50 + // simulate change (loss/restore) 51 + simulate(); 52 + 53 + CheckTrackIsProgressing(track); 54 + 55 + // stop track 56 + track.Stop(); 57 + 58 + WaitForOrAssert(() => !track.IsRunning, "Track did not stop", 1000); 59 + 60 + // seek track 61 + track.Seek(0); 62 + 63 + Assert.IsFalse(track.IsRunning); 64 + WaitForOrAssert(() => track.CurrentTime == 0, "Track did not seek correctly", 1000); 65 + } 66 + } 67 + }
+44
osu.Framework.Tests/Audio/DevicelessAudioTest.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + using NUnit.Framework; 5 + 6 + namespace osu.Framework.Tests.Audio 7 + { 8 + [TestFixture] 9 + public class DevicelessAudioTest : AudioThreadTest 10 + { 11 + public override void SetUp() 12 + { 13 + base.SetUp(); 14 + 15 + // lose all devices 16 + Manager.SimulateDeviceLoss(); 17 + } 18 + 19 + [Test] 20 + public void TestPlayTrackWithoutDevices() 21 + { 22 + var track = Manager.Tracks.Get("Tracks.sample-track.mp3"); 23 + 24 + // start track 25 + track.Restart(); 26 + Assert.IsTrue(track.IsRunning); 27 + 28 + CheckTrackIsProgressing(track); 29 + 30 + // stop track 31 + track.Stop(); 32 + 33 + WaitForOrAssert(() => !track.IsRunning, "Track did not stop", 1000); 34 + 35 + Assert.IsFalse(track.IsRunning); 36 + 37 + // seek track 38 + track.Seek(0); 39 + 40 + Assert.IsFalse(track.IsRunning); 41 + WaitForOrAssert(() => track.CurrentTime == 0, "Track did not seek correctly", 1000); 42 + } 43 + } 44 + }
+1 -1
osu.Framework.Tests/Audio/TrackBassTest.cs
··· 26 26 // Initialize bass with no audio to make sure the test remains consistent even if there is no audio device. 27 27 Bass.Init(0); 28 28 29 - resources = new DllResourceStore("osu.Framework.Tests.dll"); 29 + resources = new DllResourceStore(typeof(TrackBassTest).Assembly); 30 30 31 31 track = new TrackBass(resources.GetStream("Resources.Tracks.sample-track.mp3")); 32 32 updateTrack();
+4 -6
osu.Framework.Tests/IO/FontStoreTest.cs
··· 17 17 [OneTimeSetUp] 18 18 public void OneTimeSetUp() 19 19 { 20 - storage = new TemporaryNativeStorage("fontstore-test"); 21 - fontResourceStore = new NamespacedResourceStore<byte[]>(new DllResourceStore(typeof(Drawable).Assembly.Location), "Resources.Fonts.OpenSans"); 22 - 23 - storage.GetFullPath("./", true); 20 + storage = new TemporaryNativeStorage("fontstore-test", createIfEmpty: true); 21 + fontResourceStore = new NamespacedResourceStore<byte[]>(new DllResourceStore(typeof(Drawable).Assembly), "Resources.Fonts.OpenSans"); 24 22 } 25 23 26 24 [Test] 27 25 public void TestNestedScaleAdjust() 28 26 { 29 - var fontStore = new FontStore(new GlyphStore(fontResourceStore, "OpenSans") { CacheStorage = storage }, scaleAdjust: 100); 30 - var nestedFontStore = new FontStore(new GlyphStore(fontResourceStore, "OpenSans-Bold") { CacheStorage = storage }, 10); 27 + var fontStore = new FontStore(new RawCachingGlyphStore(fontResourceStore, "OpenSans") { CacheStorage = storage }, scaleAdjust: 100); 28 + var nestedFontStore = new FontStore(new RawCachingGlyphStore(fontResourceStore, "OpenSans-Bold") { CacheStorage = storage }, 10); 31 29 32 30 fontStore.AddStore(nestedFontStore); 33 31
+2 -2
osu.Framework.Tests/IO/TestWebRequest.cs
··· 160 160 Assert.IsTrue(request.Completed); 161 161 Assert.IsTrue(request.Aborted); 162 162 163 - Assert.IsTrue(request.ResponseString == null); 163 + Assert.IsTrue(request.GetResponseString() == null); 164 164 Assert.IsNotNull(finishedException); 165 165 } 166 166 ··· 183 183 Assert.IsTrue(request.Completed); 184 184 Assert.IsTrue(request.Aborted); 185 185 186 - Assert.IsEmpty(request.ResponseString); 186 + Assert.IsEmpty(request.GetResponseString()); 187 187 188 188 Assert.IsTrue(hasThrown); 189 189 }
+10
osu.Framework.Tests/MathUtils/TestInterpolation.cs
··· 2 2 // See the LICENCE file in the repository root for full licence text. 3 3 4 4 using System; 5 + using System.Collections.Generic; 6 + using System.Linq; 5 7 using NUnit.Framework; 6 8 using osu.Framework.Graphics; 7 9 using osu.Framework.MathUtils; ··· 12 14 [TestFixture] 13 15 public class TestInterpolation 14 16 { 17 + [TestCaseSource(nameof(getEasings))] 18 + public void TestEasingStartsAtZero(Easing easing) => Assert.That(Interpolation.ApplyEasing(easing, 0), Is.EqualTo(0).Within(Precision.DOUBLE_EPSILON)); 19 + 20 + [TestCaseSource(nameof(getEasings))] 21 + public void TestEasingEndsAtOne(Easing easing) => Assert.That(Interpolation.ApplyEasing(easing, 1), Is.EqualTo(1).Within(Precision.DOUBLE_EPSILON)); 22 + 23 + private static IEnumerable<Easing> getEasings() => Enum.GetValues(typeof(Easing)).OfType<Easing>(); 24 + 15 25 [Test] 16 26 public void TestLerp() 17 27 {
+6 -1
osu.Framework.Tests/TemporaryNativeStorage.cs
··· 8 8 { 9 9 public class TemporaryNativeStorage : NativeStorage, IDisposable 10 10 { 11 - public TemporaryNativeStorage(string name, GameHost host = null) 11 + public TemporaryNativeStorage(string name, GameHost host = null, bool createIfEmpty = false) 12 12 : base(name, host) 13 13 { 14 + if (createIfEmpty) 15 + { 16 + // create directory 17 + GetFullPath("./", true); 18 + } 14 19 } 15 20 16 21 public void Dispose()
+1 -2
osu.Framework.Tests/TestGame.cs
··· 1 1 // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 2 // See the LICENCE file in the repository root for full licence text. 3 3 4 - using System.Reflection; 5 4 using osu.Framework.Allocation; 6 5 using osu.Framework.IO.Stores; 7 6 ··· 12 11 [BackgroundDependencyLoader] 13 12 private void load() 14 13 { 15 - Resources.AddStore(new NamespacedResourceStore<byte[]>(new DllResourceStore(Assembly.GetExecutingAssembly().Location), "Resources")); 14 + Resources.AddStore(new NamespacedResourceStore<byte[]>(new DllResourceStore(typeof(TestGame).Assembly), "Resources")); 16 15 } 17 16 } 18 17 }
+14 -7
osu.Framework.Tests/Visual/Containers/TestSceneBufferedContainer.cs
··· 9 9 { 10 10 public class TestSceneBufferedContainer : TestSceneMasking 11 11 { 12 - private readonly BufferedContainer buffer; 13 - 14 12 public TestSceneBufferedContainer() 15 13 { 16 14 Remove(TestContainer); 17 15 16 + BufferedContainer buffer; 18 17 Add(buffer = new BufferedContainer 19 18 { 20 19 RelativeSizeAxes = Axes.Both, 21 20 Children = new[] { TestContainer } 22 21 }); 23 - } 22 + 23 + AddSliderStep("blur", 0f, 20f, 0f, blur => 24 + { 25 + buffer.BlurTo(new Vector2(blur)); 26 + }); 24 27 25 - protected override void LoadComplete() 26 - { 27 - base.LoadComplete(); 28 + AddSliderStep("fbo scale (x)", 0.01f, 4f, 1f, scale => 29 + { 30 + buffer.FrameBufferScale = new Vector2(scale, buffer.FrameBufferScale.Y); 31 + }); 28 32 29 - buffer.BlurTo(new Vector2(20), 1000).Then().BlurTo(Vector2.Zero, 1000).Loop(); 33 + AddSliderStep("fbo scale (y)", 0.01f, 4f, 1f, scale => 34 + { 35 + buffer.FrameBufferScale = new Vector2(buffer.FrameBufferScale.X, scale); 36 + }); 30 37 } 31 38 } 32 39 }
+1 -1
osu.Framework.Tests/Visual/Containers/TestSceneMasking.cs
··· 155 155 box.OnUpdate += delegate 156 156 { 157 157 box.Rotation += 0.05f; 158 - box.CornerRadius = 100 + 100 * (float)Math.Sin(box.Rotation * 0.01); 158 + box.CornerRadius = 100 + 100 * MathF.Sin(box.Rotation * 0.01f); 159 159 }; 160 160 break; 161 161 }
+130
osu.Framework.Tests/Visual/Drawables/TestSceneEasingCurves.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + using System; 5 + using System.Linq; 6 + using osu.Framework.Bindables; 7 + using osu.Framework.Extensions.IEnumerableExtensions; 8 + using osu.Framework.Graphics; 9 + using osu.Framework.Graphics.Containers; 10 + using osu.Framework.Graphics.Shapes; 11 + using osu.Framework.Graphics.Sprites; 12 + using osu.Framework.Testing; 13 + using osuTK; 14 + using osuTK.Graphics; 15 + 16 + namespace osu.Framework.Tests.Visual.Drawables 17 + { 18 + public class TestSceneEasingCurves : TestScene 19 + { 20 + private const float default_size = 300; 21 + 22 + public TestSceneEasingCurves() 23 + { 24 + FillFlowContainer easingsContainer = null; 25 + 26 + var easingTypes = Enum.GetValues(typeof(Easing)) 27 + .OfType<Easing>() 28 + .ToList(); 29 + 30 + AddStep("set up easings", () => Child = new BasicScrollContainer 31 + { 32 + RelativeSizeAxes = Axes.Both, 33 + Child = easingsContainer = new FillFlowContainer 34 + { 35 + RelativeSizeAxes = Axes.X, 36 + AutoSizeAxes = Axes.Y, 37 + Children = easingTypes.Select(type => new Visualiser(type)) 38 + .ToArray() 39 + } 40 + }); 41 + 42 + AddSliderStep("resize easings", default_size, 3 * default_size, default_size, size => 43 + { 44 + easingsContainer?.Children?.OfType<Visualiser>().ForEach(easing => easing.ResizeTo(new Vector2(size))); 45 + }); 46 + 47 + foreach (var type in easingTypes) 48 + { 49 + AddToggleStep($"toggle {type}", enabled => 50 + { 51 + var easingContainer = easingsContainer.Children.OfType<Visualiser>().Single(easing => easing.Easing == type); 52 + easingContainer.Visible.Value = enabled; 53 + }); 54 + } 55 + } 56 + 57 + private class Visualiser : Container 58 + { 59 + private const float movement_duration = 1000f; 60 + private const float pause_duration = 500f; 61 + 62 + public readonly Easing Easing; 63 + 64 + public Bindable<bool> Visible { get; } = new BindableBool(); 65 + 66 + private readonly CircularContainer dot; 67 + 68 + public Visualiser(Easing easing) 69 + { 70 + Easing = easing; 71 + 72 + Size = new Vector2(default_size); 73 + Padding = new MarginPadding(25); 74 + 75 + InternalChildren = new Drawable[] 76 + { 77 + new Container 78 + { 79 + RelativeSizeAxes = Axes.Both, 80 + Anchor = Anchor.BottomCentre, 81 + Origin = Anchor.BottomCentre, 82 + Children = new Drawable[] 83 + { 84 + new Box 85 + { 86 + Colour = Color4.DimGray, 87 + RelativeSizeAxes = Axes.Both 88 + }, 89 + dot = new CircularContainer 90 + { 91 + Origin = Anchor.Centre, 92 + RelativePositionAxes = Axes.Both, 93 + Size = new Vector2(10), 94 + Masking = true, 95 + Child = new Box 96 + { 97 + RelativeSizeAxes = Axes.Both, 98 + Colour = Color4.White 99 + } 100 + } 101 + } 102 + }, 103 + new SpriteText 104 + { 105 + Anchor = Anchor.TopCentre, 106 + Origin = Anchor.TopCentre, 107 + Y = 10, 108 + Text = easing.ToString() 109 + }, 110 + }; 111 + } 112 + 113 + protected override void LoadComplete() 114 + { 115 + base.LoadComplete(); 116 + 117 + Visible.BindValueChanged(e => Alpha = e.NewValue ? 1 : 0, true); 118 + 119 + dot.MoveToX(1.0f, movement_duration, Easing) 120 + .Then(pause_duration) 121 + .MoveToX(0.0f, movement_duration, Easing) 122 + .Loop(pause_duration); 123 + dot.MoveToY(1.0f, movement_duration) 124 + .Then(pause_duration) 125 + .MoveToY(0.0f, movement_duration) 126 + .Loop(pause_duration); 127 + } 128 + } 129 + } 130 + }
+1 -3
osu.Framework.Tests/Visual/FrameworkTestScene.cs
··· 1 1 // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 2 // See the LICENCE file in the repository root for full licence text. 3 3 4 - using System.IO; 5 - using System.Reflection; 6 4 using osu.Framework.Allocation; 7 5 using osu.Framework.IO.Stores; 8 6 using osu.Framework.Testing; ··· 18 16 [BackgroundDependencyLoader] 19 17 private void load() 20 18 { 21 - Resources.AddStore(new NamespacedResourceStore<byte[]>(new DllResourceStore(Path.GetFileName(Assembly.GetExecutingAssembly().Location)), "Resources")); 19 + Resources.AddStore(new NamespacedResourceStore<byte[]>(new DllResourceStore(typeof(FrameworkTestScene).Assembly), "Resources")); 22 20 } 23 21 } 24 22 }
+4 -4
osu.Framework.Tests/Visual/Input/TestSceneInputResampler.cs
··· 165 165 Texture = texture; 166 166 Colour = colour; 167 167 168 - const int target_raw = 1024; 168 + const float target_raw = 1024; 169 169 170 - for (int i = 0; i < target_raw; i++) 170 + for (float i = 0; i < target_raw; i++) 171 171 { 172 - float x = (float)(Math.Sin(i / (double)target_raw * (Math.PI * 0.5)) * 200) + 50.5f; 173 - float y = (float)(Math.Cos(i / (double)target_raw * (Math.PI * 0.5)) * 200) + 50.5f; 172 + float x = (MathF.Sin(i / target_raw * (MathF.PI * 0.5f)) * 200) + 50.5f; 173 + float y = (MathF.Cos(i / target_raw * (MathF.PI * 0.5f)) * 200) + 50.5f; 174 174 Vector2 v = keepFraction ? new Vector2(x, y) : new Vector2((int)x, (int)y); 175 175 if (raw) 176 176 AddRawVertex(v);
+6 -2
osu.Framework.Tests/Visual/Sprites/TestSceneTextures.cs
··· 1 1 // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 2 // See the LICENCE file in the repository root for full licence text. 3 3 4 + using System; 4 5 using NUnit.Framework; 5 6 using osu.Framework.Allocation; 6 7 using osu.Framework.Graphics.Containers; ··· 54 55 avatar2.Dispose(); 55 56 }); 56 57 57 - AddUntilStep("gl textures disposed", () => texture.ReferenceCount == 0); 58 + assertAvailability(() => texture, false); 58 59 } 59 60 60 61 /// <summary> ··· 68 69 AddStep("get texture", () => texture = largeStore.Get("https://a.ppy.sh/3")); 69 70 AddStep("dispose texture", () => texture.Dispose()); 70 71 71 - AddAssert("texture is not available", () => !texture.Available); 72 + assertAvailability(() => texture, false); 72 73 } 73 74 74 75 /// <summary> ··· 84 85 85 86 AddAssert("texture is still available", () => texture.Available); 86 87 } 88 + 89 + private void assertAvailability(Func<Texture> textureFunc, bool available) 90 + => AddAssert($"texture available = {available}", () => ((TextureWithRefCount)textureFunc()).IsDisposed == !available); 87 91 88 92 private Avatar addSprite(string url) 89 93 {
-1
osu.Framework.Tests/Visual/UserInterface/TestSceneContextMenu.cs
··· 84 84 assertMenuInCentre(() => box2); 85 85 } 86 86 87 - [Ignore("needs to be fixed")] 88 87 [Test] 89 88 public void TestMenuHiddenWhenTargetHidden() 90 89 {
+1 -1
osu.Framework.Tests/Visual/UserInterface/TestSceneMarkdownContainer.cs
··· 159 159 AddStep("MarkdownFromInternet", () => 160 160 { 161 161 req = new WebRequest("https://raw.githubusercontent.com/ppy/osu-wiki/master/wiki/Skinning/skin.ini/en.md"); 162 - req.Finished += () => Schedule(() => markdownContainer.Text = req.ResponseString); 162 + req.Finished += () => Schedule(() => markdownContainer.Text = req.GetResponseString()); 163 163 164 164 Task.Run(() => req.PerformAsync()); 165 165 });
+80 -150
osu.Framework/Audio/AudioManager.cs
··· 39 39 /// <summary> 40 40 /// The names of all available audio devices. 41 41 /// </summary> 42 + /// <remarks> 43 + /// This property does not contain the names of disabled audio devices. 44 + /// </remarks> 42 45 public IEnumerable<string> AudioDeviceNames => audioDeviceNames; 43 46 44 47 /// <summary> ··· 56 59 /// <see cref="string.Empty"/> denotes the OS default. 57 60 /// </summary> 58 61 public readonly Bindable<string> AudioDevice = new Bindable<string>(); 59 - 60 - private string currentAudioDevice; 61 62 62 63 /// <summary> 63 64 /// Volume of all samples played game-wide. ··· 119 120 return store; 120 121 }); 121 122 122 - scheduler.Add(() => 123 + // check for device validity every 100ms 124 + scheduler.AddDelayed(() => 123 125 { 124 126 try 125 127 { 126 - setAudioDevice(); 128 + if (!IsCurrentDeviceValid()) 129 + setAudioDevice(); 127 130 } 128 131 catch 129 132 { 130 133 } 131 - }); 134 + }, 100, true); 132 135 133 - scheduler.AddDelayed(delegate 136 + // enumerate new list of devices every second 137 + scheduler.AddDelayed(() => 134 138 { 135 - updateAvailableAudioDevices(); 136 - checkAudioDeviceChanged(); 139 + try 140 + { 141 + setAudioDevice(AudioDevice.Value); 142 + } 143 + catch 144 + { 145 + } 137 146 }, 1000, true); 138 147 } 139 148 ··· 149 158 150 159 private void onDeviceChanged(ValueChangedEvent<string> args) 151 160 { 152 - scheduler.Add(() => setAudioDevice(string.IsNullOrEmpty(args.NewValue) ? null : args.NewValue)); 161 + scheduler.Add(() => setAudioDevice(args.NewValue)); 153 162 } 154 163 155 164 /// <summary> 156 - /// Returns a list of the names of recognized audio devices. 157 - /// </summary> 158 - /// <remarks> 159 - /// The No Sound device that is in the list of Audio Devices that are stored internally is not returned. 160 - /// Regarding the .Skip(1) as implementation for removing "No Sound", see http://bass.radio42.com/help/html/e5a666b4-1bdd-d1cb-555e-ce041997d52f.htm. 161 - /// </remarks> 162 - /// <returns>A list of the names of recognized audio devices.</returns> 163 - private IEnumerable<string> getDeviceNames(IEnumerable<DeviceInfo> devices) => devices.Skip(1).Select(d => d.Name); 164 - 165 - /// <summary> 166 165 /// Obtains the <see cref="TrackStore"/> corresponding to a given resource store. 167 166 /// Returns the global <see cref="TrackStore"/> if no resource store is passed. 168 167 /// </summary> ··· 190 189 return sm; 191 190 } 192 191 193 - private IEnumerable<DeviceInfo> enumerateAllDevices() 194 - { 195 - int deviceCount = Bass.DeviceCount; 196 - for (int i = 0; i < deviceCount; i++) 197 - yield return Bass.GetDeviceInfo(i); 198 - } 192 + private DeviceInfo currentAudioDevice; 199 193 200 - private bool setAudioDevice(string preferredDevice = null) 194 + /// <summary> 195 + /// Sets the output audio device by its name. 196 + /// This will automatically fall back to the system default device on failure. 197 + /// </summary> 198 + /// <param name="deviceName">Name of the audio device, or null to use the configured device preference <see cref="AudioDevice"/>.</param> 199 + private bool setAudioDevice(string deviceName = null) 201 200 { 202 201 updateAvailableAudioDevices(); 203 202 204 - string oldDevice = currentAudioDevice; 205 - string newDevice = preferredDevice; 203 + deviceName ??= AudioDevice.Value; 206 204 207 - if (string.IsNullOrEmpty(newDevice)) 208 - newDevice = audioDevices.Find(df => df.IsDefault).Name; 205 + // try using the specified device 206 + if (setAudioDevice(audioDevices.FindIndex(d => d.Name == deviceName))) 207 + return true; 209 208 210 - bool oldDeviceValid = Bass.CurrentDevice >= 0; 209 + // try using the system default device 210 + if (setAudioDevice(audioDevices.FindIndex(d => d.Name != deviceName && d.IsDefault))) 211 + return true; 211 212 212 - if (oldDeviceValid) 213 - { 214 - DeviceInfo oldDeviceInfo = Bass.GetDeviceInfo(Bass.CurrentDevice); 215 - oldDeviceValid &= oldDeviceInfo.IsEnabled && oldDeviceInfo.IsInitialized; 216 - } 217 - 218 - if (newDevice == oldDevice && oldDeviceValid) 213 + // no audio devices can be used, so try using Bass-provided "No sound" device as last resort 214 + if (setAudioDevice(Bass.NoSoundDevice)) 219 215 return true; 220 216 221 - if (string.IsNullOrEmpty(newDevice)) 222 - { 223 - Logger.Log(@"BASS Initialization failed (no audio device present)"); 224 - return false; 225 - } 217 + //we're fucked. even "No sound" device won't initialise. 218 + currentAudioDevice = default; 219 + return false; 220 + } 226 221 227 - int newDeviceIndex = audioDevices.FindIndex(df => df.Name == newDevice); 222 + private bool setAudioDevice(int deviceIndex) 223 + { 224 + var device = audioDevices.ElementAtOrDefault(deviceIndex); 228 225 229 - DeviceInfo newDeviceInfo = new DeviceInfo(); 230 - 231 - try 232 - { 233 - if (newDeviceIndex >= 0) 234 - newDeviceInfo = Bass.GetDeviceInfo(newDeviceIndex); 235 - //we may have previously initialised this device. 236 - } 237 - catch 238 - { 239 - } 226 + // device is invalid 227 + if (!device.IsEnabled) 228 + return false; 240 229 241 - if (oldDeviceValid && (newDeviceInfo.Driver == null || !newDeviceInfo.IsEnabled)) 242 - { 243 - //handles the case we are trying to load a user setting which is currently unavailable, 244 - //and we have already fallen back to a sane default. 230 + // same device 231 + if (device.IsInitialized && device.Name == currentAudioDevice.Name) 245 232 return true; 246 - } 247 233 248 - if (!Bass.Init(newDeviceIndex) && Bass.LastError != Errors.Already) 249 - { 250 - //the new device didn't go as planned. we need another option. 251 - 252 - if (preferredDevice == null) 253 - { 254 - //we're fucked. the default device won't initialise. 255 - currentAudioDevice = null; 256 - return false; 257 - } 258 - 259 - //let's try again using the default device. 260 - return setAudioDevice(); 261 - } 234 + // initialize new device 235 + if (!InitBass(deviceIndex) && Bass.LastError != Errors.Already) 236 + return false; 262 237 263 238 if (Bass.LastError == Errors.Already) 264 239 { 265 240 // We check if the initialization error is that we already initialized the device 266 241 // If it is, it means we can just tell Bass to use the already initialized device without much 267 242 // other fuzz. 268 - Bass.CurrentDevice = newDeviceIndex; 243 + Bass.CurrentDevice = deviceIndex; 269 244 Bass.Free(); 270 - Bass.Init(newDeviceIndex); 245 + InitBass(deviceIndex); 271 246 } 272 247 273 248 Trace.Assert(Bass.LastError == Errors.OK); ··· 275 250 Logger.Log($@"BASS Initialized 276 251 BASS Version: {Bass.Version} 277 252 BASS FX Version: {ManagedBass.Fx.BassFx.Version} 278 - Device: {newDeviceInfo.Name} 279 - Drive: {newDeviceInfo.Driver}"); 253 + Device: {device.Name} 254 + Drive: {device.Driver}"); 280 255 281 256 //we have successfully initialised a new device. 282 - currentAudioDevice = newDevice; 257 + currentAudioDevice = device; 283 258 284 - UpdateDevice(newDeviceIndex); 259 + UpdateDevice(deviceIndex); 285 260 286 261 Bass.PlaybackBufferLength = 100; 287 262 Bass.UpdatePeriod = 5; ··· 289 264 return true; 290 265 } 291 266 267 + /// <summary> 268 + /// This method calls <see cref="Bass.Init(int, int, DeviceInitFlags, IntPtr, IntPtr)"/>. 269 + /// It can be overridden for unit testing. 270 + /// </summary> 271 + protected virtual bool InitBass(int device) => Bass.Init(device); 272 + 292 273 private void updateAvailableAudioDevices() 293 274 { 294 - var currentDeviceList = enumerateAllDevices().Where(d => d.IsEnabled).ToList(); 295 - var currentDeviceNames = getDeviceNames(currentDeviceList).ToList(); 275 + audioDevices = EnumerateAllDevices().ToList(); 296 276 297 - var newDevices = currentDeviceNames.Except(audioDeviceNames).ToList(); 298 - var lostDevices = audioDeviceNames.Except(currentDeviceNames).ToList(); 277 + // Bass should always be providing "No sound" device 278 + Trace.Assert(audioDevices.Count > 0, "Bass did not provide any audio devices."); 279 + 280 + var oldDeviceNames = audioDeviceNames; 281 + var newDeviceNames = audioDeviceNames = audioDevices.Skip(1).Where(d => d.IsEnabled).Select(d => d.Name).ToList(); 282 + 283 + var newDevices = newDeviceNames.Except(oldDeviceNames).ToList(); 284 + var lostDevices = oldDeviceNames.Except(newDeviceNames).ToList(); 299 285 300 286 if (newDevices.Count > 0 || lostDevices.Count > 0) 301 287 { ··· 307 293 OnLostDevice?.Invoke(d); 308 294 }); 309 295 } 296 + } 310 297 311 - audioDevices = currentDeviceList; 312 - audioDeviceNames = currentDeviceNames; 298 + protected virtual IEnumerable<DeviceInfo> EnumerateAllDevices() 299 + { 300 + int deviceCount = Bass.DeviceCount; 301 + for (int i = 0; i < deviceCount; i++) 302 + yield return Bass.GetDeviceInfo(i); 313 303 } 314 304 315 - private void checkAudioDeviceChanged() 305 + protected virtual bool IsCurrentDeviceValid() 316 306 { 317 - try 318 - { 319 - if (AudioDevice.Value == string.Empty) 320 - { 321 - // use default device 322 - var device = Bass.GetDeviceInfo(Bass.CurrentDevice); 307 + var deviceIndex = Bass.CurrentDevice; 308 + var device = deviceIndex == Bass.DefaultDevice ? default : Bass.GetDeviceInfo(deviceIndex); 323 309 324 - if (!device.IsDefault && !setAudioDevice()) 325 - { 326 - if (!device.IsEnabled || !setAudioDevice(device.Name)) 327 - { 328 - foreach (var d in enumerateAllDevices()) 329 - { 330 - if (d.Name == device.Name || !d.IsEnabled) 331 - continue; 332 - 333 - if (setAudioDevice(d.Name)) 334 - break; 335 - } 336 - } 337 - } 338 - } 339 - else 340 - { 341 - // use whatever is the preferred device 342 - var device = Bass.GetDeviceInfo(Bass.CurrentDevice); 343 - 344 - if (device.Name == AudioDevice.Value) 345 - { 346 - if (!device.IsEnabled && !setAudioDevice()) 347 - { 348 - foreach (var d in enumerateAllDevices()) 349 - { 350 - if (d.Name == device.Name || !d.IsEnabled) 351 - continue; 352 - 353 - if (setAudioDevice(d.Name)) 354 - break; 355 - } 356 - } 357 - } 358 - else 359 - { 360 - var preferredDevice = enumerateAllDevices().SingleOrDefault(d => d.Name == AudioDevice.Value); 361 - 362 - if (preferredDevice.Name == AudioDevice.Value && preferredDevice.IsEnabled) 363 - setAudioDevice(preferredDevice.Name); 364 - else if (!device.IsEnabled && !setAudioDevice()) 365 - { 366 - foreach (var d in enumerateAllDevices()) 367 - { 368 - if (d.Name == device.Name || !d.IsEnabled) 369 - continue; 370 - 371 - if (setAudioDevice(d.Name)) 372 - break; 373 - } 374 - } 375 - } 376 - } 377 - } 378 - catch 379 - { 380 - } 310 + return device.IsEnabled && device.IsInitialized; 381 311 } 382 312 383 313 public override string ToString() => $@"{GetType().ReadableName()} ({currentAudioDevice})";
+36 -11
osu.Framework/Audio/Track/TrackBass.cs
··· 137 137 InvalidateState(); 138 138 } 139 139 140 + /// <summary> 141 + /// Returns whether the playback state is considered to be running or not. 142 + /// This will only return true for <see cref="PlaybackState.Playing"/> and <see cref="PlaybackState.Stalled"/>. 143 + /// </summary> 144 + private static bool isRunningState(PlaybackState state) => state == PlaybackState.Playing || state == PlaybackState.Stalled; 145 + 140 146 void IBassAudio.UpdateDevice(int deviceIndex) 141 147 { 142 148 Bass.ChannelSetDevice(activeStream, deviceIndex); 143 149 Trace.Assert(Bass.LastError == Errors.OK); 150 + 151 + // Bass may leave us in an invalid state after the output device changes (this is true for "No sound" device) 152 + // if the observed state was playing before change, we should force things into a good state. 153 + if (isPlayed) 154 + { 155 + // While on windows, changing to "No sound" changes the playback state correctly, 156 + // on macOS it is left in a playing-but-stalled state. Forcefully stopping first fixes this. 157 + stopInternal(); 158 + startInternal(); 159 + } 144 160 } 145 161 146 162 protected override void UpdateState() 147 163 { 148 - isRunning = Bass.ChannelIsActive(activeStream) == PlaybackState.Playing; 164 + var running = isRunningState(Bass.ChannelIsActive(activeStream)); 165 + var bytePosition = Bass.ChannelGetPosition(activeStream); 166 + 167 + // because device validity check isn't done frequently, when switching to "No sound" device, 168 + // there will be a brief time where this track will be stopped, before we resume it manually (see comments in UpdateDevice(int).) 169 + // this makes us appear to be playing, even if we may not be. 170 + isRunning = running || (isPlayed && bytePosition != byteLength); 149 171 150 - Interlocked.Exchange(ref currentTime, Bass.ChannelBytes2Seconds(activeStream, Bass.ChannelGetPosition(activeStream)) * 1000); 172 + Interlocked.Exchange(ref currentTime, Bass.ChannelBytes2Seconds(activeStream, bytePosition) * 1000); 151 173 152 174 var leftChannel = isPlayed ? Bass.ChannelGetLevelLeft(activeStream) / 32768f : -1; 153 175 var rightChannel = isPlayed ? Bass.ChannelGetLevelRight(activeStream) / 32768f : -1; ··· 207 229 208 230 public Task StopAsync() => EnqueueAction(() => 209 231 { 210 - if (Bass.ChannelIsActive(activeStream) == PlaybackState.Playing) 211 - Bass.ChannelPause(activeStream); 212 - 232 + stopInternal(); 213 233 isPlayed = false; 214 234 }); 235 + 236 + private bool stopInternal() => isRunningState(Bass.ChannelIsActive(activeStream)) && Bass.ChannelPause(activeStream); 215 237 216 238 private int direction; 217 239 ··· 230 252 231 253 public Task StartAsync() => EnqueueAction(() => 232 254 { 255 + if (startInternal()) 256 + isPlayed = true; 257 + }); 258 + 259 + private bool startInternal() 260 + { 233 261 // Bass will restart the track if it has reached its end. This behavior isn't desirable so block locally. 234 262 if (Bass.ChannelGetPosition(activeStream) == byteLength) 235 - return; 263 + return false; 236 264 237 - if (Bass.ChannelPlay(activeStream)) 238 - isPlayed = true; 239 - else 240 - isRunning = false; 241 - }); 265 + return Bass.ChannelPlay(activeStream); 266 + } 242 267 243 268 public override bool Seek(double seek) => SeekAsync(seek).Result; 244 269
-3
osu.Framework/Configuration/FrameworkConfigManager.cs
··· 38 38 Set(FrameworkSetting.IgnoredInputHandlers, string.Empty); 39 39 Set(FrameworkSetting.CursorSensitivity, 1.0, 0.1, 6, 0.01); 40 40 Set(FrameworkSetting.Locale, string.Empty); 41 - Set(FrameworkSetting.PerformanceLogging, false); 42 41 } 43 42 44 43 public FrameworkConfigManager(Storage storage, IDictionary<FrameworkSetting, object> defaultOverrides = null) ··· 87 86 IgnoredInputHandlers, 88 87 CursorSensitivity, 89 88 MapAbsoluteInputToWindow, 90 - 91 - PerformanceLogging 92 89 } 93 90 }
+3 -1
osu.Framework/Configuration/FrameworkDebugConfig.cs
··· 17 17 base.InitialiseDefaults(); 18 18 19 19 Set(DebugSetting.BypassFrontToBackPass, false); 20 + Set(DebugSetting.PerformanceLogging, false); 20 21 } 21 22 } 22 23 23 24 public enum DebugSetting 24 25 { 25 - BypassFrontToBackPass 26 + BypassFrontToBackPass, 27 + PerformanceLogging 26 28 } 27 29 }
+1 -1
osu.Framework/Development/DebugUtils.cs
··· 33 33 ); 34 34 35 35 /// <summary> 36 - /// Whether the framework is currently logging performance issues via <see cref="FrameworkSetting.PerformanceLogging"/>. 36 + /// Whether the framework is currently logging performance issues via <see cref="DebugSetting.PerformanceLogging"/>. 37 37 /// This should be used only when a configuration is not available via DI or otherwise (ie. in a static context). 38 38 /// </summary> 39 39 public static bool LogPerformanceIssues { get; internal set; }
+4 -4
osu.Framework/Extensions/MatrixExtensions/MatrixExtensions.cs
··· 28 28 29 29 public static void RotateFromLeft(ref Matrix3 m, float radians) 30 30 { 31 - float cos = (float)Math.Cos(radians); 32 - float sin = (float)Math.Sin(radians); 31 + float cos = MathF.Cos(radians); 32 + float sin = MathF.Sin(radians); 33 33 34 34 Vector3 row0 = m.Row0 * cos + m.Row1 * sin; 35 35 m.Row1 = m.Row1 * cos - m.Row0 * sin; ··· 38 38 39 39 public static void RotateFromRight(ref Matrix3 m, float radians) 40 40 { 41 - float cos = (float)Math.Cos(radians); 42 - float sin = (float)Math.Sin(radians); 41 + float cos = MathF.Cos(radians); 42 + float sin = MathF.Sin(radians); 43 43 44 44 //Vector3 column0 = m.Column0 * cos + m.Column1 * sin; 45 45 float m11 = m.M11 * cos - m.M12 * sin;
+25 -8
osu.Framework/Game.cs
··· 45 45 46 46 public ShaderManager Shaders { get; private set; } 47 47 48 + /// <summary> 49 + /// A store containing fonts accessible game-wide. 50 + /// </summary> 51 + /// <remarks> 52 + /// It is recommended to use <see cref="AddFont"/> when adding new fonts. 53 + /// </remarks> 48 54 public FontStore Fonts { get; private set; } 49 55 50 56 private FontStore localFonts; ··· 105 111 private void load(FrameworkConfigManager config) 106 112 { 107 113 Resources = new ResourceStore<byte[]>(); 108 - Resources.AddStore(new NamespacedResourceStore<byte[]>(new DllResourceStore(@"osu.Framework.dll"), @"Resources")); 114 + Resources.AddStore(new NamespacedResourceStore<byte[]>(new DllResourceStore(typeof(Game).Assembly), @"Resources")); 109 115 110 116 Textures = new TextureStore(Host.CreateTextureLoaderStore(new NamespacedResourceStore<byte[]>(Resources, @"Textures"))); 111 117 Textures.AddStore(Host.CreateTextureLoaderStore(new OnlineStore())); ··· 143 149 // note that currently this means there could be two async font load operations. 144 150 Fonts.AddStore(localFonts = new FontStore(useAtlas: false)); 145 151 146 - localFonts.AddStore(new GlyphStore(Resources, @"Fonts/OpenSans/OpenSans")); 147 - localFonts.AddStore(new GlyphStore(Resources, @"Fonts/OpenSans/OpenSans-Bold")); 148 - localFonts.AddStore(new GlyphStore(Resources, @"Fonts/OpenSans/OpenSans-Italic")); 149 - localFonts.AddStore(new GlyphStore(Resources, @"Fonts/OpenSans/OpenSans-BoldItalic")); 152 + addFont(localFonts, Resources, @"Fonts/OpenSans/OpenSans"); 153 + addFont(localFonts, Resources, @"Fonts/OpenSans/OpenSans-Bold"); 154 + addFont(localFonts, Resources, @"Fonts/OpenSans/OpenSans-Italic"); 155 + addFont(localFonts, Resources, @"Fonts/OpenSans/OpenSans-BoldItalic"); 150 156 151 - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/FontAwesome5/FontAwesome-Solid")); 152 - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/FontAwesome5/FontAwesome-Regular")); 153 - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/FontAwesome5/FontAwesome-Brands")); 157 + addFont(Fonts, Resources, @"Fonts/FontAwesome5/FontAwesome-Solid"); 158 + addFont(Fonts, Resources, @"Fonts/FontAwesome5/FontAwesome-Regular"); 159 + addFont(Fonts, Resources, @"Fonts/FontAwesome5/FontAwesome-Brands"); 154 160 155 161 dependencies.Cache(Fonts); 156 162 157 163 Localisation = new LocalisationManager(config); 158 164 dependencies.Cache(Localisation); 159 165 } 166 + 167 + /// <summary> 168 + /// Add a font to be globally accessible to the game. 169 + /// </summary> 170 + /// <param name="store">The backing store with font resources.</param> 171 + /// <param name="assetName">The base name of the font.</param> 172 + public void AddFont(ResourceStore<byte[]> store, string assetName = null) 173 + => addFont(Fonts, store, assetName); 174 + 175 + private void addFont(FontStore target, ResourceStore<byte[]> store, string assetName = null) 176 + => target.AddStore(new RawCachingGlyphStore(store, assetName, Host.CreateTextureLoaderStore(store))); 160 177 161 178 protected override void LoadComplete() 162 179 {
+7 -1
osu.Framework/Graphics/BufferedDrawNode.cs
··· 36 36 37 37 private Color4 backgroundColour; 38 38 private RectangleF screenSpaceDrawRectangle; 39 + private Vector2 frameBufferScale; 39 40 private Vector2 frameBufferSize; 40 41 41 42 public BufferedDrawNode(IBufferedDrawable source, DrawNode child, BufferedDrawNodeSharedData sharedData) ··· 52 53 backgroundColour = Source.BackgroundColour; 53 54 screenSpaceDrawRectangle = Source.ScreenSpaceDrawQuad.AABBFloat; 54 55 DrawColourInfo = Source.FrameBufferDrawColour ?? new DrawColourInfo(Color4.White, base.DrawColourInfo.Blending); 56 + frameBufferScale = Source.FrameBufferScale; 55 57 56 - frameBufferSize = new Vector2((float)Math.Ceiling(screenSpaceDrawRectangle.Width), (float)Math.Ceiling(screenSpaceDrawRectangle.Height)); 58 + frameBufferSize = new Vector2(MathF.Ceiling(screenSpaceDrawRectangle.Width * frameBufferScale.X), MathF.Ceiling(screenSpaceDrawRectangle.Height * frameBufferScale.Y)); 57 59 DrawRectangle = SharedData.PixelSnapping 58 60 ? new RectangleF(screenSpaceDrawRectangle.X, screenSpaceDrawRectangle.Y, frameBufferSize.X, frameBufferSize.Y) 59 61 : screenSpaceDrawRectangle; ··· 162 164 163 165 // Match viewport to FrameBuffer such that we don't draw unnecessary pixels. 164 166 GLWrapper.PushViewport(new RectangleI(0, 0, (int)frameBufferSize.X, (int)frameBufferSize.Y)); 167 + GLWrapper.PushScissor(new RectangleI(0, 0, (int)frameBufferSize.X, (int)frameBufferSize.Y)); 168 + GLWrapper.PushScissorOffset(screenSpaceMaskingRect.Location); 165 169 166 170 return new ValueInvokeOnDisposal(returnViewport); 167 171 } 168 172 169 173 private void returnViewport() 170 174 { 175 + GLWrapper.PopScissorOffset(); 171 176 GLWrapper.PopViewport(); 177 + GLWrapper.PopScissor(); 172 178 GLWrapper.PopMaskingInfo(); 173 179 } 174 180
+15
osu.Framework/Graphics/Containers/BufferedContainer.cs
··· 176 176 } 177 177 } 178 178 179 + private Vector2 frameBufferScale = Vector2.One; 180 + 181 + public Vector2 FrameBufferScale 182 + { 183 + get => frameBufferScale; 184 + set 185 + { 186 + if (frameBufferScale == value) 187 + return; 188 + 189 + frameBufferScale = value; 190 + ForceRedraw(); 191 + } 192 + } 193 + 179 194 /// <summary> 180 195 /// Whether the rendered framebuffer shall be cached until <see cref="ForceRedraw"/> is called 181 196 /// or the size of the container (i.e. framebuffer) changes.
+1 -1
osu.Framework/Graphics/Containers/BufferedContainer_DrawNode.cs
··· 108 108 blurShader.GetUniform<Vector2>(@"g_TexSize").UpdateValue(ref size); 109 109 110 110 float radians = -MathHelper.DegreesToRadians(blurRotation); 111 - Vector2 blur = new Vector2((float)Math.Cos(radians), (float)Math.Sin(radians)); 111 + Vector2 blur = new Vector2(MathF.Cos(radians), MathF.Sin(radians)); 112 112 blurShader.GetUniform<Vector2>(@"g_BlurDirection").UpdateValue(ref blur); 113 113 114 114 blurShader.Bind();
+2 -2
osu.Framework/Graphics/Containers/CompositeDrawable.cs
··· 1455 1455 Vector2 u = ToParentSpace(new Vector2(cRadius, 0)) - offset; 1456 1456 Vector2 v = ToParentSpace(new Vector2(0, cRadius)) - offset; 1457 1457 Vector2 inflation = new Vector2( 1458 - (float)Math.Sqrt(u.X * u.X + v.X * v.X), 1459 - (float)Math.Sqrt(u.Y * u.Y + v.Y * v.Y) 1458 + MathF.Sqrt(u.X * u.X + v.X * v.X), 1459 + MathF.Sqrt(u.Y * u.Y + v.Y * v.Y) 1460 1460 ); 1461 1461 1462 1462 RectangleF result = ToParentSpace(drawRect).AABBFloat.Inflate(inflation);
+1 -1
osu.Framework/Graphics/Containers/CompositeDrawable_DrawNode.cs
··· 23 23 /// </summary> 24 24 protected class CompositeDrawableDrawNode : DrawNode, ICompositeDrawNode 25 25 { 26 - private static readonly float cos_45 = (float)Math.Cos(Math.PI / 4); 26 + private static readonly float cos_45 = MathF.Cos(MathF.PI / 4); 27 27 28 28 protected new CompositeDrawable Source => (CompositeDrawable)base.Source; 29 29
+1 -1
osu.Framework/Graphics/Cursor/ContextMenuContainer.cs
··· 107 107 108 108 if (menu.State != MenuState.Open || menuTarget == null) return; 109 109 110 - if ((menuTarget as Drawable)?.FindClosestParent<ContextMenuContainer>() != this) 110 + if ((menuTarget as Drawable)?.FindClosestParent<ContextMenuContainer>() != this || (!menuTarget?.IsPresent ?? false)) 111 111 { 112 112 cancelDisplay(); 113 113 return;
+1 -1
osu.Framework/Graphics/Cursor/TooltipContainer.cs
··· 117 117 // Clamp position to tooltip container 118 118 tooltipPos.X = Math.Min(tooltipPos.X, DrawWidth - CurrentTooltip.DrawSize.X - 5); 119 119 float dX = Math.Max(0, tooltipPos.X - cursorCentre.X); 120 - float dY = (float)Math.Sqrt(boundingRadius * boundingRadius - dX * dX); 120 + float dY = MathF.Sqrt(boundingRadius * boundingRadius - dX * dX); 121 121 122 122 if (tooltipPos.Y > DrawHeight - CurrentTooltip.DrawSize.Y - 5) 123 123 tooltipPos.Y = cursorCentre.Y - dY - CurrentTooltip.DrawSize.Y;
+9
osu.Framework/Graphics/IBufferedDrawable.cs
··· 2 2 // See the LICENCE file in the repository root for full licence text. 3 3 4 4 using osu.Framework.Graphics.OpenGL.Buffers; 5 + using osuTK; 5 6 using osuTK.Graphics; 6 7 7 8 namespace osu.Framework.Graphics ··· 26 27 /// A null value implies the <see cref="FrameBuffer"/>s should be drawn as they are. 27 28 /// </summary> 28 29 DrawColourInfo? FrameBufferDrawColour { get; } 30 + 31 + /// <summary> 32 + /// The scale of the <see cref="FrameBuffer"/>s drawn relative to the size of this <see cref="IBufferedDrawable"/>. 33 + /// </summary> 34 + /// <remarks> 35 + /// The contents of the <see cref="FrameBuffer"/>s are populated at this scale, however the scale of <see cref="Drawable"/>s remains unaffected. 36 + /// </remarks> 37 + Vector2 FrameBufferScale { get; } 29 38 } 30 39 }
+2
osu.Framework/Graphics/Lines/Path.cs
··· 271 271 272 272 public DrawColourInfo? FrameBufferDrawColour => base.DrawColourInfo; 273 273 274 + public Vector2 FrameBufferScale { get; } = Vector2.One; 275 + 274 276 // The path should not receive the true colour to avoid colour doubling when the frame-buffer is rendered to the back-buffer. 275 277 public override DrawColourInfo DrawColourInfo => new DrawColourInfo(Color4.White, base.DrawColourInfo.Blending); 276 278
+5 -5
osu.Framework/Graphics/Lines/Path_DrawNode.cs
··· 55 55 pathShader = Source.pathShader; 56 56 } 57 57 58 - private Vector2 pointOnCircle(float angle) => new Vector2((float)Math.Sin(angle), -(float)Math.Cos(angle)); 58 + private Vector2 pointOnCircle(float angle) => new Vector2(MathF.Sin(angle), -MathF.Cos(angle)); 59 59 60 60 private Vector2 relativePosition(Vector2 localPos) => Vector2.Divide(localPos, drawSize); 61 61 ··· 65 65 66 66 private void addLineCap(Vector2 origin, float theta, float thetaDiff, RectangleF texRect) 67 67 { 68 - const float step = MathHelper.Pi / MAX_RES; 68 + const float step = MathF.PI / MAX_RES; 69 69 70 70 float dir = Math.Sign(thetaDiff); 71 71 thetaDiff = dir * thetaDiff; ··· 73 73 int amountPoints = (int)Math.Ceiling(thetaDiff / step); 74 74 75 75 if (dir < 0) 76 - theta += MathHelper.Pi; 76 + theta += MathF.PI; 77 77 78 78 Vector2 current = origin + pointOnCircle(theta) * radius; 79 79 Color4 currentColour = colourAt(current); ··· 183 183 184 184 // Offset by 0.5 pixels inwards to ensure we never sample texels outside the bounds 185 185 RectangleF texRect = texture.GetTextureRect(new RectangleF(0.5f, 0.5f, texture.Width - 1, texture.Height - 1)); 186 - addLineCap(line.StartPoint, theta + MathHelper.Pi, MathHelper.Pi, texRect); 186 + addLineCap(line.StartPoint, theta + MathF.PI, MathF.PI, texRect); 187 187 188 188 for (int i = 1; i < segments.Count; ++i) 189 189 { ··· 195 195 theta = nextTheta; 196 196 } 197 197 198 - addLineCap(line.EndPoint, theta, MathHelper.Pi, texRect); 198 + addLineCap(line.EndPoint, theta, MathF.PI, texRect); 199 199 200 200 foreach (Line segment in segments) 201 201 addLineQuads(segment, texRect);
+1 -1
osu.Framework/Graphics/Lines/SmoothPath.cs
··· 61 61 raw[i, 0] = new Rgba32(colour.R, colour.G, colour.B, colour.A * Math.Min(progress / aa_portion, 1)); 62 62 } 63 63 64 - var texture = new TextureWithRefCount(textureWidth, 1, true); 64 + var texture = new DisposableTexture(textureWidth, 1, true); 65 65 texture.SetData(new TextureUpload(raw)); 66 66 Texture = texture; 67 67
+107 -56
osu.Framework/Graphics/OpenGL/GLWrapper.cs
··· 14 14 using osuTK.Graphics; 15 15 using osuTK.Graphics.ES30; 16 16 using osu.Framework.Statistics; 17 - using osu.Framework.MathUtils; 18 17 using osu.Framework.Graphics.Primitives; 19 18 using osu.Framework.Graphics.Colour; 20 19 using osu.Framework.Graphics.OpenGL.Buffers; ··· 34 33 public static MaskingInfo CurrentMaskingInfo { get; private set; } 35 34 public static RectangleI Viewport { get; private set; } 36 35 public static RectangleF Ortho { get; private set; } 37 - public static Matrix4 ProjectionMatrix { get; private set; } 36 + public static RectangleI Scissor { get; private set; } 37 + public static Vector2I ScissorOffset { get; private set; } 38 + public static Matrix4 ProjectionMatrix { get; set; } 38 39 public static DepthInfo CurrentDepthInfo { get; private set; } 39 40 40 41 public static float BackbufferDrawDepth { get; private set; } ··· 130 131 frame_buffer_stack.Clear(); 131 132 depth_stack.Clear(); 132 133 scissor_state_stack.Clear(); 134 + scissor_offset_stack.Clear(); 133 135 134 136 BindFrameBuffer(DefaultFrameBuffer); 135 137 136 - scissor_rect_stack.Push(new RectangleI(0, 0, (int)size.X, (int)size.Y)); 137 - 138 + Scissor = RectangleI.Empty; 139 + ScissorOffset = Vector2I.Zero; 138 140 Viewport = RectangleI.Empty; 139 141 Ortho = RectangleF.Empty; 140 142 141 143 PushScissorState(true); 142 144 PushViewport(new RectangleI(0, 0, (int)size.X, (int)size.Y)); 145 + PushScissor(new RectangleI(0, 0, (int)size.X, (int)size.Y)); 146 + PushScissorOffset(Vector2I.Zero); 143 147 PushMaskingInfo(new MaskingInfo 144 148 { 145 149 ScreenSpaceAABB = new RectangleI(0, 0, (int)size.X, (int)size.Y), ··· 380 384 Viewport = actualRect; 381 385 382 386 GL.Viewport(Viewport.Left, Viewport.Top, Viewport.Width, Viewport.Height); 383 - 384 - UpdateScissorToCurrentViewportAndOrtho(); 385 387 } 386 388 387 389 /// <summary> ··· 402 404 Viewport = actualRect; 403 405 404 406 GL.Viewport(Viewport.Left, Viewport.Top, Viewport.Width, Viewport.Height); 407 + } 405 408 406 - UpdateScissorToCurrentViewportAndOrtho(); 409 + /// <summary> 410 + /// Applies a new scissor rectangle. 411 + /// </summary> 412 + /// <param name="scissor">The scissor rectangle.</param> 413 + public static void PushScissor(RectangleI scissor) 414 + { 415 + FlushCurrentBatch(); 416 + 417 + scissor_rect_stack.Push(scissor); 418 + if (Scissor == scissor) 419 + return; 420 + 421 + Scissor = scissor; 422 + setScissor(scissor); 423 + } 424 + 425 + /// <summary> 426 + /// Applies the last scissor rectangle. 427 + /// </summary> 428 + public static void PopScissor() 429 + { 430 + Trace.Assert(scissor_rect_stack.Count > 1); 431 + 432 + FlushCurrentBatch(); 433 + 434 + scissor_rect_stack.Pop(); 435 + RectangleI scissor = scissor_rect_stack.Peek(); 436 + 437 + if (Scissor == scissor) 438 + return; 439 + 440 + Scissor = scissor; 441 + setScissor(scissor); 442 + } 443 + 444 + private static void setScissor(RectangleI scissor) 445 + { 446 + if (scissor.Width < 0) 447 + { 448 + scissor.X += scissor.Width; 449 + scissor.Width = -scissor.Width; 450 + } 451 + 452 + if (scissor.Height < 0) 453 + { 454 + scissor.Y += scissor.Height; 455 + scissor.Height = -scissor.Height; 456 + } 457 + 458 + GL.Scissor(scissor.X, Viewport.Height - scissor.Bottom, scissor.Width, scissor.Height); 459 + } 460 + 461 + private static readonly Stack<Vector2I> scissor_offset_stack = new Stack<Vector2I>(); 462 + 463 + /// <summary> 464 + /// Applies an offset to the scissor rectangle. 465 + /// </summary> 466 + /// <param name="offset">The offset.</param> 467 + public static void PushScissorOffset(Vector2I offset) 468 + { 469 + FlushCurrentBatch(); 470 + 471 + scissor_offset_stack.Push(offset); 472 + if (ScissorOffset == offset) 473 + return; 474 + 475 + ScissorOffset = offset; 476 + } 477 + 478 + /// <summary> 479 + /// Applies the last scissor rectangle offset. 480 + /// </summary> 481 + public static void PopScissorOffset() 482 + { 483 + Trace.Assert(scissor_offset_stack.Count > 1); 484 + 485 + FlushCurrentBatch(); 486 + 487 + scissor_offset_stack.Pop(); 488 + Vector2I offset = scissor_offset_stack.Peek(); 489 + 490 + if (ScissorOffset == offset) 491 + return; 492 + 493 + ScissorOffset = offset; 407 494 } 408 495 409 496 private static readonly Stack<RectangleF> ortho_stack = new Stack<RectangleF>(); ··· 424 511 425 512 ProjectionMatrix = Matrix4.CreateOrthographicOffCenter(Ortho.Left, Ortho.Right, Ortho.Bottom, Ortho.Top, -1, 1); 426 513 GlobalPropertyManager.Set(GlobalProperty.ProjMatrix, ProjectionMatrix); 427 - 428 - UpdateScissorToCurrentViewportAndOrtho(); 429 514 } 430 515 431 516 /// <summary> ··· 447 532 448 533 ProjectionMatrix = Matrix4.CreateOrthographicOffCenter(Ortho.Left, Ortho.Right, Ortho.Bottom, Ortho.Top, -1, 1); 449 534 GlobalPropertyManager.Set(GlobalProperty.ProjMatrix, ProjectionMatrix); 450 - 451 - UpdateScissorToCurrentViewportAndOrtho(); 452 535 } 453 536 454 537 private static readonly Stack<MaskingInfo> masking_stack = new Stack<MaskingInfo>(); ··· 456 539 private static readonly Stack<int> frame_buffer_stack = new Stack<int>(); 457 540 private static readonly Stack<DepthInfo> depth_stack = new Stack<DepthInfo>(); 458 541 459 - public static void UpdateScissorToCurrentViewportAndOrtho() 460 - { 461 - RectangleF viewportRect = Viewport; 462 - Vector2 offset = viewportRect.TopLeft - Ortho.TopLeft; 463 - 464 - RectangleI currentScissorRect = scissor_rect_stack.Peek(); 465 - 466 - RectangleI scissorRect = new RectangleI( 467 - currentScissorRect.X + (int)Math.Floor(offset.X), 468 - Viewport.Height - currentScissorRect.Bottom - (int)Math.Ceiling(offset.Y), 469 - currentScissorRect.Width, 470 - currentScissorRect.Height); 471 - 472 - if (!Precision.AlmostEquals(offset, Vector2.Zero)) 473 - { 474 - ++scissorRect.Width; 475 - ++scissorRect.Height; 476 - } 477 - 478 - GL.Scissor(scissorRect.X, scissorRect.Y, scissorRect.Width, scissorRect.Height); 479 - } 480 - 481 542 private static void setMaskingInfo(MaskingInfo maskingInfo, bool isPushing, bool overwritePreviousScissor) 482 543 { 483 544 FlushCurrentBatch(); ··· 513 574 if (maskingInfo.Hollow) 514 575 GlobalPropertyManager.Set(GlobalProperty.InnerCornerRadius, maskingInfo.HollowCornerRadius); 515 576 516 - RectangleI actualRect = maskingInfo.ScreenSpaceAABB; 517 - actualRect.X += Viewport.X; 518 - actualRect.Y += Viewport.Y; 519 - 520 - // Ensure the rectangle only has positive width and height. (Required by OGL) 521 - if (actualRect.Width < 0) 577 + if (isPushing) 522 578 { 523 - actualRect.X += actualRect.Width; 524 - actualRect.Width = -actualRect.Width; 525 - } 579 + // When drawing to a viewport that doesn't match the projection size (e.g. via framebuffers), the resultant image will be scaled 580 + Vector2 viewportScale = Vector2.Divide(Viewport.Size, Ortho.Size); 526 581 527 - if (actualRect.Height < 0) 528 - { 529 - actualRect.Y += actualRect.Height; 530 - actualRect.Height = -actualRect.Height; 531 - } 582 + Vector2 location = (maskingInfo.ScreenSpaceAABB.Location - ScissorOffset) * viewportScale; 583 + Vector2 size = maskingInfo.ScreenSpaceAABB.Size * viewportScale; 532 584 533 - if (isPushing) 534 - { 535 - scissor_rect_stack.Push(overwritePreviousScissor ? actualRect : RectangleI.Intersect(scissor_rect_stack.Peek(), actualRect)); 585 + RectangleI actualRect = new RectangleI( 586 + (int)Math.Floor(location.X), 587 + (int)Math.Floor(location.Y), 588 + (int)Math.Ceiling(size.X), 589 + (int)Math.Ceiling(size.Y)); 590 + 591 + PushScissor(overwritePreviousScissor ? actualRect : RectangleI.Intersect(scissor_rect_stack.Peek(), actualRect)); 536 592 } 537 593 else 538 - { 539 - Trace.Assert(scissor_rect_stack.Count > 1); 540 - scissor_rect_stack.Pop(); 541 - } 542 - 543 - UpdateScissorToCurrentViewportAndOrtho(); 594 + PopScissor(); 544 595 } 545 596 546 597 internal static void FlushCurrentBatch()
-11
osu.Framework/Graphics/OpenGL/Textures/TextureGL.cs
··· 2 2 // See the LICENCE file in the repository root for full licence text. 3 3 4 4 using System; 5 - using System.Threading; 6 5 using osu.Framework.Graphics.Batches; 7 6 using osu.Framework.Graphics.Primitives; 8 7 using osuTK.Graphics.ES30; ··· 23 22 ~TextureGL() 24 23 { 25 24 Dispose(false); 26 - } 27 - 28 - internal int ReferenceCount; 29 - 30 - public void Reference() => Interlocked.Increment(ref ReferenceCount); 31 - 32 - public void Dereference() 33 - { 34 - if (Interlocked.Decrement(ref ReferenceCount) == 0) 35 - Dispose(); 36 25 } 37 26 38 27 /// <summary>
+1 -1
osu.Framework/Graphics/Primitives/Line.cs
··· 31 31 /// <summary> 32 32 /// The direction of the second point from the first. 33 33 /// </summary> 34 - public float Theta => (float)Math.Atan2(EndPoint.Y - StartPoint.Y, EndPoint.X - StartPoint.X); 34 + public float Theta => MathF.Atan2(EndPoint.Y - StartPoint.Y, EndPoint.X - StartPoint.X); 35 35 36 36 /// <summary> 37 37 /// The direction of this <see cref="Line"/>.
+1 -1
osu.Framework/Graphics/Primitives/RectangleF.cs
··· 343 343 float distX = Math.Max(0.0f, Math.Max(localSpacePos.X - Right, Left - localSpacePos.X)); 344 344 float distY = Math.Max(0.0f, Math.Max(localSpacePos.Y - Bottom, Top - localSpacePos.Y)); 345 345 346 - return (float)Math.Pow(distX, exponent) + (float)Math.Pow(distY, exponent); 346 + return MathF.Pow(distX, exponent) + MathF.Pow(distY, exponent); 347 347 } 348 348 349 349 // This could be optimized further in the future, but made for a simple implementation right now.
+1 -1
osu.Framework/Graphics/Sprites/SpriteIcon.cs
··· 103 103 //squared result for quadratic fall-off seems to give the best result. 104 104 var avgColour = (Color4)DrawColourInfo.Colour.AverageColour; 105 105 106 - spriteShadow.Alpha = (float)Math.Pow(Math.Max(Math.Max(avgColour.R, avgColour.G), avgColour.B), 2); 106 + spriteShadow.Alpha = MathF.Pow(Math.Max(Math.Max(avgColour.R, avgColour.G), avgColour.B), 2); 107 107 108 108 layout.Validate(); 109 109 }
+1 -1
osu.Framework/Graphics/Sprites/SpriteText_DrawNode.cs
··· 51 51 Shader.Bind(); 52 52 53 53 var avgColour = (Color4)DrawColourInfo.Colour.AverageColour; 54 - float shadowAlpha = (float)Math.Pow(Math.Max(Math.Max(avgColour.R, avgColour.G), avgColour.B), 2); 54 + float shadowAlpha = MathF.Pow(Math.Max(Math.Max(avgColour.R, avgColour.G), avgColour.B), 2); 55 55 56 56 //adjust shadow alpha based on highest component intensity to avoid muddy display of darker text. 57 57 //squared result for quadratic fall-off seems to give the best result.
+30
osu.Framework/Graphics/Textures/DisposableTexture.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + using osu.Framework.Graphics.OpenGL.Textures; 5 + using osuTK.Graphics.ES30; 6 + 7 + namespace osu.Framework.Graphics.Textures 8 + { 9 + /// <summary> 10 + /// A texture which can cleans up any resources held by the underlying <see cref="TextureGL"/> on <see cref="Dispose"/>. 11 + /// </summary> 12 + public class DisposableTexture : Texture 13 + { 14 + public DisposableTexture(TextureGL textureGl) 15 + : base(textureGl) 16 + { 17 + } 18 + 19 + public DisposableTexture(int width, int height, bool manualMipmaps = false, All filteringMode = All.Linear) 20 + : base(width, height, manualMipmaps, filteringMode) 21 + { 22 + } 23 + 24 + protected override void Dispose(bool isDisposing) 25 + { 26 + base.Dispose(isDisposing); 27 + TextureGL.Dispose(); 28 + } 29 + } 30 + }
+24 -4
osu.Framework/Graphics/Textures/LargeTextureStore.cs
··· 1 1 // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 2 // See the LICENCE file in the repository root for full licence text. 3 3 4 + using System.Collections.Generic; 5 + using System.Diagnostics; 6 + using System.Threading; 4 7 using osu.Framework.IO.Stores; 5 8 using osuTK.Graphics.ES30; 6 9 ··· 11 14 /// </summary> 12 15 public class LargeTextureStore : TextureStore 13 16 { 17 + private readonly object referenceCountLock = new object(); 18 + private readonly Dictionary<string, TextureWithRefCount.ReferenceCount> referenceCounts = new Dictionary<string, TextureWithRefCount.ReferenceCount>(); 19 + 14 20 public LargeTextureStore(IResourceStore<TextureUpload> store = null) 15 21 : base(store, false, All.Linear, true) 16 22 { ··· 25 31 /// <returns>The texture.</returns> 26 32 public override Texture Get(string name) 27 33 { 28 - var baseTex = base.Get(name); 34 + lock (referenceCountLock) 35 + { 36 + var tex = base.Get(name); 29 37 30 - if (baseTex?.TextureGL == null) return null; 38 + if (tex?.TextureGL == null) 39 + return null; 31 40 32 - // encapsulate texture for ref counting 33 - return new TextureWithRefCount(baseTex.TextureGL) { ScaleAdjust = ScaleAdjust }; 41 + if (!referenceCounts.TryGetValue(name, out TextureWithRefCount.ReferenceCount count)) 42 + referenceCounts[name] = count = new TextureWithRefCount.ReferenceCount(referenceCountLock, () => onAllReferencesLost(name)); 43 + 44 + return new TextureWithRefCount(tex.TextureGL, count); 45 + } 46 + } 47 + 48 + private void onAllReferencesLost(string name) 49 + { 50 + Debug.Assert(Monitor.IsEntered(referenceCountLock)); 51 + 52 + referenceCounts.Remove(name); 53 + Purge(name); 34 54 } 35 55 } 36 56 }
+2 -2
osu.Framework/Graphics/Textures/TextureLoaderStore.cs
··· 43 43 44 44 public Stream GetStream(string name) => store.GetStream(name); 45 45 46 - protected virtual Image<TPixel> ImageFromStream<TPixel>(Stream stream) where TPixel : struct, IPixel<TPixel> 47 - => Image.Load<TPixel>(stream); 46 + protected virtual Image<TPixel> ImageFromStream<TPixel>(Stream stream) where TPixel : unmanaged, IPixel<TPixel> 47 + => TextureUpload.LoadFromStream<TPixel>(stream); 48 48 49 49 public IEnumerable<string> GetAvailableResources() => store.GetAvailableResources(); 50 50
+15 -2
osu.Framework/Graphics/Textures/TextureStore.cs
··· 67 67 glTexture = new TextureGLSingle(upload.Width, upload.Height, manualMipmaps, filteringMode); 68 68 69 69 Texture tex = new Texture(glTexture) { ScaleAdjust = ScaleAdjust }; 70 - 71 70 tex.SetData(upload); 72 71 73 72 return tex; ··· 89 88 lock (textureCache) 90 89 { 91 90 // refresh the texture if no longer available (may have been previously disposed). 92 - if (!textureCache.TryGetValue(name, out var tex) || tex?.Available == false) 91 + if (!textureCache.TryGetValue(name, out var tex)) 93 92 { 94 93 try 95 94 { ··· 102 101 } 103 102 104 103 return tex; 104 + } 105 + } 106 + 107 + /// <summary> 108 + /// Disposes and removes a texture with the specified name from the texture cache. 109 + /// </summary> 110 + /// <param name="name">The name of the texture to purge from the cache.</param> 111 + protected void Purge(string name) 112 + { 113 + lock (textureCache) 114 + { 115 + if (textureCache.TryGetValue(name, out var tex)) 116 + tex.Dispose(); 117 + textureCache.Remove(name); 105 118 } 106 119 } 107 120 }
+27 -1
osu.Framework/Graphics/Textures/TextureUpload.cs
··· 3 3 4 4 using System; 5 5 using System.IO; 6 + using System.Runtime.InteropServices; 6 7 using osu.Framework.Graphics.OpenGL; 7 8 using osu.Framework.Graphics.OpenGL.Buffers; 8 9 using osu.Framework.Graphics.Primitives; 10 + using osu.Framework.Logging; 9 11 using osuTK.Graphics.ES30; 10 12 using SixLabors.ImageSharp; 11 13 using SixLabors.ImageSharp.Advanced; 12 14 using SixLabors.ImageSharp.PixelFormats; 15 + using StbiSharp; 13 16 14 17 namespace osu.Framework.Graphics.Textures 15 18 { ··· 59 62 60 63 /// <summary> 61 64 /// Create an upload from an arbitrary image stream. 65 + /// Note that this bypasses per-platform image loading optimisations. 66 + /// Use <see cref="TextureLoaderStore"/> as provided from GameHost where possible. 62 67 /// </summary> 63 68 /// <param name="stream">The image content.</param> 64 69 public TextureUpload(Stream stream) 65 - : this(Image.Load<Rgba32>(stream)) 70 + : this(LoadFromStream<Rgba32>(stream)) 71 + { 72 + } 73 + 74 + internal static Image<TPixel> LoadFromStream<TPixel>(Stream stream) where TPixel : unmanaged, IPixel<TPixel> 66 75 { 76 + long initialPos = stream.Position; 77 + 78 + try 79 + { 80 + using (var m = new MemoryStream()) 81 + { 82 + stream.CopyTo(m); 83 + using (var stbiImage = Stbi.LoadFromMemory(m, 4)) 84 + return Image.LoadPixelData(MemoryMarshal.Cast<byte, TPixel>(stbiImage.Data), stbiImage.Width, stbiImage.Height); 85 + } 86 + } 87 + catch (Exception e) 88 + { 89 + Logger.Error(e, "Texture could not be loaded via STB; falling back to ImageSharp."); 90 + stream.Position = initialPos; 91 + return Image.Load<TPixel>(stream); 92 + } 67 93 } 68 94 69 95 /// <summary>
+56 -23
osu.Framework/Graphics/Textures/TextureWithRefCount.cs
··· 2 2 // See the LICENCE file in the repository root for full licence text. 3 3 4 4 using System; 5 - using osu.Framework.Graphics.OpenGL; 5 + using System.Threading; 6 6 using osu.Framework.Graphics.OpenGL.Textures; 7 - using osuTK.Graphics.ES30; 8 7 9 8 namespace osu.Framework.Graphics.Textures 10 9 { 11 10 /// <summary> 12 - /// A texture which updates the reference count of the underlying <see cref="TextureGL"/> on ctor and disposal. 11 + /// A texture which shares a common reference count with all other textures using the same <see cref="TextureGL"/>. 13 12 /// </summary> 14 - public class TextureWithRefCount : Texture 13 + internal class TextureWithRefCount : Texture 15 14 { 16 - public TextureWithRefCount(TextureGL textureGl) 15 + private readonly ReferenceCount count; 16 + 17 + public TextureWithRefCount(TextureGL textureGl, ReferenceCount count) 17 18 : base(textureGl) 18 19 { 19 - textureGl.Reference(); 20 - } 20 + this.count = count; 21 21 22 - public TextureWithRefCount(int width, int height, bool manualMipmaps = false, All filteringMode = All.Linear) 23 - : this(new TextureGLSingle(width, height, manualMipmaps, filteringMode)) 24 - { 22 + count.Increment(); 25 23 } 26 - 27 - internal int ReferenceCount => base.TextureGL.ReferenceCount; 28 24 29 25 public sealed override TextureGL TextureGL 30 26 { 31 27 get 32 28 { 33 - var tex = base.TextureGL; 34 - if (tex.ReferenceCount <= 0) 29 + if (!Available) 35 30 throw new InvalidOperationException($"Attempting to access a {nameof(TextureWithRefCount)}'s underlying texture after all references are lost."); 36 31 37 - return tex; 32 + return base.TextureGL; 38 33 } 39 34 } 40 35 41 - // The base property references TextureGL, but doing so may throw an exception (above) 36 + // The base property invokes the overridden TextureGL property, which will throw an exception if not available 37 + // So this property is redirected to reference the intended member 42 38 public sealed override bool Available => base.TextureGL.Available; 43 - 44 - #region Disposal 45 39 46 40 ~TextureWithRefCount() 47 41 { ··· 49 43 Dispose(false); 50 44 } 51 45 52 - private bool isDisposed; 46 + public bool IsDisposed { get; private set; } 53 47 54 48 protected override void Dispose(bool isDisposing) 55 49 { 56 50 base.Dispose(isDisposing); 57 51 58 - if (isDisposed) 52 + if (IsDisposed) 59 53 return; 60 54 61 - isDisposed = true; 55 + IsDisposed = true; 62 56 63 - GLWrapper.ScheduleDisposal(() => base.TextureGL.Dereference()); 57 + count.Decrement(); 64 58 } 65 59 66 - #endregion 60 + public class ReferenceCount 61 + { 62 + private readonly object lockObject; 63 + private readonly Action onAllReferencesLost; 64 + 65 + private int referenceCount; 66 + 67 + /// <summary> 68 + /// Creates a new <see cref="ReferenceCount"/>. 69 + /// </summary> 70 + /// <param name="lockObject">The <see cref="object"/> which locks will be taken out on.</param> 71 + /// <param name="onAllReferencesLost">A delegate to invoke after all references have been lost.</param> 72 + public ReferenceCount(object lockObject, Action onAllReferencesLost) 73 + { 74 + this.lockObject = lockObject; 75 + this.onAllReferencesLost = onAllReferencesLost; 76 + } 77 + 78 + /// <summary> 79 + /// Increments the reference count. 80 + /// </summary> 81 + public void Increment() 82 + { 83 + lock (lockObject) 84 + Interlocked.Increment(ref referenceCount); 85 + } 86 + 87 + /// <summary> 88 + /// Decrements the reference count, invoking <see cref="onAllReferencesLost"/> if there are no remaining references. 89 + /// The delegate is invoked while a lock on the provided <see cref="lockObject"/> is held. 90 + /// </summary> 91 + public void Decrement() 92 + { 93 + lock (lockObject) 94 + { 95 + if (Interlocked.Decrement(ref referenceCount) == 0) 96 + onAllReferencesLost?.Invoke(); 97 + } 98 + } 99 + } 67 100 } 68 101 }
+7 -5
osu.Framework/Graphics/UserInterface/CircularProgressDrawNode.cs
··· 17 17 { 18 18 private const float arc_tolerance = 0.1f; 19 19 20 + private const float two_pi = MathF.PI * 2; 21 + 20 22 protected new CircularProgress Source => (CircularProgress)base.Source; 21 23 22 24 private LinearBatch<TexturedVertex2D> halfCircleBatch; ··· 38 40 39 41 texture = Source.Texture; 40 42 drawSize = Source.DrawSize; 41 - angle = (float)Source.Current.Value * MathHelper.TwoPi; 43 + angle = (float)Source.Current.Value * two_pi; 42 44 innerRadius = Source.InnerRadius; 43 45 } 44 46 45 - private Vector2 pointOnCircle(float angle) => new Vector2((float)Math.Sin(angle), -(float)Math.Cos(angle)); 46 - private float angleToUnitInterval(float angle) => angle / MathHelper.TwoPi + (angle >= 0 ? 0 : 1); 47 + private Vector2 pointOnCircle(float angle) => new Vector2(MathF.Sin(angle), -MathF.Cos(angle)); 48 + private float angleToUnitInterval(float angle) => angle / two_pi + (angle >= 0 ? 0 : 1); 47 49 48 50 // Gets colour at the localPos position in the unit square of our Colour gradient box. 49 51 private Color4 colourAt(Vector2 localPos) => DrawColourInfo.Colour.HasSingleColour ··· 109 111 110 112 // Clamps the angle so we don't overshoot. 111 113 // dir is used so negative angles result in negative angularOffset. 112 - float angularOffset = Math.Min(fract * MathHelper.TwoPi, dir * angle); 113 - float normalisedOffset = angularOffset / MathHelper.TwoPi; 114 + float angularOffset = Math.Min(fract * two_pi, dir * angle); 115 + float normalisedOffset = angularOffset / two_pi; 114 116 115 117 if (dir < 0) 116 118 normalisedOffset += 1.0f;
+1 -1
osu.Framework/Graphics/UserInterface/SliderBar.cs
··· 172 172 return false; 173 173 174 174 var step = KeyboardStep != 0 ? KeyboardStep : (Convert.ToSingle(currentNumberInstantaneous.MaxValue) - Convert.ToSingle(currentNumberInstantaneous.MinValue)) / 20; 175 - if (currentNumberInstantaneous.IsInteger) step = (float)Math.Ceiling(step); 175 + if (currentNumberInstantaneous.IsInteger) step = MathF.Ceiling(step); 176 176 177 177 switch (e.Key) 178 178 {
+1 -1
osu.Framework/Graphics/Vector2Extensions.cs
··· 50 50 /// <param name="result">The distance</param> 51 51 public static void Distance(ref Vector2 vec1, ref Vector2 vec2, out float result) 52 52 { 53 - result = (float)Math.Sqrt((vec2.X - vec1.X) * (vec2.X - vec1.X) + (vec2.Y - vec1.Y) * (vec2.Y - vec1.Y)); 53 + result = MathF.Sqrt((vec2.X - vec1.X) * (vec2.X - vec1.X) + (vec2.Y - vec1.Y) * (vec2.Y - vec1.Y)); 54 54 } 55 55 56 56 /// <summary>
+1 -1
osu.Framework/IO/Network/JsonWebRequest.cs
··· 18 18 { 19 19 } 20 20 21 - protected override void ProcessResponse() => ResponseObject = JsonConvert.DeserializeObject<T>(ResponseString); 21 + protected override void ProcessResponse() => ResponseObject = JsonConvert.DeserializeObject<T>(GetResponseString()); 22 22 23 23 public T ResponseObject { get; private set; } 24 24 }
+33 -25
osu.Framework/IO/Network/WebRequest.cs
··· 160 160 161 161 public Stream ResponseStream; 162 162 163 - public string ResponseString 163 + [Obsolete("Use GetResponseString method instead")] // can be removed 20200521 164 + public string ResponseString => GetResponseString(); 165 + 166 + /// <summary> 167 + /// Retrieve the full response body as a UTF8 encoded string. 168 + /// </summary> 169 + /// <returns>The response body.</returns> 170 + public string GetResponseString() 164 171 { 165 - get 172 + try 173 + { 174 + ResponseStream.Seek(0, SeekOrigin.Begin); 175 + StreamReader r = new StreamReader(ResponseStream, Encoding.UTF8); 176 + return r.ReadToEnd(); 177 + } 178 + catch 166 179 { 167 - try 168 - { 169 - ResponseStream.Seek(0, SeekOrigin.Begin); 170 - StreamReader r = new StreamReader(ResponseStream, Encoding.UTF8); 171 - return r.ReadToEnd(); 172 - } 173 - catch 174 - { 175 - return null; 176 - } 180 + return null; 177 181 } 178 182 } 179 183 180 - public byte[] ResponseData 184 + [Obsolete("Use GetResponseData method instead")] // can be removed 20200521 185 + public byte[] ResponseData => GetResponseData(); 186 + 187 + /// <summary> 188 + /// Retrieve the full response body as an array of bytes. 189 + /// </summary> 190 + /// <returns>The response body.</returns> 191 + public byte[] GetResponseData() 181 192 { 182 - get 193 + try 194 + { 195 + byte[] data = new byte[ResponseStream.Length]; 196 + ResponseStream.Seek(0, SeekOrigin.Begin); 197 + ResponseStream.Read(data, 0, data.Length); 198 + return data; 199 + } 200 + catch 183 201 { 184 - try 185 - { 186 - byte[] data = new byte[ResponseStream.Length]; 187 - ResponseStream.Seek(0, SeekOrigin.Begin); 188 - ResponseStream.Read(data, 0, data.Length); 189 - return data; 190 - } 191 - catch 192 - { 193 - return null; 194 - } 202 + return null; 195 203 } 196 204 } 197 205
+14 -3
osu.Framework/IO/Stores/DllResourceStore.cs
··· 19 19 { 20 20 string filePath = Path.Combine(Path.GetDirectoryName(Assembly.GetCallingAssembly().Location), dllName); 21 21 22 - // prefer the local file if it exists, else load from assembly cache. 23 - assembly = System.IO.File.Exists(filePath) ? Assembly.LoadFrom(filePath) : Assembly.Load(Path.GetFileNameWithoutExtension(dllName)); 22 + // prefer the local file if it exists, else load from standard dependency resolver. 23 + assembly = System.IO.File.Exists(filePath) ? Assembly.LoadFile(filePath) : Assembly.Load(Path.GetFileNameWithoutExtension(dllName)); 24 + 25 + prefix = assembly.GetName().Name; 26 + } 27 + 28 + public DllResourceStore(AssemblyName name) 29 + : this(Assembly.Load(name)) 30 + { 31 + } 24 32 25 - prefix = Path.GetFileNameWithoutExtension(dllName); 33 + public DllResourceStore(Assembly assembly) 34 + { 35 + this.assembly = assembly; 36 + prefix = assembly.GetName().Name; 26 37 } 27 38 28 39 public byte[] Get(string name)
+2 -2
osu.Framework/IO/Stores/FontStore.cs
··· 61 61 62 62 case GlyphStore gs: 63 63 64 - if (gs.CacheStorage == null) 65 - gs.CacheStorage = cacheStorage; 64 + if (gs is RawCachingGlyphStore raw && raw.CacheStorage == null) 65 + raw.CacheStorage = cacheStorage; 66 66 67 67 glyphStores.Add(gs); 68 68 queueLoad(gs);
+64 -115
osu.Framework/IO/Stores/GlyphStore.cs
··· 1 - // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 2 // See the LICENCE file in the repository root for full licence text. 3 3 4 4 using System; ··· 6 6 using System.IO; 7 7 using System.Linq; 8 8 using System.Threading.Tasks; 9 - using osu.Framework.Extensions; 10 9 using osu.Framework.Graphics.Textures; 11 10 using osu.Framework.Logging; 12 - using osu.Framework.Platform; 13 11 using osu.Framework.Text; 14 12 using SharpFNT; 15 13 using SixLabors.ImageSharp; 16 14 using SixLabors.ImageSharp.Advanced; 17 15 using SixLabors.ImageSharp.PixelFormats; 18 - using SixLabors.Primitives; 19 16 20 17 namespace osu.Framework.IO.Stores 21 18 { 19 + /// <summary> 20 + /// A basic glyph store that will load font sprite sheets every character retrieval. 21 + /// </summary> 22 22 public class GlyphStore : IResourceStore<TextureUpload>, IGlyphStore 23 23 { 24 - private readonly string assetName; 24 + protected readonly string AssetName; 25 + 26 + protected readonly IResourceStore<TextureUpload> TextureLoader; 25 27 26 28 public readonly string FontName; 27 29 28 - private const float default_size = 96; 29 - 30 - private readonly ResourceStore<byte[]> store; 30 + protected readonly ResourceStore<byte[]> Store; 31 31 32 32 protected BitmapFont Font => completionSource.Task.Result; 33 33 34 34 private readonly TaskCompletionSource<BitmapFont> completionSource = new TaskCompletionSource<BitmapFont>(); 35 35 36 - internal Storage CacheStorage; 37 - 38 - private Task fontLoadTask; 39 - 40 - public GlyphStore(ResourceStore<byte[]> store, string assetName = null) 36 + /// <summary> 37 + /// Create a new glyph store. 38 + /// </summary> 39 + /// <param name="store">The store to provide font resources.</param> 40 + /// <param name="assetName">The base name of thße font.</param> 41 + /// <param name="textureLoader">An optional platform-specific store for loading textures. Should load for the store provided in <param ref="param"/>.</param> 42 + public GlyphStore(ResourceStore<byte[]> store, string assetName = null, IResourceStore<TextureUpload> textureLoader = null) 41 43 { 42 - this.store = new ResourceStore<byte[]>(store); 44 + Store = new ResourceStore<byte[]>(store); 43 45 44 - this.store.AddExtension("fnt"); 45 - this.store.AddExtension("bin"); 46 + Store.AddExtension("fnt"); 47 + Store.AddExtension("bin"); 46 48 47 - this.assetName = assetName; 49 + AssetName = assetName; 50 + TextureLoader = textureLoader; 48 51 49 52 FontName = assetName?.Split('/').Last(); 50 53 } 51 54 55 + private Task fontLoadTask; 56 + 52 57 public Task LoadFontAsync() => fontLoadTask ??= Task.Factory.StartNew(() => 53 58 { 54 59 try 55 60 { 56 61 BitmapFont font; 57 - using (var s = store.GetStream($@"{assetName}")) 62 + using (var s = Store.GetStream($@"{AssetName}")) 58 63 font = BitmapFont.FromStream(s, FormatHint.Binary, false); 59 64 60 65 completionSource.SetResult(font); 61 66 } 62 67 catch (Exception ex) 63 68 { 64 - Logger.Error(ex, $"Couldn't load font asset from {assetName}."); 69 + Logger.Error(ex, $"Couldn't load font asset from {AssetName}."); 65 70 completionSource.SetResult(null); 66 71 throw; 67 72 } ··· 79 84 return Font.Common.Base; 80 85 } 81 86 82 - public TextureUpload Get(string name) 83 - { 84 - if (name.Length > 1 && !name.StartsWith($@"{FontName}/", StringComparison.Ordinal)) 85 - return null; 86 - 87 - if (!Font.Characters.TryGetValue(name.Last(), out Character c)) 88 - return null; 89 - 90 - return loadCharacter(c); 91 - } 92 - 93 - public virtual async Task<TextureUpload> GetAsync(string name) 87 + protected virtual TextureUpload GetPageImage(int page) 94 88 { 95 - if (name.Length > 1 && !name.StartsWith($@"{FontName}/", StringComparison.Ordinal)) 96 - return null; 89 + if (TextureLoader != null) 90 + return TextureLoader.Get(GetFilenameForPage(page)); 97 91 98 - if (!(await completionSource.Task).Characters.TryGetValue(name.Last(), out Character c)) 99 - return null; 100 - 101 - return loadCharacter(c); 92 + using (var stream = Store.GetStream(GetFilenameForPage(page))) 93 + return new TextureUpload(stream); 102 94 } 103 95 104 - Task<CharacterGlyph> IResourceStore<CharacterGlyph>.GetAsync(string name) => Task.Run(() => ((IGlyphStore)this).Get(name[0])); 105 - 106 - CharacterGlyph IResourceStore<CharacterGlyph>.Get(string name) => Get(name[0]); 96 + protected string GetFilenameForPage(int page) 97 + => $@"{AssetName}_{page.ToString().PadLeft((Font.Pages.Count - 1).ToString().Length, '0')}.png"; 107 98 108 99 public CharacterGlyph Get(char character) 109 100 { ··· 113 104 114 105 public int GetKerning(char left, char right) => Font.GetKerningAmount(left, right); 115 106 116 - private readonly Dictionary<int, PageInfo> pageLookup = new Dictionary<int, PageInfo>(); 107 + Task<CharacterGlyph> IResourceStore<CharacterGlyph>.GetAsync(string name) => Task.Run(() => ((IGlyphStore)this).Get(name[0])); 117 108 118 - private class PageInfo 119 - { 120 - public string Filename; 121 - public Size Size; 122 - } 109 + CharacterGlyph IResourceStore<CharacterGlyph>.Get(string name) => Get(name[0]); 123 110 124 - private TextureUpload loadCharacter(Character c) 111 + public TextureUpload Get(string name) 125 112 { 126 - if (!pageLookup.TryGetValue(c.Page, out var pageInfo)) 127 - { 128 - string filename = $@"{assetName}_{c.Page.ToString().PadLeft((Font.Pages.Count - 1).ToString().Length, '0')}.png"; 113 + if (name.Length > 1 && !name.StartsWith($@"{FontName}/", StringComparison.Ordinal)) 114 + return null; 129 115 130 - using (var stream = store.GetStream(filename)) 131 - using (var convert = Image.Load<Rgba32>(stream)) 132 - { 133 - string streamMd5 = stream.ComputeMD5Hash(); 134 - string filenameMd5 = filename.ComputeMD5Hash(); 116 + if (!Font.Characters.TryGetValue(name.Last(), out Character c)) 117 + return null; 135 118 136 - string accessFilename = $"{filenameMd5}#{streamMd5}"; 119 + return LoadCharacter(c); 120 + } 137 121 138 - var existing = CacheStorage.GetFiles(string.Empty, $"{accessFilename}*").FirstOrDefault(); 122 + public virtual async Task<TextureUpload> GetAsync(string name) 123 + { 124 + if (name.Length > 1 && !name.StartsWith($@"{FontName}/", StringComparison.Ordinal)) 125 + return null; 139 126 140 - if (existing != null) 141 - { 142 - var split = existing.Split('#'); 143 - pageLookup[c.Page] = pageInfo = new PageInfo 144 - { 145 - Size = new Size(int.Parse(split[2]), int.Parse(split[3])), 146 - Filename = existing 147 - }; 148 - } 149 - else 150 - { 151 - // todo: use i# memoryallocator once netstandard supports stream operations 152 - byte[] output = new byte[convert.Width * convert.Height]; 127 + if (!(await completionSource.Task).Characters.TryGetValue(name.Last(), out Character c)) 128 + return null; 153 129 154 - var pxl = convert.GetPixelSpan(); 130 + return LoadCharacter(c); 131 + } 155 132 156 - for (int i = 0; i < convert.Width * convert.Height; i++) 157 - output[i] = pxl[i].A; 133 + protected int LoadedGlyphCount; 158 134 159 - // ensure any stale cached versions are deleted. 160 - foreach (var f in CacheStorage.GetFiles(string.Empty, $"{filenameMd5}*")) 161 - CacheStorage.Delete(f); 135 + protected virtual TextureUpload LoadCharacter(Character character) 136 + { 137 + var page = GetPageImage(character.Page); 138 + LoadedGlyphCount++; 162 139 163 - accessFilename += $"#{convert.Width}#{convert.Height}"; 140 + var image = new Image<Rgba32>(SixLabors.ImageSharp.Configuration.Default, character.Width, character.Height, new Rgba32(255, 255, 255, 0)); 164 141 165 - using (var outStream = CacheStorage.GetStream(accessFilename, FileAccess.Write, FileMode.Create)) 166 - outStream.Write(output, 0, output.Length); 142 + var dest = image.GetPixelSpan(); 143 + var source = page.Data; 167 144 168 - pageLookup[c.Page] = pageInfo = new PageInfo 169 - { 170 - Size = new Size(convert.Width, convert.Height), 171 - Filename = accessFilename 172 - }; 173 - } 174 - } 175 - } 176 - 177 - int pageWidth = pageInfo.Size.Width; 178 - 179 - int charWidth = c.Width; 180 - int charHeight = c.Height; 145 + // the spritesheet may have unused pixels trimmed 146 + int readableHeight = Math.Min(character.Height, page.Height - character.Y); 147 + int readableWidth = Math.Min(character.Width, page.Width - character.X); 181 148 182 - if (readBuffer == null || readBuffer.Length < pageWidth) 183 - readBuffer = new byte[pageWidth]; 184 - 185 - var image = new Image<Rgba32>(SixLabors.ImageSharp.Configuration.Default, charWidth, charHeight, new Rgba32(255, 255, 255, 0)); 186 - 187 - using (var stream = CacheStorage.GetStream(pageInfo.Filename)) 149 + for (int y = 0; y < readableHeight; y++) 188 150 { 189 - var pixels = image.GetPixelSpan(); 190 - stream.Seek(pageWidth * c.Y, SeekOrigin.Current); 191 - 192 - // the spritesheet may have unused pixels trimmed 193 - int readableHeight = Math.Min(c.Height, pageInfo.Size.Height - c.Y); 194 - int readableWidth = Math.Min(c.Width, pageWidth - c.X); 195 - 196 - for (int y = 0; y < readableHeight; y++) 197 - { 198 - stream.Read(readBuffer, 0, pageWidth); 199 - 200 - int writeOffset = y * charWidth; 151 + int readOffset = (character.Y + y) * page.Width + character.X; 152 + int writeOffset = y * character.Width; 201 153 202 - for (int x = 0; x < readableWidth; x++) 203 - pixels[writeOffset + x] = new Rgba32(255, 255, 255, readBuffer[c.X + x]); 204 - } 154 + for (int x = 0; x < readableWidth; x++) 155 + dest[writeOffset + x] = source[readOffset + x]; 205 156 } 206 157 207 158 return new TextureUpload(image); 208 159 } 209 - 210 - private byte[] readBuffer; 211 160 212 161 public Stream GetStream(string name) => throw new NotSupportedException(); 213 162
+2 -2
osu.Framework/IO/Stores/OnlineStore.cs
··· 21 21 using (WebRequest req = new WebRequest($@"{url}")) 22 22 { 23 23 await req.PerformAsync(); 24 - return req.ResponseData; 24 + return req.GetResponseData(); 25 25 } 26 26 } 27 27 catch ··· 42 42 using (WebRequest req = new WebRequest($@"{url}")) 43 43 { 44 44 req.Perform(); 45 - return req.ResponseData; 45 + return req.GetResponseData(); 46 46 } 47 47 } 48 48 catch
+135
osu.Framework/IO/Stores/RawCachingGlyphStore.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + using System; 5 + using System.Collections.Generic; 6 + using System.IO; 7 + using System.Linq; 8 + using osu.Framework.Extensions; 9 + using osu.Framework.Graphics.Textures; 10 + using osu.Framework.Platform; 11 + using SharpFNT; 12 + using SixLabors.ImageSharp; 13 + using SixLabors.ImageSharp.Advanced; 14 + using SixLabors.ImageSharp.PixelFormats; 15 + using SixLabors.Primitives; 16 + 17 + namespace osu.Framework.IO.Stores 18 + { 19 + /// <summary> 20 + /// A glyph store which caches font sprite sheets as raw pixels to disk on first use. 21 + /// </summary> 22 + /// <remarks> 23 + /// This results in memory efficient lookups with good performance on solid state backed devices. 24 + /// </remarks> 25 + public class RawCachingGlyphStore : GlyphStore 26 + { 27 + public Storage CacheStorage; 28 + 29 + public RawCachingGlyphStore(ResourceStore<byte[]> store, string assetName = null, IResourceStore<TextureUpload> textureLoader = null) 30 + : base(store, assetName, textureLoader) 31 + { 32 + } 33 + 34 + private readonly Dictionary<int, PageInfo> pageLookup = new Dictionary<int, PageInfo>(); 35 + 36 + protected override TextureUpload LoadCharacter(Character character) 37 + { 38 + if (!pageLookup.TryGetValue(character.Page, out var pageInfo)) 39 + pageInfo = createCachedPageInfo(character.Page); 40 + 41 + return createTextureUpload(character, pageInfo); 42 + } 43 + 44 + private PageInfo createCachedPageInfo(int page) 45 + { 46 + string filename = GetFilenameForPage(page); 47 + 48 + using (var stream = Store.GetStream(filename)) 49 + { 50 + string streamMd5 = stream.ComputeMD5Hash(); 51 + string filenameMd5 = filename.ComputeMD5Hash(); 52 + 53 + string accessFilename = $"{filenameMd5}#{streamMd5}"; 54 + 55 + var existing = CacheStorage.GetFiles(string.Empty, $"{accessFilename}*").FirstOrDefault(); 56 + 57 + if (existing != null) 58 + { 59 + var split = existing.Split('#'); 60 + return pageLookup[page] = new PageInfo 61 + { 62 + Size = new Size(int.Parse(split[2]), int.Parse(split[3])), 63 + Filename = existing 64 + }; 65 + } 66 + 67 + using (var convert = GetPageImage(page)) 68 + { 69 + // todo: use i# memoryallocator once netstandard supports stream operations 70 + byte[] output = new byte[convert.Width * convert.Height]; 71 + 72 + var pxl = convert.Data; 73 + 74 + for (int i = 0; i < convert.Width * convert.Height; i++) 75 + output[i] = pxl[i].A; 76 + 77 + // ensure any stale cached versions are deleted. 78 + foreach (var f in CacheStorage.GetFiles(string.Empty, $"{filenameMd5}*")) 79 + CacheStorage.Delete(f); 80 + 81 + accessFilename += $"#{convert.Width}#{convert.Height}"; 82 + 83 + using (var outStream = CacheStorage.GetStream(accessFilename, FileAccess.Write, FileMode.Create)) 84 + outStream.Write(output, 0, output.Length); 85 + 86 + return pageLookup[page] = new PageInfo 87 + { 88 + Size = new Size(convert.Width, convert.Height), 89 + Filename = accessFilename 90 + }; 91 + } 92 + } 93 + } 94 + 95 + private TextureUpload createTextureUpload(Character character, PageInfo page) 96 + { 97 + int pageWidth = page.Size.Width; 98 + 99 + if (readBuffer == null || readBuffer.Length < pageWidth) 100 + readBuffer = new byte[pageWidth]; 101 + 102 + var image = new Image<Rgba32>(SixLabors.ImageSharp.Configuration.Default, character.Width, character.Height, new Rgba32(255, 255, 255, 0)); 103 + 104 + using (var source = CacheStorage.GetStream(page.Filename)) 105 + { 106 + var dest = image.GetPixelSpan(); 107 + source.Seek(pageWidth * character.Y, SeekOrigin.Current); 108 + 109 + // the spritesheet may have unused pixels trimmed 110 + int readableHeight = Math.Min(character.Height, page.Size.Height - character.Y); 111 + int readableWidth = Math.Min(character.Width, pageWidth - character.X); 112 + 113 + for (int y = 0; y < readableHeight; y++) 114 + { 115 + source.Read(readBuffer, 0, pageWidth); 116 + 117 + int writeOffset = y * character.Width; 118 + 119 + for (int x = 0; x < readableWidth; x++) 120 + dest[writeOffset + x] = new Rgba32(255, 255, 255, readBuffer[character.X + x]); 121 + } 122 + } 123 + 124 + return new TextureUpload(image); 125 + } 126 + 127 + private byte[] readBuffer; 128 + 129 + private class PageInfo 130 + { 131 + public string Filename; 132 + public Size Size; 133 + } 134 + } 135 + }
+1 -1
osu.Framework/Input/InputResampler.cs
··· 45 45 } 46 46 47 47 // HD if it has fractions 48 - if (position.X - (float)Math.Truncate(position.X) != 0) 48 + if (position.X - MathF.Truncate(position.X) != 0) 49 49 isRawInput = true; 50 50 } 51 51
+32 -17
osu.Framework/MathUtils/Interpolation.cs
··· 248 248 val1.Height + t * (val2.X - val1.Height)); 249 249 } 250 250 251 - public static double ApplyEasing(Easing easing, double time) 252 - { 253 - const double elastic_const = 2 * Math.PI / .3; 254 - const double elastic_const2 = .3 / 4; 251 + #region Easing constants 252 + 253 + private const double elastic_const = 2 * Math.PI / .3; 254 + private const double elastic_const2 = .3 / 4; 255 + 256 + private const double back_const = 1.70158; 257 + private const double back_const2 = back_const * 1.525; 258 + 259 + private const double bounce_const = 1 / 2.75; 255 260 256 - const double back_const = 1.70158; 257 - const double back_const2 = back_const * 1.525; 261 + // constants used to fix expo and elastic curves to start/end at 0/1 262 + private static readonly double expo_offset = Math.Pow(2, -10); 263 + private static readonly double elastic_offset_full = Math.Pow(2, -11); 264 + private static readonly double elastic_offset_half = Math.Pow(2, -10) * Math.Sin((.5 - elastic_const2) * elastic_const); 265 + private static readonly double elastic_offset_quarter = Math.Pow(2, -10) * Math.Sin((.25 - elastic_const2) * elastic_const); 266 + private static readonly double in_out_elastic_offset = Math.Pow(2, -10) * Math.Sin((1 - elastic_const2 * 1.5) * elastic_const / 1.5); 258 267 259 - const double bounce_const = 1 / 2.75; 268 + #endregion 260 269 270 + public static double ApplyEasing(Easing easing, double time) 271 + { 261 272 switch (easing) 262 273 { 263 274 default: ··· 319 330 return .5 - .5 * Math.Cos(Math.PI * time); 320 331 321 332 case Easing.InExpo: 322 - return Math.Pow(2, 10 * (time - 1)); 333 + return Math.Pow(2, 10 * (time - 1)) + expo_offset * (time - 1); 323 334 324 335 case Easing.OutExpo: 325 - return -Math.Pow(2, -10 * time) + 1; 336 + return -Math.Pow(2, -10 * time) + 1 + expo_offset * time; 326 337 327 338 case Easing.InOutExpo: 328 - if (time < .5) return .5 * Math.Pow(2, 20 * time - 10); 339 + if (time < .5) return .5 * (Math.Pow(2, 20 * time - 10) + expo_offset * (2 * time - 1)); 329 340 330 - return 1 - .5 * Math.Pow(2, -20 * time + 10); 341 + return 1 - .5 * (Math.Pow(2, -20 * time + 10) + expo_offset * (-2 * time + 1)); 331 342 332 343 case Easing.InCirc: 333 344 return 1 - Math.Sqrt(1 - time * time); ··· 341 352 return .5 * Math.Sqrt(1 - (time -= 2) * time) + .5; 342 353 343 354 case Easing.InElastic: 344 - return -Math.Pow(2, -10 + 10 * time) * Math.Sin((1 - elastic_const2 - time) * elastic_const); 355 + return -Math.Pow(2, -10 + 10 * time) * Math.Sin((1 - elastic_const2 - time) * elastic_const) + elastic_offset_full * (1 - time); 345 356 346 357 case Easing.OutElastic: 347 - return Math.Pow(2, -10 * time) * Math.Sin((time - elastic_const2) * elastic_const) + 1; 358 + return Math.Pow(2, -10 * time) * Math.Sin((time - elastic_const2) * elastic_const) + 1 - elastic_offset_full * time; 348 359 349 360 case Easing.OutElasticHalf: 350 - return Math.Pow(2, -10 * time) * Math.Sin((.5 * time - elastic_const2) * elastic_const) + 1; 361 + return Math.Pow(2, -10 * time) * Math.Sin((.5 * time - elastic_const2) * elastic_const) + 1 - elastic_offset_half * time; 351 362 352 363 case Easing.OutElasticQuarter: 353 - return Math.Pow(2, -10 * time) * Math.Sin((.25 * time - elastic_const2) * elastic_const) + 1; 364 + return Math.Pow(2, -10 * time) * Math.Sin((.25 * time - elastic_const2) * elastic_const) + 1 - elastic_offset_quarter * time; 354 365 355 366 case Easing.InOutElastic: 356 367 if ((time *= 2) < 1) 357 - return -.5 * Math.Pow(2, -10 + 10 * time) * Math.Sin((1 - elastic_const2 * 1.5 - time) * elastic_const / 1.5); 368 + { 369 + return -.5 * (Math.Pow(2, -10 + 10 * time) * Math.Sin((1 - elastic_const2 * 1.5 - time) * elastic_const / 1.5) 370 + - in_out_elastic_offset * (1 - time)); 371 + } 358 372 359 - return .5 * Math.Pow(2, -10 * --time) * Math.Sin((time - elastic_const2 * 1.5) * elastic_const / 1.5) + 1; 373 + return .5 * (Math.Pow(2, -10 * --time) * Math.Sin((time - elastic_const2 * 1.5) * elastic_const / 1.5) 374 + - in_out_elastic_offset * time) + 1; 360 375 361 376 case Easing.InBack: 362 377 return time * time * ((back_const + 1) * time - back_const);
+3 -3
osu.Framework/Physics/RigidBodyContainer.cs
··· 152 152 { 153 153 Vector2 a = corners[i]; 154 154 155 - float startTheta = (i - 1) * (float)Math.PI / 2; 155 + float startTheta = (i - 1) * MathF.PI / 2; 156 156 157 157 for (int j = 0; j < amount_corner_steps; ++j) 158 158 { 159 - float theta = startTheta + j * (float)Math.PI / (2 * (amount_corner_steps - 1)); 159 + float theta = startTheta + j * MathF.PI / (2 * (amount_corner_steps - 1)); 160 160 161 - Vector2 normal = new Vector2((float)Math.Sin(theta), (float)Math.Cos(theta)); 161 + Vector2 normal = new Vector2(MathF.Sin(theta), MathF.Cos(theta)); 162 162 Vertices.Add(a + offsets[i] + normal * cornerRadius); 163 163 Normals.Add(normal); 164 164 }
+1 -1
osu.Framework/Platform/GameHost.cs
··· 808 808 809 809 cursorSensitivity = Config.GetBindable<double>(FrameworkSetting.CursorSensitivity); 810 810 811 - Config.BindWith(FrameworkSetting.PerformanceLogging, performanceLogging); 811 + DebugConfig.BindWith(DebugSetting.PerformanceLogging, performanceLogging); 812 812 performanceLogging.BindValueChanged(logging => 813 813 { 814 814 threads.ForEach(t => t.Monitor.EnablePerformanceProfiling = logging.NewValue);
-9
osu.Framework/Platform/Linux/LinuxGameHost.cs
··· 1 1 // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 2 // See the LICENCE file in the repository root for full licence text. 3 3 4 - using osu.Framework.Platform.Linux.Native; 5 4 using osu.Framework.Platform.Linux.Sdl; 6 5 using osuTK; 7 6 ··· 12 11 internal LinuxGameHost(string gameName, bool bindIPC = false, ToolkitOptions toolkitOptions = default, bool portableInstallation = false, bool useSdl = false) 13 12 : base(gameName, bindIPC, toolkitOptions, portableInstallation, useSdl) 14 13 { 15 - } 16 - 17 - protected override void SetupForRun() 18 - { 19 - base.SetupForRun(); 20 - 21 - // required for the time being to address libbass_fx.so load failures (see https://github.com/ppy/osu/issues/2852) 22 - Library.Load("libbass.so", Library.LoadFlags.RTLD_LAZY | Library.LoadFlags.RTLD_GLOBAL); 23 14 } 24 15 25 16 protected override IWindow CreateWindow() =>
-46
osu.Framework/Platform/Linux/Native/Library.cs
··· 1 - // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 - // See the LICENCE file in the repository root for full licence text. 3 - 4 - using System; 5 - using System.IO; 6 - using System.Runtime.InteropServices; 7 - 8 - namespace osu.Framework.Platform.Linux.Native 9 - { 10 - public static class Library 11 - { 12 - [DllImport("libdl.so.2", EntryPoint = "dlopen")] 13 - private static extern IntPtr dlopen(string library, LoadFlags flags); 14 - 15 - /// <summary> 16 - /// Loads a library with flags to use with dlopen. Uses <see cref="LoadFlags"/> for the flags 17 - /// 18 - /// Uses NATIVE_DLL_SEARCH_DIRECTORIES and then ld.so for library paths 19 - /// </summary> 20 - /// <param name="library">Full name of the library</param> 21 - /// <param name="flags">See 'man dlopen' for more information.</param> 22 - public static void Load(string library, LoadFlags flags) 23 - { 24 - var paths = (string)AppContext.GetData("NATIVE_DLL_SEARCH_DIRECTORIES"); 25 - 26 - foreach (var path in paths.Split(':')) 27 - { 28 - if (dlopen(Path.Combine(path, library), flags) != IntPtr.Zero) 29 - break; 30 - } 31 - } 32 - 33 - [Flags] 34 - public enum LoadFlags 35 - { 36 - RTLD_LAZY = 0x00001, 37 - RTLD_NOW = 0x00002, 38 - RTLD_BINDING_MASK = 0x00003, 39 - RTLD_NOLOAD = 0x00004, 40 - RTLD_DEEPBIND = 0x00008, 41 - RTLD_GLOBAL = 0x00100, 42 - RTLD_LOCAL = 0x00000, 43 - RTLD_NODELETE = 0x01000 44 - } 45 - } 46 - }
-3
osu.Framework/Platform/Windows/WindowsStorage.cs
··· 3 3 4 4 using System; 5 5 using System.Diagnostics; 6 - using System.Text.RegularExpressions; 7 6 8 7 namespace osu.Framework.Platform.Windows 9 8 { ··· 12 11 public WindowsStorage(string baseName, DesktopGameHost host) 13 12 : base(baseName, host) 14 13 { 15 - // allows traversal of long directory/filenames beyond the standard limitations (see https://stackoverflow.com/a/5188559) 16 - BasePath = Regex.Replace(BasePath, @"^([a-zA-Z]):\\", @"\\?\$1:\"); 17 14 } 18 15 19 16 public override void OpenInNativeExplorer() => Process.Start("explorer.exe", GetFullPath(string.Empty));
+4 -4
osu.Framework/Testing/TestBrowser.cs
··· 131 131 var resources = game.Resources; 132 132 133 133 //Roboto 134 - fonts.AddStore(new GlyphStore(resources, @"Fonts/Roboto/Roboto-Regular")); 135 - fonts.AddStore(new GlyphStore(resources, @"Fonts/Roboto/Roboto-Bold")); 134 + game.AddFont(resources, @"Fonts/Roboto/Roboto-Regular"); 135 + game.AddFont(resources, @"Fonts/Roboto/Roboto-Bold"); 136 136 137 137 //RobotoCondensed 138 - fonts.AddStore(new GlyphStore(resources, @"Fonts/RobotoCondensed/RobotoCondensed-Regular")); 139 - fonts.AddStore(new GlyphStore(resources, @"Fonts/RobotoCondensed/RobotoCondensed-Bold")); 138 + game.AddFont(resources, @"Fonts/RobotoCondensed/RobotoCondensed-Regular"); 139 + game.AddFont(resources, @"Fonts/RobotoCondensed/RobotoCondensed-Bold"); 140 140 141 141 showLogOverlay = frameworkConfig.GetBindable<bool>(FrameworkSetting.ShowLogOverlay); 142 142
+5 -8
osu.Framework/osu.Framework.csproj
··· 1 1 <Project Sdk="Microsoft.NET.Sdk"> 2 2 <PropertyGroup Label="Project"> 3 - <TargetFrameworks>netcoreapp3.0;netstandard2.0</TargetFrameworks> 3 + <TargetFrameworks>netcoreapp3.0;netstandard2.1</TargetFrameworks> 4 4 <OutputType>Library</OutputType> 5 5 <AllowUnsafeBlocks>true</AllowUnsafeBlocks> 6 6 <AssemblyTitle>osu!framework</AssemblyTitle> ··· 26 26 <ItemGroup Label="Package References"> 27 27 <PackageReference Include="Markdig" Version="0.18.0" /> 28 28 <PackageReference Include="FFmpeg.AutoGen" Version="4.2.0" /> 29 - <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="2.9.7" PrivateAssets="All" /> 29 + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="2.9.8" PrivateAssets="All" /> 30 30 <PackageReference Include="Microsoft.Extensions.ObjectPool" Version="3.0.1" /> 31 - <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0-beta2-19554-01" PrivateAssets="All" /> 31 + <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" /> 32 32 <PackageReference Include="SharpFNT" Version="1.1.0" /> 33 33 <PackageReference Include="SixLabors.ImageSharp" Version="1.0.0-beta0007" /> 34 34 <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.3.1" /> 35 - <PackageReference Include="System.Runtime.InteropServices" Version="4.3.0" /> 36 - <PackageReference Include="Microsoft.Diagnostics.Runtime" Version="1.1.46104" /> 35 + <PackageReference Include="Microsoft.Diagnostics.Runtime" Version="1.1.57604" /> 37 36 <PackageReference Include="NUnit" Version="3.12.0" /> 38 - <PackageReference Include="System.Net.Http" Version="4.3.4" /> 39 37 <PackageReference Include="ManagedBass" Version="2.0.4" /> 40 38 <PackageReference Include="ManagedBass.Fx" Version="2.0.1" /> 41 39 <PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> 42 - <PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.6.0" /> 43 - <PackageReference Include="System.Reflection.Emit.ILGeneration" Version="4.6.0" /> 44 40 <PackageReference Include="JetBrains.Annotations" Version="2019.1.3" /> 45 41 <PackageReference Include="ppy.osuTK.NS20" Version="1.0.115" /> 46 42 <PackageReference Include="Veldrid" Version="4.7.0" /> 47 43 <PackageReference Include="Veldrid.StartupUtilities" Version="4.7.0" /> 44 + <PackageReference Include="StbiSharp" Version="1.0.8" /> 48 45 </ItemGroup> 49 46 <ItemGroup Condition="$(TargetFrameworkIdentifier) == '.NETCoreApp'"> 50 47 <!-- DO NOT use ProjectReference for native packaging project.