Merge branch 'master' into update-veldrid-spirv-on-ios

This commit is contained in:
Dean Herbert
2023-09-16 19:10:27 +09:00
committed by GitHub
31 changed files with 288 additions and 406 deletions

View File

@@ -82,18 +82,18 @@ namespace osu.Framework.Android
if (!url.CheckIsValidUrl())
throw new ArgumentException("The provided URL must be one of either http://, https:// or mailto: protocols.", nameof(url));
using (var intent = new Intent(Intent.ActionView, Uri.Parse(url)))
try
{
// Recommended way to open URLs on Android 11+
// https://developer.android.com/training/package-visibility/use-cases#open-urls-browser-or-other-app
try
using (var intent = new Intent(Intent.ActionView, Uri.Parse(url)))
{
// Recommended way to open URLs on Android 11+
// https://developer.android.com/training/package-visibility/use-cases#open-urls-browser-or-other-app
gameView.Activity.StartActivity(intent);
}
catch (ActivityNotFoundException e)
{
Logger.Error(e, $"Failed to start intent: {intent}");
}
}
catch (Exception ex)
{
Logger.Error(ex, "Unable to open external link.");
}
}

View File

@@ -692,7 +692,6 @@ namespace osu.Framework.Tests.Visual.Drawables
public bool IsRunning => offsetClock.IsRunning;
public double ElapsedFrameTime => (reversed ? -1 : 1) * trackingClock.ElapsedFrameTime;
public double FramesPerSecond => trackingClock.FramesPerSecond;
public FrameTimeInfo TimeInfo => new FrameTimeInfo { Current = CurrentTime, Elapsed = ElapsedFrameTime };
public void ProcessFrame()
{

View File

@@ -14,6 +14,7 @@ using osu.Framework.Input.Bindings;
using osu.Framework.IO.Stores;
using osu.Framework.iOS.Graphics.Textures;
using osu.Framework.iOS.Graphics.Video;
using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Framework.Platform.MacOS;
using UIKit;
@@ -58,15 +59,26 @@ namespace osu.Framework.iOS
public override void OpenUrlExternally(string url)
{
if (!url.CheckIsValidUrl())
if (!url.CheckIsValidUrl()
// App store links
&& !url.StartsWith("itms-apps://", StringComparison.Ordinal)
// Testflight links
&& !url.StartsWith("itms-beta://", StringComparison.Ordinal))
throw new ArgumentException("The provided URL must be one of either http://, https:// or mailto: protocols.", nameof(url));
UIApplication.SharedApplication.InvokeOnMainThread(() =>
try
{
NSUrl nsurl = NSUrl.FromString(url).AsNonNull();
if (UIApplication.SharedApplication.CanOpenUrl(nsurl))
UIApplication.SharedApplication.OpenUrl(nsurl, new NSDictionary(), null);
});
UIApplication.SharedApplication.InvokeOnMainThread(() =>
{
NSUrl nsurl = NSUrl.FromString(url).AsNonNull();
if (UIApplication.SharedApplication.CanOpenUrl(nsurl))
UIApplication.SharedApplication.OpenUrl(nsurl, new NSDictionary(), null);
});
}
catch (Exception ex)
{
Logger.Error(ex, "Unable to open external link.");
}
}
public override IResourceStore<TextureUpload> CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore)

View File

@@ -336,7 +336,7 @@ namespace osu.Framework.Graphics.Audio
// We're dealing with a _large_ number of points, so we need to optimise the quadToDraw * drawInfo.Matrix multiplications below
// for points that are going to be masked out anyway. This allows for higher resolution graphs at larger scales with virtually no performance loss.
// Since the points are generated in the local coordinate space, we need to convert the screen space masking quad coordinates into the local coordinate space
RectangleF localMaskingRectangle = (Quad.FromRectangle(renderer.CurrentMaskingInfo.ScreenSpaceScissorArea) * DrawInfo.MatrixInverse).AABBFloat;
RectangleF localMaskingRectangle = (Quad.FromRectangle(renderer.CurrentMaskingInfo.ScreenSpaceAABB) * DrawInfo.MatrixInverse).AABBFloat;
float separation = drawSize.X / (points.Count - 1);

View File

@@ -35,7 +35,6 @@ namespace osu.Framework.Graphics
protected RectangleF DrawRectangle { get; private set; }
private Color4 backgroundColour;
private RectangleF localDrawRectangle;
private RectangleF screenSpaceDrawRectangle;
private Vector2 frameBufferScale;
private Vector2 frameBufferSize;
@@ -53,7 +52,6 @@ namespace osu.Framework.Graphics
base.ApplyState();
backgroundColour = Source.BackgroundColour;
localDrawRectangle = Source.DrawRectangle;
screenSpaceDrawRectangle = Source.ScreenSpaceDrawQuad.AABBFloat;
DrawColourInfo = Source.FrameBufferDrawColour ?? new DrawColourInfo(Color4.White, base.DrawColourInfo.Blending);
frameBufferScale = Source.FrameBufferScale;
@@ -157,12 +155,17 @@ namespace osu.Framework.Graphics
private IDisposable establishFrameBufferViewport(IRenderer renderer)
{
// Disable masking for generating the frame buffer since masking will be re-applied
// when actually drawing later on anyways. This allows more information to be captured
// in the frame buffer and helps with cached buffers being re-used.
RectangleI screenSpaceMaskingRect = new RectangleI((int)Math.Floor(screenSpaceDrawRectangle.X), (int)Math.Floor(screenSpaceDrawRectangle.Y), (int)frameBufferSize.X + 1,
(int)frameBufferSize.Y + 1);
renderer.PushMaskingInfo(new MaskingInfo
{
ScreenSpaceScissorArea = screenSpaceDrawRectangle,
MaskingArea = localDrawRectangle,
ToMaskingSpace = DrawInfo.MatrixInverse,
ToScissorSpace = Matrix3.Identity,
ScreenSpaceAABB = screenSpaceMaskingRect,
MaskingRect = screenSpaceDrawRectangle,
ToMaskingSpace = Matrix3.Identity,
BlendRange = 1,
AlphaExponent = 1,
}, true);
@@ -170,12 +173,14 @@ namespace osu.Framework.Graphics
// Match viewport to FrameBuffer such that we don't draw unnecessary pixels.
renderer.PushViewport(new RectangleI(0, 0, (int)frameBufferSize.X, (int)frameBufferSize.Y));
renderer.PushScissor(new RectangleI(0, 0, (int)frameBufferSize.X, (int)frameBufferSize.Y));
renderer.PushScissorOffset(screenSpaceMaskingRect.Location);
return new ValueInvokeOnDisposal<(BufferedDrawNode node, IRenderer renderer)>((this, renderer), tup => tup.node.returnViewport(tup.renderer));
}
private void returnViewport(IRenderer renderer)
{
renderer.PopScissorOffset();
renderer.PopViewport();
renderer.PopScissor();
renderer.PopMaskingInfo();

View File

@@ -266,7 +266,7 @@ namespace osu.Framework.Graphics.Containers
private void load(ShaderManager shaders)
{
TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE);
blurShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2_NO_MASKING, FragmentShaderDescriptor.BLUR);
blurShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.BLUR);
}
protected override DrawNode CreateDrawNode() => new BufferedContainerDrawNode(this, sharedData);

View File

@@ -43,7 +43,7 @@ namespace osu.Framework.Graphics.Containers
private MaskingInfo? maskingInfo;
/// <summary>
/// The screen-space version of <see cref="MaskingInfo.MaskingArea"/>.
/// The screen-space version of <see cref="MaskingInfo.MaskingRect"/>.
/// Used as cache of screen-space masking quads computed in previous frames.
/// Assign null to reset.
/// </summary>
@@ -92,11 +92,10 @@ namespace osu.Framework.Graphics.Containers
? null
: new MaskingInfo
{
ScreenSpaceScissorArea = Source.ScreenSpaceDrawQuad.AABBFloat,
MaskingArea = Source.DrawRectangle.Normalize(),
ScreenSpaceAABB = Source.ScreenSpaceDrawQuad.AABB,
MaskingRect = Source.DrawRectangle.Normalize(),
ConservativeScreenSpaceQuad = Quad.FromRectangle(shrunkDrawRectangle) * DrawInfo.Matrix,
ToMaskingSpace = DrawInfo.MatrixInverse,
ToScissorSpace = Matrix3.Identity,
CornerRadius = Source.effectiveCornerRadius,
CornerExponent = Source.CornerExponent,
BorderThickness = Source.BorderThickness,
@@ -122,13 +121,13 @@ namespace osu.Framework.Graphics.Containers
if (maskingInfo == null || edgeEffect.Type == EdgeEffectType.None || edgeEffect.Radius <= 0.0f || edgeEffect.Colour.Alpha <= 0)
return;
RectangleF effectRect = maskingInfo.Value.MaskingArea.Inflate(edgeEffect.Radius).Offset(edgeEffect.Offset);
RectangleF effectRect = maskingInfo.Value.MaskingRect.Inflate(edgeEffect.Radius).Offset(edgeEffect.Offset);
screenSpaceMaskingQuad ??= Quad.FromRectangle(effectRect) * DrawInfo.Matrix;
MaskingInfo edgeEffectMaskingInfo = maskingInfo.Value;
edgeEffectMaskingInfo.MaskingArea = effectRect;
edgeEffectMaskingInfo.ScreenSpaceScissorArea = screenSpaceMaskingQuad.Value.AABBFloat;
edgeEffectMaskingInfo.MaskingRect = effectRect;
edgeEffectMaskingInfo.ScreenSpaceAABB = screenSpaceMaskingQuad.Value.AABB;
edgeEffectMaskingInfo.CornerRadius = maskingInfo.Value.CornerRadius + edgeEffect.Radius + edgeEffect.Roundness;
edgeEffectMaskingInfo.BorderThickness = 0;
// HACK HACK HACK. We abuse blend range to give us the linear alpha gradient of

View File

@@ -283,7 +283,6 @@ namespace osu.Framework.Graphics.OpenGL
var glShader = (GLShader)Shader!;
glShader.BindUniformBlock("g_GlobalUniforms", GlobalUniformBuffer!);
glShader.BindUniformBlock("g_MaskingBuffer", ShaderMaskingStack!.CurrentBuffer);
int currentUniformBinding = 0;
int currentStorageBinding = 0;

View File

@@ -29,7 +29,6 @@ namespace osu.Framework.Graphics.Rendering.Dummy
public bool IsUvOriginTopLeft => true;
public bool IsClipSpaceYInverted => true;
public ref readonly MaskingInfo CurrentMaskingInfo => ref maskingInfo;
public int CurrentMaskingIndex => 0;
private readonly MaskingInfo maskingInfo;
public RectangleI Viewport => RectangleI.Empty;

View File

@@ -12,6 +12,7 @@ namespace osu.Framework.Graphics.Rendering
SetBlendMask,
SetDepthInfo,
SetFrameBuffer,
SetMasking,
SetProjection,
SetScissor,
SetShader,

View File

@@ -16,9 +16,23 @@ namespace osu.Framework.Graphics.Rendering
public UniformBool IsUvOriginTopLeft;
public UniformMatrix4 ProjMatrix;
public UniformMatrix3 ToMaskingSpace;
public UniformBool IsMasking;
public UniformFloat CornerRadius;
public UniformFloat CornerExponent;
private readonly UniformPadding4 pad2;
public UniformVector4 MaskingRect;
public UniformFloat BorderThickness;
private readonly UniformPadding12 pad3;
public UniformMatrix4 BorderColour;
public UniformFloat MaskingBlendRange;
public UniformFloat AlphaExponent;
public UniformVector2 EdgeOffset;
public UniformBool DiscardInner;
public UniformFloat InnerCornerRadius;
public UniformInt WrapModeS;
public UniformInt WrapModeT;
private readonly UniformPadding8 pad1;
}
}

View File

@@ -98,8 +98,6 @@ namespace osu.Framework.Graphics.Rendering
/// </summary>
ref readonly MaskingInfo CurrentMaskingInfo { get; }
int CurrentMaskingIndex { get; }
/// <summary>
/// The current viewport.
/// </summary>
@@ -278,6 +276,17 @@ namespace osu.Framework.Graphics.Rendering
/// </summary>
void PopScissor();
/// <summary>
/// Applies a new scissor offset to the scissor rectangle.
/// </summary>
/// <param name="offset">The scissor offset.</param>
void PushScissorOffset(Vector2I offset);
/// <summary>
/// Restores the last scissor offset.
/// </summary>
void PopScissorOffset();
/// <summary>
/// Applies a new projection matrix.
/// </summary>

View File

@@ -10,34 +10,18 @@ namespace osu.Framework.Graphics.Rendering
{
public struct MaskingInfo : IEquatable<MaskingInfo>
{
/// <summary>
/// A rectangle that defines the scissor area in screen-space coordinates.
/// </summary>
public RectangleF ScreenSpaceScissorArea;
public RectangleI ScreenSpaceAABB;
public RectangleF MaskingRect;
/// <summary>
/// A rectangle that defines the masking area in the local-space (i.e. <see cref="Drawable.DrawRectangle"/>) of the masking container.
/// </summary>
public RectangleF MaskingArea;
/// <summary>
/// A quad representing the internal "safe" (without borders, corners, and AA smoothening) area of the masking container.
/// </summary>
/// <remarks>
/// This is used to clip drawn polygons during the front-to-back pass such that only areas guaranteed to be visible are drawn.
/// </remarks>
public Quad ConservativeScreenSpaceQuad;
/// <summary>
/// A matrix that converts from vertex coordinates to the space of <see cref="MaskingArea"/>.
/// This matrix transforms screen space coordinates to masking space (likely the parent
/// space of the container doing the masking).
/// It is used by a shader to determine which pixels to discard.
/// </summary>
public Matrix3 ToMaskingSpace;
/// <summary>
/// A matrix that converts from vertex coordinates to the space of <see cref="ScreenSpaceScissorArea"/>.
/// </summary>
public Matrix3 ToScissorSpace;
public float CornerRadius;
public float CornerExponent;
@@ -55,11 +39,10 @@ namespace osu.Framework.Graphics.Rendering
public readonly bool Equals(MaskingInfo other) => this == other;
public static bool operator ==(in MaskingInfo left, in MaskingInfo right) =>
left.ScreenSpaceScissorArea == right.ScreenSpaceScissorArea &&
left.MaskingArea == right.MaskingArea &&
left.ScreenSpaceAABB == right.ScreenSpaceAABB &&
left.MaskingRect == right.MaskingRect &&
left.ConservativeScreenSpaceQuad.Equals(right.ConservativeScreenSpaceQuad) &&
left.ToMaskingSpace == right.ToMaskingSpace &&
left.ToScissorSpace == right.ToScissorSpace &&
left.CornerRadius == right.CornerRadius &&
left.CornerExponent == right.CornerExponent &&
left.BorderThickness == right.BorderThickness &&

View File

@@ -59,7 +59,6 @@ namespace osu.Framework.Graphics.Rendering
public ulong FrameIndex { get; private set; }
public ref readonly MaskingInfo CurrentMaskingInfo => ref currentMaskingInfo;
public int CurrentMaskingIndex => ShaderMaskingStack?.CurrentOffset ?? 0;
public RectangleI Viewport { get; private set; }
public RectangleI Scissor { get; private set; }
@@ -131,8 +130,6 @@ namespace osu.Framework.Graphics.Rendering
private readonly LockedWeakList<Texture> allTextures = new LockedWeakList<Texture>();
protected IUniformBuffer<GlobalUniformData>? GlobalUniformBuffer { get; private set; }
protected ShaderStorageBufferObjectStack<ShaderMaskingInfo>? ShaderMaskingStack { get; private set; }
private IVertexBatch<TexturedVertex2D>? defaultQuadBatch;
private IVertexBatch? currentActiveBatch;
private MaskingInfo currentMaskingInfo;
@@ -199,10 +196,6 @@ namespace osu.Framework.Graphics.Rendering
IsUvOriginTopLeft = IsUvOriginTopLeft
};
// 60 elements keeps the total data length under 16KiB (16320).
ShaderMaskingStack ??= new ShaderStorageBufferObjectStack<ShaderMaskingInfo>(this, 60, 8192);
ShaderMaskingStack.Clear();
Debug.Assert(defaultQuadBatch != null);
FrameIndex++;
@@ -255,12 +248,12 @@ namespace osu.Framework.Graphics.Rendering
PushScissorState(true);
PushViewport(new RectangleI(0, 0, (int)windowSize.X, (int)windowSize.Y));
PushScissor(new RectangleI(0, 0, (int)windowSize.X, (int)windowSize.Y));
PushScissorOffset(Vector2I.Zero);
PushMaskingInfo(new MaskingInfo
{
ScreenSpaceScissorArea = new RectangleI(0, 0, (int)windowSize.X, (int)windowSize.Y),
MaskingArea = new RectangleF(0, 0, windowSize.X, windowSize.Y),
ScreenSpaceAABB = new RectangleI(0, 0, (int)windowSize.X, (int)windowSize.Y),
MaskingRect = new RectangleF(0, 0, windowSize.X, windowSize.Y),
ToMaskingSpace = Matrix3.Identity,
ToScissorSpace = Matrix3.Identity,
BlendRange = 1,
AlphaExponent = 1,
CornerExponent = 2.5f,
@@ -496,6 +489,12 @@ namespace osu.Framework.Graphics.Rendering
setScissorState(enabled);
}
public void PushScissorOffset(Vector2I offset)
{
scissorOffsetStack.Push(offset);
setScissorOffset(offset);
}
public void PopScissor()
{
Trace.Assert(scissorRectStack.Count > 1);
@@ -512,6 +511,14 @@ namespace osu.Framework.Graphics.Rendering
setScissorState(scissorStateStack.Peek());
}
public void PopScissorOffset()
{
Trace.Assert(scissorOffsetStack.Count > 1);
scissorOffsetStack.Pop();
setScissorOffset(scissorOffsetStack.Peek());
}
private void setScissor(RectangleI scissor)
{
if (scissor.Width < 0)
@@ -552,6 +559,15 @@ namespace osu.Framework.Graphics.Rendering
ScissorState = enabled;
}
private void setScissorOffset(Vector2I offset)
{
if (ScissorOffset == offset)
return;
FlushCurrentBatch(FlushBatchSource.SetScissor);
ScissorOffset = offset;
}
/// <summary>
/// Updates the graphics device with a new scissor rectangle.
/// </summary>
@@ -616,74 +632,71 @@ namespace osu.Framework.Graphics.Rendering
if (CurrentMaskingInfo == maskingInfo)
return;
FlushCurrentBatch(FlushBatchSource.SetMasking);
GlobalUniformBuffer!.Data = GlobalUniformBuffer.Data with
{
IsMasking = IsMaskingActive,
MaskingRect = new Vector4(
maskingInfo.MaskingRect.Left,
maskingInfo.MaskingRect.Top,
maskingInfo.MaskingRect.Right,
maskingInfo.MaskingRect.Bottom),
ToMaskingSpace = maskingInfo.ToMaskingSpace,
CornerRadius = maskingInfo.CornerRadius,
CornerExponent = maskingInfo.CornerExponent,
BorderThickness = maskingInfo.BorderThickness / maskingInfo.BlendRange,
BorderColour = maskingInfo.BorderThickness > 0
? new Matrix4(
// TopLeft
maskingInfo.BorderColour.TopLeft.SRGB.R,
maskingInfo.BorderColour.TopLeft.SRGB.G,
maskingInfo.BorderColour.TopLeft.SRGB.B,
maskingInfo.BorderColour.TopLeft.SRGB.A,
// BottomLeft
maskingInfo.BorderColour.BottomLeft.SRGB.R,
maskingInfo.BorderColour.BottomLeft.SRGB.G,
maskingInfo.BorderColour.BottomLeft.SRGB.B,
maskingInfo.BorderColour.BottomLeft.SRGB.A,
// TopRight
maskingInfo.BorderColour.TopRight.SRGB.R,
maskingInfo.BorderColour.TopRight.SRGB.G,
maskingInfo.BorderColour.TopRight.SRGB.B,
maskingInfo.BorderColour.TopRight.SRGB.A,
// BottomRight
maskingInfo.BorderColour.BottomRight.SRGB.R,
maskingInfo.BorderColour.BottomRight.SRGB.G,
maskingInfo.BorderColour.BottomRight.SRGB.B,
maskingInfo.BorderColour.BottomRight.SRGB.A)
: GlobalUniformBuffer.Data.BorderColour,
MaskingBlendRange = maskingInfo.BlendRange,
AlphaExponent = maskingInfo.AlphaExponent,
EdgeOffset = maskingInfo.EdgeOffset,
DiscardInner = maskingInfo.Hollow,
InnerCornerRadius = maskingInfo.Hollow
? maskingInfo.HollowCornerRadius
: GlobalUniformBuffer.Data.InnerCornerRadius
};
if (isPushing)
{
RectangleF scissorRect = maskingInfo.ScreenSpaceScissorArea;
// When drawing to a viewport that doesn't match the projection size (e.g. via framebuffers), the resultant image will be scaled
Vector2 projectionScale = new Vector2(ProjectionMatrix.Row0.X / 2, -ProjectionMatrix.Row1.Y / 2);
Vector2 viewportScale = Vector2.Multiply(Viewport.Size, projectionScale);
if (!overwritePreviousScissor)
{
Vector4 currentSmiScissorRectangle = ShaderMaskingStack!.CurrentBuffer[ShaderMaskingStack.CurrentOffset].ScissorRect;
RectangleF currentScissorRectangle = RectangleF.FromLTRB(
currentSmiScissorRectangle.X,
currentSmiScissorRectangle.Y,
currentSmiScissorRectangle.Z,
currentSmiScissorRectangle.W);
Vector2 location = (maskingInfo.ScreenSpaceAABB.Location - ScissorOffset) * viewportScale;
Vector2 size = maskingInfo.ScreenSpaceAABB.Size * viewportScale;
scissorRect = RectangleF.Intersect(currentScissorRectangle, scissorRect);
}
RectangleI actualRect = new RectangleI(
(int)Math.Floor(location.X),
(int)Math.Floor(location.Y),
(int)Math.Ceiling(size.X),
(int)Math.Ceiling(size.Y));
ShaderMaskingStack!.Push(new ShaderMaskingInfo
{
IsMasking = IsMaskingActive,
MaskingRect = new Vector4(
maskingInfo.MaskingArea.Left,
maskingInfo.MaskingArea.Top,
maskingInfo.MaskingArea.Right,
maskingInfo.MaskingArea.Bottom),
ScissorRect = new Vector4(
scissorRect.Left,
scissorRect.Top,
scissorRect.Right,
scissorRect.Bottom),
ToMaskingSpace = new Matrix4(maskingInfo.ToMaskingSpace),
ToScissorSpace = new Matrix4(maskingInfo.ToScissorSpace),
CornerRadius = maskingInfo.CornerRadius,
CornerExponent = maskingInfo.CornerExponent,
BorderThickness = maskingInfo.BorderThickness / maskingInfo.BlendRange,
BorderColour = maskingInfo.BorderThickness > 0
? new Matrix4(
// TopLeft
maskingInfo.BorderColour.TopLeft.SRGB.R,
maskingInfo.BorderColour.TopLeft.SRGB.G,
maskingInfo.BorderColour.TopLeft.SRGB.B,
maskingInfo.BorderColour.TopLeft.SRGB.A,
// BottomLeft
maskingInfo.BorderColour.BottomLeft.SRGB.R,
maskingInfo.BorderColour.BottomLeft.SRGB.G,
maskingInfo.BorderColour.BottomLeft.SRGB.B,
maskingInfo.BorderColour.BottomLeft.SRGB.A,
// TopRight
maskingInfo.BorderColour.TopRight.SRGB.R,
maskingInfo.BorderColour.TopRight.SRGB.G,
maskingInfo.BorderColour.TopRight.SRGB.B,
maskingInfo.BorderColour.TopRight.SRGB.A,
// BottomRight
maskingInfo.BorderColour.BottomRight.SRGB.R,
maskingInfo.BorderColour.BottomRight.SRGB.G,
maskingInfo.BorderColour.BottomRight.SRGB.B,
maskingInfo.BorderColour.BottomRight.SRGB.A)
: ShaderMaskingStack.CurrentBuffer[ShaderMaskingStack.CurrentOffset].BorderColour,
MaskingBlendRange = maskingInfo.BlendRange,
AlphaExponent = maskingInfo.AlphaExponent,
EdgeOffset = maskingInfo.EdgeOffset,
DiscardInner = maskingInfo.Hollow,
InnerCornerRadius = maskingInfo.Hollow
? maskingInfo.HollowCornerRadius
: ShaderMaskingStack.CurrentBuffer[ShaderMaskingStack.CurrentOffset].InnerCornerRadius,
});
PushScissor(overwritePreviousScissor ? actualRect : RectangleI.Intersect(scissorRectStack.Peek(), actualRect));
}
else
ShaderMaskingStack!.Pop();
PopScissor();
currentMaskingInfo = maskingInfo;
}

View File

@@ -263,14 +263,9 @@ namespace osu.Framework.Graphics.Rendering
public static void PushLocalMatrix(this IRenderer renderer, Matrix4 matrix)
{
var currentMasking = renderer.CurrentMaskingInfo;
// Normally, ToMaskingSpace is used to convert from screen-space coordinates to local coordinates in the masking-space.
// But if a local matrix is pushed, then vertices will instead be provided in local-space, such that:
// 1. To convert to masking-space we need to first convert to screen-space.
// 2. To convert to scissor-space we need to convert to screen-space.
// normally toMaskingSpace is fed vertices already in screen space coordinates,
// but since we are modifying the matrix the vertices are in local space
currentMasking.ToMaskingSpace = new Matrix3(matrix) * currentMasking.ToMaskingSpace;
currentMasking.ToScissorSpace = new Matrix3(matrix);
renderer.PushMaskingInfo(currentMasking, true);
renderer.PushProjectionMatrix(matrix * renderer.ProjectionMatrix);
}
@@ -279,13 +274,10 @@ namespace osu.Framework.Graphics.Rendering
public static void PushLocalMatrix(this IRenderer renderer, Matrix3 matrix)
{
var currentMasking = renderer.CurrentMaskingInfo;
// Normally, ToMaskingSpace is used to convert from screen-space coordinates to local coordinates in the masking-space.
// But if a local matrix is pushed, then vertices will instead be provided in local-space, such that:
// 1. To convert to masking-space we need to first convert to screen-space.
// 2. To convert to scissor-space we need to convert to screen-space.
// normally toMaskingSpace is fed vertices already in screen space coordinates,
// but since we are modifying the matrix the vertices are in local space
currentMasking.ToMaskingSpace = matrix * currentMasking.ToMaskingSpace;
currentMasking.ToScissorSpace = matrix;
renderer.PushMaskingInfo(currentMasking, true);
// this makes sure it also works for 3D vertices like the ones path uses
Matrix4 mat = new Matrix4(matrix);
@@ -293,8 +285,6 @@ namespace osu.Framework.Graphics.Rendering
mat.Row2.X = 0;
mat.Row3.Y = mat.Row2.Y;
mat.Row2.Y = 0;
renderer.PushMaskingInfo(currentMasking, true);
renderer.PushProjectionMatrix(mat * renderer.ProjectionMatrix);
}

View File

@@ -1,32 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Runtime.InteropServices;
using osu.Framework.Graphics.Shaders.Types;
namespace osu.Framework.Graphics.Rendering
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public record struct ShaderMaskingInfo
{
public UniformMatrix4 ToMaskingSpace;
public UniformMatrix4 ToScissorSpace;
public UniformBool IsMasking;
public UniformFloat CornerRadius;
public UniformFloat CornerExponent;
public UniformFloat BorderThickness;
public UniformVector4 MaskingRect;
public UniformVector4 ScissorRect;
public UniformMatrix4 BorderColour;
public UniformFloat MaskingBlendRange;
public UniformFloat AlphaExponent;
public UniformVector2 EdgeOffset;
public UniformBool DiscardInner;
public UniformFloat InnerCornerRadius;
private readonly UniformPadding8 pad1;
}
}

View File

@@ -30,9 +30,6 @@ namespace osu.Framework.Graphics.Rendering.Vertices
[VertexMember(1, VertexAttribPointerType.Float)]
private readonly float backbufferDrawDepth;
[VertexMember(1, VertexAttribPointerType.Int)]
private readonly int maskingIndex;
[Obsolete("Initialise this type with an IRenderer instead", true)]
public TexturedVertex2D()
{
@@ -43,7 +40,6 @@ namespace osu.Framework.Graphics.Rendering.Vertices
{
this = default; // explicitly initialise all members to default values
backbufferDrawDepth = renderer.BackbufferDrawDepth;
maskingIndex = renderer.CurrentMaskingIndex;
}
public readonly bool Equals(TexturedVertex2D other) =>
@@ -52,7 +48,6 @@ namespace osu.Framework.Graphics.Rendering.Vertices
&& Colour.Equals(other.Colour)
&& TextureRect.Equals(other.TextureRect)
&& BlendRange.Equals(other.BlendRange)
&& backbufferDrawDepth == other.backbufferDrawDepth
&& maskingIndex == other.maskingIndex;
&& backbufferDrawDepth == other.backbufferDrawDepth;
}
}

View File

@@ -21,13 +21,6 @@ namespace osu.Framework.Graphics.Rendering.Vertices
[VertexMember(2, VertexAttribPointerType.Float)]
public Vector2 TexturePosition;
[VertexMember(1, VertexAttribPointerType.Int)]
private readonly int maskingIndex;
public readonly bool Equals(TexturedVertex3D other)
=> Position.Equals(other.Position)
&& TexturePosition.Equals(other.TexturePosition)
&& Colour.Equals(other.Colour)
&& maskingIndex == other.maskingIndex;
public readonly bool Equals(TexturedVertex3D other) => Position.Equals(other.Position) && TexturePosition.Equals(other.TexturePosition) && Colour.Equals(other.Colour);
}
}

View File

@@ -149,7 +149,6 @@ namespace osu.Framework.Graphics.Shaders
public static class VertexShaderDescriptor
{
public const string TEXTURE_2 = "Texture2D";
public const string TEXTURE_2_NO_MASKING = "Texture2D_NoMasking";
public const string TEXTURE_3 = "Texture3D";
public const string POSITION = "Position";
}

View File

@@ -547,7 +547,6 @@ namespace osu.Framework.Graphics.Veldrid
var veldridShader = (VeldridShader)Shader!;
veldridShader.BindUniformBlock("g_GlobalUniforms", GlobalUniformBuffer!);
veldridShader.BindUniformBlock("g_MaskingBuffer", ShaderMaskingStack!.CurrentBuffer);
pipeline.PrimitiveTopology = type;
Array.Resize(ref pipeline.ResourceLayouts, veldridShader.LayoutCount);

View File

@@ -9,6 +9,7 @@ using System.IO;
using System.Threading.Tasks;
using osu.Framework.Configuration;
using osu.Framework.Extensions;
using osu.Framework.Logging;
namespace osu.Framework.Platform
{
@@ -81,7 +82,14 @@ namespace osu.Framework.Platform
if (!url.CheckIsValidUrl())
throw new ArgumentException("The provided URL must be one of either http://, https:// or mailto: protocols.", nameof(url));
openUsingShellExecute(url);
try
{
openUsingShellExecute(url);
}
catch (Exception ex)
{
Logger.Error(ex, "Unable to open external link.");
}
}
public override bool PresentFileExternally(string filename)

View File

@@ -19,6 +19,19 @@ layout(std140, set = -1, binding = 0) uniform g_GlobalUniforms
bool g_IsUvOriginTopLeft;
mat4 g_ProjMatrix;
mat3 g_ToMaskingSpace;
bool g_IsMasking;
highp float g_CornerRadius;
highp float g_CornerExponent;
highp vec4 g_MaskingRect;
highp float g_BorderThickness;
lowp mat4 g_BorderColour;
mediump float g_MaskingBlendRange;
lowp float g_AlphaExponent;
highp vec2 g_EdgeOffset;
bool g_DiscardInner;
highp float g_InnerCornerRadius;
// 0 -> None
// 1 -> ClampToEdge

View File

@@ -1,52 +0,0 @@
#ifndef INTERNAL_MASKING_INFO_H
#define INTERNAL_MASKING_INFO_H
#extension GL_ARB_shader_storage_buffer_object : enable
struct MaskingInfo
{
mat4 ToMaskingSpace;
mat4 ToScissorSpace;
bool IsMasking;
highp float CornerRadius;
highp float CornerExponent;
highp float BorderThickness;
highp vec4 MaskingRect;
highp vec4 ScissorRect;
lowp mat4 BorderColour;
mediump float MaskingBlendRange;
lowp float AlphaExponent;
highp vec2 EdgeOffset;
bool DiscardInner;
highp float InnerCornerRadius;
vec2 pad1;
};
MaskingInfo g_MaskingInfo;
#ifndef OSU_GRAPHICS_NO_SSBO
layout(std140, set = -2, binding = 0) readonly buffer g_MaskingBuffer
{
MaskingInfo Data[];
} MaskingBuffer;
#else // OSU_GRAPHICS_NO_SSBO
layout(std140, set = -2, binding = 0) uniform g_MaskingBuffer
{
MaskingInfo Data[60];
} MaskingBuffer;
#endif // OSU_GRAPHICS_NO_SSBO
void InitMasking(int index)
{
g_MaskingInfo = MaskingBuffer.Data[index];
}
#endif // INTERNAL_MASKING_INFO_H

View File

@@ -1,8 +1,6 @@
#ifndef MASKING_H
#define MASKING_H
#include "Internal/sh_MaskingInfo.h"
layout(location = 0) in highp vec2 v_MaskingPosition;
layout(location = 1) in lowp vec4 v_Colour;
@@ -13,27 +11,14 @@ layout(location = 1) in lowp vec4 v_Colour;
#endif
layout(location = 4) in mediump vec2 v_BlendRange;
layout(location = 5) flat in int v_MaskingIndex;
layout(location = 6) in highp vec2 v_ScissorPosition;
/// Positive if outside the rect, negative if inside the rect.
highp float distanceFromScissorRect()
{
highp vec2 topLeftOffset = g_MaskingInfo.ScissorRect.xy - v_ScissorPosition;
highp vec2 bottomRightOffset = v_ScissorPosition - g_MaskingInfo.ScissorRect.zw;
highp vec2 distanceFromShrunkRect = max(bottomRightOffset, topLeftOffset);
return max(distanceFromShrunkRect.x, distanceFromShrunkRect.y);
}
highp float distanceFromRoundedRect(highp vec2 offset, highp float radius)
{
highp vec2 maskingPosition = v_MaskingPosition + offset;
// Compute offset distance from masking rect in masking space.
highp vec2 topLeftOffset = g_MaskingInfo.MaskingRect.xy - maskingPosition;
highp vec2 bottomRightOffset = maskingPosition - g_MaskingInfo.MaskingRect.zw;
highp vec2 topLeftOffset = g_MaskingRect.xy - maskingPosition;
highp vec2 bottomRightOffset = maskingPosition - g_MaskingRect.zw;
highp vec2 distanceFromShrunkRect = max(
bottomRightOffset + vec2(radius),
@@ -48,7 +33,7 @@ highp float distanceFromRoundedRect(highp vec2 offset, highp float radius)
else
{
distanceFromShrunkRect = max(vec2(0.0), distanceFromShrunkRect);
return pow(pow(distanceFromShrunkRect.x, g_MaskingInfo.CornerExponent) + pow(distanceFromShrunkRect.y, g_MaskingInfo.CornerExponent), 1.0 / g_MaskingInfo.CornerExponent);
return pow(pow(distanceFromShrunkRect.x, g_CornerExponent) + pow(distanceFromShrunkRect.y, g_CornerExponent), 1.0 / g_CornerExponent);
}
}
@@ -70,53 +55,46 @@ highp float distanceFromDrawingRect(mediump vec2 texCoord)
lowp vec4 getBorderColour()
{
highp vec2 relativeTexCoord = v_MaskingPosition / (g_MaskingInfo.MaskingRect.zw - g_MaskingInfo.MaskingRect.xy);
lowp vec4 top = mix(g_MaskingInfo.BorderColour[0], g_MaskingInfo.BorderColour[2], relativeTexCoord.x);
lowp vec4 bottom = mix(g_MaskingInfo.BorderColour[1], g_MaskingInfo.BorderColour[3], relativeTexCoord.x);
highp vec2 relativeTexCoord = v_MaskingPosition / (g_MaskingRect.zw - g_MaskingRect.xy);
lowp vec4 top = mix(g_BorderColour[0], g_BorderColour[2], relativeTexCoord.x);
lowp vec4 bottom = mix(g_BorderColour[1], g_BorderColour[3], relativeTexCoord.x);
return mix(top, bottom, relativeTexCoord.y);
}
lowp vec4 getRoundedColor(lowp vec4 texel, mediump vec2 texCoord)
{
InitMasking(v_MaskingIndex);
if (!g_MaskingInfo.IsMasking && v_BlendRange == vec2(0.0))
if (!g_IsMasking && v_BlendRange == vec2(0.0))
{
return v_Colour * texel;
}
if (distanceFromScissorRect() > 0)
{
discard;
}
highp float dist = distanceFromRoundedRect(vec2(0.0), g_MaskingInfo.CornerRadius);
highp float dist = distanceFromRoundedRect(vec2(0.0), g_CornerRadius);
lowp float alphaFactor = 1.0;
// Discard inner pixels
if (g_MaskingInfo.DiscardInner)
if (g_DiscardInner)
{
highp float innerDist = (g_MaskingInfo.EdgeOffset == vec2(0.0) && g_MaskingInfo.InnerCornerRadius == g_MaskingInfo.CornerRadius) ?
dist : distanceFromRoundedRect(g_MaskingInfo.EdgeOffset, g_MaskingInfo.InnerCornerRadius);
highp float innerDist = (g_EdgeOffset == vec2(0.0) && g_InnerCornerRadius == g_CornerRadius) ?
dist : distanceFromRoundedRect(g_EdgeOffset, g_InnerCornerRadius);
// v_BlendRange is set from outside in a hacky way to tell us the g_MaskingInfo.MaskingBlendRange used for the rounded
// v_BlendRange is set from outside in a hacky way to tell us the g_MaskingBlendRange used for the rounded
// corners of the edge effect container itself. We can then derive the alpha factor for smooth inner edge
// effect from that.
highp float innerBlendFactor = (g_MaskingInfo.InnerCornerRadius - g_MaskingInfo.MaskingBlendRange - innerDist) / v_BlendRange.x;
highp float innerBlendFactor = (g_InnerCornerRadius - g_MaskingBlendRange - innerDist) / v_BlendRange.x;
if (innerBlendFactor > 1.0)
{
return vec4(0.0);
}
// We exponentiate our factor to exactly counteract the later exponentiation by g_MaskingInfo.AlphaExponent for a smoother inner border.
alphaFactor = pow(min(1.0 - innerBlendFactor, 1.0), 1.0 / g_MaskingInfo.AlphaExponent);
// We exponentiate our factor to exactly counteract the later exponentiation by g_AlphaExponent for a smoother inner border.
alphaFactor = pow(min(1.0 - innerBlendFactor, 1.0), 1.0 / g_AlphaExponent);
}
dist /= g_MaskingInfo.MaskingBlendRange;
dist /= g_MaskingBlendRange;
// This correction is needed to avoid fading of the alpha value for radii below 1px.
highp float radiusCorrection = g_MaskingInfo.CornerRadius <= 0.0 ? g_MaskingInfo.MaskingBlendRange : max(0.0, g_MaskingInfo.MaskingBlendRange - g_MaskingInfo.CornerRadius);
highp float fadeStart = (g_MaskingInfo.CornerRadius + radiusCorrection) / g_MaskingInfo.MaskingBlendRange;
highp float radiusCorrection = g_CornerRadius <= 0.0 ? g_MaskingBlendRange : max(0.0, g_MaskingBlendRange - g_CornerRadius);
highp float fadeStart = (g_CornerRadius + radiusCorrection) / g_MaskingBlendRange;
alphaFactor *= min(fadeStart - dist, 1.0);
if (v_BlendRange.x > 0.0 || v_BlendRange.y > 0.0)
@@ -130,9 +108,9 @@ lowp vec4 getRoundedColor(lowp vec4 texel, mediump vec2 texCoord)
}
// This ends up softening glow without negatively affecting edge smoothness much.
alphaFactor = pow(alphaFactor, g_MaskingInfo.AlphaExponent);
alphaFactor = pow(alphaFactor, g_AlphaExponent);
highp float borderStart = 1.0 + fadeStart - g_MaskingInfo.BorderThickness;
highp float borderStart = 1.0 + fadeStart - g_BorderThickness;
lowp float colourWeight = min(borderStart - dist, 1.0);
lowp vec4 contentColour = v_Colour * texel;

View File

@@ -2,7 +2,6 @@
#define TEXTURE2D_VS
#include "sh_Utils.h"
#include "Internal/sh_MaskingInfo.h"
layout(location = 0) in highp vec2 m_Position;
layout(location = 1) in lowp vec4 m_Colour;
@@ -10,33 +9,23 @@ layout(location = 2) in highp vec2 m_TexCoord;
layout(location = 3) in highp vec4 m_TexRect;
layout(location = 4) in mediump vec2 m_BlendRange;
layout(location = 5) in highp float m_BackbufferDrawDepth;
layout(location = 6) in int m_MaskingIndex;
layout(location = 0) out highp vec2 v_MaskingPosition;
layout(location = 1) out lowp vec4 v_Colour;
layout(location = 2) out highp vec2 v_TexCoord;
layout(location = 3) out highp vec4 v_TexRect;
layout(location = 4) out mediump vec2 v_BlendRange;
layout(location = 5) flat out int v_MaskingIndex;
layout(location = 6) out highp vec2 v_ScissorPosition;
void main(void)
{
InitMasking(m_MaskingIndex);
// Transform from screen space to masking space.
highp vec4 maskingPos = g_MaskingInfo.ToMaskingSpace * vec4(m_Position, 1.0, 0.0);
highp vec3 maskingPos = g_ToMaskingSpace * vec3(m_Position, 1.0);
v_MaskingPosition = maskingPos.xy / maskingPos.z;
// Transform from screen space to scissor space.
highp vec4 scissorPos = g_MaskingInfo.ToScissorSpace * vec4(m_Position, 1.0, 0.0);
v_ScissorPosition = scissorPos.xy / scissorPos.z;
v_Colour = m_Colour;
v_TexCoord = m_TexCoord;
v_TexRect = m_TexRect;
v_BlendRange = m_BlendRange;
v_MaskingIndex = m_MaskingIndex;
gl_Position = g_ProjMatrix * vec4(m_Position, 1.0, 1.0);

View File

@@ -1,32 +0,0 @@
#ifndef TEXTURE2D_NO_MASKING_VS
#define TEXTURE2D_NO_MASKING_VS
#include "sh_Utils.h"
layout(location = 0) in highp vec2 m_Position;
layout(location = 1) in lowp vec4 m_Colour;
layout(location = 2) in highp vec2 m_TexCoord;
layout(location = 3) in highp vec4 m_TexRect;
layout(location = 4) in mediump vec2 m_BlendRange;
layout(location = 5) in highp float m_BackbufferDrawDepth;
layout(location = 0) out highp vec2 v_MaskingPosition;
layout(location = 1) out lowp vec4 v_Colour;
layout(location = 2) out highp vec2 v_TexCoord;
layout(location = 3) out highp vec4 v_TexRect;
layout(location = 4) out mediump vec2 v_BlendRange;
void main(void)
{
v_Colour = m_Colour;
v_TexCoord = m_TexCoord;
v_TexRect = m_TexRect;
v_BlendRange = m_BlendRange;
gl_Position = g_ProjMatrix * vec4(m_Position, 1.0, 1.0);
if (g_BackbufferDraw)
gl_Position.z = m_BackbufferDrawDepth;
}
#endif

View File

@@ -2,39 +2,28 @@
#define TEXTURE3D_VS
#include "sh_Utils.h"
#include "Internal/sh_MaskingInfo.h"
layout(location = 0) in highp vec3 m_Position;
layout(location = 1) in lowp vec4 m_Colour;
layout(location = 2) in highp vec2 m_TexCoord;
layout(location = 3) in int m_MaskingIndex;
layout(location = 0) out highp vec2 v_MaskingPosition;
layout(location = 1) out lowp vec4 v_Colour;
layout(location = 2) out highp vec2 v_TexCoord;
layout(location = 3) out highp vec4 v_TexRect;
layout(location = 4) out mediump vec2 v_BlendRange;
layout(location = 5) flat out int v_MaskingIndex;
layout(location = 6) out highp vec2 v_ScissorPosition;
void main(void)
{
InitMasking(m_MaskingIndex);
// Transform from screen space to masking space.
highp vec4 maskingPos = g_MaskingInfo.ToMaskingSpace * vec4(m_Position.xy, 1.0, 0.0);
// Transform to position to masking space.
vec3 maskingPos = g_ToMaskingSpace * vec3(m_Position.xy, 1.0);
v_MaskingPosition = maskingPos.xy / maskingPos.z;
// Transform from screen space to scissor space.
highp vec4 scissorPos = g_MaskingInfo.ToScissorSpace * vec4(m_Position.xy, 1.0, 0.0);
v_ScissorPosition = scissorPos.xy / scissorPos.z;
v_TexRect = vec4(0.0);
v_BlendRange = vec2(0.0);
v_Colour = m_Colour;
v_TexCoord = m_TexCoord;
v_MaskingIndex = m_MaskingIndex;
gl_Position = g_ProjMatrix * vec4(m_Position, 1.0);
}

View File

@@ -3,6 +3,7 @@
using osu.Framework.Extensions.TypeExtensions;
using System;
using System.Diagnostics;
using System.Linq;
namespace osu.Framework.Timing
@@ -23,13 +24,11 @@ namespace osu.Framework.Timing
public FramedClock(IClock? source = null, bool processSource = true)
{
this.processSource = processSource;
Source = source ?? new StopwatchClock(true);
ChangeSource(Source);
ChangeSource(source ?? new StopwatchClock(true));
Debug.Assert(Source != null);
}
public FrameTimeInfo TimeInfo => new FrameTimeInfo { Elapsed = ElapsedFrameTime, Current = CurrentTime };
private readonly double[] betweenFrameTimes = new double[128];
private long totalFramesProcessed;
@@ -58,12 +57,10 @@ namespace osu.Framework.Timing
private const int fps_calculation_interval = 250;
public void ChangeSource(IClock? source)
public void ChangeSource(IClock source)
{
if (source == null) return;
CurrentTime = LastFrameTime = source.CurrentTime;
Source = source;
CurrentTime = LastFrameTime = source.CurrentTime;
}
public virtual void ProcessFrame()

View File

@@ -20,7 +20,11 @@ namespace osu.Framework.Timing
/// </summary>
double FramesPerSecond { get; }
FrameTimeInfo TimeInfo { get; }
FrameTimeInfo TimeInfo => new FrameTimeInfo
{
Elapsed = ElapsedFrameTime,
Current = CurrentTime,
};
/// <summary>
/// Processes one frame. Generally should be run once per update loop.

View File

@@ -17,6 +17,6 @@ namespace osu.Framework.Timing
/// Change the source clock.
/// </summary>
/// <param name="source">The new source clock.</param>
void ChangeSource(IClock? source);
void ChangeSource(IClock source);
}
}

View File

@@ -7,50 +7,27 @@ namespace osu.Framework.Timing
{
/// <summary>
/// A clock which uses an internal stopwatch to interpolate (smooth out) a source.
/// Note that this will NOT function unless a source has been set.
/// </summary>
public class InterpolatingFramedClock : IFrameBasedClock, ISourceChangeableClock
{
private readonly FramedClock clock = new FramedClock(new StopwatchClock(true));
public IClock? Source { get; private set; }
protected IFrameBasedClock? FramedSourceClock;
protected double LastInterpolatedTime;
protected double CurrentInterpolatedTime;
public FrameTimeInfo TimeInfo => new FrameTimeInfo { Elapsed = ElapsedFrameTime, Current = CurrentTime };
public double FramesPerSecond => 0;
public virtual void ChangeSource(IClock? source)
{
if (source != null)
{
Source = source;
FramedSourceClock = Source as IFrameBasedClock ?? new FramedClock(Source);
}
currentTime = 0;
LastInterpolatedTime = 0;
CurrentInterpolatedTime = 0;
}
public InterpolatingFramedClock(IClock? source = null)
{
ChangeSource(source);
}
public virtual double CurrentTime => currentTime;
private double currentTime;
/// <summary>
/// The amount of error that is allowed between the source and interpolated time before the interpolated time is ignored and the source time is used.
/// </summary>
public virtual double AllowableErrorMilliseconds => 1000.0 / 60 * 2 * Rate;
private bool sourceIsRunning;
/// <summary>
/// Whether interpolation was applied at the last processed frame.
/// </summary>
/// <remarks>
/// If <see cref="Drift"/> becomes too high (as defined by <see cref="AllowableErrorMilliseconds"/>),
/// interpolation will be bypassed in order to provide a more correct time value.
/// </remarks>
public bool IsInterpolating { get; private set; }
/// <summary>
/// The drift in milliseconds between the source and interpolation at the last processed frame.
/// </summary>
public double Drift => CurrentTime - (FramedSourceClock?.CurrentTime ?? 0);
public virtual double Rate
{
@@ -60,38 +37,63 @@ namespace osu.Framework.Timing
public virtual bool IsRunning => sourceIsRunning;
public virtual double Drift => CurrentTime - (FramedSourceClock?.CurrentTime ?? 0);
public virtual double ElapsedFrameTime => currentInterpolatedTime - lastInterpolatedTime;
public virtual double ElapsedFrameTime => CurrentInterpolatedTime - LastInterpolatedTime;
public IClock? Source { get; private set; }
/// <summary>
/// Whether time is being interpolated for the frame currently being processed.
/// </summary>
public bool IsInterpolating { get; private set; }
public virtual double CurrentTime => currentTime;
protected IFrameBasedClock? FramedSourceClock;
private readonly FramedClock realtimeClock = new FramedClock(new StopwatchClock(true));
private double lastInterpolatedTime;
private double currentInterpolatedTime;
private bool sourceIsRunning;
private double currentTime;
public InterpolatingFramedClock(IClock? source = null)
{
ChangeSource(source);
}
public virtual void ChangeSource(IClock? source)
{
Source = source ?? new StopwatchClock(true);
// We need a frame-based source to correctly process interpolation.
// If the provided source is not already a framed clock, encapsulate it in one.
FramedSourceClock = Source as IFrameBasedClock ?? new FramedClock(source);
resetInterpolation();
}
public virtual void ProcessFrame()
{
if (FramedSourceClock == null) return;
clock.ProcessFrame();
realtimeClock.ProcessFrame();
FramedSourceClock.ProcessFrame();
sourceIsRunning = FramedSourceClock.IsRunning;
LastInterpolatedTime = currentTime;
lastInterpolatedTime = currentTime;
if (FramedSourceClock.IsRunning)
{
if (FramedSourceClock.ElapsedFrameTime != 0)
IsInterpolating = true;
CurrentInterpolatedTime += clock.ElapsedFrameTime * Rate;
currentInterpolatedTime += realtimeClock.ElapsedFrameTime * Rate;
if (!IsInterpolating || Math.Abs(FramedSourceClock.CurrentTime - CurrentInterpolatedTime) > AllowableErrorMilliseconds)
if (!IsInterpolating || Math.Abs(FramedSourceClock.CurrentTime - currentInterpolatedTime) > AllowableErrorMilliseconds)
{
// if we've exceeded the allowable error, we should use the source clock's time value.
// seeking backwards should only be allowed if the source is explicitly doing that.
CurrentInterpolatedTime = FramedSourceClock.ElapsedFrameTime < 0 ? FramedSourceClock.CurrentTime : Math.Max(LastInterpolatedTime, FramedSourceClock.CurrentTime);
currentInterpolatedTime = FramedSourceClock.ElapsedFrameTime < 0 ? FramedSourceClock.CurrentTime : Math.Max(lastInterpolatedTime, FramedSourceClock.CurrentTime);
// once interpolation fails, we don't want to resume interpolating until the source clock starts to move again.
IsInterpolating = false;
@@ -99,14 +101,23 @@ namespace osu.Framework.Timing
else
{
//if we differ from the elapsed time of the source, let's adjust for the difference.
CurrentInterpolatedTime += (FramedSourceClock.CurrentTime - CurrentInterpolatedTime) / 8;
currentInterpolatedTime += (FramedSourceClock.CurrentTime - currentInterpolatedTime) / 8;
// limit the direction of travel to avoid seeking against the flow.
CurrentInterpolatedTime = Rate >= 0 ? Math.Max(LastInterpolatedTime, CurrentInterpolatedTime) : Math.Min(LastInterpolatedTime, CurrentInterpolatedTime);
currentInterpolatedTime = Rate >= 0 ? Math.Max(lastInterpolatedTime, currentInterpolatedTime) : Math.Min(lastInterpolatedTime, currentInterpolatedTime);
}
}
currentTime = sourceIsRunning ? CurrentInterpolatedTime : FramedSourceClock.CurrentTime;
currentTime = sourceIsRunning ? currentInterpolatedTime : FramedSourceClock.CurrentTime;
}
private void resetInterpolation()
{
currentTime = 0;
lastInterpolatedTime = 0;
currentInterpolatedTime = 0;
}
double IFrameBasedClock.FramesPerSecond => 0;
}
}