···41 Assert.GreaterOrEqual(interpolating.CurrentTime, source.CurrentTime, "Interpolating should not jump before source time.");
4243 Thread.Sleep((int)(interpolating.AllowableErrorMilliseconds / 2));
044 }
004546 // test with test clock elapsing
47 lastValue = interpolating.CurrentTime;
···5253 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.");
0005556 Thread.Sleep((int)(interpolating.AllowableErrorMilliseconds / 2));
057 }
000000000000000000000000000000000058 }
5960 [Test]
···41 Assert.GreaterOrEqual(interpolating.CurrentTime, source.CurrentTime, "Interpolating should not jump before source time.");
4243 Thread.Sleep((int)(interpolating.AllowableErrorMilliseconds / 2));
44+ lastValue = interpolating.CurrentTime;
45 }
46+47+ int interpolatingCount = 0;
4849 // test with test clock elapsing
50 lastValue = interpolating.CurrentTime;
···5556 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++;
6162 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 }
100101 [Test]
···2// See the LICENCE file in the repository root for full licence text.
34using NUnit.Framework;
05using osu.Framework.Testing;
67namespace 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 }
4849 [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 }
5657 protected override ITestSceneTestRunner CreateRunner() => new TestRunner();
···2// See the LICENCE file in the repository root for full licence text.
34using NUnit.Framework;
5+using osu.Framework.Development;
6using osu.Framework.Testing;
78namespace 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 }
4950 [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 }
5758 protected override ITestSceneTestRunner CreateRunner() => new TestRunner();
···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-}
···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+}
···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-}
···8using System.Linq;
9using System.Threading;
10using osuTK;
11-using osu.Framework.Graphics.OpenGL;
12using osuTK.Graphics;
13using osu.Framework.Graphics.Shaders;
14using osu.Framework.Extensions.IEnumerableExtensions;
···22using System.Threading.Tasks;
23using osu.Framework.Development;
24using osu.Framework.Extensions.ExceptionExtensions;
025using osu.Framework.Graphics.Primitives;
26using 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);
200201 // 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
···937938 #region DrawNode
939940- 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- };
969970- n.EdgeEffect = EdgeEffect;
971- n.ScreenSpaceMaskingQuad = null;
972- n.Shader = shader;
973- n.ForceLocalVertexBatch = ForceLocalVertexBatch;
974-975- base.ApplyDrawNode(node);
976- }
977978 private bool forceLocalVertexBatch;
979···1058 }
1059 }
10601061- 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;
10681069- if (!(base.GenerateDrawNodeSubtree(frame, treeIndex, forceNewDrawNode) is CompositeDrawNode cNode))
001070 return null;
10711072 if (cNode.Children == null)
1073 cNode.Children = new List<DrawNode>(aliveInternalChildren.Count);
10741075- if (AddChildDrawNodes)
1076 {
1077- List<DrawNode> target = cNode.Children;
1078-1079 int j = 0;
1080- addFromComposite(frame, treeIndex, forceNewDrawNode, ref j, this, target);
10811082- if (j < target.Count)
1083- target.RemoveRange(j, target.Count - j);
1084 }
10851086- return cNode;
1087 }
10881089 #endregion
···8using System.Linq;
9using System.Threading;
10using osuTK;
011using osuTK.Graphics;
12using osu.Framework.Graphics.Shaders;
13using osu.Framework.Extensions.IEnumerableExtensions;
···21using System.Threading.Tasks;
22using osu.Framework.Development;
23using osu.Framework.Extensions.ExceptionExtensions;
24+using osu.Framework.Graphics.Effects;
25using osu.Framework.Graphics.Primitives;
26using 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);
200201 // 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
···937938 #region DrawNode
939940+ internal IShader Shader { get; private set; }
0000000000000000000000000000941942+ protected override DrawNode CreateDrawNode() => new CompositeDrawableDrawNode(this);
000000943944 private bool forceLocalVertexBatch;
945···1024 }
1025 }
1026001027 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;
10321033+ DrawNode node = base.GenerateDrawNodeSubtree(frame, treeIndex, forceNewDrawNode);
1034+1035+ if (!(node is ICompositeDrawNode cNode))
1036 return null;
10371038 if (cNode.Children == null)
1039 cNode.Children = new List<DrawNode>(aliveInternalChildren.Count);
10401041+ if (cNode.AddChildDrawNodes)
1042 {
001043 int j = 0;
1044+ addFromComposite(frame, treeIndex, forceNewDrawNode, ref j, this, cNode.Children);
10451046+ if (j < cNode.Children.Count)
1047+ cNode.Children.RemoveRange(j, cNode.Children.Count - j);
1048 }
10491050+ return node;
1051 }
10521053 #endregion
···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; }
2122- public DrawColourInfo DrawColourInfo { get; internal set; }
0002324 /// <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.
0000000000000000000028 /// </summary>
29- public long InvalidationID { get; internal set; }
000003031 /// <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"/>.
018 /// </summary>
19+ protected DrawInfo DrawInfo { get; private set; }
2021+ /// <summary>
22+ /// Contains the colour and blending information of this <see cref="DrawNode"/>.
23+ /// </summary>
24+ protected internal DrawColourInfo DrawColourInfo { get; internal set; }
2526 /// <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+ }
5758 /// <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();
16341635 // 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();
16371638 /// <summary>
1639 /// Invalidates draw matrix and autosize caches.
···1681 alreadyInvalidated &= !drawColourInfoBacking.Invalidate();
16821683 if (!alreadyInvalidated || (invalidation & Invalidation.DrawNode) > 0)
1684- invalidationID = invalidation_counter.Increment();
16851686 OnInvalidate?.Invoke(this);
1687···1723 FrameStatistics.Increment(StatisticsCounterType.DrawNodeCtor);
1724 }
17251726- if (invalidationID != node.InvalidationID)
1727 {
1728- ApplyDrawNode(node);
1729 FrameStatistics.Increment(StatisticsCounterType.DrawNodeAppl);
1730 }
1731···1733 }
17341735 /// <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();
17511752 #endregion
1753···2253 Colour = 1 << 3,
22542255 /// <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();
16341635 // 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();
16371638 /// <summary>
1639 /// Invalidates draw matrix and autosize caches.
···1681 alreadyInvalidated &= !drawColourInfoBacking.Invalidate();
16821683 if (!alreadyInvalidated || (invalidation & Invalidation.DrawNode) > 0)
1684+ InvalidationID = invalidation_counter.Increment();
16851686 OnInvalidate?.Invoke(this);
1687···1723 FrameStatistics.Increment(StatisticsCounterType.DrawNodeCtor);
1724 }
17251726+ if (InvalidationID != node.InvalidationID)
1727 {
1728+ node.ApplyState();
1729 FrameStatistics.Increment(StatisticsCounterType.DrawNodeAppl);
1730 }
1731···1733 }
17341735 /// <summary>
000000000001736 /// 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);
17401741 #endregion
1742···2242 Colour = 1 << 3,
22432244 /// <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;
7172- private readonly ProxyDrawable proxyDrawable;
7374 public ProxyDrawNode(ProxyDrawable proxyDrawable)
075 {
76- this.proxyDrawable = proxyDrawable;
77 }
7879 public override void Draw(Action<TexturedVertex2D> vertexAction)
80 {
81- var target = proxyDrawable.originalDrawNodes[DrawNodeIndex];
82 if (target == null)
83 return;
8485- if (proxyDrawable.drawNodeValidationIds[DrawNodeIndex] != FrameCount)
86 return;
8788 target.Draw(vertexAction);
···69 /// </summary>
70 public ulong FrameCount;
7172+ protected new ProxyDrawable Source => (ProxyDrawable)base.Source;
7374 public ProxyDrawNode(ProxyDrawable proxyDrawable)
75+ : base(proxyDrawable)
76 {
077 }
7879 public override void Draw(Action<TexturedVertex2D> vertexAction)
80 {
81+ var target = Source.originalDrawNodes[DrawNodeIndex];
82 if (target == null)
83 return;
8485+ if (Source.drawNodeValidationIds[DrawNodeIndex] != FrameCount)
86 return;
8788 target.Draw(vertexAction);
···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
···00000000000000000
···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
···0000000000000000000000000
···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+}
···30 DrawInfo DrawInfo { get; }
3132 /// <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
···00000000000000000000000
···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+}
···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 }
7100000072 /// <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 }
7172+ 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;
000000084 }
85}
···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;
02021 /// <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;
2526 /// <summary>
27 /// Maximum value that can be set for <see cref="EdgeSmoothness"/> on either axis.
···4950 #endregion
5152- 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- }
6869 [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 }
7576 private Texture texture;
···100 }
101 }
102103- private Vector2 inflationAmount;
104105 protected override Quad ComputeScreenSpaceDrawQuad()
106 {
107 if (EdgeSmoothness == Vector2.Zero)
108 {
109- inflationAmount = Vector2.Zero;
110 return base.ComputeScreenSpaceDrawQuad();
111 }
112···116117 Vector3 scale = DrawInfo.MatrixInverse.ExtractScale();
118119- inflationAmount = new Vector2(scale.X * EdgeSmoothness.X, scale.Y * EdgeSmoothness.Y);
120- return ToScreenSpace(DrawRectangle.Inflate(inflationAmount));
121 }
122123 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; }
2122 /// <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; }
2627 /// <summary>
28 /// Maximum value that can be set for <see cref="EdgeSmoothness"/> on either axis.
···5051 #endregion
5253+ protected override DrawNode CreateDrawNode() => new SpriteDrawNode(this);
0000000000000005455 [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 }
6162 private Texture texture;
···86 }
87 }
8889+ public Vector2 InflationAmount { get; private set; }
9091 protected override Quad ComputeScreenSpaceDrawQuad()
92 {
93 if (EdgeSmoothness == Vector2.Zero)
94 {
95+ InflationAmount = Vector2.Zero;
96 return base.ComputeScreenSpaceDrawQuad();
97 }
98···102103 Vector3 scale = DrawInfo.MatrixInverse.ExtractScale();
104105+ InflationAmount = new Vector2(scale.X * EdgeSmoothness.X, scale.Y * EdgeSmoothness.Y);
106+ return ToScreenSpace(DrawRectangle.Inflate(InflationAmount));
107 }
108109 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.
30004using osu.Framework.Graphics.Primitives;
5-using osu.Framework.Graphics.Shaders;
6using osu.Framework.Graphics.Textures;
7using osuTK.Graphics.ES30;
8-using osu.Framework.Graphics.OpenGL;
9-using osuTK;
10-using System;
11-using osu.Framework.Graphics.OpenGL.Vertices;
1213namespace 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;
0000000002526- public IShader TextureShader;
27- public IShader RoundedTextureShader;
02829- private bool needsRoundedShader => GLWrapper.IsMaskingActive || InflationAmount != Vector2.Zero;
000003031 protected virtual void Blit(Action<TexturedVertex2D> vertexAction)
32 {
···41 if (Texture?.Available != true)
42 return;
4344- IShader shader = needsRoundedShader ? RoundedTextureShader : TextureShader;
45-46- shader.Bind();
47-48 Texture.TextureGL.WrapMode = WrapTexture ? TextureWrapMode.Repeat : TextureWrapMode.ClampToEdge;
490050 Blit(vertexAction);
5152- shader.Unbind();
53 }
0054 }
55}
···5using System.Collections.Generic;
6using System.Diagnostics;
7using System.Drawing;
8-using System.IO;
9using System.Linq;
10-using System.Reflection;
11using System.Runtime;
12using System.Runtime.ExceptionServices;
13using System.Runtime.InteropServices;
14using System.Threading;
15using System.Threading.Tasks;
16-using NUnit.Framework;
17using osuTK;
18using osuTK.Graphics;
19using osuTK.Graphics.ES30;
···21using osu.Framework.Allocation;
22using osu.Framework.Bindables;
23using osu.Framework.Configuration;
024using osu.Framework.Extensions.IEnumerableExtensions;
25using osu.Framework.Graphics;
26using osu.Framework.Graphics.Containers;
···417418 public void Run(Game game)
419 {
00420 if (ExecutionState != ExecutionState.Idle)
421 throw new InvalidOperationException("A game that has already been run cannot be restarted.");
422···446447 FileSafety.DeleteCleanupDirectory();
448449- 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);
462463 Logger.GameIdentifier = Name;
464 Logger.VersionIdentifier = assembly.GetName().Version.ToString();
···5using System.Collections.Generic;
6using System.Diagnostics;
7using System.Drawing;
08using System.Linq;
09using System.Runtime;
10using System.Runtime.ExceptionServices;
11using System.Runtime.InteropServices;
12using System.Threading;
13using System.Threading.Tasks;
014using osuTK;
15using osuTK.Graphics;
16using osuTK.Graphics.ES30;
···18using osu.Framework.Allocation;
19using osu.Framework.Bindables;
20using osu.Framework.Configuration;
21+using osu.Framework.Development;
22using osu.Framework.Extensions.IEnumerableExtensions;
23using osu.Framework.Graphics;
24using osu.Framework.Graphics.Containers;
···415416 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···446447 FileSafety.DeleteCleanupDirectory();
448449+ var assembly = DebugUtils.GetEntryAssembly();
450+ string assemblyPath = DebugUtils.GetEntryPath();
00000000000451452 Logger.GameIdentifier = Name;
453 Logger.VersionIdentifier = assembly.GetName().Version.ToString();
+2-5
osu.Framework/Testing/TestScene.cs
···18using System.Threading.Tasks;
19using System.Threading;
20using NUnit.Framework.Internal;
02122namespace osu.Framework.Testing
23{
···35 private Task runTask;
36 private ITestSceneTestRunner runner;
3738- 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
···18using System.Threading.Tasks;
19using System.Threading;
20using NUnit.Framework.Internal;
21+using osu.Framework.Development;
2223namespace osu.Framework.Testing
24{
···36 private Task runTask;
37 private ITestSceneTestRunner runner;
380039 [OneTimeSetUp]
40 public void SetupGameHost()
41 {
0042 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
···8384 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;
08889 // once interpolation fails, we don't want to resume interpolating until the source clock starts to move again.
90 allowInterpolation = false;
···8384 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);
8990 // once interpolation fails, we don't want to resume interpolating until the source clock starts to move again.
91 allowInterpolation = false;