···4141 Assert.GreaterOrEqual(interpolating.CurrentTime, source.CurrentTime, "Interpolating should not jump before source time.");
42424343 Thread.Sleep((int)(interpolating.AllowableErrorMilliseconds / 2));
4444+ lastValue = interpolating.CurrentTime;
4445 }
4646+4747+ int interpolatingCount = 0;
45484649 // test with test clock elapsing
4750 lastValue = interpolating.CurrentTime;
···52555356 Assert.GreaterOrEqual(interpolating.CurrentTime, lastValue, "Interpolating should not jump against rate.");
5457 Assert.LessOrEqual(Math.Abs(interpolating.CurrentTime - source.CurrentTime), interpolating.AllowableErrorMilliseconds, "Interpolating should be within allowance.");
5858+5959+ if (interpolating.CurrentTime != source.CurrentTime)
6060+ interpolatingCount++;
55615662 Thread.Sleep((int)(interpolating.AllowableErrorMilliseconds / 2));
6363+ lastValue = interpolating.CurrentTime;
5764 }
6565+6666+ Assert.Greater(interpolatingCount, 10);
6767+ }
6868+6969+ [Test]
7070+ public void NeverInterpolatesBackwardsOnInterpolationFail()
7171+ {
7272+ double lastValue = interpolating.CurrentTime;
7373+7474+ source.Start();
7575+ source.Rate = 10; // use a higher rate to ensure we may seek backwards.
7676+7777+ int sleepTime = (int)(interpolating.AllowableErrorMilliseconds / 2);
7878+7979+ int interpolatingCount = 0;
8080+8181+ for (int i = 0; i < 200; i++)
8282+ {
8383+ if (i < 100) // stop the elapsing at some point in time. should still work as source's ElapsedTime is zero.
8484+ source.CurrentTime += sleepTime * source.Rate;
8585+8686+ interpolating.ProcessFrame();
8787+8888+ if (interpolating.CurrentTime != source.CurrentTime)
8989+ interpolatingCount++;
9090+9191+ Assert.GreaterOrEqual(interpolating.CurrentTime, lastValue, "Interpolating should not jump against rate.");
9292+ Assert.LessOrEqual(Math.Abs(interpolating.CurrentTime - source.CurrentTime), interpolating.AllowableErrorMilliseconds, "Interpolating should be within allowance.");
9393+9494+ Thread.Sleep(sleepTime);
9595+ lastValue = interpolating.CurrentTime;
9696+ }
9797+9898+ Assert.Greater(interpolatingCount, 10);
5899 }
5910060101 [Test]
···11-// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
22-// See the LICENCE file in the repository root for full licence text.
33-44-using System.Collections.Generic;
55-using osu.Framework.Graphics.OpenGL;
66-using osu.Framework.Graphics.OpenGL.Buffers;
77-using osuTK;
88-using osuTK.Graphics.ES30;
99-using osuTK.Graphics;
1010-using osu.Framework.Graphics.Primitives;
1111-using osu.Framework.Allocation;
1212-using osu.Framework.Graphics.Shaders;
1313-using System;
1414-using osu.Framework.Graphics.Colour;
1515-using osu.Framework.Graphics.OpenGL.Vertices;
1616-using System.Diagnostics;
1717-1818-namespace osu.Framework.Graphics.Containers
1919-{
2020- public class BufferedContainerDrawNodeSharedData : IDisposable
2121- {
2222- /// <summary>
2323- /// The <see cref="FrameBuffer"/>s to render to.
2424- /// These are used in a ping-pong manner to render effects <see cref="BufferedContainerDrawNode"/>.
2525- /// </summary>
2626- public readonly FrameBuffer[] FrameBuffers = new FrameBuffer[3];
2727-2828- /// <summary>
2929- /// The version of drawn contents currently present in <see cref="FrameBuffers"/>.
3030- /// This should only be modified by <see cref="BufferedContainerDrawNode"/>.
3131- /// </summary>
3232- public long DrawVersion = -1;
3333-3434- public BufferedContainerDrawNodeSharedData()
3535- {
3636- for (int i = 0; i < FrameBuffers.Length; i++)
3737- FrameBuffers[i] = new FrameBuffer();
3838- }
3939-4040- ~BufferedContainerDrawNodeSharedData()
4141- {
4242- Dispose(false);
4343- }
4444-4545- public void Dispose()
4646- {
4747- Dispose(true);
4848- GC.SuppressFinalize(this);
4949- }
5050-5151- protected virtual void Dispose(bool isDisposing)
5252- {
5353- for (int i = 0; i < FrameBuffers.Length; i++)
5454- FrameBuffers[i].Dispose();
5555- }
5656- }
5757-5858- public class BufferedContainerDrawNode : CompositeDrawNode
5959- {
6060- public bool DrawOriginal;
6161- public Color4 BackgroundColour;
6262- public ColourInfo EffectColour;
6363- public BlendingParameters EffectBlending;
6464- public EffectPlacement EffectPlacement;
6565-6666- public Vector2 BlurSigma;
6767- public Vector2I BlurRadius;
6868- public float BlurRotation;
6969-7070- public long UpdateVersion;
7171-7272- public RectangleF ScreenSpaceDrawRectangle;
7373- public All FilteringMode;
7474-7575- /// <summary>
7676- /// The <see cref="RenderbufferInternalFormat"/>s to use when drawing children.
7777- /// </summary>
7878- public readonly List<RenderbufferInternalFormat> Formats = new List<RenderbufferInternalFormat>();
7979-8080- /// <summary>
8181- /// The <see cref="IShader"/> to use when rendering blur effects.
8282- /// </summary>
8383- public IShader BlurShader;
8484-8585- public BufferedContainerDrawNodeSharedData SharedData;
8686-8787- /// <summary>
8888- /// Whether this <see cref="BufferedContainerDrawNode"/> should have its children re-drawn.
8989- /// </summary>
9090- public bool RequiresRedraw => UpdateVersion > SharedData.DrawVersion;
9191-9292- private ValueInvokeOnDisposal establishFrameBufferViewport(Vector2 roundedSize)
9393- {
9494- // Disable masking for generating the frame buffer since masking will be re-applied
9595- // when actually drawing later on anyways. This allows more information to be captured
9696- // in the frame buffer and helps with cached buffers being re-used.
9797- RectangleI screenSpaceMaskingRect = new RectangleI((int)Math.Floor(ScreenSpaceDrawRectangle.X), (int)Math.Floor(ScreenSpaceDrawRectangle.Y), (int)roundedSize.X + 1, (int)roundedSize.Y + 1);
9898-9999- GLWrapper.PushMaskingInfo(new MaskingInfo
100100- {
101101- ScreenSpaceAABB = screenSpaceMaskingRect,
102102- MaskingRect = ScreenSpaceDrawRectangle,
103103- ToMaskingSpace = Matrix3.Identity,
104104- BlendRange = 1,
105105- AlphaExponent = 1,
106106- }, true);
107107-108108- // Match viewport to FrameBuffer such that we don't draw unnecessary pixels.
109109- GLWrapper.PushViewport(new RectangleI(0, 0, (int)roundedSize.X, (int)roundedSize.Y));
110110-111111- return new ValueInvokeOnDisposal(returnViewport);
112112- }
113113-114114- private void returnViewport()
115115- {
116116- GLWrapper.PopViewport();
117117- GLWrapper.PopMaskingInfo();
118118- }
119119-120120- private ValueInvokeOnDisposal bindFrameBuffer(FrameBuffer frameBuffer, Vector2 requestedSize)
121121- {
122122- if (!frameBuffer.IsInitialized)
123123- frameBuffer.Initialize(true, FilteringMode);
124124-125125- // These additional render buffers are only required if e.g. depth
126126- // or stencil information needs to also be stored somewhere.
127127- foreach (var f in Formats)
128128- frameBuffer.Attach(f);
129129-130130- // This setter will also take care of allocating a texture of appropriate size within the framebuffer.
131131- frameBuffer.Size = requestedSize;
132132-133133- frameBuffer.Bind();
134134-135135- return new ValueInvokeOnDisposal(frameBuffer.Unbind);
136136- }
137137-138138- private void drawFrameBufferToBackBuffer(FrameBuffer frameBuffer, RectangleF drawRectangle, ColourInfo colourInfo)
139139- {
140140- // The strange Y coordinate and Height are a result of OpenGL coordinate systems having Y grow upwards and not downwards.
141141- RectangleF textureRect = new RectangleF(0, frameBuffer.Texture.Height, frameBuffer.Texture.Width, -frameBuffer.Texture.Height);
142142- if (frameBuffer.Texture.Bind())
143143- // Color was already applied by base.Draw(); no need to re-apply. Thus we use White here.
144144- frameBuffer.Texture.DrawQuad(drawRectangle, textureRect, colourInfo);
145145- }
146146-147147- private void drawChildren(Action<TexturedVertex2D> vertexAction, Vector2 frameBufferSize)
148148- {
149149- // Fill the frame buffer with drawn children
150150- using (bindFrameBuffer(currentFrameBuffer, frameBufferSize))
151151- {
152152- // We need to draw children as if they were zero-based to the top-left of the texture.
153153- // We can do this by adding a translation component to our (orthogonal) projection matrix.
154154- GLWrapper.PushOrtho(ScreenSpaceDrawRectangle);
155155-156156- GLWrapper.Clear(new ClearInfo(BackgroundColour));
157157- base.Draw(vertexAction);
158158-159159- GLWrapper.PopOrtho();
160160- }
161161- }
162162-163163- private void drawBlurredFrameBuffer(int kernelRadius, float sigma, float blurRotation)
164164- {
165165- FrameBuffer source = currentFrameBuffer;
166166- FrameBuffer target = advanceFrameBuffer();
167167-168168- GLWrapper.SetBlend(new BlendingInfo(BlendingMode.None));
169169-170170- using (bindFrameBuffer(target, source.Size))
171171- {
172172- BlurShader.GetUniform<int>(@"g_Radius").UpdateValue(ref kernelRadius);
173173- BlurShader.GetUniform<float>(@"g_Sigma").UpdateValue(ref sigma);
174174-175175- Vector2 size = source.Size;
176176- BlurShader.GetUniform<Vector2>(@"g_TexSize").UpdateValue(ref size);
177177-178178- float radians = -MathHelper.DegreesToRadians(blurRotation);
179179- Vector2 blur = new Vector2((float)Math.Cos(radians), (float)Math.Sin(radians));
180180- BlurShader.GetUniform<Vector2>(@"g_BlurDirection").UpdateValue(ref blur);
181181-182182- BlurShader.Bind();
183183- drawFrameBufferToBackBuffer(source, new RectangleF(0, 0, source.Texture.Width, source.Texture.Height), ColourInfo.SingleColour(Color4.White));
184184- BlurShader.Unbind();
185185- }
186186- }
187187-188188- private int currentFrameBufferIndex;
189189- private FrameBuffer currentFrameBuffer => SharedData.FrameBuffers[currentFrameBufferIndex];
190190- private FrameBuffer advanceFrameBuffer() => SharedData.FrameBuffers[currentFrameBufferIndex = (currentFrameBufferIndex + 1) % 2];
191191-192192- /// <summary>
193193- /// Makes sure the first frame buffer is always the one we want to draw from.
194194- /// This saves us the need to sync the draw indices across draw node trees
195195- /// since the SharedData.FrameBuffers array is already shared.
196196- /// </summary>
197197- private void finalizeFrameBuffer()
198198- {
199199- if (currentFrameBufferIndex != 0)
200200- {
201201- Trace.Assert(currentFrameBufferIndex == 1,
202202- $"Only the first two framebuffers should be the last to be written to at the end of {nameof(Draw)}.");
203203-204204- FrameBuffer temp = SharedData.FrameBuffers[0];
205205- SharedData.FrameBuffers[0] = SharedData.FrameBuffers[1];
206206- SharedData.FrameBuffers[1] = temp;
207207-208208- currentFrameBufferIndex = 0;
209209- }
210210- }
211211-212212- // Our effects will be drawn into framebuffers 0 and 1. If we want to preserve the originally
213213- // drawn children we need to put them in a separate buffer; in this case buffer 2. Otherwise,
214214- // we do not want to allocate a third buffer for nothing and hence we start with 0.
215215- private int originalIndex => DrawOriginal && (BlurRadius.X > 0 || BlurRadius.Y > 0) ? 2 : 0;
216216-217217- public override void Draw(Action<TexturedVertex2D> vertexAction)
218218- {
219219- currentFrameBufferIndex = originalIndex;
220220-221221- Vector2 frameBufferSize = new Vector2((float)Math.Ceiling(ScreenSpaceDrawRectangle.Width), (float)Math.Ceiling(ScreenSpaceDrawRectangle.Height));
222222- if (RequiresRedraw)
223223- {
224224- SharedData.DrawVersion = UpdateVersion;
225225-226226- using (establishFrameBufferViewport(frameBufferSize))
227227- {
228228- drawChildren(vertexAction, frameBufferSize);
229229-230230- // Blur post-processing in case a blur radius is defined.
231231- if (BlurRadius.X > 0 || BlurRadius.Y > 0)
232232- {
233233- GL.Disable(EnableCap.ScissorTest);
234234-235235- if (BlurRadius.X > 0) drawBlurredFrameBuffer(BlurRadius.X, BlurSigma.X, BlurRotation);
236236- if (BlurRadius.Y > 0) drawBlurredFrameBuffer(BlurRadius.Y, BlurSigma.Y, BlurRotation + 90);
237237-238238- GL.Enable(EnableCap.ScissorTest);
239239- }
240240- }
241241-242242- finalizeFrameBuffer();
243243- }
244244-245245- RectangleF drawRectangle = FilteringMode == All.Nearest
246246- ? new RectangleF(ScreenSpaceDrawRectangle.X, ScreenSpaceDrawRectangle.Y, frameBufferSize.X, frameBufferSize.Y)
247247- : ScreenSpaceDrawRectangle;
248248-249249- Shader.Bind();
250250-251251- if (DrawOriginal && EffectPlacement == EffectPlacement.InFront)
252252- {
253253- GLWrapper.SetBlend(DrawColourInfo.Blending);
254254- drawFrameBufferToBackBuffer(SharedData.FrameBuffers[originalIndex], drawRectangle, DrawColourInfo.Colour);
255255- }
256256-257257- // Blit the final framebuffer to screen.
258258- GLWrapper.SetBlend(new BlendingInfo(EffectBlending));
259259-260260- ColourInfo effectColour = DrawColourInfo.Colour;
261261- effectColour.ApplyChild(EffectColour);
262262- drawFrameBufferToBackBuffer(SharedData.FrameBuffers[0], drawRectangle, effectColour);
263263-264264- if (DrawOriginal && EffectPlacement == EffectPlacement.Behind)
265265- {
266266- GLWrapper.SetBlend(DrawColourInfo.Blending);
267267- drawFrameBufferToBackBuffer(SharedData.FrameBuffers[originalIndex], drawRectangle, DrawColourInfo.Colour);
268268- }
269269-270270- Shader.Unbind();
271271- }
272272- }
273273-}
···11+// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
22+// See the LICENCE file in the repository root for full licence text.
33+44+using System.Collections.Generic;
55+using osu.Framework.Graphics.OpenGL;
66+using osu.Framework.Graphics.OpenGL.Buffers;
77+using osuTK;
88+using osuTK.Graphics.ES30;
99+using osuTK.Graphics;
1010+using osu.Framework.Graphics.Primitives;
1111+using osu.Framework.Allocation;
1212+using osu.Framework.Graphics.Shaders;
1313+using System;
1414+using osu.Framework.Graphics.Colour;
1515+using osu.Framework.Graphics.OpenGL.Vertices;
1616+using System.Diagnostics;
1717+using osu.Framework.MathUtils;
1818+1919+namespace osu.Framework.Graphics.Containers
2020+{
2121+ public partial class BufferedContainer<T>
2222+ {
2323+ private class BufferedContainerDrawNode : CompositeDrawableDrawNode
2424+ {
2525+ protected new BufferedContainer<T> Source => (BufferedContainer<T>)base.Source;
2626+2727+ private bool drawOriginal;
2828+ private Color4 backgroundColour;
2929+ private ColourInfo effectColour;
3030+ private BlendingParameters effectBlending;
3131+ private EffectPlacement effectPlacement;
3232+3333+ private Vector2 blurSigma;
3434+ private Vector2I blurRadius;
3535+ private float blurRotation;
3636+3737+ private long updateVersion;
3838+3939+ private RectangleF screenSpaceDrawRectangle;
4040+ private All filteringMode;
4141+4242+ private readonly List<RenderbufferInternalFormat> formats = new List<RenderbufferInternalFormat>();
4343+4444+ private IShader blurShader;
4545+4646+ private readonly BufferedContainerDrawNodeSharedData sharedData;
4747+4848+ public BufferedContainerDrawNode(BufferedContainer<T> source, BufferedContainerDrawNodeSharedData sharedData)
4949+ : base(source)
5050+ {
5151+ this.sharedData = sharedData;
5252+ }
5353+5454+ public override void ApplyState()
5555+ {
5656+ base.ApplyState();
5757+5858+ screenSpaceDrawRectangle = Source.ScreenSpaceDrawQuad.AABBFloat;
5959+ filteringMode = Source.PixelSnapping ? All.Nearest : All.Linear;
6060+6161+ updateVersion = Source.updateVersion;
6262+ backgroundColour = Source.BackgroundColour;
6363+6464+ BlendingParameters localEffectBlending = Source.EffectBlending;
6565+ if (localEffectBlending.Mode == BlendingMode.Inherit)
6666+ localEffectBlending.Mode = Source.Blending.Mode;
6767+6868+ if (localEffectBlending.RGBEquation == BlendingEquation.Inherit)
6969+ localEffectBlending.RGBEquation = Source.Blending.RGBEquation;
7070+7171+ if (localEffectBlending.AlphaEquation == BlendingEquation.Inherit)
7272+ localEffectBlending.AlphaEquation = Source.Blending.AlphaEquation;
7373+7474+ effectColour = Source.EffectColour;
7575+ effectBlending = localEffectBlending;
7676+ effectPlacement = Source.EffectPlacement;
7777+7878+ drawOriginal = Source.DrawOriginal;
7979+ blurSigma = Source.BlurSigma;
8080+ blurRadius = new Vector2I(Blur.KernelSize(blurSigma.X), Blur.KernelSize(blurSigma.Y));
8181+ blurRotation = Source.BlurRotation;
8282+8383+ formats.Clear();
8484+ formats.AddRange(Source.attachedFormats);
8585+8686+ blurShader = Source.blurShader;
8787+8888+ // BufferedContainer overrides DrawColourInfo for children, but needs to be reset to draw ourselves
8989+ DrawColourInfo = Source.baseDrawColourInfo;
9090+ }
9191+9292+ public override bool AddChildDrawNodes => RequiresRedraw;
9393+9494+ /// <summary>
9595+ /// Whether this <see cref="BufferedContainerDrawNode"/> should have its children re-drawn.
9696+ /// </summary>
9797+ public bool RequiresRedraw => updateVersion > sharedData.DrawVersion;
9898+9999+ private ValueInvokeOnDisposal establishFrameBufferViewport(Vector2 roundedSize)
100100+ {
101101+ // Disable masking for generating the frame buffer since masking will be re-applied
102102+ // when actually drawing later on anyways. This allows more information to be captured
103103+ // in the frame buffer and helps with cached buffers being re-used.
104104+ RectangleI screenSpaceMaskingRect = new RectangleI((int)Math.Floor(screenSpaceDrawRectangle.X), (int)Math.Floor(screenSpaceDrawRectangle.Y), (int)roundedSize.X + 1,
105105+ (int)roundedSize.Y + 1);
106106+107107+ GLWrapper.PushMaskingInfo(new MaskingInfo
108108+ {
109109+ ScreenSpaceAABB = screenSpaceMaskingRect,
110110+ MaskingRect = screenSpaceDrawRectangle,
111111+ ToMaskingSpace = Matrix3.Identity,
112112+ BlendRange = 1,
113113+ AlphaExponent = 1,
114114+ }, true);
115115+116116+ // Match viewport to FrameBuffer such that we don't draw unnecessary pixels.
117117+ GLWrapper.PushViewport(new RectangleI(0, 0, (int)roundedSize.X, (int)roundedSize.Y));
118118+119119+ return new ValueInvokeOnDisposal(returnViewport);
120120+ }
121121+122122+ private void returnViewport()
123123+ {
124124+ GLWrapper.PopViewport();
125125+ GLWrapper.PopMaskingInfo();
126126+ }
127127+128128+ private ValueInvokeOnDisposal bindFrameBuffer(FrameBuffer frameBuffer, Vector2 requestedSize)
129129+ {
130130+ if (!frameBuffer.IsInitialized)
131131+ frameBuffer.Initialize(true, filteringMode);
132132+133133+ // These additional render buffers are only required if e.g. depth
134134+ // or stencil information needs to also be stored somewhere.
135135+ foreach (var f in formats)
136136+ frameBuffer.Attach(f);
137137+138138+ // This setter will also take care of allocating a texture of appropriate size within the framebuffer.
139139+ frameBuffer.Size = requestedSize;
140140+141141+ frameBuffer.Bind();
142142+143143+ return new ValueInvokeOnDisposal(frameBuffer.Unbind);
144144+ }
145145+146146+ private void drawFrameBufferToBackBuffer(FrameBuffer frameBuffer, RectangleF drawRectangle, ColourInfo colourInfo)
147147+ {
148148+ // The strange Y coordinate and Height are a result of OpenGL coordinate systems having Y grow upwards and not downwards.
149149+ RectangleF textureRect = new RectangleF(0, frameBuffer.Texture.Height, frameBuffer.Texture.Width, -frameBuffer.Texture.Height);
150150+ if (frameBuffer.Texture.Bind())
151151+ // Color was already applied by base.Draw(); no need to re-apply. Thus we use White here.
152152+ frameBuffer.Texture.DrawQuad(drawRectangle, textureRect, colourInfo);
153153+ }
154154+155155+ private void drawChildren(Action<TexturedVertex2D> vertexAction, Vector2 frameBufferSize)
156156+ {
157157+ // Fill the frame buffer with drawn children
158158+ using (bindFrameBuffer(currentFrameBuffer, frameBufferSize))
159159+ {
160160+ // We need to draw children as if they were zero-based to the top-left of the texture.
161161+ // We can do this by adding a translation component to our (orthogonal) projection matrix.
162162+ GLWrapper.PushOrtho(screenSpaceDrawRectangle);
163163+164164+ GLWrapper.Clear(new ClearInfo(backgroundColour));
165165+ base.Draw(vertexAction);
166166+167167+ GLWrapper.PopOrtho();
168168+ }
169169+ }
170170+171171+ private void drawBlurredFrameBuffer(int kernelRadius, float sigma, float blurRotation)
172172+ {
173173+ FrameBuffer source = currentFrameBuffer;
174174+ FrameBuffer target = advanceFrameBuffer();
175175+176176+ GLWrapper.SetBlend(new BlendingInfo(BlendingMode.None));
177177+178178+ using (bindFrameBuffer(target, source.Size))
179179+ {
180180+ blurShader.GetUniform<int>(@"g_Radius").UpdateValue(ref kernelRadius);
181181+ blurShader.GetUniform<float>(@"g_Sigma").UpdateValue(ref sigma);
182182+183183+ Vector2 size = source.Size;
184184+ blurShader.GetUniform<Vector2>(@"g_TexSize").UpdateValue(ref size);
185185+186186+ float radians = -MathHelper.DegreesToRadians(blurRotation);
187187+ Vector2 blur = new Vector2((float)Math.Cos(radians), (float)Math.Sin(radians));
188188+ blurShader.GetUniform<Vector2>(@"g_BlurDirection").UpdateValue(ref blur);
189189+190190+ blurShader.Bind();
191191+ drawFrameBufferToBackBuffer(source, new RectangleF(0, 0, source.Texture.Width, source.Texture.Height), ColourInfo.SingleColour(Color4.White));
192192+ blurShader.Unbind();
193193+ }
194194+ }
195195+196196+ private int currentFrameBufferIndex;
197197+ private FrameBuffer currentFrameBuffer => sharedData.FrameBuffers[currentFrameBufferIndex];
198198+ private FrameBuffer advanceFrameBuffer() => sharedData.FrameBuffers[currentFrameBufferIndex = (currentFrameBufferIndex + 1) % 2];
199199+200200+ /// <summary>
201201+ /// Makes sure the first frame buffer is always the one we want to draw from.
202202+ /// This saves us the need to sync the draw indices across draw node trees
203203+ /// since the SharedData.FrameBuffers array is already shared.
204204+ /// </summary>
205205+ private void finalizeFrameBuffer()
206206+ {
207207+ if (currentFrameBufferIndex != 0)
208208+ {
209209+ Trace.Assert(currentFrameBufferIndex == 1,
210210+ $"Only the first two framebuffers should be the last to be written to at the end of {nameof(Draw)}.");
211211+212212+ FrameBuffer temp = sharedData.FrameBuffers[0];
213213+ sharedData.FrameBuffers[0] = sharedData.FrameBuffers[1];
214214+ sharedData.FrameBuffers[1] = temp;
215215+216216+ currentFrameBufferIndex = 0;
217217+ }
218218+ }
219219+220220+ // Our effects will be drawn into framebuffers 0 and 1. If we want to preserve the originally
221221+ // drawn children we need to put them in a separate buffer; in this case buffer 2. Otherwise,
222222+ // we do not want to allocate a third buffer for nothing and hence we start with 0.
223223+ private int originalIndex => drawOriginal && (blurRadius.X > 0 || blurRadius.Y > 0) ? 2 : 0;
224224+225225+ public override void Draw(Action<TexturedVertex2D> vertexAction)
226226+ {
227227+ currentFrameBufferIndex = originalIndex;
228228+229229+ Vector2 frameBufferSize = new Vector2((float)Math.Ceiling(screenSpaceDrawRectangle.Width), (float)Math.Ceiling(screenSpaceDrawRectangle.Height));
230230+ if (RequiresRedraw)
231231+ {
232232+ sharedData.DrawVersion = updateVersion;
233233+234234+ using (establishFrameBufferViewport(frameBufferSize))
235235+ {
236236+ drawChildren(vertexAction, frameBufferSize);
237237+238238+ // Blur post-processing in case a blur radius is defined.
239239+ if (blurRadius.X > 0 || blurRadius.Y > 0)
240240+ {
241241+ GL.Disable(EnableCap.ScissorTest);
242242+243243+ if (blurRadius.X > 0) drawBlurredFrameBuffer(blurRadius.X, blurSigma.X, blurRotation);
244244+ if (blurRadius.Y > 0) drawBlurredFrameBuffer(blurRadius.Y, blurSigma.Y, blurRotation + 90);
245245+246246+ GL.Enable(EnableCap.ScissorTest);
247247+ }
248248+ }
249249+250250+ finalizeFrameBuffer();
251251+ }
252252+253253+ RectangleF drawRectangle = filteringMode == All.Nearest
254254+ ? new RectangleF(screenSpaceDrawRectangle.X, screenSpaceDrawRectangle.Y, frameBufferSize.X, frameBufferSize.Y)
255255+ : screenSpaceDrawRectangle;
256256+257257+ Shader.Bind();
258258+259259+ if (drawOriginal && effectPlacement == EffectPlacement.InFront)
260260+ {
261261+ GLWrapper.SetBlend(DrawColourInfo.Blending);
262262+ drawFrameBufferToBackBuffer(sharedData.FrameBuffers[originalIndex], drawRectangle, DrawColourInfo.Colour);
263263+ }
264264+265265+ // Blit the final framebuffer to screen.
266266+ GLWrapper.SetBlend(new BlendingInfo(effectBlending));
267267+268268+ ColourInfo finalEffectColour = DrawColourInfo.Colour;
269269+ finalEffectColour.ApplyChild(effectColour);
270270+ drawFrameBufferToBackBuffer(sharedData.FrameBuffers[0], drawRectangle, finalEffectColour);
271271+272272+ if (drawOriginal && effectPlacement == EffectPlacement.Behind)
273273+ {
274274+ GLWrapper.SetBlend(DrawColourInfo.Blending);
275275+ drawFrameBufferToBackBuffer(sharedData.FrameBuffers[originalIndex], drawRectangle, DrawColourInfo.Colour);
276276+ }
277277+278278+ Shader.Unbind();
279279+ }
280280+ }
281281+282282+ private class BufferedContainerDrawNodeSharedData : IDisposable
283283+ {
284284+ /// <summary>
285285+ /// The <see cref="FrameBuffer"/>s to render to.
286286+ /// These are used in a ping-pong manner to render effects <see cref="BufferedContainerDrawNode"/>.
287287+ /// </summary>
288288+ public readonly FrameBuffer[] FrameBuffers = new FrameBuffer[3];
289289+290290+ /// <summary>
291291+ /// The version of drawn contents currently present in <see cref="FrameBuffers"/>.
292292+ /// This should only be modified by <see cref="BufferedContainerDrawNode"/>.
293293+ /// </summary>
294294+ public long DrawVersion = -1;
295295+296296+ public BufferedContainerDrawNodeSharedData()
297297+ {
298298+ for (int i = 0; i < FrameBuffers.Length; i++)
299299+ FrameBuffers[i] = new FrameBuffer();
300300+ }
301301+302302+ ~BufferedContainerDrawNodeSharedData()
303303+ {
304304+ dispose();
305305+ }
306306+307307+ public void Dispose()
308308+ {
309309+ dispose();
310310+ GC.SuppressFinalize(this);
311311+ }
312312+313313+ private void dispose()
314314+ {
315315+ for (int i = 0; i < FrameBuffers.Length; i++)
316316+ FrameBuffers[i].Dispose();
317317+ }
318318+ }
319319+ }
320320+}
···11-// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
22-// See the LICENCE file in the repository root for full licence text.
33-44-using System.Collections.Generic;
55-using osu.Framework.Graphics.OpenGL;
66-using osu.Framework.Graphics.Primitives;
77-using osu.Framework.Graphics.Shaders;
88-using osu.Framework.Graphics.Batches;
99-using osuTK;
1010-using osu.Framework.Graphics.Textures;
1111-using osu.Framework.Graphics.Colour;
1212-using System;
1313-using osu.Framework.Graphics.OpenGL.Vertices;
1414-1515-namespace osu.Framework.Graphics.Containers
1616-{
1717- /// <summary>
1818- /// Types of edge effects that can be applied to <see cref="CompositeDrawable"/>s.
1919- /// </summary>
2020- public enum EdgeEffectType
2121- {
2222- None,
2323- Glow,
2424- Shadow,
2525- }
2626-2727- /// <summary>
2828- /// Parametrizes the appearance of an edge effect.
2929- /// </summary>
3030- public struct EdgeEffectParameters : IEquatable<EdgeEffectParameters>
3131- {
3232- /// <summary>
3333- /// Colour of the edge effect.
3434- /// </summary>
3535- public SRGBColour Colour;
3636-3737- /// <summary>
3838- /// Positional offset applied to the edge effect.
3939- /// Useful for off-center shadows.
4040- /// </summary>
4141- public Vector2 Offset;
4242-4343- /// <summary>
4444- /// The type of the edge effect.
4545- /// </summary>
4646- public EdgeEffectType Type;
4747-4848- /// <summary>
4949- /// How round the edge effect should appear. Adds to the <see cref="CompositeDrawable.CornerRadius"/>
5050- /// of the corresponding <see cref="CompositeDrawable"/>. Not to confuse with the <see cref="Radius"/>.
5151- /// </summary>
5252- public float Roundness;
5353-5454- /// <summary>
5555- /// How "thick" the edge effect is around the <see cref="CompositeDrawable"/>. In other words: At what distance
5656- /// from the <see cref="CompositeDrawable"/>'s border the edge effect becomes fully invisible.
5757- /// </summary>
5858- public float Radius;
5959-6060- /// <summary>
6161- /// Whether the inside of the EdgeEffect rectangle should be empty.
6262- /// </summary>
6363- public bool Hollow;
6464-6565- public bool Equals(EdgeEffectParameters other) =>
6666- Colour.Equals(other.Colour) &&
6767- Offset == other.Offset &&
6868- Type == other.Type &&
6969- Roundness == other.Roundness &&
7070- Radius == other.Radius;
7171-7272- public override string ToString() => Type != EdgeEffectType.None ? $@"{Radius} {Type}EdgeEffect" : @"EdgeEffect (Disabled)";
7373- }
7474-7575- /// <summary>
7676- /// A draw node responsible for rendering a <see cref="CompositeDrawable"/> and the
7777- /// <see cref="DrawNode"/>s of its children.
7878- /// </summary>
7979- public class CompositeDrawNode : DrawNode
8080- {
8181- /// <summary>
8282- /// The <see cref="DrawNode"/>s of the children of our <see cref="CompositeDrawable"/>.
8383- /// </summary>
8484- public List<DrawNode> Children;
8585-8686- /// <summary>
8787- /// Information about how masking of children should be carried out.
8888- /// </summary>
8989- public MaskingInfo? MaskingInfo;
9090-9191- /// <summary>
9292- /// The screen-space version of <see cref="OpenGL.MaskingInfo.MaskingRect"/>.
9393- /// Used as cache of screen-space masking quads computed in previous frames.
9494- /// Assign null to reset.
9595- /// </summary>
9696- public Quad? ScreenSpaceMaskingQuad;
9797-9898- /// <summary>
9999- /// Information about how the edge effect should be rendered.
100100- /// </summary>
101101- public EdgeEffectParameters EdgeEffect;
102102-103103- /// <summary>
104104- /// The shader to use for rendering.
105105- /// </summary>
106106- public IShader Shader;
107107-108108- /// <summary>
109109- /// Whether to use a local vertex batch for rendering. If false, a parenting vertex batch will be used.
110110- /// </summary>
111111- public bool ForceLocalVertexBatch;
112112-113113- /// <summary>
114114- /// The vertex batch used for rendering.
115115- /// </summary>
116116- private QuadBatch<TexturedVertex2D> vertexBatch;
117117-118118- private void drawEdgeEffect()
119119- {
120120- if (MaskingInfo == null || EdgeEffect.Type == EdgeEffectType.None || EdgeEffect.Radius <= 0.0f || EdgeEffect.Colour.Linear.A <= 0.0f)
121121- return;
122122-123123- RectangleF effectRect = MaskingInfo.Value.MaskingRect.Inflate(EdgeEffect.Radius).Offset(EdgeEffect.Offset);
124124- if (!ScreenSpaceMaskingQuad.HasValue)
125125- ScreenSpaceMaskingQuad = Quad.FromRectangle(effectRect) * DrawInfo.Matrix;
126126-127127- MaskingInfo edgeEffectMaskingInfo = MaskingInfo.Value;
128128- edgeEffectMaskingInfo.MaskingRect = effectRect;
129129- edgeEffectMaskingInfo.ScreenSpaceAABB = ScreenSpaceMaskingQuad.Value.AABB;
130130- edgeEffectMaskingInfo.CornerRadius = MaskingInfo.Value.CornerRadius + EdgeEffect.Radius + EdgeEffect.Roundness;
131131- edgeEffectMaskingInfo.BorderThickness = 0;
132132- // HACK HACK HACK. We abuse blend range to give us the linear alpha gradient of
133133- // the edge effect along its radius using the same rounded-corners shader.
134134- edgeEffectMaskingInfo.BlendRange = EdgeEffect.Radius;
135135- edgeEffectMaskingInfo.AlphaExponent = 2;
136136- edgeEffectMaskingInfo.EdgeOffset = EdgeEffect.Offset;
137137- edgeEffectMaskingInfo.Hollow = EdgeEffect.Hollow;
138138- edgeEffectMaskingInfo.HollowCornerRadius = MaskingInfo.Value.CornerRadius + EdgeEffect.Radius;
139139-140140- GLWrapper.PushMaskingInfo(edgeEffectMaskingInfo);
141141-142142- GLWrapper.SetBlend(new BlendingInfo(EdgeEffect.Type == EdgeEffectType.Glow ? BlendingMode.Additive : BlendingMode.Mixture));
143143-144144- Shader.Bind();
145145-146146- ColourInfo colour = ColourInfo.SingleColour(EdgeEffect.Colour);
147147- colour.TopLeft.MultiplyAlpha(DrawColourInfo.Colour.TopLeft.Linear.A);
148148- colour.BottomLeft.MultiplyAlpha(DrawColourInfo.Colour.BottomLeft.Linear.A);
149149- colour.TopRight.MultiplyAlpha(DrawColourInfo.Colour.TopRight.Linear.A);
150150- colour.BottomRight.MultiplyAlpha(DrawColourInfo.Colour.BottomRight.Linear.A);
151151-152152- Texture.WhitePixel.DrawQuad(
153153- ScreenSpaceMaskingQuad.Value,
154154- colour, null, null, null,
155155- // HACK HACK HACK. We re-use the unused vertex blend range to store the original
156156- // masking blend range when rendering edge effects. This is needed for smooth inner edges
157157- // with a hollow edge effect.
158158- new Vector2(MaskingInfo.Value.BlendRange));
159159-160160- Shader.Unbind();
161161-162162- GLWrapper.PopMaskingInfo();
163163- }
164164-165165- private const int min_amount_children_to_warrant_batch = 5;
166166-167167- private bool mayHaveOwnVertexBatch(int amountChildren) => ForceLocalVertexBatch || amountChildren >= min_amount_children_to_warrant_batch;
168168-169169- private void updateVertexBatch()
170170- {
171171- if (Children == null)
172172- return;
173173-174174- // This logic got roughly copied from the old osu! code base. These constants seem to have worked well so far.
175175- int clampedAmountChildren = MathHelper.Clamp(Children.Count, 1, 1000);
176176- if (mayHaveOwnVertexBatch(clampedAmountChildren) && (vertexBatch == null || vertexBatch.Size < clampedAmountChildren))
177177- vertexBatch = new QuadBatch<TexturedVertex2D>(clampedAmountChildren * 2, 500);
178178- }
179179-180180- public override void Draw(Action<TexturedVertex2D> vertexAction)
181181- {
182182- updateVertexBatch();
183183-184184- // Prefer to use own vertex batch instead of the parent-owned one.
185185- if (vertexBatch != null)
186186- vertexAction = vertexBatch.AddAction;
187187-188188- base.Draw(vertexAction);
189189-190190- drawEdgeEffect();
191191- if (MaskingInfo != null)
192192- {
193193- MaskingInfo info = MaskingInfo.Value;
194194- if (info.BorderThickness > 0)
195195- info.BorderColour *= DrawColourInfo.Colour.AverageColour;
196196-197197- GLWrapper.PushMaskingInfo(info);
198198- }
199199-200200- if (Children != null)
201201- for (int i = 0; i < Children.Count; i++)
202202- Children[i].Draw(vertexAction);
203203-204204- if (MaskingInfo != null)
205205- GLWrapper.PopMaskingInfo();
206206- }
207207-208208- protected override void Dispose(bool isDisposing)
209209- {
210210- base.Dispose(isDisposing);
211211-212212- vertexBatch?.Dispose();
213213- }
214214- }
215215-}
···99{
1010 /// <summary>
1111 /// Contains all the information required to draw a single <see cref="Drawable"/>.
1212- /// A hierarchy of DrawNodes is passed to the draw thread for rendering every frame.
1212+ /// A hierarchy of <see cref="DrawNode"/>s is passed to the draw thread for rendering every frame.
1313 /// </summary>
1414 public class DrawNode : IDisposable
1515 {
1616 /// <summary>
1717- /// Contains a linear transformation, colour information, and blending information
1818- /// of this draw node.
1717+ /// Contains the linear transformation of this <see cref="DrawNode"/>.
1918 /// </summary>
2020- public DrawInfo DrawInfo { get; internal set; }
1919+ protected DrawInfo DrawInfo { get; private set; }
21202222- public DrawColourInfo DrawColourInfo { get; internal set; }
2121+ /// <summary>
2222+ /// Contains the colour and blending information of this <see cref="DrawNode"/>.
2323+ /// </summary>
2424+ protected internal DrawColourInfo DrawColourInfo { get; internal set; }
23252426 /// <summary>
2527 /// Identifies the state of this draw node with an invalidation state of its corresponding
2626- /// <see cref="Drawable"/>. Whenever the invalidation state of this draw node disagrees
2727- /// with the state of its <see cref="Drawable"/> it has to be updated.
2828+ /// <see cref="Drawable"/>. An update is required when the invalidation state of this draw node disagrees
2929+ /// with the invalidation state of its <see cref="Drawable"/>.
3030+ /// </summary>
3131+ protected internal long InvalidationID { get; private set; }
3232+3333+ /// <summary>
3434+ /// The <see cref="Drawable"/> which this <see cref="DrawNode"/> draws.
3535+ /// </summary>
3636+ protected readonly IDrawable Source;
3737+3838+ /// <summary>
3939+ /// Creates a new <see cref="DrawNode"/>.
4040+ /// </summary>
4141+ /// <param name="source">The <see cref="Drawable"/> to draw with this <see cref="DrawNode"/>.</param>
4242+ public DrawNode(IDrawable source)
4343+ {
4444+ Source = source;
4545+ }
4646+4747+ /// <summary>
4848+ /// Applies the state of <see cref="Source"/> to this <see cref="DrawNode"/> for use in rendering.
4949+ /// The applied state must remain immutable.
2850 /// </summary>
2929- public long InvalidationID { get; internal set; }
5151+ public virtual void ApplyState()
5252+ {
5353+ DrawInfo = Source.DrawInfo;
5454+ DrawColourInfo = Source.DrawColourInfo;
5555+ InvalidationID = Source.InvalidationID;
5656+ }
30573158 /// <summary>
3259 /// Draws this draw node to the screen.
+6-17
osu.Framework/Graphics/Drawable.cs
···16331633 private static readonly AtomicCounter invalidation_counter = new AtomicCounter();
1634163416351635 // Make sure we start out with a value of 1 such that ApplyDrawNode is always called at least once
16361636- private long invalidationID = invalidation_counter.Increment();
16361636+ public long InvalidationID { get; private set; } = invalidation_counter.Increment();
1637163716381638 /// <summary>
16391639 /// Invalidates draw matrix and autosize caches.
···16811681 alreadyInvalidated &= !drawColourInfoBacking.Invalidate();
1682168216831683 if (!alreadyInvalidated || (invalidation & Invalidation.DrawNode) > 0)
16841684- invalidationID = invalidation_counter.Increment();
16841684+ InvalidationID = invalidation_counter.Increment();
1685168516861686 OnInvalidate?.Invoke(this);
16871687···17231723 FrameStatistics.Increment(StatisticsCounterType.DrawNodeCtor);
17241724 }
1725172517261726- if (invalidationID != node.InvalidationID)
17261726+ if (InvalidationID != node.InvalidationID)
17271727 {
17281728- ApplyDrawNode(node);
17281728+ node.ApplyState();
17291729 FrameStatistics.Increment(StatisticsCounterType.DrawNodeAppl);
17301730 }
17311731···17331733 }
1734173417351735 /// <summary>
17361736- /// Fills a given draw node with all information required to draw this drawable.
17371737- /// </summary>
17381738- /// <param name="node">The node to fill with information.</param>
17391739- protected virtual void ApplyDrawNode(DrawNode node)
17401740- {
17411741- node.DrawInfo = DrawInfo;
17421742- node.DrawColourInfo = DrawColourInfo;
17431743- node.InvalidationID = invalidationID;
17441744- }
17451745-17461746- /// <summary>
17471736 /// Creates a draw node capable of containing all information required to draw this drawable.
17481737 /// </summary>
17491738 /// <returns>The created draw node.</returns>
17501750- protected virtual DrawNode CreateDrawNode() => new DrawNode();
17391739+ protected virtual DrawNode CreateDrawNode() => new DrawNode(this);
1751174017521741 #endregion
17531742···22532242 Colour = 1 << 3,
2254224322552244 /// <summary>
22562256- /// <see cref="Drawable.ApplyDrawNode(Graphics.DrawNode)"/> has to be invoked on all old draw nodes.
22452245+ /// <see cref="Graphics.DrawNode.ApplyState"/> has to be invoked on all old draw nodes.
22572246 /// </summary>
22582247 DrawNode = 1 << 4,
22592248
+4-4
osu.Framework/Graphics/Drawable_ProxyDrawable.cs
···6969 /// </summary>
7070 public ulong FrameCount;
71717272- private readonly ProxyDrawable proxyDrawable;
7272+ protected new ProxyDrawable Source => (ProxyDrawable)base.Source;
73737474 public ProxyDrawNode(ProxyDrawable proxyDrawable)
7575+ : base(proxyDrawable)
7576 {
7676- this.proxyDrawable = proxyDrawable;
7777 }
78787979 public override void Draw(Action<TexturedVertex2D> vertexAction)
8080 {
8181- var target = proxyDrawable.originalDrawNodes[DrawNodeIndex];
8181+ var target = Source.originalDrawNodes[DrawNodeIndex];
8282 if (target == null)
8383 return;
84848585- if (proxyDrawable.drawNodeValidationIds[DrawNodeIndex] != FrameCount)
8585+ if (Source.drawNodeValidationIds[DrawNodeIndex] != FrameCount)
8686 return;
87878888 target.Draw(vertexAction);
···11+// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
22+// See the LICENCE file in the repository root for full licence text.
33+44+using System;
55+using osu.Framework.Graphics.Colour;
66+using osu.Framework.Graphics.Containers;
77+using osuTK;
88+99+namespace osu.Framework.Graphics.Effects
1010+{
1111+ /// <summary>
1212+ /// Parametrizes the appearance of an edge effect.
1313+ /// </summary>
1414+ public struct EdgeEffectParameters : IEquatable<EdgeEffectParameters>
1515+ {
1616+ /// <summary>
1717+ /// Colour of the edge effect.
1818+ /// </summary>
1919+ public SRGBColour Colour;
2020+2121+ /// <summary>
2222+ /// Positional offset applied to the edge effect.
2323+ /// Useful for off-center shadows.
2424+ /// </summary>
2525+ public Vector2 Offset;
2626+2727+ /// <summary>
2828+ /// The type of the edge effect.
2929+ /// </summary>
3030+ public EdgeEffectType Type;
3131+3232+ /// <summary>
3333+ /// How round the edge effect should appear. Adds to the <see cref="CompositeDrawable.CornerRadius"/>
3434+ /// of the corresponding <see cref="CompositeDrawable"/>. Not to confuse with the <see cref="Radius"/>.
3535+ /// </summary>
3636+ public float Roundness;
3737+3838+ /// <summary>
3939+ /// How "thick" the edge effect is around the <see cref="CompositeDrawable"/>. In other words: At what distance
4040+ /// from the <see cref="CompositeDrawable"/>'s border the edge effect becomes fully invisible.
4141+ /// </summary>
4242+ public float Radius;
4343+4444+ /// <summary>
4545+ /// Whether the inside of the EdgeEffect rectangle should be empty.
4646+ /// </summary>
4747+ public bool Hollow;
4848+4949+ public bool Equals(EdgeEffectParameters other) =>
5050+ Colour.Equals(other.Colour) &&
5151+ Offset == other.Offset &&
5252+ Type == other.Type &&
5353+ Roundness == other.Roundness &&
5454+ Radius == other.Radius;
5555+5656+ public override string ToString() => Type != EdgeEffectType.None ? $@"{Radius} {Type}EdgeEffect" : @"EdgeEffect (Disabled)";
5757+ }
5858+}
+17
osu.Framework/Graphics/Effects/EdgeEffectType.cs
···11+// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
22+// See the LICENCE file in the repository root for full licence text.
33+44+using osu.Framework.Graphics.Containers;
55+66+namespace osu.Framework.Graphics.Effects
77+{
88+ /// <summary>
99+ /// Types of edge effects that can be applied to <see cref="CompositeDrawable"/>s.
1010+ /// </summary>
1111+ public enum EdgeEffectType
1212+ {
1313+ None,
1414+ Glow,
1515+ Shadow,
1616+ }
1717+}
+25
osu.Framework/Graphics/ICompositeDrawNode.cs
···11+// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
22+// See the LICENCE file in the repository root for full licence text.
33+44+using System.Collections.Generic;
55+using osu.Framework.Graphics.Containers;
66+77+namespace osu.Framework.Graphics
88+{
99+ /// <summary>
1010+ /// Interface for <see cref="DrawNode"/>s which compose child <see cref="DrawNode"/>s.
1111+ /// <see cref="DrawNode"/>s implementing this interface can be used in <see cref="CompositeDrawable"/>s.
1212+ /// </summary>
1313+ public interface ICompositeDrawNode
1414+ {
1515+ /// <summary>
1616+ /// The child <see cref="DrawNode"/>s to draw.
1717+ /// </summary>
1818+ List<DrawNode> Children { get; set; }
1919+2020+ /// <summary>
2121+ /// Whether <see cref="Children"/> should be updated by the parenting <see cref="CompositeDrawable"/>.
2222+ /// </summary>
2323+ bool AddChildDrawNodes { get; }
2424+ }
2525+}
+11
osu.Framework/Graphics/IDrawable.cs
···3030 DrawInfo DrawInfo { get; }
31313232 /// <summary>
3333+ /// Contains the colour and blending information of this <see cref="Drawable"/> that are used during draw.
3434+ /// </summary>
3535+ DrawColourInfo DrawColourInfo { get; }
3636+3737+ /// <summary>
3338 /// The screen-space quad this drawable occupies.
3439 /// </summary>
3540 Quad ScreenSpaceDrawQuad { get; }
···97102 /// Hide sprite instantly.
98103 /// </summary>
99104 void Hide();
105105+106106+ /// <summary>
107107+ /// The current invalidation ID of this <see cref="Drawable"/>.
108108+ /// Incremented every time the <see cref="DrawNode"/> should be re-validated.
109109+ /// </summary>
110110+ long InvalidationID { get; }
100111 }
101112}
+23
osu.Framework/Graphics/ITexturedShaderDrawable.cs
···11+// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
22+// See the LICENCE file in the repository root for full licence text.
33+44+using osu.Framework.Graphics.Shaders;
55+66+namespace osu.Framework.Graphics
77+{
88+ /// <summary>
99+ /// Interface for <see cref="Drawable"/>s which can be drawn by <see cref="TexturedShaderDrawNode"/>s.
1010+ /// </summary>
1111+ public interface ITexturedShaderDrawable : IDrawable
1212+ {
1313+ /// <summary>
1414+ /// The <see cref="IShader"/> to be used for rendering when masking is not required.
1515+ /// </summary>
1616+ IShader TextureShader { get; }
1717+1818+ /// <summary>
1919+ /// The <see cref="IShader"/> to be used for rendering when masking is required.
2020+ /// </summary>
2121+ IShader RoundedTextureShader { get; }
2222+ }
2323+}
···55using System.Collections.Generic;
66using System.Diagnostics;
77using System.Drawing;
88-using System.IO;
98using System.Linq;
1010-using System.Reflection;
119using System.Runtime;
1210using System.Runtime.ExceptionServices;
1311using System.Runtime.InteropServices;
1412using System.Threading;
1513using System.Threading.Tasks;
1616-using NUnit.Framework;
1714using osuTK;
1815using osuTK.Graphics;
1916using osuTK.Graphics.ES30;
···2118using osu.Framework.Allocation;
2219using osu.Framework.Bindables;
2320using osu.Framework.Configuration;
2121+using osu.Framework.Development;
2422using osu.Framework.Extensions.IEnumerableExtensions;
2523using osu.Framework.Graphics;
2624using osu.Framework.Graphics.Containers;
···417415418416 public void Run(Game game)
419417 {
418418+ DebugUtils.HostAssembly = game.GetType().Assembly;
419419+420420 if (ExecutionState != ExecutionState.Idle)
421421 throw new InvalidOperationException("A game that has already been run cannot be restarted.");
422422···446446447447 FileSafety.DeleteCleanupDirectory();
448448449449- string assemblyPath;
450450- var assembly = Assembly.GetEntryAssembly();
451451-452452- // when running under nunit + netcore, entry assembly becomes nunit itself (testhost, Version=15.0.0.0), which isn't what we want.
453453- if (assembly == null || assembly.Location.Contains("testhost"))
454454- {
455455- assembly = Assembly.GetExecutingAssembly();
456456-457457- // From nuget, the executing assembly will also be wrong
458458- assemblyPath = TestContext.CurrentContext.TestDirectory;
459459- }
460460- else
461461- assemblyPath = Path.GetDirectoryName(assembly.Location);
449449+ var assembly = DebugUtils.GetEntryAssembly();
450450+ string assemblyPath = DebugUtils.GetEntryPath();
462451463452 Logger.GameIdentifier = Name;
464453 Logger.VersionIdentifier = assembly.GetName().Version.ToString();
+2-5
osu.Framework/Testing/TestScene.cs
···1818using System.Threading.Tasks;
1919using System.Threading;
2020using NUnit.Framework.Internal;
2121+using osu.Framework.Development;
21222223namespace osu.Framework.Testing
2324{
···3536 private Task runTask;
3637 private ITestSceneTestRunner runner;
37383838- internal bool IsNUnitRunning;
3939-4039 [OneTimeSetUp]
4140 public void SetupGameHost()
4241 {
4343- IsNUnitRunning = true;
4444-4542 host = new HeadlessGameHost($"{GetType().Name}-{Guid.NewGuid()}", realtime: false);
4643 runner = CreateRunner();
4744···8582 [SetUp]
8683 public void SetUpTestForNUnit()
8784 {
8888- if (IsNUnitRunning)
8585+ if (DebugUtils.IsNUnitRunning)
8986 {
9087 // Since the host is created in OneTimeSetUp, all game threads will have the fixture's execution context
9188 // 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
···83838484 if (!allowInterpolation || Math.Abs(FramedSourceClock.CurrentTime - CurrentInterpolatedTime) > AllowableErrorMilliseconds)
8585 {
8686- //if we've exceeded the allowable error, we should use the source clock's time value.
8787- CurrentInterpolatedTime = FramedSourceClock.CurrentTime;
8686+ // if we've exceeded the allowable error, we should use the source clock's time value.
8787+ // seeking backwards should only be allowed if the source is explicitly doing that.
8888+ CurrentInterpolatedTime = FramedSourceClock.ElapsedFrameTime < 0 ? FramedSourceClock.CurrentTime : Math.Max(LastInterpolatedTime, FramedSourceClock.CurrentTime);
88898990 // once interpolation fails, we don't want to resume interpolating until the source clock starts to move again.
9091 allowInterpolation = false;