A game framework written with osu! in mind.

Merge remote-tracking branch 'ppy/master' into rename_test_case

+1275 -1100
+41
osu.Framework.Tests/Clocks/InterpolatingClockTest.cs
··· 41 Assert.GreaterOrEqual(interpolating.CurrentTime, source.CurrentTime, "Interpolating should not jump before source time."); 42 43 Thread.Sleep((int)(interpolating.AllowableErrorMilliseconds / 2)); 44 } 45 46 // test with test clock elapsing 47 lastValue = interpolating.CurrentTime; ··· 52 53 Assert.GreaterOrEqual(interpolating.CurrentTime, lastValue, "Interpolating should not jump against rate."); 54 Assert.LessOrEqual(Math.Abs(interpolating.CurrentTime - source.CurrentTime), interpolating.AllowableErrorMilliseconds, "Interpolating should be within allowance."); 55 56 Thread.Sleep((int)(interpolating.AllowableErrorMilliseconds / 2)); 57 } 58 } 59 60 [Test]
··· 41 Assert.GreaterOrEqual(interpolating.CurrentTime, source.CurrentTime, "Interpolating should not jump before source time."); 42 43 Thread.Sleep((int)(interpolating.AllowableErrorMilliseconds / 2)); 44 + lastValue = interpolating.CurrentTime; 45 } 46 + 47 + int interpolatingCount = 0; 48 49 // test with test clock elapsing 50 lastValue = interpolating.CurrentTime; ··· 55 56 Assert.GreaterOrEqual(interpolating.CurrentTime, lastValue, "Interpolating should not jump against rate."); 57 Assert.LessOrEqual(Math.Abs(interpolating.CurrentTime - source.CurrentTime), interpolating.AllowableErrorMilliseconds, "Interpolating should be within allowance."); 58 + 59 + if (interpolating.CurrentTime != source.CurrentTime) 60 + interpolatingCount++; 61 62 Thread.Sleep((int)(interpolating.AllowableErrorMilliseconds / 2)); 63 + lastValue = interpolating.CurrentTime; 64 } 65 + 66 + Assert.Greater(interpolatingCount, 10); 67 + } 68 + 69 + [Test] 70 + public void NeverInterpolatesBackwardsOnInterpolationFail() 71 + { 72 + double lastValue = interpolating.CurrentTime; 73 + 74 + source.Start(); 75 + source.Rate = 10; // use a higher rate to ensure we may seek backwards. 76 + 77 + int sleepTime = (int)(interpolating.AllowableErrorMilliseconds / 2); 78 + 79 + int interpolatingCount = 0; 80 + 81 + for (int i = 0; i < 200; i++) 82 + { 83 + if (i < 100) // stop the elapsing at some point in time. should still work as source's ElapsedTime is zero. 84 + source.CurrentTime += sleepTime * source.Rate; 85 + 86 + interpolating.ProcessFrame(); 87 + 88 + if (interpolating.CurrentTime != source.CurrentTime) 89 + interpolatingCount++; 90 + 91 + Assert.GreaterOrEqual(interpolating.CurrentTime, lastValue, "Interpolating should not jump against rate."); 92 + Assert.LessOrEqual(Math.Abs(interpolating.CurrentTime - source.CurrentTime), interpolating.AllowableErrorMilliseconds, "Interpolating should be within allowance."); 93 + 94 + Thread.Sleep(sleepTime); 95 + lastValue = interpolating.CurrentTime; 96 + } 97 + 98 + Assert.Greater(interpolatingCount, 10); 99 } 100 101 [Test]
-1
osu.Framework.Tests/Visual/Containers/TestSceneCachedBufferedContainer.cs
··· 18 public override IReadOnlyList<Type> RequiredTypes => new[] 19 { 20 typeof(BufferedContainer), 21 - typeof(BufferedContainerDrawNode), 22 }; 23 24 public TestSceneCachedBufferedContainer()
··· 18 public override IReadOnlyList<Type> RequiredTypes => new[] 19 { 20 typeof(BufferedContainer), 21 }; 22 23 public TestSceneCachedBufferedContainer()
+1
osu.Framework.Tests/Visual/Containers/TestSceneMasking.cs
··· 4 using System; 5 using osu.Framework.Graphics; 6 using osu.Framework.Graphics.Containers; 7 using osu.Framework.Graphics.Shapes; 8 using osu.Framework.Graphics.Sprites; 9 using osu.Framework.Input.Events;
··· 4 using System; 5 using osu.Framework.Graphics; 6 using osu.Framework.Graphics.Containers; 7 + using osu.Framework.Graphics.Effects; 8 using osu.Framework.Graphics.Shapes; 9 using osu.Framework.Graphics.Sprites; 10 using osu.Framework.Input.Events;
+1
osu.Framework.Tests/Visual/Drawables/TestSceneHollowEdgeEffect.cs
··· 3 4 using osu.Framework.Graphics; 5 using osu.Framework.Graphics.Containers; 6 using osu.Framework.Graphics.Shapes; 7 using osu.Framework.Graphics.Sprites; 8 using osu.Framework.Testing;
··· 3 4 using osu.Framework.Graphics; 5 using osu.Framework.Graphics.Containers; 6 + using osu.Framework.Graphics.Effects; 7 using osu.Framework.Graphics.Shapes; 8 using osu.Framework.Graphics.Sprites; 9 using osu.Framework.Testing;
+1
osu.Framework.Tests/Visual/Drawables/TestSceneTransformSequence.cs
··· 3 4 using osu.Framework.Graphics; 5 using osu.Framework.Graphics.Containers; 6 using osu.Framework.Graphics.Shapes; 7 using osu.Framework.Graphics.Sprites; 8 using osu.Framework.Graphics.Transforms;
··· 3 4 using osu.Framework.Graphics; 5 using osu.Framework.Graphics.Containers; 6 + using osu.Framework.Graphics.Effects; 7 using osu.Framework.Graphics.Shapes; 8 using osu.Framework.Graphics.Sprites; 9 using osu.Framework.Graphics.Transforms;
+4 -3
osu.Framework.Tests/Visual/Testing/TestSceneTest.cs
··· 2 // See the LICENCE file in the repository root for full licence text. 3 4 using NUnit.Framework; 5 using osu.Framework.Testing; 6 7 namespace osu.Framework.Tests.Visual.Testing ··· 31 // [SetUp] gets run via TestConstructor() when we are running under nUnit. 32 // note that in TestBrowser's case, this does not invoke SetUp methods, so we skip this increment. 33 // schedule is required to ensure that IsNUnitRunning is initialised. 34 - if (IsNUnitRunning) 35 testRunCount++; 36 }); 37 ··· 43 { 44 AddStep("increment run count", () => testRunCount++); 45 AddAssert("correct setup run count", () => testRunCount == setupRun); 46 - AddAssert("correct setup steps run count", () => (IsNUnitRunning ? testRunCount : 2) == setupStepsRun); 47 } 48 49 [Test] ··· 51 { 52 AddStep("increment run count", () => testRunCount++); 53 AddAssert("correct setup run count", () => testRunCount == setupRun); 54 - AddAssert("correct setup steps run count", () => (IsNUnitRunning ? testRunCount : 2) == setupStepsRun); 55 } 56 57 protected override ITestSceneTestRunner CreateRunner() => new TestRunner();
··· 2 // See the LICENCE file in the repository root for full licence text. 3 4 using NUnit.Framework; 5 + using osu.Framework.Development; 6 using osu.Framework.Testing; 7 8 namespace osu.Framework.Tests.Visual.Testing ··· 32 // [SetUp] gets run via TestConstructor() when we are running under nUnit. 33 // note that in TestBrowser's case, this does not invoke SetUp methods, so we skip this increment. 34 // schedule is required to ensure that IsNUnitRunning is initialised. 35 + if (DebugUtils.IsNUnitRunning) 36 testRunCount++; 37 }); 38 ··· 44 { 45 AddStep("increment run count", () => testRunCount++); 46 AddAssert("correct setup run count", () => testRunCount == setupRun); 47 + AddAssert("correct setup steps run count", () => (DebugUtils.IsNUnitRunning ? testRunCount : 2) == setupStepsRun); 48 } 49 50 [Test] ··· 52 { 53 AddStep("increment run count", () => testRunCount++); 54 AddAssert("correct setup run count", () => testRunCount == setupRun); 55 + AddAssert("correct setup steps run count", () => (DebugUtils.IsNUnitRunning ? testRunCount : 2) == setupStepsRun); 56 } 57 58 protected override ITestSceneTestRunner CreateRunner() => new TestRunner();
+30 -1
osu.Framework/Development/DebugUtils.cs
··· 3 4 using System; 5 using System.Diagnostics; 6 using System.Linq; 7 using System.Reflection; 8 9 namespace osu.Framework.Development 10 { 11 public static class DebugUtils 12 { 13 public static bool IsDebugBuild => is_debug_build.Value; 14 15 private static readonly Lazy<bool> is_debug_build = new Lazy<bool>(() => 16 - isDebugAssembly(typeof(DebugUtils).Assembly) || isDebugAssembly(Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly()) 17 ); 18 19 // https://stackoverflow.com/a/2186634 20 private static bool isDebugAssembly(Assembly assembly) => assembly.GetCustomAttributes(false).OfType<DebuggableAttribute>().Any(da => da.IsJITTrackingEnabled); 21 } 22 }
··· 3 4 using System; 5 using System.Diagnostics; 6 + using System.IO; 7 using System.Linq; 8 using System.Reflection; 9 + using NUnit.Framework; 10 11 namespace osu.Framework.Development 12 { 13 public static class DebugUtils 14 { 15 + internal static Assembly HostAssembly { get; set; } 16 + 17 + public static bool IsNUnitRunning => is_nunit_running.Value; 18 + 19 + private static readonly Lazy<bool> is_nunit_running = new Lazy<bool>(() => 20 + { 21 + var entry = Assembly.GetEntryAssembly(); 22 + 23 + // when running under nunit + netcore, entry assembly becomes nunit itself (testhost, Version=15.0.0.0), which isn't what we want. 24 + return entry == null || entry.Location.Contains("testhost"); 25 + } 26 + ); 27 + 28 public static bool IsDebugBuild => is_debug_build.Value; 29 30 private static readonly Lazy<bool> is_debug_build = new Lazy<bool>(() => 31 + isDebugAssembly(typeof(DebugUtils).Assembly) || isDebugAssembly(GetEntryAssembly()) 32 ); 33 34 // https://stackoverflow.com/a/2186634 35 private static bool isDebugAssembly(Assembly assembly) => assembly.GetCustomAttributes(false).OfType<DebuggableAttribute>().Any(da => da.IsJITTrackingEnabled); 36 + 37 + /// <summary> 38 + /// Get the entry assembly, even when running under nUnit. 39 + /// </summary> 40 + /// <returns>The entry assembly (usually obtained via <see cref="Assembly.GetEntryAssembly()"/>.</returns> 41 + public static Assembly GetEntryAssembly() => 42 + HostAssembly ?? Assembly.GetEntryAssembly(); 43 + 44 + /// <summary> 45 + /// Get the entry path, even when running under nUnit. 46 + /// </summary> 47 + /// <returns>The entry assembly (usually obtained via the entry assembly's <see cref="Assembly.Location"/>.</returns> 48 + public static string GetEntryPath() => 49 + IsNUnitRunning ? TestContext.CurrentContext.TestDirectory : Path.GetDirectoryName(GetEntryAssembly().Location); 50 } 51 }
+47 -51
osu.Framework/Graphics/Audio/WaveformGraph.cs
··· 182 cancelSource = null; 183 } 184 185 - protected override DrawNode CreateDrawNode() => new WaveformDrawNode(); 186 - 187 - protected override void ApplyDrawNode(DrawNode node) 188 - { 189 - var n = (WaveformDrawNode)node; 190 - 191 - n.Shader = shader; 192 - n.Texture = texture; 193 - n.DrawSize = DrawSize; 194 - n.Points = ResampledWaveform?.GetPoints(); 195 - n.Channels = ResampledWaveform?.GetChannels() ?? 0; 196 - n.LowColour = lowColour ?? DrawColourInfo.Colour; 197 - n.MidColour = midColour ?? DrawColourInfo.Colour; 198 - n.HighColour = highColour ?? DrawColourInfo.Colour; 199 - 200 - base.ApplyDrawNode(node); 201 - } 202 203 protected override void Dispose(bool isDisposing) 204 { ··· 208 209 private class WaveformDrawNode : DrawNode 210 { 211 - public IShader Shader; 212 - public Texture Texture; 213 214 - public Vector2 DrawSize; 215 - public int Channels; 216 217 - public Color4 LowColour; 218 - public Color4 MidColour; 219 - public Color4 HighColour; 220 221 - private IReadOnlyList<WaveformPoint> points; 222 223 private double highMax; 224 private double midMax; 225 private double lowMax; 226 227 - public IReadOnlyList<WaveformPoint> Points 228 { 229 - get => points; 230 - set 231 - { 232 - points = value; 233 234 - if (points?.Any() == true) 235 - { 236 - highMax = points.Max(p => p.HighIntensity); 237 - midMax = points.Max(p => p.MidIntensity); 238 - lowMax = points.Max(p => p.LowIntensity); 239 - } 240 } 241 } 242 ··· 246 { 247 base.Draw(vertexAction); 248 249 - if (Texture?.Available != true || points == null || points.Count == 0) 250 return; 251 252 - Shader.Bind(); 253 - Texture.TextureGL.Bind(); 254 255 Vector2 localInflationAmount = new Vector2(0, 1) * DrawInfo.MatrixInverse.ExtractScale().Xy; 256 ··· 259 // Since the points are generated in the local coordinate space, we need to convert the screen space masking quad coordinates into the local coordinate space 260 RectangleF localMaskingRectangle = (Quad.FromRectangle(GLWrapper.CurrentMaskingInfo.ScreenSpaceAABB) * DrawInfo.MatrixInverse).AABBFloat; 261 262 - float separation = DrawSize.X / (points.Count - 1); 263 264 for (int i = 0; i < points.Count - 1; i++) 265 { ··· 275 Color4 colour = DrawColourInfo.Colour; 276 277 // colouring is applied in the order of interest to a viewer. 278 - colour = Interpolation.ValueAt(points[i].MidIntensity / midMax, colour, MidColour, 0, 1); 279 // high end (cymbal) can help find beat, so give it priority over mids. 280 - colour = Interpolation.ValueAt(points[i].HighIntensity / highMax, colour, HighColour, 0, 1); 281 // low end (bass drum) is generally the best visual aid for beat matching, so give it priority over high/mid. 282 - colour = Interpolation.ValueAt(points[i].LowIntensity / lowMax, colour, LowColour, 0, 1); 283 284 Quad quadToDraw; 285 286 - switch (Channels) 287 { 288 default: 289 case 2: 290 { 291 - float height = DrawSize.Y / 2; 292 quadToDraw = new Quad( 293 new Vector2(leftX, height - points[i].Amplitude[0] * height), 294 new Vector2(rightX, height - points[i + 1].Amplitude[0] * height), ··· 300 case 1: 301 { 302 quadToDraw = new Quad( 303 - new Vector2(leftX, DrawSize.Y - points[i].Amplitude[0] * DrawSize.Y), 304 - new Vector2(rightX, DrawSize.Y - points[i + 1].Amplitude[0] * DrawSize.Y), 305 - new Vector2(leftX, DrawSize.Y), 306 - new Vector2(rightX, DrawSize.Y) 307 ); 308 break; 309 } 310 } 311 312 quadToDraw *= DrawInfo.Matrix; 313 - Texture.DrawQuad(quadToDraw, colour, null, vertexBatch.AddAction, Vector2.Divide(localInflationAmount, quadToDraw.Size)); 314 } 315 316 - Shader.Unbind(); 317 } 318 319 protected override void Dispose(bool isDisposing)
··· 182 cancelSource = null; 183 } 184 185 + protected override DrawNode CreateDrawNode() => new WaveformDrawNode(this); 186 187 protected override void Dispose(bool isDisposing) 188 { ··· 192 193 private class WaveformDrawNode : DrawNode 194 { 195 + private IShader shader; 196 + private Texture texture; 197 198 + private IReadOnlyList<WaveformPoint> points; 199 200 + private Vector2 drawSize; 201 + private int channels; 202 203 + private Color4 lowColour; 204 + private Color4 midColour; 205 + private Color4 highColour; 206 207 private double highMax; 208 private double midMax; 209 private double lowMax; 210 211 + protected new WaveformGraph Source => (WaveformGraph)base.Source; 212 + 213 + public WaveformDrawNode(WaveformGraph source) 214 + : base(source) 215 { 216 + } 217 218 + public override void ApplyState() 219 + { 220 + base.ApplyState(); 221 + 222 + shader = Source.shader; 223 + texture = Source.texture; 224 + drawSize = Source.DrawSize; 225 + points = Source.ResampledWaveform?.GetPoints(); 226 + channels = Source.ResampledWaveform?.GetChannels() ?? 0; 227 + lowColour = Source.lowColour ?? DrawColourInfo.Colour; 228 + midColour = Source.midColour ?? DrawColourInfo.Colour; 229 + highColour = Source.highColour ?? DrawColourInfo.Colour; 230 + 231 + if (points?.Any() == true) 232 + { 233 + highMax = points.Max(p => p.HighIntensity); 234 + midMax = points.Max(p => p.MidIntensity); 235 + lowMax = points.Max(p => p.LowIntensity); 236 } 237 } 238 ··· 242 { 243 base.Draw(vertexAction); 244 245 + if (texture?.Available != true || points == null || points.Count == 0) 246 return; 247 248 + shader.Bind(); 249 + texture.TextureGL.Bind(); 250 251 Vector2 localInflationAmount = new Vector2(0, 1) * DrawInfo.MatrixInverse.ExtractScale().Xy; 252 ··· 255 // Since the points are generated in the local coordinate space, we need to convert the screen space masking quad coordinates into the local coordinate space 256 RectangleF localMaskingRectangle = (Quad.FromRectangle(GLWrapper.CurrentMaskingInfo.ScreenSpaceAABB) * DrawInfo.MatrixInverse).AABBFloat; 257 258 + float separation = drawSize.X / (points.Count - 1); 259 260 for (int i = 0; i < points.Count - 1; i++) 261 { ··· 271 Color4 colour = DrawColourInfo.Colour; 272 273 // colouring is applied in the order of interest to a viewer. 274 + colour = Interpolation.ValueAt(points[i].MidIntensity / midMax, colour, midColour, 0, 1); 275 // high end (cymbal) can help find beat, so give it priority over mids. 276 + colour = Interpolation.ValueAt(points[i].HighIntensity / highMax, colour, highColour, 0, 1); 277 // low end (bass drum) is generally the best visual aid for beat matching, so give it priority over high/mid. 278 + colour = Interpolation.ValueAt(points[i].LowIntensity / lowMax, colour, lowColour, 0, 1); 279 280 Quad quadToDraw; 281 282 + switch (channels) 283 { 284 default: 285 case 2: 286 { 287 + float height = drawSize.Y / 2; 288 quadToDraw = new Quad( 289 new Vector2(leftX, height - points[i].Amplitude[0] * height), 290 new Vector2(rightX, height - points[i + 1].Amplitude[0] * height), ··· 296 case 1: 297 { 298 quadToDraw = new Quad( 299 + new Vector2(leftX, drawSize.Y - points[i].Amplitude[0] * drawSize.Y), 300 + new Vector2(rightX, drawSize.Y - points[i + 1].Amplitude[0] * drawSize.Y), 301 + new Vector2(leftX, drawSize.Y), 302 + new Vector2(rightX, drawSize.Y) 303 ); 304 break; 305 } 306 } 307 308 quadToDraw *= DrawInfo.Matrix; 309 + texture.DrawQuad(quadToDraw, colour, null, vertexBatch.AddAction, Vector2.Divide(localInflationAmount, quadToDraw.Size)); 310 } 311 312 + shader.Unbind(); 313 } 314 315 protected override void Dispose(bool isDisposing)
+4 -61
osu.Framework/Graphics/Containers/BufferedContainer.cs
··· 35 /// appearance of the container at the cost of performance. Such effects include 36 /// uniform fading of children, blur, and other post-processing effects. 37 /// </summary> 38 - public class BufferedContainer<T> : Container<T>, IBufferedContainer 39 where T : Drawable 40 { 41 private bool drawOriginal; ··· 240 blurShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.BLUR); 241 } 242 243 - private bool addChildDrawNodes; 244 - internal override bool AddChildDrawNodes => addChildDrawNodes; 245 - 246 private readonly BufferedContainerDrawNodeSharedData sharedData = new BufferedContainerDrawNodeSharedData(); 247 248 - protected override DrawNode CreateDrawNode() => new BufferedContainerDrawNode(); 249 - 250 - protected override void ApplyDrawNode(DrawNode node) 251 - { 252 - BufferedContainerDrawNode n = (BufferedContainerDrawNode)node; 253 - 254 - n.ScreenSpaceDrawRectangle = ScreenSpaceDrawQuad.AABBFloat; 255 - n.FilteringMode = pixelSnapping ? All.Nearest : All.Linear; 256 - 257 - n.UpdateVersion = updateVersion; 258 - n.BackgroundColour = backgroundColour; 259 - 260 - BlendingParameters localEffectBlending = EffectBlending; 261 - if (localEffectBlending.Mode == BlendingMode.Inherit) 262 - localEffectBlending.Mode = Blending.Mode; 263 - 264 - if (localEffectBlending.RGBEquation == BlendingEquation.Inherit) 265 - localEffectBlending.RGBEquation = Blending.RGBEquation; 266 - 267 - if (localEffectBlending.AlphaEquation == BlendingEquation.Inherit) 268 - localEffectBlending.AlphaEquation = Blending.AlphaEquation; 269 - 270 - n.EffectColour = effectColour; 271 - n.EffectBlending = localEffectBlending; 272 - n.EffectPlacement = effectPlacement; 273 - 274 - n.DrawOriginal = drawOriginal; 275 - n.BlurSigma = blurSigma; 276 - n.BlurRadius = new Vector2I(Blur.KernelSize(BlurSigma.X), Blur.KernelSize(BlurSigma.Y)); 277 - n.BlurRotation = blurRotation; 278 - 279 - n.Formats.Clear(); 280 - n.Formats.AddRange(attachedFormats); 281 - 282 - n.BlurShader = blurShader; 283 - 284 - n.SharedData = sharedData; 285 - 286 - base.ApplyDrawNode(node); 287 - 288 - // Our own draw node should contain our correct color, hence we have 289 - // to undo our overridden DrawInfo getter here. 290 - n.DrawColourInfo = new DrawColourInfo(base.DrawColourInfo.Colour, n.DrawColourInfo.Blending); 291 - 292 - // Only need to generate child draw nodes if the framebuffers will get redrawn this time around 293 - addChildDrawNodes = n.RequiresRedraw; 294 - } 295 - 296 - internal override DrawNode GenerateDrawNodeSubtree(ulong frame, int treeIndex, bool forceNewDrawNode) 297 - { 298 - var result = base.GenerateDrawNodeSubtree(frame, treeIndex, forceNewDrawNode); 299 - 300 - // The framebuffers may be redrawn this time around, but will be cached the next time around 301 - addChildDrawNodes = false; 302 - 303 - return result; 304 - } 305 306 private readonly List<RenderbufferInternalFormat> attachedFormats = new List<RenderbufferInternalFormat>(); 307 ··· 371 372 childrenUpdateVersion = updateVersion; 373 } 374 375 public override DrawColourInfo DrawColourInfo 376 {
··· 35 /// appearance of the container at the cost of performance. Such effects include 36 /// uniform fading of children, blur, and other post-processing effects. 37 /// </summary> 38 + public partial class BufferedContainer<T> : Container<T>, IBufferedContainer 39 where T : Drawable 40 { 41 private bool drawOriginal; ··· 240 blurShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.BLUR); 241 } 242 243 private readonly BufferedContainerDrawNodeSharedData sharedData = new BufferedContainerDrawNodeSharedData(); 244 245 + protected override DrawNode CreateDrawNode() => new BufferedContainerDrawNode(this, sharedData); 246 247 private readonly List<RenderbufferInternalFormat> attachedFormats = new List<RenderbufferInternalFormat>(); 248 ··· 312 313 childrenUpdateVersion = updateVersion; 314 } 315 + 316 + private DrawColourInfo baseDrawColourInfo => base.DrawColourInfo; 317 318 public override DrawColourInfo DrawColourInfo 319 {
-273
osu.Framework/Graphics/Containers/BufferedContainerDrawNode.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 osu.Framework.Graphics.OpenGL; 6 - using osu.Framework.Graphics.OpenGL.Buffers; 7 - using osuTK; 8 - using osuTK.Graphics.ES30; 9 - using osuTK.Graphics; 10 - using osu.Framework.Graphics.Primitives; 11 - using osu.Framework.Allocation; 12 - using osu.Framework.Graphics.Shaders; 13 - using System; 14 - using osu.Framework.Graphics.Colour; 15 - using osu.Framework.Graphics.OpenGL.Vertices; 16 - using System.Diagnostics; 17 - 18 - namespace osu.Framework.Graphics.Containers 19 - { 20 - public class BufferedContainerDrawNodeSharedData : IDisposable 21 - { 22 - /// <summary> 23 - /// The <see cref="FrameBuffer"/>s to render to. 24 - /// These are used in a ping-pong manner to render effects <see cref="BufferedContainerDrawNode"/>. 25 - /// </summary> 26 - public readonly FrameBuffer[] FrameBuffers = new FrameBuffer[3]; 27 - 28 - /// <summary> 29 - /// The version of drawn contents currently present in <see cref="FrameBuffers"/>. 30 - /// This should only be modified by <see cref="BufferedContainerDrawNode"/>. 31 - /// </summary> 32 - public long DrawVersion = -1; 33 - 34 - public BufferedContainerDrawNodeSharedData() 35 - { 36 - for (int i = 0; i < FrameBuffers.Length; i++) 37 - FrameBuffers[i] = new FrameBuffer(); 38 - } 39 - 40 - ~BufferedContainerDrawNodeSharedData() 41 - { 42 - Dispose(false); 43 - } 44 - 45 - public void Dispose() 46 - { 47 - Dispose(true); 48 - GC.SuppressFinalize(this); 49 - } 50 - 51 - protected virtual void Dispose(bool isDisposing) 52 - { 53 - for (int i = 0; i < FrameBuffers.Length; i++) 54 - FrameBuffers[i].Dispose(); 55 - } 56 - } 57 - 58 - public class BufferedContainerDrawNode : CompositeDrawNode 59 - { 60 - public bool DrawOriginal; 61 - public Color4 BackgroundColour; 62 - public ColourInfo EffectColour; 63 - public BlendingParameters EffectBlending; 64 - public EffectPlacement EffectPlacement; 65 - 66 - public Vector2 BlurSigma; 67 - public Vector2I BlurRadius; 68 - public float BlurRotation; 69 - 70 - public long UpdateVersion; 71 - 72 - public RectangleF ScreenSpaceDrawRectangle; 73 - public All FilteringMode; 74 - 75 - /// <summary> 76 - /// The <see cref="RenderbufferInternalFormat"/>s to use when drawing children. 77 - /// </summary> 78 - public readonly List<RenderbufferInternalFormat> Formats = new List<RenderbufferInternalFormat>(); 79 - 80 - /// <summary> 81 - /// The <see cref="IShader"/> to use when rendering blur effects. 82 - /// </summary> 83 - public IShader BlurShader; 84 - 85 - public BufferedContainerDrawNodeSharedData SharedData; 86 - 87 - /// <summary> 88 - /// Whether this <see cref="BufferedContainerDrawNode"/> should have its children re-drawn. 89 - /// </summary> 90 - public bool RequiresRedraw => UpdateVersion > SharedData.DrawVersion; 91 - 92 - private ValueInvokeOnDisposal establishFrameBufferViewport(Vector2 roundedSize) 93 - { 94 - // Disable masking for generating the frame buffer since masking will be re-applied 95 - // when actually drawing later on anyways. This allows more information to be captured 96 - // in the frame buffer and helps with cached buffers being re-used. 97 - RectangleI screenSpaceMaskingRect = new RectangleI((int)Math.Floor(ScreenSpaceDrawRectangle.X), (int)Math.Floor(ScreenSpaceDrawRectangle.Y), (int)roundedSize.X + 1, (int)roundedSize.Y + 1); 98 - 99 - GLWrapper.PushMaskingInfo(new MaskingInfo 100 - { 101 - ScreenSpaceAABB = screenSpaceMaskingRect, 102 - MaskingRect = ScreenSpaceDrawRectangle, 103 - ToMaskingSpace = Matrix3.Identity, 104 - BlendRange = 1, 105 - AlphaExponent = 1, 106 - }, true); 107 - 108 - // Match viewport to FrameBuffer such that we don't draw unnecessary pixels. 109 - GLWrapper.PushViewport(new RectangleI(0, 0, (int)roundedSize.X, (int)roundedSize.Y)); 110 - 111 - return new ValueInvokeOnDisposal(returnViewport); 112 - } 113 - 114 - private void returnViewport() 115 - { 116 - GLWrapper.PopViewport(); 117 - GLWrapper.PopMaskingInfo(); 118 - } 119 - 120 - private ValueInvokeOnDisposal bindFrameBuffer(FrameBuffer frameBuffer, Vector2 requestedSize) 121 - { 122 - if (!frameBuffer.IsInitialized) 123 - frameBuffer.Initialize(true, FilteringMode); 124 - 125 - // These additional render buffers are only required if e.g. depth 126 - // or stencil information needs to also be stored somewhere. 127 - foreach (var f in Formats) 128 - frameBuffer.Attach(f); 129 - 130 - // This setter will also take care of allocating a texture of appropriate size within the framebuffer. 131 - frameBuffer.Size = requestedSize; 132 - 133 - frameBuffer.Bind(); 134 - 135 - return new ValueInvokeOnDisposal(frameBuffer.Unbind); 136 - } 137 - 138 - private void drawFrameBufferToBackBuffer(FrameBuffer frameBuffer, RectangleF drawRectangle, ColourInfo colourInfo) 139 - { 140 - // The strange Y coordinate and Height are a result of OpenGL coordinate systems having Y grow upwards and not downwards. 141 - RectangleF textureRect = new RectangleF(0, frameBuffer.Texture.Height, frameBuffer.Texture.Width, -frameBuffer.Texture.Height); 142 - if (frameBuffer.Texture.Bind()) 143 - // Color was already applied by base.Draw(); no need to re-apply. Thus we use White here. 144 - frameBuffer.Texture.DrawQuad(drawRectangle, textureRect, colourInfo); 145 - } 146 - 147 - private void drawChildren(Action<TexturedVertex2D> vertexAction, Vector2 frameBufferSize) 148 - { 149 - // Fill the frame buffer with drawn children 150 - using (bindFrameBuffer(currentFrameBuffer, frameBufferSize)) 151 - { 152 - // We need to draw children as if they were zero-based to the top-left of the texture. 153 - // We can do this by adding a translation component to our (orthogonal) projection matrix. 154 - GLWrapper.PushOrtho(ScreenSpaceDrawRectangle); 155 - 156 - GLWrapper.Clear(new ClearInfo(BackgroundColour)); 157 - base.Draw(vertexAction); 158 - 159 - GLWrapper.PopOrtho(); 160 - } 161 - } 162 - 163 - private void drawBlurredFrameBuffer(int kernelRadius, float sigma, float blurRotation) 164 - { 165 - FrameBuffer source = currentFrameBuffer; 166 - FrameBuffer target = advanceFrameBuffer(); 167 - 168 - GLWrapper.SetBlend(new BlendingInfo(BlendingMode.None)); 169 - 170 - using (bindFrameBuffer(target, source.Size)) 171 - { 172 - BlurShader.GetUniform<int>(@"g_Radius").UpdateValue(ref kernelRadius); 173 - BlurShader.GetUniform<float>(@"g_Sigma").UpdateValue(ref sigma); 174 - 175 - Vector2 size = source.Size; 176 - BlurShader.GetUniform<Vector2>(@"g_TexSize").UpdateValue(ref size); 177 - 178 - float radians = -MathHelper.DegreesToRadians(blurRotation); 179 - Vector2 blur = new Vector2((float)Math.Cos(radians), (float)Math.Sin(radians)); 180 - BlurShader.GetUniform<Vector2>(@"g_BlurDirection").UpdateValue(ref blur); 181 - 182 - BlurShader.Bind(); 183 - drawFrameBufferToBackBuffer(source, new RectangleF(0, 0, source.Texture.Width, source.Texture.Height), ColourInfo.SingleColour(Color4.White)); 184 - BlurShader.Unbind(); 185 - } 186 - } 187 - 188 - private int currentFrameBufferIndex; 189 - private FrameBuffer currentFrameBuffer => SharedData.FrameBuffers[currentFrameBufferIndex]; 190 - private FrameBuffer advanceFrameBuffer() => SharedData.FrameBuffers[currentFrameBufferIndex = (currentFrameBufferIndex + 1) % 2]; 191 - 192 - /// <summary> 193 - /// Makes sure the first frame buffer is always the one we want to draw from. 194 - /// This saves us the need to sync the draw indices across draw node trees 195 - /// since the SharedData.FrameBuffers array is already shared. 196 - /// </summary> 197 - private void finalizeFrameBuffer() 198 - { 199 - if (currentFrameBufferIndex != 0) 200 - { 201 - Trace.Assert(currentFrameBufferIndex == 1, 202 - $"Only the first two framebuffers should be the last to be written to at the end of {nameof(Draw)}."); 203 - 204 - FrameBuffer temp = SharedData.FrameBuffers[0]; 205 - SharedData.FrameBuffers[0] = SharedData.FrameBuffers[1]; 206 - SharedData.FrameBuffers[1] = temp; 207 - 208 - currentFrameBufferIndex = 0; 209 - } 210 - } 211 - 212 - // Our effects will be drawn into framebuffers 0 and 1. If we want to preserve the originally 213 - // drawn children we need to put them in a separate buffer; in this case buffer 2. Otherwise, 214 - // we do not want to allocate a third buffer for nothing and hence we start with 0. 215 - private int originalIndex => DrawOriginal && (BlurRadius.X > 0 || BlurRadius.Y > 0) ? 2 : 0; 216 - 217 - public override void Draw(Action<TexturedVertex2D> vertexAction) 218 - { 219 - currentFrameBufferIndex = originalIndex; 220 - 221 - Vector2 frameBufferSize = new Vector2((float)Math.Ceiling(ScreenSpaceDrawRectangle.Width), (float)Math.Ceiling(ScreenSpaceDrawRectangle.Height)); 222 - if (RequiresRedraw) 223 - { 224 - SharedData.DrawVersion = UpdateVersion; 225 - 226 - using (establishFrameBufferViewport(frameBufferSize)) 227 - { 228 - drawChildren(vertexAction, frameBufferSize); 229 - 230 - // Blur post-processing in case a blur radius is defined. 231 - if (BlurRadius.X > 0 || BlurRadius.Y > 0) 232 - { 233 - GL.Disable(EnableCap.ScissorTest); 234 - 235 - if (BlurRadius.X > 0) drawBlurredFrameBuffer(BlurRadius.X, BlurSigma.X, BlurRotation); 236 - if (BlurRadius.Y > 0) drawBlurredFrameBuffer(BlurRadius.Y, BlurSigma.Y, BlurRotation + 90); 237 - 238 - GL.Enable(EnableCap.ScissorTest); 239 - } 240 - } 241 - 242 - finalizeFrameBuffer(); 243 - } 244 - 245 - RectangleF drawRectangle = FilteringMode == All.Nearest 246 - ? new RectangleF(ScreenSpaceDrawRectangle.X, ScreenSpaceDrawRectangle.Y, frameBufferSize.X, frameBufferSize.Y) 247 - : ScreenSpaceDrawRectangle; 248 - 249 - Shader.Bind(); 250 - 251 - if (DrawOriginal && EffectPlacement == EffectPlacement.InFront) 252 - { 253 - GLWrapper.SetBlend(DrawColourInfo.Blending); 254 - drawFrameBufferToBackBuffer(SharedData.FrameBuffers[originalIndex], drawRectangle, DrawColourInfo.Colour); 255 - } 256 - 257 - // Blit the final framebuffer to screen. 258 - GLWrapper.SetBlend(new BlendingInfo(EffectBlending)); 259 - 260 - ColourInfo effectColour = DrawColourInfo.Colour; 261 - effectColour.ApplyChild(EffectColour); 262 - drawFrameBufferToBackBuffer(SharedData.FrameBuffers[0], drawRectangle, effectColour); 263 - 264 - if (DrawOriginal && EffectPlacement == EffectPlacement.Behind) 265 - { 266 - GLWrapper.SetBlend(DrawColourInfo.Blending); 267 - drawFrameBufferToBackBuffer(SharedData.FrameBuffers[originalIndex], drawRectangle, DrawColourInfo.Colour); 268 - } 269 - 270 - Shader.Unbind(); 271 - } 272 - } 273 - }
···
+320
osu.Framework/Graphics/Containers/BufferedContainer_DrawNode.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 osu.Framework.Graphics.OpenGL; 6 + using osu.Framework.Graphics.OpenGL.Buffers; 7 + using osuTK; 8 + using osuTK.Graphics.ES30; 9 + using osuTK.Graphics; 10 + using osu.Framework.Graphics.Primitives; 11 + using osu.Framework.Allocation; 12 + using osu.Framework.Graphics.Shaders; 13 + using System; 14 + using osu.Framework.Graphics.Colour; 15 + using osu.Framework.Graphics.OpenGL.Vertices; 16 + using System.Diagnostics; 17 + using osu.Framework.MathUtils; 18 + 19 + namespace osu.Framework.Graphics.Containers 20 + { 21 + public partial class BufferedContainer<T> 22 + { 23 + private class BufferedContainerDrawNode : CompositeDrawableDrawNode 24 + { 25 + protected new BufferedContainer<T> Source => (BufferedContainer<T>)base.Source; 26 + 27 + private bool drawOriginal; 28 + private Color4 backgroundColour; 29 + private ColourInfo effectColour; 30 + private BlendingParameters effectBlending; 31 + private EffectPlacement effectPlacement; 32 + 33 + private Vector2 blurSigma; 34 + private Vector2I blurRadius; 35 + private float blurRotation; 36 + 37 + private long updateVersion; 38 + 39 + private RectangleF screenSpaceDrawRectangle; 40 + private All filteringMode; 41 + 42 + private readonly List<RenderbufferInternalFormat> formats = new List<RenderbufferInternalFormat>(); 43 + 44 + private IShader blurShader; 45 + 46 + private readonly BufferedContainerDrawNodeSharedData sharedData; 47 + 48 + public BufferedContainerDrawNode(BufferedContainer<T> source, BufferedContainerDrawNodeSharedData sharedData) 49 + : base(source) 50 + { 51 + this.sharedData = sharedData; 52 + } 53 + 54 + public override void ApplyState() 55 + { 56 + base.ApplyState(); 57 + 58 + screenSpaceDrawRectangle = Source.ScreenSpaceDrawQuad.AABBFloat; 59 + filteringMode = Source.PixelSnapping ? All.Nearest : All.Linear; 60 + 61 + updateVersion = Source.updateVersion; 62 + backgroundColour = Source.BackgroundColour; 63 + 64 + BlendingParameters localEffectBlending = Source.EffectBlending; 65 + if (localEffectBlending.Mode == BlendingMode.Inherit) 66 + localEffectBlending.Mode = Source.Blending.Mode; 67 + 68 + if (localEffectBlending.RGBEquation == BlendingEquation.Inherit) 69 + localEffectBlending.RGBEquation = Source.Blending.RGBEquation; 70 + 71 + if (localEffectBlending.AlphaEquation == BlendingEquation.Inherit) 72 + localEffectBlending.AlphaEquation = Source.Blending.AlphaEquation; 73 + 74 + effectColour = Source.EffectColour; 75 + effectBlending = localEffectBlending; 76 + effectPlacement = Source.EffectPlacement; 77 + 78 + drawOriginal = Source.DrawOriginal; 79 + blurSigma = Source.BlurSigma; 80 + blurRadius = new Vector2I(Blur.KernelSize(blurSigma.X), Blur.KernelSize(blurSigma.Y)); 81 + blurRotation = Source.BlurRotation; 82 + 83 + formats.Clear(); 84 + formats.AddRange(Source.attachedFormats); 85 + 86 + blurShader = Source.blurShader; 87 + 88 + // BufferedContainer overrides DrawColourInfo for children, but needs to be reset to draw ourselves 89 + DrawColourInfo = Source.baseDrawColourInfo; 90 + } 91 + 92 + public override bool AddChildDrawNodes => RequiresRedraw; 93 + 94 + /// <summary> 95 + /// Whether this <see cref="BufferedContainerDrawNode"/> should have its children re-drawn. 96 + /// </summary> 97 + public bool RequiresRedraw => updateVersion > sharedData.DrawVersion; 98 + 99 + private ValueInvokeOnDisposal establishFrameBufferViewport(Vector2 roundedSize) 100 + { 101 + // Disable masking for generating the frame buffer since masking will be re-applied 102 + // when actually drawing later on anyways. This allows more information to be captured 103 + // in the frame buffer and helps with cached buffers being re-used. 104 + RectangleI screenSpaceMaskingRect = new RectangleI((int)Math.Floor(screenSpaceDrawRectangle.X), (int)Math.Floor(screenSpaceDrawRectangle.Y), (int)roundedSize.X + 1, 105 + (int)roundedSize.Y + 1); 106 + 107 + GLWrapper.PushMaskingInfo(new MaskingInfo 108 + { 109 + ScreenSpaceAABB = screenSpaceMaskingRect, 110 + MaskingRect = screenSpaceDrawRectangle, 111 + ToMaskingSpace = Matrix3.Identity, 112 + BlendRange = 1, 113 + AlphaExponent = 1, 114 + }, true); 115 + 116 + // Match viewport to FrameBuffer such that we don't draw unnecessary pixels. 117 + GLWrapper.PushViewport(new RectangleI(0, 0, (int)roundedSize.X, (int)roundedSize.Y)); 118 + 119 + return new ValueInvokeOnDisposal(returnViewport); 120 + } 121 + 122 + private void returnViewport() 123 + { 124 + GLWrapper.PopViewport(); 125 + GLWrapper.PopMaskingInfo(); 126 + } 127 + 128 + private ValueInvokeOnDisposal bindFrameBuffer(FrameBuffer frameBuffer, Vector2 requestedSize) 129 + { 130 + if (!frameBuffer.IsInitialized) 131 + frameBuffer.Initialize(true, filteringMode); 132 + 133 + // These additional render buffers are only required if e.g. depth 134 + // or stencil information needs to also be stored somewhere. 135 + foreach (var f in formats) 136 + frameBuffer.Attach(f); 137 + 138 + // This setter will also take care of allocating a texture of appropriate size within the framebuffer. 139 + frameBuffer.Size = requestedSize; 140 + 141 + frameBuffer.Bind(); 142 + 143 + return new ValueInvokeOnDisposal(frameBuffer.Unbind); 144 + } 145 + 146 + private void drawFrameBufferToBackBuffer(FrameBuffer frameBuffer, RectangleF drawRectangle, ColourInfo colourInfo) 147 + { 148 + // The strange Y coordinate and Height are a result of OpenGL coordinate systems having Y grow upwards and not downwards. 149 + RectangleF textureRect = new RectangleF(0, frameBuffer.Texture.Height, frameBuffer.Texture.Width, -frameBuffer.Texture.Height); 150 + if (frameBuffer.Texture.Bind()) 151 + // Color was already applied by base.Draw(); no need to re-apply. Thus we use White here. 152 + frameBuffer.Texture.DrawQuad(drawRectangle, textureRect, colourInfo); 153 + } 154 + 155 + private void drawChildren(Action<TexturedVertex2D> vertexAction, Vector2 frameBufferSize) 156 + { 157 + // Fill the frame buffer with drawn children 158 + using (bindFrameBuffer(currentFrameBuffer, frameBufferSize)) 159 + { 160 + // We need to draw children as if they were zero-based to the top-left of the texture. 161 + // We can do this by adding a translation component to our (orthogonal) projection matrix. 162 + GLWrapper.PushOrtho(screenSpaceDrawRectangle); 163 + 164 + GLWrapper.Clear(new ClearInfo(backgroundColour)); 165 + base.Draw(vertexAction); 166 + 167 + GLWrapper.PopOrtho(); 168 + } 169 + } 170 + 171 + private void drawBlurredFrameBuffer(int kernelRadius, float sigma, float blurRotation) 172 + { 173 + FrameBuffer source = currentFrameBuffer; 174 + FrameBuffer target = advanceFrameBuffer(); 175 + 176 + GLWrapper.SetBlend(new BlendingInfo(BlendingMode.None)); 177 + 178 + using (bindFrameBuffer(target, source.Size)) 179 + { 180 + blurShader.GetUniform<int>(@"g_Radius").UpdateValue(ref kernelRadius); 181 + blurShader.GetUniform<float>(@"g_Sigma").UpdateValue(ref sigma); 182 + 183 + Vector2 size = source.Size; 184 + blurShader.GetUniform<Vector2>(@"g_TexSize").UpdateValue(ref size); 185 + 186 + float radians = -MathHelper.DegreesToRadians(blurRotation); 187 + Vector2 blur = new Vector2((float)Math.Cos(radians), (float)Math.Sin(radians)); 188 + blurShader.GetUniform<Vector2>(@"g_BlurDirection").UpdateValue(ref blur); 189 + 190 + blurShader.Bind(); 191 + drawFrameBufferToBackBuffer(source, new RectangleF(0, 0, source.Texture.Width, source.Texture.Height), ColourInfo.SingleColour(Color4.White)); 192 + blurShader.Unbind(); 193 + } 194 + } 195 + 196 + private int currentFrameBufferIndex; 197 + private FrameBuffer currentFrameBuffer => sharedData.FrameBuffers[currentFrameBufferIndex]; 198 + private FrameBuffer advanceFrameBuffer() => sharedData.FrameBuffers[currentFrameBufferIndex = (currentFrameBufferIndex + 1) % 2]; 199 + 200 + /// <summary> 201 + /// Makes sure the first frame buffer is always the one we want to draw from. 202 + /// This saves us the need to sync the draw indices across draw node trees 203 + /// since the SharedData.FrameBuffers array is already shared. 204 + /// </summary> 205 + private void finalizeFrameBuffer() 206 + { 207 + if (currentFrameBufferIndex != 0) 208 + { 209 + Trace.Assert(currentFrameBufferIndex == 1, 210 + $"Only the first two framebuffers should be the last to be written to at the end of {nameof(Draw)}."); 211 + 212 + FrameBuffer temp = sharedData.FrameBuffers[0]; 213 + sharedData.FrameBuffers[0] = sharedData.FrameBuffers[1]; 214 + sharedData.FrameBuffers[1] = temp; 215 + 216 + currentFrameBufferIndex = 0; 217 + } 218 + } 219 + 220 + // Our effects will be drawn into framebuffers 0 and 1. If we want to preserve the originally 221 + // drawn children we need to put them in a separate buffer; in this case buffer 2. Otherwise, 222 + // we do not want to allocate a third buffer for nothing and hence we start with 0. 223 + private int originalIndex => drawOriginal && (blurRadius.X > 0 || blurRadius.Y > 0) ? 2 : 0; 224 + 225 + public override void Draw(Action<TexturedVertex2D> vertexAction) 226 + { 227 + currentFrameBufferIndex = originalIndex; 228 + 229 + Vector2 frameBufferSize = new Vector2((float)Math.Ceiling(screenSpaceDrawRectangle.Width), (float)Math.Ceiling(screenSpaceDrawRectangle.Height)); 230 + if (RequiresRedraw) 231 + { 232 + sharedData.DrawVersion = updateVersion; 233 + 234 + using (establishFrameBufferViewport(frameBufferSize)) 235 + { 236 + drawChildren(vertexAction, frameBufferSize); 237 + 238 + // Blur post-processing in case a blur radius is defined. 239 + if (blurRadius.X > 0 || blurRadius.Y > 0) 240 + { 241 + GL.Disable(EnableCap.ScissorTest); 242 + 243 + if (blurRadius.X > 0) drawBlurredFrameBuffer(blurRadius.X, blurSigma.X, blurRotation); 244 + if (blurRadius.Y > 0) drawBlurredFrameBuffer(blurRadius.Y, blurSigma.Y, blurRotation + 90); 245 + 246 + GL.Enable(EnableCap.ScissorTest); 247 + } 248 + } 249 + 250 + finalizeFrameBuffer(); 251 + } 252 + 253 + RectangleF drawRectangle = filteringMode == All.Nearest 254 + ? new RectangleF(screenSpaceDrawRectangle.X, screenSpaceDrawRectangle.Y, frameBufferSize.X, frameBufferSize.Y) 255 + : screenSpaceDrawRectangle; 256 + 257 + Shader.Bind(); 258 + 259 + if (drawOriginal && effectPlacement == EffectPlacement.InFront) 260 + { 261 + GLWrapper.SetBlend(DrawColourInfo.Blending); 262 + drawFrameBufferToBackBuffer(sharedData.FrameBuffers[originalIndex], drawRectangle, DrawColourInfo.Colour); 263 + } 264 + 265 + // Blit the final framebuffer to screen. 266 + GLWrapper.SetBlend(new BlendingInfo(effectBlending)); 267 + 268 + ColourInfo finalEffectColour = DrawColourInfo.Colour; 269 + finalEffectColour.ApplyChild(effectColour); 270 + drawFrameBufferToBackBuffer(sharedData.FrameBuffers[0], drawRectangle, finalEffectColour); 271 + 272 + if (drawOriginal && effectPlacement == EffectPlacement.Behind) 273 + { 274 + GLWrapper.SetBlend(DrawColourInfo.Blending); 275 + drawFrameBufferToBackBuffer(sharedData.FrameBuffers[originalIndex], drawRectangle, DrawColourInfo.Colour); 276 + } 277 + 278 + Shader.Unbind(); 279 + } 280 + } 281 + 282 + private class BufferedContainerDrawNodeSharedData : IDisposable 283 + { 284 + /// <summary> 285 + /// The <see cref="FrameBuffer"/>s to render to. 286 + /// These are used in a ping-pong manner to render effects <see cref="BufferedContainerDrawNode"/>. 287 + /// </summary> 288 + public readonly FrameBuffer[] FrameBuffers = new FrameBuffer[3]; 289 + 290 + /// <summary> 291 + /// The version of drawn contents currently present in <see cref="FrameBuffers"/>. 292 + /// This should only be modified by <see cref="BufferedContainerDrawNode"/>. 293 + /// </summary> 294 + public long DrawVersion = -1; 295 + 296 + public BufferedContainerDrawNodeSharedData() 297 + { 298 + for (int i = 0; i < FrameBuffers.Length; i++) 299 + FrameBuffers[i] = new FrameBuffer(); 300 + } 301 + 302 + ~BufferedContainerDrawNodeSharedData() 303 + { 304 + dispose(); 305 + } 306 + 307 + public void Dispose() 308 + { 309 + dispose(); 310 + GC.SuppressFinalize(this); 311 + } 312 + 313 + private void dispose() 314 + { 315 + for (int i = 0; i < FrameBuffers.Length; i++) 316 + FrameBuffers[i].Dispose(); 317 + } 318 + } 319 + } 320 + }
-215
osu.Framework/Graphics/Containers/CompositeDrawNode.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 osu.Framework.Graphics.OpenGL; 6 - using osu.Framework.Graphics.Primitives; 7 - using osu.Framework.Graphics.Shaders; 8 - using osu.Framework.Graphics.Batches; 9 - using osuTK; 10 - using osu.Framework.Graphics.Textures; 11 - using osu.Framework.Graphics.Colour; 12 - using System; 13 - using osu.Framework.Graphics.OpenGL.Vertices; 14 - 15 - namespace osu.Framework.Graphics.Containers 16 - { 17 - /// <summary> 18 - /// Types of edge effects that can be applied to <see cref="CompositeDrawable"/>s. 19 - /// </summary> 20 - public enum EdgeEffectType 21 - { 22 - None, 23 - Glow, 24 - Shadow, 25 - } 26 - 27 - /// <summary> 28 - /// Parametrizes the appearance of an edge effect. 29 - /// </summary> 30 - public struct EdgeEffectParameters : IEquatable<EdgeEffectParameters> 31 - { 32 - /// <summary> 33 - /// Colour of the edge effect. 34 - /// </summary> 35 - public SRGBColour Colour; 36 - 37 - /// <summary> 38 - /// Positional offset applied to the edge effect. 39 - /// Useful for off-center shadows. 40 - /// </summary> 41 - public Vector2 Offset; 42 - 43 - /// <summary> 44 - /// The type of the edge effect. 45 - /// </summary> 46 - public EdgeEffectType Type; 47 - 48 - /// <summary> 49 - /// How round the edge effect should appear. Adds to the <see cref="CompositeDrawable.CornerRadius"/> 50 - /// of the corresponding <see cref="CompositeDrawable"/>. Not to confuse with the <see cref="Radius"/>. 51 - /// </summary> 52 - public float Roundness; 53 - 54 - /// <summary> 55 - /// How "thick" the edge effect is around the <see cref="CompositeDrawable"/>. In other words: At what distance 56 - /// from the <see cref="CompositeDrawable"/>'s border the edge effect becomes fully invisible. 57 - /// </summary> 58 - public float Radius; 59 - 60 - /// <summary> 61 - /// Whether the inside of the EdgeEffect rectangle should be empty. 62 - /// </summary> 63 - public bool Hollow; 64 - 65 - public bool Equals(EdgeEffectParameters other) => 66 - Colour.Equals(other.Colour) && 67 - Offset == other.Offset && 68 - Type == other.Type && 69 - Roundness == other.Roundness && 70 - Radius == other.Radius; 71 - 72 - public override string ToString() => Type != EdgeEffectType.None ? $@"{Radius} {Type}EdgeEffect" : @"EdgeEffect (Disabled)"; 73 - } 74 - 75 - /// <summary> 76 - /// A draw node responsible for rendering a <see cref="CompositeDrawable"/> and the 77 - /// <see cref="DrawNode"/>s of its children. 78 - /// </summary> 79 - public class CompositeDrawNode : DrawNode 80 - { 81 - /// <summary> 82 - /// The <see cref="DrawNode"/>s of the children of our <see cref="CompositeDrawable"/>. 83 - /// </summary> 84 - public List<DrawNode> Children; 85 - 86 - /// <summary> 87 - /// Information about how masking of children should be carried out. 88 - /// </summary> 89 - public MaskingInfo? MaskingInfo; 90 - 91 - /// <summary> 92 - /// The screen-space version of <see cref="OpenGL.MaskingInfo.MaskingRect"/>. 93 - /// Used as cache of screen-space masking quads computed in previous frames. 94 - /// Assign null to reset. 95 - /// </summary> 96 - public Quad? ScreenSpaceMaskingQuad; 97 - 98 - /// <summary> 99 - /// Information about how the edge effect should be rendered. 100 - /// </summary> 101 - public EdgeEffectParameters EdgeEffect; 102 - 103 - /// <summary> 104 - /// The shader to use for rendering. 105 - /// </summary> 106 - public IShader Shader; 107 - 108 - /// <summary> 109 - /// Whether to use a local vertex batch for rendering. If false, a parenting vertex batch will be used. 110 - /// </summary> 111 - public bool ForceLocalVertexBatch; 112 - 113 - /// <summary> 114 - /// The vertex batch used for rendering. 115 - /// </summary> 116 - private QuadBatch<TexturedVertex2D> vertexBatch; 117 - 118 - private void drawEdgeEffect() 119 - { 120 - if (MaskingInfo == null || EdgeEffect.Type == EdgeEffectType.None || EdgeEffect.Radius <= 0.0f || EdgeEffect.Colour.Linear.A <= 0.0f) 121 - return; 122 - 123 - RectangleF effectRect = MaskingInfo.Value.MaskingRect.Inflate(EdgeEffect.Radius).Offset(EdgeEffect.Offset); 124 - if (!ScreenSpaceMaskingQuad.HasValue) 125 - ScreenSpaceMaskingQuad = Quad.FromRectangle(effectRect) * DrawInfo.Matrix; 126 - 127 - MaskingInfo edgeEffectMaskingInfo = MaskingInfo.Value; 128 - edgeEffectMaskingInfo.MaskingRect = effectRect; 129 - edgeEffectMaskingInfo.ScreenSpaceAABB = ScreenSpaceMaskingQuad.Value.AABB; 130 - edgeEffectMaskingInfo.CornerRadius = MaskingInfo.Value.CornerRadius + EdgeEffect.Radius + EdgeEffect.Roundness; 131 - edgeEffectMaskingInfo.BorderThickness = 0; 132 - // HACK HACK HACK. We abuse blend range to give us the linear alpha gradient of 133 - // the edge effect along its radius using the same rounded-corners shader. 134 - edgeEffectMaskingInfo.BlendRange = EdgeEffect.Radius; 135 - edgeEffectMaskingInfo.AlphaExponent = 2; 136 - edgeEffectMaskingInfo.EdgeOffset = EdgeEffect.Offset; 137 - edgeEffectMaskingInfo.Hollow = EdgeEffect.Hollow; 138 - edgeEffectMaskingInfo.HollowCornerRadius = MaskingInfo.Value.CornerRadius + EdgeEffect.Radius; 139 - 140 - GLWrapper.PushMaskingInfo(edgeEffectMaskingInfo); 141 - 142 - GLWrapper.SetBlend(new BlendingInfo(EdgeEffect.Type == EdgeEffectType.Glow ? BlendingMode.Additive : BlendingMode.Mixture)); 143 - 144 - Shader.Bind(); 145 - 146 - ColourInfo colour = ColourInfo.SingleColour(EdgeEffect.Colour); 147 - colour.TopLeft.MultiplyAlpha(DrawColourInfo.Colour.TopLeft.Linear.A); 148 - colour.BottomLeft.MultiplyAlpha(DrawColourInfo.Colour.BottomLeft.Linear.A); 149 - colour.TopRight.MultiplyAlpha(DrawColourInfo.Colour.TopRight.Linear.A); 150 - colour.BottomRight.MultiplyAlpha(DrawColourInfo.Colour.BottomRight.Linear.A); 151 - 152 - Texture.WhitePixel.DrawQuad( 153 - ScreenSpaceMaskingQuad.Value, 154 - colour, null, null, null, 155 - // HACK HACK HACK. We re-use the unused vertex blend range to store the original 156 - // masking blend range when rendering edge effects. This is needed for smooth inner edges 157 - // with a hollow edge effect. 158 - new Vector2(MaskingInfo.Value.BlendRange)); 159 - 160 - Shader.Unbind(); 161 - 162 - GLWrapper.PopMaskingInfo(); 163 - } 164 - 165 - private const int min_amount_children_to_warrant_batch = 5; 166 - 167 - private bool mayHaveOwnVertexBatch(int amountChildren) => ForceLocalVertexBatch || amountChildren >= min_amount_children_to_warrant_batch; 168 - 169 - private void updateVertexBatch() 170 - { 171 - if (Children == null) 172 - return; 173 - 174 - // This logic got roughly copied from the old osu! code base. These constants seem to have worked well so far. 175 - int clampedAmountChildren = MathHelper.Clamp(Children.Count, 1, 1000); 176 - if (mayHaveOwnVertexBatch(clampedAmountChildren) && (vertexBatch == null || vertexBatch.Size < clampedAmountChildren)) 177 - vertexBatch = new QuadBatch<TexturedVertex2D>(clampedAmountChildren * 2, 500); 178 - } 179 - 180 - public override void Draw(Action<TexturedVertex2D> vertexAction) 181 - { 182 - updateVertexBatch(); 183 - 184 - // Prefer to use own vertex batch instead of the parent-owned one. 185 - if (vertexBatch != null) 186 - vertexAction = vertexBatch.AddAction; 187 - 188 - base.Draw(vertexAction); 189 - 190 - drawEdgeEffect(); 191 - if (MaskingInfo != null) 192 - { 193 - MaskingInfo info = MaskingInfo.Value; 194 - if (info.BorderThickness > 0) 195 - info.BorderColour *= DrawColourInfo.Colour.AverageColour; 196 - 197 - GLWrapper.PushMaskingInfo(info); 198 - } 199 - 200 - if (Children != null) 201 - for (int i = 0; i < Children.Count; i++) 202 - Children[i].Draw(vertexAction); 203 - 204 - if (MaskingInfo != null) 205 - GLWrapper.PopMaskingInfo(); 206 - } 207 - 208 - protected override void Dispose(bool isDisposing) 209 - { 210 - base.Dispose(isDisposing); 211 - 212 - vertexBatch?.Dispose(); 213 - } 214 - } 215 - }
···
+14 -50
osu.Framework/Graphics/Containers/CompositeDrawable.cs
··· 8 using System.Linq; 9 using System.Threading; 10 using osuTK; 11 - using osu.Framework.Graphics.OpenGL; 12 using osuTK.Graphics; 13 using osu.Framework.Graphics.Shaders; 14 using osu.Framework.Extensions.IEnumerableExtensions; ··· 22 using System.Threading.Tasks; 23 using osu.Framework.Development; 24 using osu.Framework.Extensions.ExceptionExtensions; 25 using osu.Framework.Graphics.Primitives; 26 using osu.Framework.MathUtils; 27 ··· 34 /// Additionally, <see cref="CompositeDrawable"/>s support various effects, such as masking, edge effect, 35 /// padding, and automatic sizing depending on their children. 36 /// </summary> 37 - public abstract class CompositeDrawable : Drawable 38 { 39 #region Contruction and disposal 40 ··· 195 [BackgroundDependencyLoader(true)] 196 private void load(ShaderManager shaders, CancellationToken? cancellation) 197 { 198 - if (shader == null) 199 - shader = shaders?.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED); 200 201 // We are in a potentially async context, so let's aggressively load all our children 202 // regardless of their alive state. this also gives children a clock so they can be checked ··· 937 938 #region DrawNode 939 940 - private IShader shader; 941 - 942 - protected override DrawNode CreateDrawNode() => new CompositeDrawNode(); 943 - 944 - protected override void ApplyDrawNode(DrawNode node) 945 - { 946 - CompositeDrawNode n = (CompositeDrawNode)node; 947 - 948 - if (!Masking && (BorderThickness != 0.0f || EdgeEffect.Type != EdgeEffectType.None)) 949 - throw new InvalidOperationException("Can not have border effects/edge effects if masking is disabled."); 950 - 951 - Vector3 scale = DrawInfo.MatrixInverse.ExtractScale(); 952 - 953 - n.MaskingInfo = !Masking 954 - ? (MaskingInfo?)null 955 - : new MaskingInfo 956 - { 957 - ScreenSpaceAABB = ScreenSpaceDrawQuad.AABB, 958 - MaskingRect = DrawRectangle, 959 - ToMaskingSpace = DrawInfo.MatrixInverse, 960 - CornerRadius = CornerRadius, 961 - BorderThickness = BorderThickness, 962 - BorderColour = BorderColour, 963 - // We are setting the linear blend range to the approximate size of a _pixel_ here. 964 - // This results in the optimal trade-off between crispness and smoothness of the 965 - // edges of the masked region according to sampling theory. 966 - BlendRange = MaskingSmoothness * (scale.X + scale.Y) / 2, 967 - AlphaExponent = 1, 968 - }; 969 970 - n.EdgeEffect = EdgeEffect; 971 - n.ScreenSpaceMaskingQuad = null; 972 - n.Shader = shader; 973 - n.ForceLocalVertexBatch = ForceLocalVertexBatch; 974 - 975 - base.ApplyDrawNode(node); 976 - } 977 978 private bool forceLocalVertexBatch; 979 ··· 1058 } 1059 } 1060 1061 - internal virtual bool AddChildDrawNodes => true; 1062 - 1063 internal override DrawNode GenerateDrawNodeSubtree(ulong frame, int treeIndex, bool forceNewDrawNode) 1064 { 1065 // No need for a draw node at all if there are no children and we are not glowing. 1066 if (aliveInternalChildren.Count == 0 && CanBeFlattened) 1067 return null; 1068 1069 - if (!(base.GenerateDrawNodeSubtree(frame, treeIndex, forceNewDrawNode) is CompositeDrawNode cNode)) 1070 return null; 1071 1072 if (cNode.Children == null) 1073 cNode.Children = new List<DrawNode>(aliveInternalChildren.Count); 1074 1075 - if (AddChildDrawNodes) 1076 { 1077 - List<DrawNode> target = cNode.Children; 1078 - 1079 int j = 0; 1080 - addFromComposite(frame, treeIndex, forceNewDrawNode, ref j, this, target); 1081 1082 - if (j < target.Count) 1083 - target.RemoveRange(j, target.Count - j); 1084 } 1085 1086 - return cNode; 1087 } 1088 1089 #endregion
··· 8 using System.Linq; 9 using System.Threading; 10 using osuTK; 11 using osuTK.Graphics; 12 using osu.Framework.Graphics.Shaders; 13 using osu.Framework.Extensions.IEnumerableExtensions; ··· 21 using System.Threading.Tasks; 22 using osu.Framework.Development; 23 using osu.Framework.Extensions.ExceptionExtensions; 24 + using osu.Framework.Graphics.Effects; 25 using osu.Framework.Graphics.Primitives; 26 using osu.Framework.MathUtils; 27 ··· 34 /// Additionally, <see cref="CompositeDrawable"/>s support various effects, such as masking, edge effect, 35 /// padding, and automatic sizing depending on their children. 36 /// </summary> 37 + public abstract partial class CompositeDrawable : Drawable 38 { 39 #region Contruction and disposal 40 ··· 195 [BackgroundDependencyLoader(true)] 196 private void load(ShaderManager shaders, CancellationToken? cancellation) 197 { 198 + if (Shader == null) 199 + Shader = shaders?.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED); 200 201 // We are in a potentially async context, so let's aggressively load all our children 202 // regardless of their alive state. this also gives children a clock so they can be checked ··· 937 938 #region DrawNode 939 940 + internal IShader Shader { get; private set; } 941 942 + protected override DrawNode CreateDrawNode() => new CompositeDrawableDrawNode(this); 943 944 private bool forceLocalVertexBatch; 945 ··· 1024 } 1025 } 1026 1027 internal override DrawNode GenerateDrawNodeSubtree(ulong frame, int treeIndex, bool forceNewDrawNode) 1028 { 1029 // No need for a draw node at all if there are no children and we are not glowing. 1030 if (aliveInternalChildren.Count == 0 && CanBeFlattened) 1031 return null; 1032 1033 + DrawNode node = base.GenerateDrawNodeSubtree(frame, treeIndex, forceNewDrawNode); 1034 + 1035 + if (!(node is ICompositeDrawNode cNode)) 1036 return null; 1037 1038 if (cNode.Children == null) 1039 cNode.Children = new List<DrawNode>(aliveInternalChildren.Count); 1040 1041 + if (cNode.AddChildDrawNodes) 1042 { 1043 int j = 0; 1044 + addFromComposite(frame, treeIndex, forceNewDrawNode, ref j, this, cNode.Children); 1045 1046 + if (j < cNode.Children.Count) 1047 + cNode.Children.RemoveRange(j, cNode.Children.Count - j); 1048 } 1049 1050 + return node; 1051 } 1052 1053 #endregion
+202
osu.Framework/Graphics/Containers/CompositeDrawable_DrawNode.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 osu.Framework.Graphics.OpenGL; 6 + using osu.Framework.Graphics.Primitives; 7 + using osu.Framework.Graphics.Shaders; 8 + using osu.Framework.Graphics.Batches; 9 + using osuTK; 10 + using osu.Framework.Graphics.Textures; 11 + using osu.Framework.Graphics.Colour; 12 + using System; 13 + using osu.Framework.Graphics.Effects; 14 + using osu.Framework.Graphics.OpenGL.Vertices; 15 + 16 + namespace osu.Framework.Graphics.Containers 17 + { 18 + public partial class CompositeDrawable 19 + { 20 + /// <summary> 21 + /// A draw node responsible for rendering a <see cref="CompositeDrawable"/> and the <see cref="DrawNode"/>s of its children. 22 + /// </summary> 23 + protected class CompositeDrawableDrawNode : DrawNode, ICompositeDrawNode 24 + { 25 + protected new CompositeDrawable Source => (CompositeDrawable)base.Source; 26 + 27 + /// <summary> 28 + /// The <see cref="IShader"/> to use for rendering. 29 + /// </summary> 30 + protected IShader Shader { get; private set; } 31 + 32 + /// <summary> 33 + /// The <see cref="DrawNode"/>s of the children of our <see cref="CompositeDrawable"/>. 34 + /// </summary> 35 + public List<DrawNode> Children { get; set; } 36 + 37 + /// <summary> 38 + /// Information about how masking of children should be carried out. 39 + /// </summary> 40 + private MaskingInfo? maskingInfo; 41 + 42 + /// <summary> 43 + /// The screen-space version of <see cref="OpenGL.MaskingInfo.MaskingRect"/>. 44 + /// Used as cache of screen-space masking quads computed in previous frames. 45 + /// Assign null to reset. 46 + /// </summary> 47 + private Quad? screenSpaceMaskingQuad; 48 + 49 + /// <summary> 50 + /// Information about how the edge effect should be rendered. 51 + /// </summary> 52 + private EdgeEffectParameters edgeEffect; 53 + 54 + /// <summary> 55 + /// Whether to use a local vertex batch for rendering. If false, a parenting vertex batch will be used. 56 + /// </summary> 57 + private bool forceLocalVertexBatch; 58 + 59 + /// <summary> 60 + /// The vertex batch used for rendering. 61 + /// </summary> 62 + private QuadBatch<TexturedVertex2D> vertexBatch; 63 + 64 + public CompositeDrawableDrawNode(CompositeDrawable source) 65 + : base(source) 66 + { 67 + } 68 + 69 + public override void ApplyState() 70 + { 71 + base.ApplyState(); 72 + 73 + if (!Source.Masking && (Source.BorderThickness != 0.0f || edgeEffect.Type != EdgeEffectType.None)) 74 + throw new InvalidOperationException("Can not have border effects/edge effects if masking is disabled."); 75 + 76 + Vector3 scale = DrawInfo.MatrixInverse.ExtractScale(); 77 + 78 + maskingInfo = !Source.Masking 79 + ? (MaskingInfo?)null 80 + : new MaskingInfo 81 + { 82 + ScreenSpaceAABB = Source.ScreenSpaceDrawQuad.AABB, 83 + MaskingRect = Source.DrawRectangle, 84 + ToMaskingSpace = DrawInfo.MatrixInverse, 85 + CornerRadius = Source.CornerRadius, 86 + BorderThickness = Source.BorderThickness, 87 + BorderColour = Source.BorderColour, 88 + // We are setting the linear blend range to the approximate size of a _pixel_ here. 89 + // This results in the optimal trade-off between crispness and smoothness of the 90 + // edges of the masked region according to sampling theory. 91 + BlendRange = Source.MaskingSmoothness * (scale.X + scale.Y) / 2, 92 + AlphaExponent = 1, 93 + }; 94 + 95 + edgeEffect = Source.EdgeEffect; 96 + screenSpaceMaskingQuad = null; 97 + Shader = Source.Shader; 98 + forceLocalVertexBatch = Source.ForceLocalVertexBatch; 99 + } 100 + 101 + public virtual bool AddChildDrawNodes => true; 102 + 103 + private void drawEdgeEffect() 104 + { 105 + if (maskingInfo == null || edgeEffect.Type == EdgeEffectType.None || edgeEffect.Radius <= 0.0f || edgeEffect.Colour.Linear.A <= 0.0f) 106 + return; 107 + 108 + RectangleF effectRect = maskingInfo.Value.MaskingRect.Inflate(edgeEffect.Radius).Offset(edgeEffect.Offset); 109 + 110 + if (!screenSpaceMaskingQuad.HasValue) 111 + screenSpaceMaskingQuad = Quad.FromRectangle(effectRect) * DrawInfo.Matrix; 112 + 113 + MaskingInfo edgeEffectMaskingInfo = maskingInfo.Value; 114 + edgeEffectMaskingInfo.MaskingRect = effectRect; 115 + edgeEffectMaskingInfo.ScreenSpaceAABB = screenSpaceMaskingQuad.Value.AABB; 116 + edgeEffectMaskingInfo.CornerRadius = maskingInfo.Value.CornerRadius + edgeEffect.Radius + edgeEffect.Roundness; 117 + edgeEffectMaskingInfo.BorderThickness = 0; 118 + // HACK HACK HACK. We abuse blend range to give us the linear alpha gradient of 119 + // the edge effect along its radius using the same rounded-corners shader. 120 + edgeEffectMaskingInfo.BlendRange = edgeEffect.Radius; 121 + edgeEffectMaskingInfo.AlphaExponent = 2; 122 + edgeEffectMaskingInfo.EdgeOffset = edgeEffect.Offset; 123 + edgeEffectMaskingInfo.Hollow = edgeEffect.Hollow; 124 + edgeEffectMaskingInfo.HollowCornerRadius = maskingInfo.Value.CornerRadius + edgeEffect.Radius; 125 + 126 + GLWrapper.PushMaskingInfo(edgeEffectMaskingInfo); 127 + 128 + GLWrapper.SetBlend(new BlendingInfo(edgeEffect.Type == EdgeEffectType.Glow ? BlendingMode.Additive : BlendingMode.Mixture)); 129 + 130 + Shader.Bind(); 131 + 132 + ColourInfo colour = ColourInfo.SingleColour(edgeEffect.Colour); 133 + colour.TopLeft.MultiplyAlpha(DrawColourInfo.Colour.TopLeft.Linear.A); 134 + colour.BottomLeft.MultiplyAlpha(DrawColourInfo.Colour.BottomLeft.Linear.A); 135 + colour.TopRight.MultiplyAlpha(DrawColourInfo.Colour.TopRight.Linear.A); 136 + colour.BottomRight.MultiplyAlpha(DrawColourInfo.Colour.BottomRight.Linear.A); 137 + 138 + Texture.WhitePixel.DrawQuad( 139 + screenSpaceMaskingQuad.Value, 140 + colour, null, null, null, 141 + // HACK HACK HACK. We re-use the unused vertex blend range to store the original 142 + // masking blend range when rendering edge effects. This is needed for smooth inner edges 143 + // with a hollow edge effect. 144 + new Vector2(maskingInfo.Value.BlendRange)); 145 + 146 + Shader.Unbind(); 147 + 148 + GLWrapper.PopMaskingInfo(); 149 + } 150 + 151 + private const int min_amount_children_to_warrant_batch = 5; 152 + 153 + private bool mayHaveOwnVertexBatch(int amountChildren) => forceLocalVertexBatch || amountChildren >= min_amount_children_to_warrant_batch; 154 + 155 + private void updateVertexBatch() 156 + { 157 + if (Children == null) 158 + return; 159 + 160 + // This logic got roughly copied from the old osu! code base. These constants seem to have worked well so far. 161 + int clampedAmountChildren = MathHelper.Clamp(Children.Count, 1, 1000); 162 + if (mayHaveOwnVertexBatch(clampedAmountChildren) && (vertexBatch == null || vertexBatch.Size < clampedAmountChildren)) 163 + vertexBatch = new QuadBatch<TexturedVertex2D>(clampedAmountChildren * 2, 500); 164 + } 165 + 166 + public override void Draw(Action<TexturedVertex2D> vertexAction) 167 + { 168 + updateVertexBatch(); 169 + 170 + // Prefer to use own vertex batch instead of the parent-owned one. 171 + if (vertexBatch != null) 172 + vertexAction = vertexBatch.AddAction; 173 + 174 + base.Draw(vertexAction); 175 + 176 + drawEdgeEffect(); 177 + if (maskingInfo != null) 178 + { 179 + MaskingInfo info = maskingInfo.Value; 180 + if (info.BorderThickness > 0) 181 + info.BorderColour *= DrawColourInfo.Colour.AverageColour; 182 + 183 + GLWrapper.PushMaskingInfo(info); 184 + } 185 + 186 + if (Children != null) 187 + for (int i = 0; i < Children.Count; i++) 188 + Children[i].Draw(vertexAction); 189 + 190 + if (maskingInfo != null) 191 + GLWrapper.PopMaskingInfo(); 192 + } 193 + 194 + protected override void Dispose(bool isDisposing) 195 + { 196 + base.Dispose(isDisposing); 197 + 198 + vertexBatch?.Dispose(); 199 + } 200 + } 201 + } 202 + }
+1
osu.Framework/Graphics/Containers/Container.cs
··· 9 using osuTK; 10 using System.Collections; 11 using System.Diagnostics; 12 13 namespace osu.Framework.Graphics.Containers 14 {
··· 9 using osuTK; 10 using System.Collections; 11 using System.Diagnostics; 12 + using osu.Framework.Graphics.Effects; 13 14 namespace osu.Framework.Graphics.Containers 15 {
+1
osu.Framework/Graphics/Containers/IContainer.cs
··· 4 using osuTK; 5 using System; 6 using System.Collections.Generic; 7 8 namespace osu.Framework.Graphics.Containers 9 {
··· 4 using osuTK; 5 using System; 6 using System.Collections.Generic; 7 + using osu.Framework.Graphics.Effects; 8 9 namespace osu.Framework.Graphics.Containers 10 {
+1
osu.Framework/Graphics/Cursor/CursorContainer.cs
··· 3 4 using osu.Framework.Allocation; 5 using osu.Framework.Graphics.Containers; 6 using osu.Framework.Graphics.Shapes; 7 using osu.Framework.Input; 8 using osu.Framework.Input.Events;
··· 3 4 using osu.Framework.Allocation; 5 using osu.Framework.Graphics.Containers; 6 + using osu.Framework.Graphics.Effects; 7 using osu.Framework.Graphics.Shapes; 8 using osu.Framework.Input; 9 using osu.Framework.Input.Events;
+35 -8
osu.Framework/Graphics/DrawNode.cs
··· 9 { 10 /// <summary> 11 /// Contains all the information required to draw a single <see cref="Drawable"/>. 12 - /// A hierarchy of DrawNodes is passed to the draw thread for rendering every frame. 13 /// </summary> 14 public class DrawNode : IDisposable 15 { 16 /// <summary> 17 - /// Contains a linear transformation, colour information, and blending information 18 - /// of this draw node. 19 /// </summary> 20 - public DrawInfo DrawInfo { get; internal set; } 21 22 - public DrawColourInfo DrawColourInfo { get; internal set; } 23 24 /// <summary> 25 /// Identifies the state of this draw node with an invalidation state of its corresponding 26 - /// <see cref="Drawable"/>. Whenever the invalidation state of this draw node disagrees 27 - /// with the state of its <see cref="Drawable"/> it has to be updated. 28 /// </summary> 29 - public long InvalidationID { get; internal set; } 30 31 /// <summary> 32 /// Draws this draw node to the screen.
··· 9 { 10 /// <summary> 11 /// Contains all the information required to draw a single <see cref="Drawable"/>. 12 + /// A hierarchy of <see cref="DrawNode"/>s is passed to the draw thread for rendering every frame. 13 /// </summary> 14 public class DrawNode : IDisposable 15 { 16 /// <summary> 17 + /// Contains the linear transformation of this <see cref="DrawNode"/>. 18 /// </summary> 19 + protected DrawInfo DrawInfo { get; private set; } 20 21 + /// <summary> 22 + /// Contains the colour and blending information of this <see cref="DrawNode"/>. 23 + /// </summary> 24 + protected internal DrawColourInfo DrawColourInfo { get; internal set; } 25 26 /// <summary> 27 /// Identifies the state of this draw node with an invalidation state of its corresponding 28 + /// <see cref="Drawable"/>. An update is required when the invalidation state of this draw node disagrees 29 + /// with the invalidation state of its <see cref="Drawable"/>. 30 + /// </summary> 31 + protected internal long InvalidationID { get; private set; } 32 + 33 + /// <summary> 34 + /// The <see cref="Drawable"/> which this <see cref="DrawNode"/> draws. 35 + /// </summary> 36 + protected readonly IDrawable Source; 37 + 38 + /// <summary> 39 + /// Creates a new <see cref="DrawNode"/>. 40 + /// </summary> 41 + /// <param name="source">The <see cref="Drawable"/> to draw with this <see cref="DrawNode"/>.</param> 42 + public DrawNode(IDrawable source) 43 + { 44 + Source = source; 45 + } 46 + 47 + /// <summary> 48 + /// Applies the state of <see cref="Source"/> to this <see cref="DrawNode"/> for use in rendering. 49 + /// The applied state must remain immutable. 50 /// </summary> 51 + public virtual void ApplyState() 52 + { 53 + DrawInfo = Source.DrawInfo; 54 + DrawColourInfo = Source.DrawColourInfo; 55 + InvalidationID = Source.InvalidationID; 56 + } 57 58 /// <summary> 59 /// Draws this draw node to the screen.
+6 -17
osu.Framework/Graphics/Drawable.cs
··· 1633 private static readonly AtomicCounter invalidation_counter = new AtomicCounter(); 1634 1635 // Make sure we start out with a value of 1 such that ApplyDrawNode is always called at least once 1636 - private long invalidationID = invalidation_counter.Increment(); 1637 1638 /// <summary> 1639 /// Invalidates draw matrix and autosize caches. ··· 1681 alreadyInvalidated &= !drawColourInfoBacking.Invalidate(); 1682 1683 if (!alreadyInvalidated || (invalidation & Invalidation.DrawNode) > 0) 1684 - invalidationID = invalidation_counter.Increment(); 1685 1686 OnInvalidate?.Invoke(this); 1687 ··· 1723 FrameStatistics.Increment(StatisticsCounterType.DrawNodeCtor); 1724 } 1725 1726 - if (invalidationID != node.InvalidationID) 1727 { 1728 - ApplyDrawNode(node); 1729 FrameStatistics.Increment(StatisticsCounterType.DrawNodeAppl); 1730 } 1731 ··· 1733 } 1734 1735 /// <summary> 1736 - /// Fills a given draw node with all information required to draw this drawable. 1737 - /// </summary> 1738 - /// <param name="node">The node to fill with information.</param> 1739 - protected virtual void ApplyDrawNode(DrawNode node) 1740 - { 1741 - node.DrawInfo = DrawInfo; 1742 - node.DrawColourInfo = DrawColourInfo; 1743 - node.InvalidationID = invalidationID; 1744 - } 1745 - 1746 - /// <summary> 1747 /// Creates a draw node capable of containing all information required to draw this drawable. 1748 /// </summary> 1749 /// <returns>The created draw node.</returns> 1750 - protected virtual DrawNode CreateDrawNode() => new DrawNode(); 1751 1752 #endregion 1753 ··· 2253 Colour = 1 << 3, 2254 2255 /// <summary> 2256 - /// <see cref="Drawable.ApplyDrawNode(Graphics.DrawNode)"/> has to be invoked on all old draw nodes. 2257 /// </summary> 2258 DrawNode = 1 << 4, 2259
··· 1633 private static readonly AtomicCounter invalidation_counter = new AtomicCounter(); 1634 1635 // Make sure we start out with a value of 1 such that ApplyDrawNode is always called at least once 1636 + public long InvalidationID { get; private set; } = invalidation_counter.Increment(); 1637 1638 /// <summary> 1639 /// Invalidates draw matrix and autosize caches. ··· 1681 alreadyInvalidated &= !drawColourInfoBacking.Invalidate(); 1682 1683 if (!alreadyInvalidated || (invalidation & Invalidation.DrawNode) > 0) 1684 + InvalidationID = invalidation_counter.Increment(); 1685 1686 OnInvalidate?.Invoke(this); 1687 ··· 1723 FrameStatistics.Increment(StatisticsCounterType.DrawNodeCtor); 1724 } 1725 1726 + if (InvalidationID != node.InvalidationID) 1727 { 1728 + node.ApplyState(); 1729 FrameStatistics.Increment(StatisticsCounterType.DrawNodeAppl); 1730 } 1731 ··· 1733 } 1734 1735 /// <summary> 1736 /// Creates a draw node capable of containing all information required to draw this drawable. 1737 /// </summary> 1738 /// <returns>The created draw node.</returns> 1739 + protected virtual DrawNode CreateDrawNode() => new DrawNode(this); 1740 1741 #endregion 1742 ··· 2242 Colour = 1 << 3, 2243 2244 /// <summary> 2245 + /// <see cref="Graphics.DrawNode.ApplyState"/> has to be invoked on all old draw nodes. 2246 /// </summary> 2247 DrawNode = 1 << 4, 2248
+4 -4
osu.Framework/Graphics/Drawable_ProxyDrawable.cs
··· 69 /// </summary> 70 public ulong FrameCount; 71 72 - private readonly ProxyDrawable proxyDrawable; 73 74 public ProxyDrawNode(ProxyDrawable proxyDrawable) 75 { 76 - this.proxyDrawable = proxyDrawable; 77 } 78 79 public override void Draw(Action<TexturedVertex2D> vertexAction) 80 { 81 - var target = proxyDrawable.originalDrawNodes[DrawNodeIndex]; 82 if (target == null) 83 return; 84 85 - if (proxyDrawable.drawNodeValidationIds[DrawNodeIndex] != FrameCount) 86 return; 87 88 target.Draw(vertexAction);
··· 69 /// </summary> 70 public ulong FrameCount; 71 72 + protected new ProxyDrawable Source => (ProxyDrawable)base.Source; 73 74 public ProxyDrawNode(ProxyDrawable proxyDrawable) 75 + : base(proxyDrawable) 76 { 77 } 78 79 public override void Draw(Action<TexturedVertex2D> vertexAction) 80 { 81 + var target = Source.originalDrawNodes[DrawNodeIndex]; 82 if (target == null) 83 return; 84 85 + if (Source.drawNodeValidationIds[DrawNodeIndex] != FrameCount) 86 return; 87 88 target.Draw(vertexAction);
+58
osu.Framework/Graphics/Effects/EdgeEffectParameters.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 osu.Framework.Graphics.Colour; 6 + using osu.Framework.Graphics.Containers; 7 + using osuTK; 8 + 9 + namespace osu.Framework.Graphics.Effects 10 + { 11 + /// <summary> 12 + /// Parametrizes the appearance of an edge effect. 13 + /// </summary> 14 + public struct EdgeEffectParameters : IEquatable<EdgeEffectParameters> 15 + { 16 + /// <summary> 17 + /// Colour of the edge effect. 18 + /// </summary> 19 + public SRGBColour Colour; 20 + 21 + /// <summary> 22 + /// Positional offset applied to the edge effect. 23 + /// Useful for off-center shadows. 24 + /// </summary> 25 + public Vector2 Offset; 26 + 27 + /// <summary> 28 + /// The type of the edge effect. 29 + /// </summary> 30 + public EdgeEffectType Type; 31 + 32 + /// <summary> 33 + /// How round the edge effect should appear. Adds to the <see cref="CompositeDrawable.CornerRadius"/> 34 + /// of the corresponding <see cref="CompositeDrawable"/>. Not to confuse with the <see cref="Radius"/>. 35 + /// </summary> 36 + public float Roundness; 37 + 38 + /// <summary> 39 + /// How "thick" the edge effect is around the <see cref="CompositeDrawable"/>. In other words: At what distance 40 + /// from the <see cref="CompositeDrawable"/>'s border the edge effect becomes fully invisible. 41 + /// </summary> 42 + public float Radius; 43 + 44 + /// <summary> 45 + /// Whether the inside of the EdgeEffect rectangle should be empty. 46 + /// </summary> 47 + public bool Hollow; 48 + 49 + public bool Equals(EdgeEffectParameters other) => 50 + Colour.Equals(other.Colour) && 51 + Offset == other.Offset && 52 + Type == other.Type && 53 + Roundness == other.Roundness && 54 + Radius == other.Radius; 55 + 56 + public override string ToString() => Type != EdgeEffectType.None ? $@"{Radius} {Type}EdgeEffect" : @"EdgeEffect (Disabled)"; 57 + } 58 + }
+17
osu.Framework/Graphics/Effects/EdgeEffectType.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.Containers; 5 + 6 + namespace osu.Framework.Graphics.Effects 7 + { 8 + /// <summary> 9 + /// Types of edge effects that can be applied to <see cref="CompositeDrawable"/>s. 10 + /// </summary> 11 + public enum EdgeEffectType 12 + { 13 + None, 14 + Glow, 15 + Shadow, 16 + } 17 + }
+25
osu.Framework/Graphics/ICompositeDrawNode.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 osu.Framework.Graphics.Containers; 6 + 7 + namespace osu.Framework.Graphics 8 + { 9 + /// <summary> 10 + /// Interface for <see cref="DrawNode"/>s which compose child <see cref="DrawNode"/>s. 11 + /// <see cref="DrawNode"/>s implementing this interface can be used in <see cref="CompositeDrawable"/>s. 12 + /// </summary> 13 + public interface ICompositeDrawNode 14 + { 15 + /// <summary> 16 + /// The child <see cref="DrawNode"/>s to draw. 17 + /// </summary> 18 + List<DrawNode> Children { get; set; } 19 + 20 + /// <summary> 21 + /// Whether <see cref="Children"/> should be updated by the parenting <see cref="CompositeDrawable"/>. 22 + /// </summary> 23 + bool AddChildDrawNodes { get; } 24 + } 25 + }
+11
osu.Framework/Graphics/IDrawable.cs
··· 30 DrawInfo DrawInfo { get; } 31 32 /// <summary> 33 /// The screen-space quad this drawable occupies. 34 /// </summary> 35 Quad ScreenSpaceDrawQuad { get; } ··· 97 /// Hide sprite instantly. 98 /// </summary> 99 void Hide(); 100 } 101 }
··· 30 DrawInfo DrawInfo { get; } 31 32 /// <summary> 33 + /// Contains the colour and blending information of this <see cref="Drawable"/> that are used during draw. 34 + /// </summary> 35 + DrawColourInfo DrawColourInfo { get; } 36 + 37 + /// <summary> 38 /// The screen-space quad this drawable occupies. 39 /// </summary> 40 Quad ScreenSpaceDrawQuad { get; } ··· 102 /// Hide sprite instantly. 103 /// </summary> 104 void Hide(); 105 + 106 + /// <summary> 107 + /// The current invalidation ID of this <see cref="Drawable"/>. 108 + /// Incremented every time the <see cref="DrawNode"/> should be re-validated. 109 + /// </summary> 110 + long InvalidationID { get; } 111 } 112 }
+23
osu.Framework/Graphics/ITexturedShaderDrawable.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.Shaders; 5 + 6 + namespace osu.Framework.Graphics 7 + { 8 + /// <summary> 9 + /// Interface for <see cref="Drawable"/>s which can be drawn by <see cref="TexturedShaderDrawNode"/>s. 10 + /// </summary> 11 + public interface ITexturedShaderDrawable : IDrawable 12 + { 13 + /// <summary> 14 + /// The <see cref="IShader"/> to be used for rendering when masking is not required. 15 + /// </summary> 16 + IShader TextureShader { get; } 17 + 18 + /// <summary> 19 + /// The <see cref="IShader"/> to be used for rendering when masking is required. 20 + /// </summary> 21 + IShader RoundedTextureShader { get; } 22 + } 23 + }
+6 -22
osu.Framework/Graphics/Lines/Path.cs
··· 7 using osu.Framework.Graphics.Shaders; 8 using osu.Framework.Allocation; 9 using System.Collections.Generic; 10 - using System.Linq; 11 using osu.Framework.Caching; 12 13 namespace osu.Framework.Graphics.Lines 14 { 15 - public class Path : Drawable 16 { 17 - private IShader roundedTextureShader; 18 - private IShader textureShader; 19 20 [BackgroundDependencyLoader] 21 private void load(ShaderManager shaders) 22 { 23 - roundedTextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_3, FragmentShaderDescriptor.TEXTURE_ROUNDED); 24 - textureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_3, FragmentShaderDescriptor.TEXTURE); 25 } 26 27 private readonly List<Vector2> vertices = new List<Vector2>(); ··· 175 } 176 } 177 178 - protected override DrawNode CreateDrawNode() => new PathDrawNode(); 179 - 180 - protected override void ApplyDrawNode(DrawNode node) 181 - { 182 - PathDrawNode n = (PathDrawNode)node; 183 - 184 - n.Texture = Texture; 185 - n.TextureShader = textureShader; 186 - n.RoundedTextureShader = roundedTextureShader; 187 - n.Radius = PathRadius; 188 - n.DrawSize = DrawSize; 189 - 190 - n.Segments = segments.ToList(); 191 - 192 - base.ApplyDrawNode(node); 193 - } 194 } 195 }
··· 7 using osu.Framework.Graphics.Shaders; 8 using osu.Framework.Allocation; 9 using System.Collections.Generic; 10 using osu.Framework.Caching; 11 12 namespace osu.Framework.Graphics.Lines 13 { 14 + public partial class Path : Drawable, ITexturedShaderDrawable 15 { 16 + public IShader RoundedTextureShader { get; private set; } 17 + public IShader TextureShader { get; private set; } 18 19 [BackgroundDependencyLoader] 20 private void load(ShaderManager shaders) 21 { 22 + RoundedTextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_3, FragmentShaderDescriptor.TEXTURE_ROUNDED); 23 + TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_3, FragmentShaderDescriptor.TEXTURE); 24 } 25 26 private readonly List<Vector2> vertices = new List<Vector2>(); ··· 174 } 175 } 176 177 + protected override DrawNode CreateDrawNode() => new PathDrawNode(this); 178 } 179 }
-216
osu.Framework/Graphics/Lines/PathDrawNode.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.Primitives; 5 - using osu.Framework.Graphics.Shaders; 6 - using osu.Framework.Graphics.Textures; 7 - using osuTK.Graphics.ES30; 8 - using osu.Framework.Graphics.OpenGL; 9 - using osuTK; 10 - using System; 11 - using System.Collections.Generic; 12 - using osu.Framework.Graphics.Batches; 13 - using osu.Framework.Graphics.OpenGL.Vertices; 14 - using osuTK.Graphics; 15 - 16 - namespace osu.Framework.Graphics.Lines 17 - { 18 - public class PathDrawNode : DrawNode 19 - { 20 - public const int MAX_RES = 24; 21 - 22 - public List<Line> Segments; 23 - 24 - public Vector2 DrawSize; 25 - public float Radius; 26 - public Texture Texture; 27 - 28 - public IShader TextureShader; 29 - public IShader RoundedTextureShader; 30 - 31 - // We multiply the size param by 3 such that the amount of vertices is a multiple of the amount of vertices 32 - // per primitive (triangles in this case). Otherwise overflowing the batch will result in wrong 33 - // grouping of vertices into primitives. 34 - private readonly LinearBatch<TexturedVertex3D> halfCircleBatch = new LinearBatch<TexturedVertex3D>(MAX_RES * 100 * 3, 10, PrimitiveType.Triangles); 35 - private readonly QuadBatch<TexturedVertex3D> quadBatch = new QuadBatch<TexturedVertex3D>(200, 10); 36 - 37 - private bool needsRoundedShader => GLWrapper.IsMaskingActive; 38 - 39 - private Vector2 pointOnCircle(float angle) => new Vector2((float)Math.Sin(angle), -(float)Math.Cos(angle)); 40 - 41 - private Vector2 relativePosition(Vector2 localPos) => Vector2.Divide(localPos, DrawSize); 42 - 43 - private Color4 colourAt(Vector2 localPos) => DrawColourInfo.Colour.HasSingleColour 44 - ? (Color4)DrawColourInfo.Colour 45 - : DrawColourInfo.Colour.Interpolate(relativePosition(localPos)).Linear; 46 - 47 - private void addLineCap(Vector2 origin, float theta, float thetaDiff, RectangleF texRect) 48 - { 49 - const float step = MathHelper.Pi / MAX_RES; 50 - 51 - float dir = Math.Sign(thetaDiff); 52 - thetaDiff = dir * thetaDiff; 53 - 54 - int amountPoints = (int)Math.Ceiling(thetaDiff / step); 55 - 56 - if (dir < 0) 57 - theta += MathHelper.Pi; 58 - 59 - Vector2 current = origin + pointOnCircle(theta) * Radius; 60 - Color4 currentColour = colourAt(current); 61 - current = Vector2Extensions.Transform(current, DrawInfo.Matrix); 62 - 63 - Vector2 screenOrigin = Vector2Extensions.Transform(origin, DrawInfo.Matrix); 64 - Color4 originColour = colourAt(origin); 65 - 66 - for (int i = 1; i <= amountPoints; i++) 67 - { 68 - // Center point 69 - halfCircleBatch.Add(new TexturedVertex3D 70 - { 71 - Position = new Vector3(screenOrigin.X, screenOrigin.Y, 1), 72 - TexturePosition = new Vector2(texRect.Right, texRect.Centre.Y), 73 - Colour = originColour 74 - }); 75 - 76 - // First outer point 77 - halfCircleBatch.Add(new TexturedVertex3D 78 - { 79 - Position = new Vector3(current.X, current.Y, 0), 80 - TexturePosition = new Vector2(texRect.Left, texRect.Centre.Y), 81 - Colour = currentColour 82 - }); 83 - 84 - float angularOffset = Math.Min(i * step, thetaDiff); 85 - current = origin + pointOnCircle(theta + dir * angularOffset) * Radius; 86 - currentColour = colourAt(current); 87 - current = Vector2Extensions.Transform(current, DrawInfo.Matrix); 88 - 89 - // Second outer point 90 - halfCircleBatch.Add(new TexturedVertex3D 91 - { 92 - Position = new Vector3(current.X, current.Y, 0), 93 - TexturePosition = new Vector2(texRect.Left, texRect.Centre.Y), 94 - Colour = currentColour 95 - }); 96 - } 97 - } 98 - 99 - private void addLineQuads(Line line, RectangleF texRect) 100 - { 101 - Vector2 ortho = line.OrthogonalDirection; 102 - Line lineLeft = new Line(line.StartPoint + ortho * Radius, line.EndPoint + ortho * Radius); 103 - Line lineRight = new Line(line.StartPoint - ortho * Radius, line.EndPoint - ortho * Radius); 104 - 105 - Line screenLineLeft = new Line(Vector2Extensions.Transform(lineLeft.StartPoint, DrawInfo.Matrix), Vector2Extensions.Transform(lineLeft.EndPoint, DrawInfo.Matrix)); 106 - Line screenLineRight = new Line(Vector2Extensions.Transform(lineRight.StartPoint, DrawInfo.Matrix), Vector2Extensions.Transform(lineRight.EndPoint, DrawInfo.Matrix)); 107 - Line screenLine = new Line(Vector2Extensions.Transform(line.StartPoint, DrawInfo.Matrix), Vector2Extensions.Transform(line.EndPoint, DrawInfo.Matrix)); 108 - 109 - quadBatch.Add(new TexturedVertex3D 110 - { 111 - Position = new Vector3(screenLineRight.EndPoint.X, screenLineRight.EndPoint.Y, 0), 112 - TexturePosition = new Vector2(texRect.Left, texRect.Centre.Y), 113 - Colour = colourAt(lineRight.EndPoint) 114 - }); 115 - quadBatch.Add(new TexturedVertex3D 116 - { 117 - Position = new Vector3(screenLineRight.StartPoint.X, screenLineRight.StartPoint.Y, 0), 118 - TexturePosition = new Vector2(texRect.Left, texRect.Centre.Y), 119 - Colour = colourAt(lineRight.StartPoint) 120 - }); 121 - 122 - // Each "quad" of the slider is actually rendered as 2 quads, being split in half along the approximating line. 123 - // On this line the depth is 1 instead of 0, which is done properly handle self-overlap using the depth buffer. 124 - // Thus the middle vertices need to be added twice (once for each quad). 125 - Vector3 firstMiddlePoint = new Vector3(screenLine.StartPoint.X, screenLine.StartPoint.Y, 1); 126 - Vector3 secondMiddlePoint = new Vector3(screenLine.EndPoint.X, screenLine.EndPoint.Y, 1); 127 - Color4 firstMiddleColour = colourAt(line.StartPoint); 128 - Color4 secondMiddleColour = colourAt(line.EndPoint); 129 - 130 - for (int i = 0; i < 2; ++i) 131 - { 132 - quadBatch.Add(new TexturedVertex3D 133 - { 134 - Position = firstMiddlePoint, 135 - TexturePosition = new Vector2(texRect.Right, texRect.Centre.Y), 136 - Colour = firstMiddleColour 137 - }); 138 - quadBatch.Add(new TexturedVertex3D 139 - { 140 - Position = secondMiddlePoint, 141 - TexturePosition = new Vector2(texRect.Right, texRect.Centre.Y), 142 - Colour = secondMiddleColour 143 - }); 144 - } 145 - 146 - quadBatch.Add(new TexturedVertex3D 147 - { 148 - Position = new Vector3(screenLineLeft.EndPoint.X, screenLineLeft.EndPoint.Y, 0), 149 - TexturePosition = new Vector2(texRect.Left, texRect.Centre.Y), 150 - Colour = colourAt(lineLeft.EndPoint) 151 - }); 152 - quadBatch.Add(new TexturedVertex3D 153 - { 154 - Position = new Vector3(screenLineLeft.StartPoint.X, screenLineLeft.StartPoint.Y, 0), 155 - TexturePosition = new Vector2(texRect.Left, texRect.Centre.Y), 156 - Colour = colourAt(lineLeft.StartPoint) 157 - }); 158 - } 159 - 160 - private void updateVertexBuffer() 161 - { 162 - Line line = Segments[0]; 163 - float theta = line.Theta; 164 - 165 - // Offset by 0.5 pixels inwards to ensure we never sample texels outside the bounds 166 - RectangleF texRect = Texture.GetTextureRect(new RectangleF(0.5f, 0.5f, Texture.Width - 1, Texture.Height - 1)); 167 - addLineCap(line.StartPoint, theta + MathHelper.Pi, MathHelper.Pi, texRect); 168 - 169 - for (int i = 1; i < Segments.Count; ++i) 170 - { 171 - Line nextLine = Segments[i]; 172 - float nextTheta = nextLine.Theta; 173 - addLineCap(line.EndPoint, theta, nextTheta - theta, texRect); 174 - 175 - line = nextLine; 176 - theta = nextTheta; 177 - } 178 - 179 - addLineCap(line.EndPoint, theta, MathHelper.Pi, texRect); 180 - 181 - foreach (Line segment in Segments) 182 - addLineQuads(segment, texRect); 183 - } 184 - 185 - public override void Draw(Action<TexturedVertex2D> vertexAction) 186 - { 187 - base.Draw(vertexAction); 188 - 189 - if (Texture?.Available != true || Segments.Count == 0) 190 - return; 191 - 192 - GLWrapper.PushDepthInfo(DepthInfo.Default); 193 - 194 - IShader shader = needsRoundedShader ? RoundedTextureShader : TextureShader; 195 - 196 - shader.Bind(); 197 - 198 - Texture.TextureGL.WrapMode = TextureWrapMode.ClampToEdge; 199 - Texture.TextureGL.Bind(); 200 - 201 - updateVertexBuffer(); 202 - 203 - shader.Unbind(); 204 - 205 - GLWrapper.PopDepthInfo(); 206 - } 207 - 208 - protected override void Dispose(bool isDisposing) 209 - { 210 - base.Dispose(isDisposing); 211 - 212 - halfCircleBatch.Dispose(); 213 - quadBatch.Dispose(); 214 - } 215 - } 216 - }
···
+230
osu.Framework/Graphics/Lines/Path_DrawNode.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.Primitives; 5 + using osu.Framework.Graphics.Textures; 6 + using osuTK.Graphics.ES30; 7 + using osu.Framework.Graphics.OpenGL; 8 + using osuTK; 9 + using System; 10 + using System.Collections.Generic; 11 + using osu.Framework.Graphics.Batches; 12 + using osu.Framework.Graphics.OpenGL.Vertices; 13 + using osuTK.Graphics; 14 + 15 + namespace osu.Framework.Graphics.Lines 16 + { 17 + public partial class Path 18 + { 19 + private class PathDrawNode : TexturedShaderDrawNode 20 + { 21 + public const int MAX_RES = 24; 22 + 23 + protected new Path Source => (Path)base.Source; 24 + 25 + private readonly List<Line> segments = new List<Line>(); 26 + 27 + private Texture texture; 28 + private Vector2 drawSize; 29 + private float radius; 30 + 31 + // We multiply the size param by 3 such that the amount of vertices is a multiple of the amount of vertices 32 + // per primitive (triangles in this case). Otherwise overflowing the batch will result in wrong 33 + // grouping of vertices into primitives. 34 + private readonly LinearBatch<TexturedVertex3D> halfCircleBatch = new LinearBatch<TexturedVertex3D>(MAX_RES * 100 * 3, 10, PrimitiveType.Triangles); 35 + private readonly QuadBatch<TexturedVertex3D> quadBatch = new QuadBatch<TexturedVertex3D>(200, 10); 36 + 37 + public PathDrawNode(Path source) 38 + : base(source) 39 + { 40 + } 41 + 42 + public override void ApplyState() 43 + { 44 + base.ApplyState(); 45 + 46 + segments.Clear(); 47 + segments.AddRange(Source.segments); 48 + 49 + texture = Source.Texture; 50 + drawSize = Source.DrawSize; 51 + radius = Source.PathRadius; 52 + } 53 + 54 + private Vector2 pointOnCircle(float angle) => new Vector2((float)Math.Sin(angle), -(float)Math.Cos(angle)); 55 + 56 + private Vector2 relativePosition(Vector2 localPos) => Vector2.Divide(localPos, drawSize); 57 + 58 + private Color4 colourAt(Vector2 localPos) => DrawColourInfo.Colour.HasSingleColour 59 + ? (Color4)DrawColourInfo.Colour 60 + : DrawColourInfo.Colour.Interpolate(relativePosition(localPos)).Linear; 61 + 62 + private void addLineCap(Vector2 origin, float theta, float thetaDiff, RectangleF texRect) 63 + { 64 + const float step = MathHelper.Pi / MAX_RES; 65 + 66 + float dir = Math.Sign(thetaDiff); 67 + thetaDiff = dir * thetaDiff; 68 + 69 + int amountPoints = (int)Math.Ceiling(thetaDiff / step); 70 + 71 + if (dir < 0) 72 + theta += MathHelper.Pi; 73 + 74 + Vector2 current = origin + pointOnCircle(theta) * radius; 75 + Color4 currentColour = colourAt(current); 76 + current = Vector2Extensions.Transform(current, DrawInfo.Matrix); 77 + 78 + Vector2 screenOrigin = Vector2Extensions.Transform(origin, DrawInfo.Matrix); 79 + Color4 originColour = colourAt(origin); 80 + 81 + for (int i = 1; i <= amountPoints; i++) 82 + { 83 + // Center point 84 + halfCircleBatch.Add(new TexturedVertex3D 85 + { 86 + Position = new Vector3(screenOrigin.X, screenOrigin.Y, 1), 87 + TexturePosition = new Vector2(texRect.Right, texRect.Centre.Y), 88 + Colour = originColour 89 + }); 90 + 91 + // First outer point 92 + halfCircleBatch.Add(new TexturedVertex3D 93 + { 94 + Position = new Vector3(current.X, current.Y, 0), 95 + TexturePosition = new Vector2(texRect.Left, texRect.Centre.Y), 96 + Colour = currentColour 97 + }); 98 + 99 + float angularOffset = Math.Min(i * step, thetaDiff); 100 + current = origin + pointOnCircle(theta + dir * angularOffset) * radius; 101 + currentColour = colourAt(current); 102 + current = Vector2Extensions.Transform(current, DrawInfo.Matrix); 103 + 104 + // Second outer point 105 + halfCircleBatch.Add(new TexturedVertex3D 106 + { 107 + Position = new Vector3(current.X, current.Y, 0), 108 + TexturePosition = new Vector2(texRect.Left, texRect.Centre.Y), 109 + Colour = currentColour 110 + }); 111 + } 112 + } 113 + 114 + private void addLineQuads(Line line, RectangleF texRect) 115 + { 116 + Vector2 ortho = line.OrthogonalDirection; 117 + Line lineLeft = new Line(line.StartPoint + ortho * radius, line.EndPoint + ortho * radius); 118 + Line lineRight = new Line(line.StartPoint - ortho * radius, line.EndPoint - ortho * radius); 119 + 120 + Line screenLineLeft = new Line(Vector2Extensions.Transform(lineLeft.StartPoint, DrawInfo.Matrix), Vector2Extensions.Transform(lineLeft.EndPoint, DrawInfo.Matrix)); 121 + Line screenLineRight = new Line(Vector2Extensions.Transform(lineRight.StartPoint, DrawInfo.Matrix), Vector2Extensions.Transform(lineRight.EndPoint, DrawInfo.Matrix)); 122 + Line screenLine = new Line(Vector2Extensions.Transform(line.StartPoint, DrawInfo.Matrix), Vector2Extensions.Transform(line.EndPoint, DrawInfo.Matrix)); 123 + 124 + quadBatch.Add(new TexturedVertex3D 125 + { 126 + Position = new Vector3(screenLineRight.EndPoint.X, screenLineRight.EndPoint.Y, 0), 127 + TexturePosition = new Vector2(texRect.Left, texRect.Centre.Y), 128 + Colour = colourAt(lineRight.EndPoint) 129 + }); 130 + quadBatch.Add(new TexturedVertex3D 131 + { 132 + Position = new Vector3(screenLineRight.StartPoint.X, screenLineRight.StartPoint.Y, 0), 133 + TexturePosition = new Vector2(texRect.Left, texRect.Centre.Y), 134 + Colour = colourAt(lineRight.StartPoint) 135 + }); 136 + 137 + // Each "quad" of the slider is actually rendered as 2 quads, being split in half along the approximating line. 138 + // On this line the depth is 1 instead of 0, which is done properly handle self-overlap using the depth buffer. 139 + // Thus the middle vertices need to be added twice (once for each quad). 140 + Vector3 firstMiddlePoint = new Vector3(screenLine.StartPoint.X, screenLine.StartPoint.Y, 1); 141 + Vector3 secondMiddlePoint = new Vector3(screenLine.EndPoint.X, screenLine.EndPoint.Y, 1); 142 + Color4 firstMiddleColour = colourAt(line.StartPoint); 143 + Color4 secondMiddleColour = colourAt(line.EndPoint); 144 + 145 + for (int i = 0; i < 2; ++i) 146 + { 147 + quadBatch.Add(new TexturedVertex3D 148 + { 149 + Position = firstMiddlePoint, 150 + TexturePosition = new Vector2(texRect.Right, texRect.Centre.Y), 151 + Colour = firstMiddleColour 152 + }); 153 + quadBatch.Add(new TexturedVertex3D 154 + { 155 + Position = secondMiddlePoint, 156 + TexturePosition = new Vector2(texRect.Right, texRect.Centre.Y), 157 + Colour = secondMiddleColour 158 + }); 159 + } 160 + 161 + quadBatch.Add(new TexturedVertex3D 162 + { 163 + Position = new Vector3(screenLineLeft.EndPoint.X, screenLineLeft.EndPoint.Y, 0), 164 + TexturePosition = new Vector2(texRect.Left, texRect.Centre.Y), 165 + Colour = colourAt(lineLeft.EndPoint) 166 + }); 167 + quadBatch.Add(new TexturedVertex3D 168 + { 169 + Position = new Vector3(screenLineLeft.StartPoint.X, screenLineLeft.StartPoint.Y, 0), 170 + TexturePosition = new Vector2(texRect.Left, texRect.Centre.Y), 171 + Colour = colourAt(lineLeft.StartPoint) 172 + }); 173 + } 174 + 175 + private void updateVertexBuffer() 176 + { 177 + Line line = segments[0]; 178 + float theta = line.Theta; 179 + 180 + // Offset by 0.5 pixels inwards to ensure we never sample texels outside the bounds 181 + RectangleF texRect = texture.GetTextureRect(new RectangleF(0.5f, 0.5f, texture.Width - 1, texture.Height - 1)); 182 + addLineCap(line.StartPoint, theta + MathHelper.Pi, MathHelper.Pi, texRect); 183 + 184 + for (int i = 1; i < segments.Count; ++i) 185 + { 186 + Line nextLine = segments[i]; 187 + float nextTheta = nextLine.Theta; 188 + addLineCap(line.EndPoint, theta, nextTheta - theta, texRect); 189 + 190 + line = nextLine; 191 + theta = nextTheta; 192 + } 193 + 194 + addLineCap(line.EndPoint, theta, MathHelper.Pi, texRect); 195 + 196 + foreach (Line segment in segments) 197 + addLineQuads(segment, texRect); 198 + } 199 + 200 + public override void Draw(Action<TexturedVertex2D> vertexAction) 201 + { 202 + base.Draw(vertexAction); 203 + 204 + if (texture?.Available != true || segments.Count == 0) 205 + return; 206 + 207 + GLWrapper.PushDepthInfo(DepthInfo.Default); 208 + 209 + Shader.Bind(); 210 + 211 + texture.TextureGL.WrapMode = TextureWrapMode.ClampToEdge; 212 + texture.TextureGL.Bind(); 213 + 214 + updateVertexBuffer(); 215 + 216 + Shader.Unbind(); 217 + 218 + GLWrapper.PopDepthInfo(); 219 + } 220 + 221 + protected override void Dispose(bool isDisposing) 222 + { 223 + base.Dispose(isDisposing); 224 + 225 + halfCircleBatch.Dispose(); 226 + quadBatch.Dispose(); 227 + } 228 + } 229 + } 230 + }
+6 -7
osu.Framework/Graphics/Lines/SmoothPath.cs
··· 69 textureCache.Validate(); 70 } 71 72 /// <summary> 73 /// Retrieves the colour from a position in the texture of the <see cref="Path"/>. 74 /// </summary> 75 /// <param name="position">The position within the texture. 0 indicates the outermost-point of the path, 1 indicates the centre of the path.</param> 76 /// <returns></returns> 77 protected virtual Color4 ColourAt(float position) => Color4.White; 78 - 79 - protected override void ApplyDrawNode(DrawNode node) 80 - { 81 - validateTexture(); 82 - 83 - base.ApplyDrawNode(node); 84 - } 85 } 86 }
··· 69 textureCache.Validate(); 70 } 71 72 + internal override DrawNode GenerateDrawNodeSubtree(ulong frame, int treeIndex, bool forceNewDrawNode) 73 + { 74 + validateTexture(); 75 + return base.GenerateDrawNodeSubtree(frame, treeIndex, forceNewDrawNode); 76 + } 77 + 78 /// <summary> 79 /// Retrieves the colour from a position in the texture of the <see cref="Path"/>. 80 /// </summary> 81 /// <param name="position">The position within the texture. 0 indicates the outermost-point of the path, 1 indicates the centre of the path.</param> 82 /// <returns></returns> 83 protected virtual Color4 ColourAt(float position) => Color4.White; 84 } 85 }
+8 -1
osu.Framework/Graphics/Shapes/Triangle.cs
··· 32 33 public override bool Contains(Vector2 screenSpacePos) => toTriangle(ScreenSpaceDrawQuad).Contains(screenSpacePos); 34 35 - protected override DrawNode CreateDrawNode() => new TriangleDrawNode(); 36 37 private class TriangleDrawNode : SpriteDrawNode 38 { 39 protected override void Blit(Action<TexturedVertex2D> vertexAction) 40 { 41 Texture.DrawTriangle(toTriangle(ScreenSpaceDrawQuad), DrawColourInfo.Colour, null, null,
··· 32 33 public override bool Contains(Vector2 screenSpacePos) => toTriangle(ScreenSpaceDrawQuad).Contains(screenSpacePos); 34 35 + protected override DrawNode CreateDrawNode() => new TriangleDrawNode(this); 36 37 private class TriangleDrawNode : SpriteDrawNode 38 { 39 + protected new Triangle Source => (Triangle)base.Source; 40 + 41 + public TriangleDrawNode(Triangle source) 42 + : base(source) 43 + { 44 + } 45 + 46 protected override void Blit(Action<TexturedVertex2D> vertexAction) 47 { 48 Texture.DrawTriangle(toTriangle(ScreenSpaceDrawQuad), DrawColourInfo.Colour, null, null,
+12 -26
osu.Framework/Graphics/Sprites/Sprite.cs
··· 13 /// <summary> 14 /// A sprite that displays its texture. 15 /// </summary> 16 - public class Sprite : Drawable 17 { 18 - private IShader textureShader; 19 - private IShader roundedTextureShader; 20 21 /// <summary> 22 /// True if the texture should be tiled. If you had a 16x16 texture and scaled the sprite to be 64x64 the texture would be repeated in a 4x4 grid along the size of the sprite. 23 /// </summary> 24 - public bool WrapTexture; 25 26 /// <summary> 27 /// Maximum value that can be set for <see cref="EdgeSmoothness"/> on either axis. ··· 49 50 #endregion 51 52 - protected override DrawNode CreateDrawNode() => new SpriteDrawNode(); 53 - 54 - protected override void ApplyDrawNode(DrawNode node) 55 - { 56 - SpriteDrawNode n = (SpriteDrawNode)node; 57 - 58 - n.ScreenSpaceDrawQuad = ScreenSpaceDrawQuad; 59 - n.DrawRectangle = DrawRectangle; 60 - n.Texture = Texture; 61 - n.WrapTexture = WrapTexture; 62 - n.InflationAmount = inflationAmount; 63 - n.TextureShader = textureShader; 64 - n.RoundedTextureShader = roundedTextureShader; 65 - 66 - base.ApplyDrawNode(node); 67 - } 68 69 [BackgroundDependencyLoader] 70 private void load(ShaderManager shaders) 71 { 72 - textureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE); 73 - roundedTextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED); 74 } 75 76 private Texture texture; ··· 100 } 101 } 102 103 - private Vector2 inflationAmount; 104 105 protected override Quad ComputeScreenSpaceDrawQuad() 106 { 107 if (EdgeSmoothness == Vector2.Zero) 108 { 109 - inflationAmount = Vector2.Zero; 110 return base.ComputeScreenSpaceDrawQuad(); 111 } 112 ··· 116 117 Vector3 scale = DrawInfo.MatrixInverse.ExtractScale(); 118 119 - inflationAmount = new Vector2(scale.X * EdgeSmoothness.X, scale.Y * EdgeSmoothness.Y); 120 - return ToScreenSpace(DrawRectangle.Inflate(inflationAmount)); 121 } 122 123 public override string ToString()
··· 13 /// <summary> 14 /// A sprite that displays its texture. 15 /// </summary> 16 + public class Sprite : Drawable, ITexturedShaderDrawable 17 { 18 + public IShader TextureShader { get; private set; } 19 + 20 + public IShader RoundedTextureShader { get; private set; } 21 22 /// <summary> 23 /// True if the texture should be tiled. If you had a 16x16 texture and scaled the sprite to be 64x64 the texture would be repeated in a 4x4 grid along the size of the sprite. 24 /// </summary> 25 + public bool WrapTexture { get; set; } 26 27 /// <summary> 28 /// Maximum value that can be set for <see cref="EdgeSmoothness"/> on either axis. ··· 50 51 #endregion 52 53 + protected override DrawNode CreateDrawNode() => new SpriteDrawNode(this); 54 55 [BackgroundDependencyLoader] 56 private void load(ShaderManager shaders) 57 { 58 + TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE); 59 + RoundedTextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED); 60 } 61 62 private Texture texture; ··· 86 } 87 } 88 89 + public Vector2 InflationAmount { get; private set; } 90 91 protected override Quad ComputeScreenSpaceDrawQuad() 92 { 93 if (EdgeSmoothness == Vector2.Zero) 94 { 95 + InflationAmount = Vector2.Zero; 96 return base.ComputeScreenSpaceDrawQuad(); 97 } 98 ··· 102 103 Vector3 scale = DrawInfo.MatrixInverse.ExtractScale(); 104 105 + InflationAmount = new Vector2(scale.X * EdgeSmoothness.X, scale.Y * EdgeSmoothness.Y); 106 + return ToScreenSpace(DrawRectangle.Inflate(InflationAmount)); 107 } 108 109 public override string ToString()
+32 -19
osu.Framework/Graphics/Sprites/SpriteDrawNode.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.Primitives; 5 - using osu.Framework.Graphics.Shaders; 6 using osu.Framework.Graphics.Textures; 7 using osuTK.Graphics.ES30; 8 - using osu.Framework.Graphics.OpenGL; 9 - using osuTK; 10 - using System; 11 - using osu.Framework.Graphics.OpenGL.Vertices; 12 13 namespace osu.Framework.Graphics.Sprites 14 { 15 /// <summary> 16 /// Draw node containing all necessary information to draw a <see cref="Sprite"/>. 17 /// </summary> 18 - public class SpriteDrawNode : DrawNode 19 { 20 - public Texture Texture; 21 - public Quad ScreenSpaceDrawQuad; 22 - public RectangleF DrawRectangle; 23 - public Vector2 InflationAmount; 24 - public bool WrapTexture; 25 26 - public IShader TextureShader; 27 - public IShader RoundedTextureShader; 28 29 - private bool needsRoundedShader => GLWrapper.IsMaskingActive || InflationAmount != Vector2.Zero; 30 31 protected virtual void Blit(Action<TexturedVertex2D> vertexAction) 32 { ··· 41 if (Texture?.Available != true) 42 return; 43 44 - IShader shader = needsRoundedShader ? RoundedTextureShader : TextureShader; 45 - 46 - shader.Bind(); 47 - 48 Texture.TextureGL.WrapMode = WrapTexture ? TextureWrapMode.Repeat : TextureWrapMode.ClampToEdge; 49 50 Blit(vertexAction); 51 52 - shader.Unbind(); 53 } 54 } 55 }
··· 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 osuTK; 5 + using System; 6 + using osu.Framework.Graphics.OpenGL.Vertices; 7 using osu.Framework.Graphics.Primitives; 8 using osu.Framework.Graphics.Textures; 9 using osuTK.Graphics.ES30; 10 11 namespace osu.Framework.Graphics.Sprites 12 { 13 /// <summary> 14 /// Draw node containing all necessary information to draw a <see cref="Sprite"/>. 15 /// </summary> 16 + public class SpriteDrawNode : TexturedShaderDrawNode 17 { 18 + protected Texture Texture { get; private set; } 19 + protected Quad ScreenSpaceDrawQuad { get; private set; } 20 + 21 + protected RectangleF DrawRectangle { get; private set; } 22 + protected Vector2 InflationAmount { get; private set; } 23 + 24 + protected bool WrapTexture { get; private set; } 25 + 26 + protected new Sprite Source => (Sprite)base.Source; 27 + 28 + public SpriteDrawNode(Sprite source) 29 + : base(source) 30 + { 31 + } 32 33 + public override void ApplyState() 34 + { 35 + base.ApplyState(); 36 37 + Texture = Source.Texture; 38 + ScreenSpaceDrawQuad = Source.ScreenSpaceDrawQuad; 39 + DrawRectangle = Source.DrawRectangle; 40 + InflationAmount = Source.InflationAmount; 41 + WrapTexture = Source.WrapTexture; 42 + } 43 44 protected virtual void Blit(Action<TexturedVertex2D> vertexAction) 45 { ··· 54 if (Texture?.Available != true) 55 return; 56 57 Texture.TextureGL.WrapMode = WrapTexture ? TextureWrapMode.Repeat : TextureWrapMode.ClampToEdge; 58 59 + Shader.Bind(); 60 + 61 Blit(vertexAction); 62 63 + Shader.Unbind(); 64 } 65 + 66 + protected override bool RequiresRoundedShader => base.RequiresRoundedShader || InflationAmount != Vector2.Zero; 67 } 68 }
+7 -27
osu.Framework/Graphics/Sprites/SpriteText.cs
··· 23 /// <summary> 24 /// A container for simple text rendering purposes. If more complex text rendering is required, use <see cref="TextFlowContainer"/> instead. 25 /// </summary> 26 - public partial class SpriteText : Drawable, IHasLineBaseHeight, IHasText, IHasFilterTerms, IFillFlowContainer, IHasCurrentValue<string> 27 { 28 private const float default_text_size = 20; 29 private static readonly Vector2 shadow_offset = new Vector2(0, 0.06f); ··· 38 39 private float spaceWidth; 40 41 - private IShader textureShader; 42 - private IShader roundedTextureShader; 43 44 public SpriteText() 45 { ··· 65 }, true); 66 67 spaceWidth = getTextureForCharacter('.')?.DisplayWidth * 2 ?? 1; 68 - textureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE); 69 - roundedTextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED); 70 71 // Pre-cache the characters in the texture store 72 foreach (var character in displayedText) ··· 553 554 #region DrawNode 555 556 - protected override DrawNode CreateDrawNode() => new SpriteTextDrawNode(); 557 - 558 - protected override void ApplyDrawNode(DrawNode node) 559 - { 560 - base.ApplyDrawNode(node); 561 - 562 - var n = (SpriteTextDrawNode)node; 563 - 564 - n.Parts.Clear(); 565 - n.Parts.AddRange(screenSpaceCharacters); 566 - 567 - n.Shadow = Shadow; 568 - 569 - n.TextureShader = textureShader; 570 - n.RoundedTextureShader = roundedTextureShader; 571 - 572 - if (Shadow) 573 - { 574 - n.ShadowColour = ShadowColour; 575 - n.ShadowOffset = shadowOffset; 576 - } 577 - } 578 579 #endregion 580
··· 23 /// <summary> 24 /// A container for simple text rendering purposes. If more complex text rendering is required, use <see cref="TextFlowContainer"/> instead. 25 /// </summary> 26 + public partial class SpriteText : Drawable, IHasLineBaseHeight, ITexturedShaderDrawable, IHasText, IHasFilterTerms, IFillFlowContainer, IHasCurrentValue<string> 27 { 28 private const float default_text_size = 20; 29 private static readonly Vector2 shadow_offset = new Vector2(0, 0.06f); ··· 38 39 private float spaceWidth; 40 41 + public IShader TextureShader { get; private set; } 42 + public IShader RoundedTextureShader { get; private set; } 43 44 public SpriteText() 45 { ··· 65 }, true); 66 67 spaceWidth = getTextureForCharacter('.')?.DisplayWidth * 2 ?? 1; 68 + 69 + TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE); 70 + RoundedTextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED); 71 72 // Pre-cache the characters in the texture store 73 foreach (var character in displayedText) ··· 554 555 #region DrawNode 556 557 + protected override DrawNode CreateDrawNode() => new SpriteTextDrawNode(this); 558 559 #endregion 560
+38 -25
osu.Framework/Graphics/Sprites/SpriteText_DrawNode.cs
··· 4 using System; 5 using System.Collections.Generic; 6 using osu.Framework.Graphics.Colour; 7 - using osu.Framework.Graphics.OpenGL; 8 using osu.Framework.Graphics.OpenGL.Vertices; 9 using osu.Framework.Graphics.Primitives; 10 - using osu.Framework.Graphics.Shaders; 11 using osu.Framework.Graphics.Textures; 12 using osuTK; 13 using osuTK.Graphics; ··· 16 { 17 public partial class SpriteText 18 { 19 - internal class SpriteTextDrawNode : DrawNode 20 { 21 - public bool Shadow; 22 - public ColourInfo ShadowColour; 23 - public Vector2 ShadowOffset; 24 25 - public IShader TextureShader; 26 - public IShader RoundedTextureShader; 27 28 - internal readonly List<ScreenSpaceCharacterPart> Parts = new List<ScreenSpaceCharacterPart>(); 29 30 - private bool needsRoundedShader => GLWrapper.IsMaskingActive; 31 32 public override void Draw(Action<TexturedVertex2D> vertexAction) 33 { 34 base.Draw(vertexAction); 35 36 - IShader shader = needsRoundedShader ? RoundedTextureShader : TextureShader; 37 - 38 - shader.Bind(); 39 40 var avgColour = (Color4)DrawColourInfo.Colour.AverageColour; 41 float shadowAlpha = (float)Math.Pow(Math.Max(Math.Max(avgColour.R, avgColour.G), avgColour.B), 2); 42 43 //adjust shadow alpha based on highest component intensity to avoid muddy display of darker text. 44 //squared result for quadratic fall-off seems to give the best result. 45 - var shadowColour = DrawColourInfo.Colour; 46 - shadowColour.ApplyChild(ShadowColour.MultiplyAlpha(shadowAlpha)); 47 48 - for (int i = 0; i < Parts.Count; i++) 49 { 50 - if (Shadow) 51 { 52 - var shadowQuad = Parts[i].DrawQuad; 53 - shadowQuad.TopLeft += ShadowOffset; 54 - shadowQuad.TopRight += ShadowOffset; 55 - shadowQuad.BottomLeft += ShadowOffset; 56 - shadowQuad.BottomRight += ShadowOffset; 57 58 - Parts[i].Texture.DrawQuad(shadowQuad, shadowColour, vertexAction: vertexAction); 59 } 60 61 - Parts[i].Texture.DrawQuad(Parts[i].DrawQuad, DrawColourInfo.Colour, vertexAction: vertexAction); 62 } 63 64 - shader.Unbind(); 65 } 66 } 67
··· 4 using System; 5 using System.Collections.Generic; 6 using osu.Framework.Graphics.Colour; 7 using osu.Framework.Graphics.OpenGL.Vertices; 8 using osu.Framework.Graphics.Primitives; 9 using osu.Framework.Graphics.Textures; 10 using osuTK; 11 using osuTK.Graphics; ··· 14 { 15 public partial class SpriteText 16 { 17 + internal class SpriteTextDrawNode : TexturedShaderDrawNode 18 { 19 + protected new SpriteText Source => (SpriteText)base.Source; 20 21 + private bool shadow; 22 + private ColourInfo shadowColour; 23 + private Vector2 shadowOffset; 24 25 + private readonly List<ScreenSpaceCharacterPart> parts = new List<ScreenSpaceCharacterPart>(); 26 27 + public SpriteTextDrawNode(SpriteText source) 28 + : base(source) 29 + { 30 + } 31 + 32 + public override void ApplyState() 33 + { 34 + base.ApplyState(); 35 + 36 + parts.Clear(); 37 + parts.AddRange(Source.screenSpaceCharacters); 38 + shadow = Source.Shadow; 39 + 40 + if (shadow) 41 + { 42 + shadowColour = Source.ShadowColour; 43 + shadowOffset = Source.shadowOffset; 44 + } 45 + } 46 47 public override void Draw(Action<TexturedVertex2D> vertexAction) 48 { 49 base.Draw(vertexAction); 50 51 + Shader.Bind(); 52 53 var avgColour = (Color4)DrawColourInfo.Colour.AverageColour; 54 float shadowAlpha = (float)Math.Pow(Math.Max(Math.Max(avgColour.R, avgColour.G), avgColour.B), 2); 55 56 //adjust shadow alpha based on highest component intensity to avoid muddy display of darker text. 57 //squared result for quadratic fall-off seems to give the best result. 58 + var finalShadowColour = DrawColourInfo.Colour; 59 + finalShadowColour.ApplyChild(shadowColour.MultiplyAlpha(shadowAlpha)); 60 61 + for (int i = 0; i < parts.Count; i++) 62 { 63 + if (shadow) 64 { 65 + var shadowQuad = parts[i].DrawQuad; 66 + shadowQuad.TopLeft += shadowOffset; 67 + shadowQuad.TopRight += shadowOffset; 68 + shadowQuad.BottomLeft += shadowOffset; 69 + shadowQuad.BottomRight += shadowOffset; 70 71 + parts[i].Texture.DrawQuad(shadowQuad, finalShadowColour, vertexAction: vertexAction); 72 } 73 74 + parts[i].Texture.DrawQuad(parts[i].DrawQuad, DrawColourInfo.Colour, vertexAction: vertexAction); 75 } 76 77 + Shader.Unbind(); 78 } 79 } 80
+33
osu.Framework/Graphics/TexturedShaderDrawNode.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; 5 + using osu.Framework.Graphics.Shaders; 6 + 7 + namespace osu.Framework.Graphics 8 + { 9 + public abstract class TexturedShaderDrawNode : DrawNode 10 + { 11 + protected IShader Shader => RequiresRoundedShader ? roundedTextureShader : textureShader; 12 + 13 + private IShader textureShader; 14 + private IShader roundedTextureShader; 15 + 16 + protected new ITexturedShaderDrawable Source => (ITexturedShaderDrawable)base.Source; 17 + 18 + protected TexturedShaderDrawNode(ITexturedShaderDrawable source) 19 + : base(source) 20 + { 21 + } 22 + 23 + public override void ApplyState() 24 + { 25 + base.ApplyState(); 26 + 27 + textureShader = Source.TextureShader; 28 + roundedTextureShader = Source.RoundedTextureShader; 29 + } 30 + 31 + protected virtual bool RequiresRoundedShader => GLWrapper.IsMaskingActive; 32 + } 33 + }
+1
osu.Framework/Graphics/TransformableExtensions.cs
··· 10 using System.Linq; 11 using JetBrains.Annotations; 12 using osu.Framework.Bindables; 13 using osu.Framework.MathUtils; 14 15 namespace osu.Framework.Graphics
··· 10 using System.Linq; 11 using JetBrains.Annotations; 12 using osu.Framework.Bindables; 13 + using osu.Framework.Graphics.Effects; 14 using osu.Framework.MathUtils; 15 16 namespace osu.Framework.Graphics
+6 -20
osu.Framework/Graphics/UserInterface/CircularProgress.cs
··· 11 12 namespace osu.Framework.Graphics.UserInterface 13 { 14 - public class CircularProgress : Drawable, IHasCurrentValue<double> 15 { 16 private readonly Bindable<double> current = new Bindable<double>(); 17 ··· 33 Current.ValueChanged += newValue => Invalidate(Invalidation.DrawNode); 34 } 35 36 - private IShader roundedTextureShader; 37 - private IShader textureShader; 38 39 #region Disposal 40 ··· 48 49 #endregion 50 51 - protected override DrawNode CreateDrawNode() => new CircularProgressDrawNode(); 52 - 53 - protected override void ApplyDrawNode(DrawNode node) 54 - { 55 - CircularProgressDrawNode n = (CircularProgressDrawNode)node; 56 - 57 - n.Texture = Texture; 58 - n.TextureShader = textureShader; 59 - n.RoundedTextureShader = roundedTextureShader; 60 - n.DrawSize = DrawSize; 61 - n.Angle = (float)Current.Value * MathHelper.TwoPi; 62 - n.InnerRadius = innerRadius; 63 - 64 - base.ApplyDrawNode(node); 65 - } 66 67 public TransformSequence<CircularProgress> FillTo(double newValue, double duration = 0, Easing easing = Easing.None) 68 => this.TransformBindableTo(Current, newValue, duration, easing); ··· 70 [BackgroundDependencyLoader] 71 private void load(ShaderManager shaders) 72 { 73 - roundedTextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED); 74 - textureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE); 75 } 76 77 private Texture texture = Texture.WhitePixel;
··· 11 12 namespace osu.Framework.Graphics.UserInterface 13 { 14 + public class CircularProgress : Drawable, ITexturedShaderDrawable, IHasCurrentValue<double> 15 { 16 private readonly Bindable<double> current = new Bindable<double>(); 17 ··· 33 Current.ValueChanged += newValue => Invalidate(Invalidation.DrawNode); 34 } 35 36 + public IShader RoundedTextureShader { get; private set; } 37 + public IShader TextureShader { get; private set; } 38 39 #region Disposal 40 ··· 48 49 #endregion 50 51 + protected override DrawNode CreateDrawNode() => new CircularProgressDrawNode(this); 52 53 public TransformSequence<CircularProgress> FillTo(double newValue, double duration = 0, Easing easing = Easing.None) 54 => this.TransformBindableTo(Current, newValue, duration, easing); ··· 56 [BackgroundDependencyLoader] 57 private void load(ShaderManager shaders) 58 { 59 + RoundedTextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED); 60 + TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE); 61 } 62 63 private Texture texture = Texture.WhitePixel;
+38 -29
osu.Framework/Graphics/UserInterface/CircularProgressDrawNode.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.Shaders; 5 using osu.Framework.Graphics.Textures; 6 using osuTK.Graphics.ES30; 7 - using osu.Framework.Graphics.OpenGL; 8 using osuTK; 9 using System; 10 using osu.Framework.Graphics.Batches; ··· 15 16 namespace osu.Framework.Graphics.UserInterface 17 { 18 - public class CircularProgressDrawNode : DrawNode 19 { 20 - public const int MAXRES = 24; 21 - public float Angle; 22 - public float InnerRadius = 1; 23 24 - public Vector2 DrawSize; 25 - public Texture Texture; 26 27 - public IShader TextureShader; 28 - public IShader RoundedTextureShader; 29 30 // We add 2 to the size param to account for the first triangle needing every vertex passed, subsequent triangles use the last two vertices of the previous triangle. 31 - // MAXRES is being multiplied by 2 to account for each circle part needing 2 triangles 32 // Otherwise overflowing the batch will result in wrong grouping of vertices into primitives. 33 - private readonly LinearBatch<TexturedVertex2D> halfCircleBatch = new LinearBatch<TexturedVertex2D>(MAXRES * 100 * 2 + 2, 10, PrimitiveType.TriangleStrip); 34 - 35 - private bool needsRoundedShader => GLWrapper.IsMaskingActive; 36 37 private Vector2 pointOnCircle(float angle) => new Vector2((float)Math.Sin(angle), -(float)Math.Cos(angle)); 38 private float angleToUnitInterval(float angle) => angle / MathHelper.TwoPi + (angle >= 0 ? 0 : 1); ··· 47 private void updateVertexBuffer() 48 { 49 const float start_angle = 0; 50 - const float step = MathHelper.Pi / MAXRES; 51 52 - float dir = Math.Sign(Angle); 53 54 - int amountPoints = (int)Math.Ceiling(Math.Abs(Angle) / step); 55 56 Matrix3 transformationMatrix = DrawInfo.Matrix; 57 - MatrixExtensions.ScaleFromLeft(ref transformationMatrix, DrawSize); 58 59 Vector2 current = origin + pointOnCircle(start_angle) * 0.5f; 60 Color4 currentColour = colourAt(current); ··· 64 Color4 originColour = colourAt(origin); 65 66 // Offset by 0.5 pixels inwards to ensure we never sample texels outside the bounds 67 - RectangleF texRect = Texture.GetTextureRect(new RectangleF(0.5f, 0.5f, Texture.Width - 1, Texture.Height - 1)); 68 69 float prevOffset = dir >= 0 ? 0 : 1; 70 71 // First center point 72 halfCircleBatch.Add(new TexturedVertex2D 73 { 74 - Position = Vector2.Lerp(current, screenOrigin, InnerRadius), 75 TexturePosition = new Vector2(dir >= 0 ? texRect.Left : texRect.Right, texRect.Top), 76 Colour = originColour 77 }); ··· 88 { 89 // Clamps the angle so we don't overshoot. 90 // dir is used so negative angles result in negative angularOffset. 91 - float angularOffset = dir * Math.Min(i * step, dir * Angle); 92 float normalisedOffset = angularOffset / MathHelper.TwoPi; 93 if (dir < 0) 94 { ··· 103 // current center point 104 halfCircleBatch.Add(new TexturedVertex2D 105 { 106 - Position = Vector2.Lerp(current, screenOrigin, InnerRadius), 107 TexturePosition = new Vector2(texRect.Left + (normalisedOffset + prevOffset) / 2 * texRect.Width, texRect.Top), 108 Colour = originColour 109 }); ··· 124 { 125 base.Draw(vertexAction); 126 127 - if (Texture?.Available != true) 128 return; 129 130 - IShader shader = needsRoundedShader ? RoundedTextureShader : TextureShader; 131 - 132 - shader.Bind(); 133 134 - Texture.TextureGL.WrapMode = TextureWrapMode.ClampToEdge; 135 - Texture.TextureGL.Bind(); 136 137 updateVertexBuffer(); 138 139 - shader.Unbind(); 140 } 141 142 protected override void Dispose(bool isDisposing)
··· 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.Textures; 5 using osuTK.Graphics.ES30; 6 using osuTK; 7 using System; 8 using osu.Framework.Graphics.Batches; ··· 13 14 namespace osu.Framework.Graphics.UserInterface 15 { 16 + public class CircularProgressDrawNode : TexturedShaderDrawNode 17 { 18 + public const int MAX_RES = 24; 19 20 + protected new CircularProgress Source => (CircularProgress)base.Source; 21 22 + private float angle; 23 + private float innerRadius = 1; 24 + 25 + private Vector2 drawSize; 26 + private Texture texture; 27 + 28 + public CircularProgressDrawNode(CircularProgress source) 29 + : base(source) 30 + { 31 + } 32 + 33 + public override void ApplyState() 34 + { 35 + base.ApplyState(); 36 + 37 + texture = Source.Texture; 38 + drawSize = Source.DrawSize; 39 + angle = (float)Source.Current.Value * MathHelper.TwoPi; 40 + innerRadius = Source.InnerRadius; 41 + } 42 43 // We add 2 to the size param to account for the first triangle needing every vertex passed, subsequent triangles use the last two vertices of the previous triangle. 44 + // MAX_RES is being multiplied by 2 to account for each circle part needing 2 triangles 45 // Otherwise overflowing the batch will result in wrong grouping of vertices into primitives. 46 + private readonly LinearBatch<TexturedVertex2D> halfCircleBatch = new LinearBatch<TexturedVertex2D>(MAX_RES * 100 * 2 + 2, 10, PrimitiveType.TriangleStrip); 47 48 private Vector2 pointOnCircle(float angle) => new Vector2((float)Math.Sin(angle), -(float)Math.Cos(angle)); 49 private float angleToUnitInterval(float angle) => angle / MathHelper.TwoPi + (angle >= 0 ? 0 : 1); ··· 58 private void updateVertexBuffer() 59 { 60 const float start_angle = 0; 61 + const float step = MathHelper.Pi / MAX_RES; 62 63 + float dir = Math.Sign(angle); 64 65 + int amountPoints = (int)Math.Ceiling(Math.Abs(angle) / step); 66 67 Matrix3 transformationMatrix = DrawInfo.Matrix; 68 + MatrixExtensions.ScaleFromLeft(ref transformationMatrix, drawSize); 69 70 Vector2 current = origin + pointOnCircle(start_angle) * 0.5f; 71 Color4 currentColour = colourAt(current); ··· 75 Color4 originColour = colourAt(origin); 76 77 // Offset by 0.5 pixels inwards to ensure we never sample texels outside the bounds 78 + RectangleF texRect = texture.GetTextureRect(new RectangleF(0.5f, 0.5f, texture.Width - 1, texture.Height - 1)); 79 80 float prevOffset = dir >= 0 ? 0 : 1; 81 82 // First center point 83 halfCircleBatch.Add(new TexturedVertex2D 84 { 85 + Position = Vector2.Lerp(current, screenOrigin, innerRadius), 86 TexturePosition = new Vector2(dir >= 0 ? texRect.Left : texRect.Right, texRect.Top), 87 Colour = originColour 88 }); ··· 99 { 100 // Clamps the angle so we don't overshoot. 101 // dir is used so negative angles result in negative angularOffset. 102 + float angularOffset = dir * Math.Min(i * step, dir * angle); 103 float normalisedOffset = angularOffset / MathHelper.TwoPi; 104 if (dir < 0) 105 { ··· 114 // current center point 115 halfCircleBatch.Add(new TexturedVertex2D 116 { 117 + Position = Vector2.Lerp(current, screenOrigin, innerRadius), 118 TexturePosition = new Vector2(texRect.Left + (normalisedOffset + prevOffset) / 2 * texRect.Width, texRect.Top), 119 Colour = originColour 120 }); ··· 135 { 136 base.Draw(vertexAction); 137 138 + if (texture?.Available != true) 139 return; 140 141 + Shader.Bind(); 142 143 + texture.TextureGL.WrapMode = TextureWrapMode.ClampToEdge; 144 + texture.TextureGL.Bind(); 145 146 updateVertexBuffer(); 147 148 + Shader.Unbind(); 149 } 150 151 protected override void Dispose(bool isDisposing)
+1 -1
osu.Framework/MathUtils/Interpolation.cs
··· 7 using osuTK; 8 using osuTK.Graphics; 9 using osu.Framework.Graphics.Colour; 10 - using osu.Framework.Graphics.Containers; 11 using osu.Framework.Graphics.Primitives; 12 13 namespace osu.Framework.MathUtils
··· 7 using osuTK; 8 using osuTK.Graphics; 9 using osu.Framework.Graphics.Colour; 10 + using osu.Framework.Graphics.Effects; 11 using osu.Framework.Graphics.Primitives; 12 13 namespace osu.Framework.MathUtils
+5 -16
osu.Framework/Platform/GameHost.cs
··· 5 using System.Collections.Generic; 6 using System.Diagnostics; 7 using System.Drawing; 8 - using System.IO; 9 using System.Linq; 10 - using System.Reflection; 11 using System.Runtime; 12 using System.Runtime.ExceptionServices; 13 using System.Runtime.InteropServices; 14 using System.Threading; 15 using System.Threading.Tasks; 16 - using NUnit.Framework; 17 using osuTK; 18 using osuTK.Graphics; 19 using osuTK.Graphics.ES30; ··· 21 using osu.Framework.Allocation; 22 using osu.Framework.Bindables; 23 using osu.Framework.Configuration; 24 using osu.Framework.Extensions.IEnumerableExtensions; 25 using osu.Framework.Graphics; 26 using osu.Framework.Graphics.Containers; ··· 417 418 public void Run(Game game) 419 { 420 if (ExecutionState != ExecutionState.Idle) 421 throw new InvalidOperationException("A game that has already been run cannot be restarted."); 422 ··· 446 447 FileSafety.DeleteCleanupDirectory(); 448 449 - string assemblyPath; 450 - var assembly = Assembly.GetEntryAssembly(); 451 - 452 - // when running under nunit + netcore, entry assembly becomes nunit itself (testhost, Version=15.0.0.0), which isn't what we want. 453 - if (assembly == null || assembly.Location.Contains("testhost")) 454 - { 455 - assembly = Assembly.GetExecutingAssembly(); 456 - 457 - // From nuget, the executing assembly will also be wrong 458 - assemblyPath = TestContext.CurrentContext.TestDirectory; 459 - } 460 - else 461 - assemblyPath = Path.GetDirectoryName(assembly.Location); 462 463 Logger.GameIdentifier = Name; 464 Logger.VersionIdentifier = assembly.GetName().Version.ToString();
··· 5 using System.Collections.Generic; 6 using System.Diagnostics; 7 using System.Drawing; 8 using System.Linq; 9 using System.Runtime; 10 using System.Runtime.ExceptionServices; 11 using System.Runtime.InteropServices; 12 using System.Threading; 13 using System.Threading.Tasks; 14 using osuTK; 15 using osuTK.Graphics; 16 using osuTK.Graphics.ES30; ··· 18 using osu.Framework.Allocation; 19 using osu.Framework.Bindables; 20 using osu.Framework.Configuration; 21 + using osu.Framework.Development; 22 using osu.Framework.Extensions.IEnumerableExtensions; 23 using osu.Framework.Graphics; 24 using osu.Framework.Graphics.Containers; ··· 415 416 public void Run(Game game) 417 { 418 + DebugUtils.HostAssembly = game.GetType().Assembly; 419 + 420 if (ExecutionState != ExecutionState.Idle) 421 throw new InvalidOperationException("A game that has already been run cannot be restarted."); 422 ··· 446 447 FileSafety.DeleteCleanupDirectory(); 448 449 + var assembly = DebugUtils.GetEntryAssembly(); 450 + string assemblyPath = DebugUtils.GetEntryPath(); 451 452 Logger.GameIdentifier = Name; 453 Logger.VersionIdentifier = assembly.GetName().Version.ToString();
+2 -5
osu.Framework/Testing/TestScene.cs
··· 18 using System.Threading.Tasks; 19 using System.Threading; 20 using NUnit.Framework.Internal; 21 22 namespace osu.Framework.Testing 23 { ··· 35 private Task runTask; 36 private ITestSceneTestRunner runner; 37 38 - internal bool IsNUnitRunning; 39 - 40 [OneTimeSetUp] 41 public void SetupGameHost() 42 { 43 - IsNUnitRunning = true; 44 - 45 host = new HeadlessGameHost($"{GetType().Name}-{Guid.NewGuid()}", realtime: false); 46 runner = CreateRunner(); 47 ··· 85 [SetUp] 86 public void SetUpTestForNUnit() 87 { 88 - if (IsNUnitRunning) 89 { 90 // Since the host is created in OneTimeSetUp, all game threads will have the fixture's execution context 91 // This is undesirable since each test is run using those same threads, so we must make sure the execution context
··· 18 using System.Threading.Tasks; 19 using System.Threading; 20 using NUnit.Framework.Internal; 21 + using osu.Framework.Development; 22 23 namespace osu.Framework.Testing 24 { ··· 36 private Task runTask; 37 private ITestSceneTestRunner runner; 38 39 [OneTimeSetUp] 40 public void SetupGameHost() 41 { 42 host = new HeadlessGameHost($"{GetType().Name}-{Guid.NewGuid()}", realtime: false); 43 runner = CreateRunner(); 44 ··· 82 [SetUp] 83 public void SetUpTestForNUnit() 84 { 85 + if (DebugUtils.IsNUnitRunning) 86 { 87 // Since the host is created in OneTimeSetUp, all game threads will have the fixture's execution context 88 // This is undesirable since each test is run using those same threads, so we must make sure the execution context
+3 -2
osu.Framework/Timing/InterpolatingFramedClock.cs
··· 83 84 if (!allowInterpolation || Math.Abs(FramedSourceClock.CurrentTime - CurrentInterpolatedTime) > AllowableErrorMilliseconds) 85 { 86 - //if we've exceeded the allowable error, we should use the source clock's time value. 87 - CurrentInterpolatedTime = FramedSourceClock.CurrentTime; 88 89 // once interpolation fails, we don't want to resume interpolating until the source clock starts to move again. 90 allowInterpolation = false;
··· 83 84 if (!allowInterpolation || Math.Abs(FramedSourceClock.CurrentTime - CurrentInterpolatedTime) > AllowableErrorMilliseconds) 85 { 86 + // if we've exceeded the allowable error, we should use the source clock's time value. 87 + // seeking backwards should only be allowed if the source is explicitly doing that. 88 + CurrentInterpolatedTime = FramedSourceClock.ElapsedFrameTime < 0 ? FramedSourceClock.CurrentTime : Math.Max(LastInterpolatedTime, FramedSourceClock.CurrentTime); 89 90 // once interpolation fails, we don't want to resume interpolating until the source clock starts to move again. 91 allowInterpolation = false;