Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to change background dim colour in grayscale #30391

Draft
wants to merge 47 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
48e5d18
Added "Background Colour" setting
Uncomfy Sep 16, 2024
261e5ab
Made Background.Sprite replaceable
Uncomfy Sep 20, 2024
30b3798
Made a custom class for BeatmapBackground with a custom BeatmapBackgr…
Uncomfy Sep 20, 2024
1d57a83
Made BeatmapBackgroundSprite use a custom shader
Uncomfy Sep 20, 2024
42579a6
Added a custom DrawNode for BeatmapBackgroundSprite
Uncomfy Sep 20, 2024
5b78d6f
Passing parameters to the shader
Uncomfy Sep 20, 2024
d088fc3
Made BeatmapBackground shader handle dimming
Uncomfy Sep 20, 2024
be56dd7
Added support for DimColour
Uncomfy Sep 20, 2024
b5156d9
Pass a full colour for DimColour instead of a single value into a shader
Uncomfy Sep 20, 2024
fa965fa
Changed BeatmapBackground class name to DimmableBeatmapBackground to …
Uncomfy Sep 21, 2024
7a6f197
Moved DimmableBeatmapBackground to its own file
Uncomfy Sep 21, 2024
4c3401f
Made DimmableBeatmapBackground inherit from BeatmapBackground, switch…
Uncomfy Sep 21, 2024
9283107
Switched to using Color4 for DimColour instead of float
Uncomfy Sep 21, 2024
a341c3a
Made DimmableBeatmapBackgroundSprite invalidate DrawNode on DimColour…
Uncomfy Sep 21, 2024
b7311fd
Formatting fixes
Uncomfy Sep 21, 2024
221ff9f
Changed the way colour is reported in TestSceneUserDimBackgrounds.cs
Uncomfy Sep 21, 2024
8b1264b
Added DimColour to user settings in TestSceneUserDimBackgrounds.cs
Uncomfy Sep 21, 2024
1d696ba
Updated TestSceneUserDimBackgrounds.cs to test for colour offset caus…
Uncomfy Sep 21, 2024
c99dd87
Handle IgnoreUserSettings for DimColour
Uncomfy Sep 21, 2024
b7531d1
Updated the way TestSceneUserDimBackgrounds.cs checks if background i…
Uncomfy Sep 21, 2024
e1b06d3
Integrated DimmableBeatmapBackground into BeatmapBackground
Uncomfy Sep 22, 2024
bab981e
Use existing methods to update shader variables
Uncomfy Oct 3, 2024
8f95210
Made background receive correct dim values when it's set
Uncomfy Oct 3, 2024
3281b88
Disable DimColour when user settings are ignored
Uncomfy Oct 3, 2024
d1fe76b
Made BufferedContainer handle dimming when it's present
Uncomfy Oct 9, 2024
20c2619
Added default values for BeatmapBackgroundSprite
Uncomfy Oct 9, 2024
eaa1476
Added explanations on why and how colour is split into two components
Uncomfy Oct 13, 2024
e73efd7
Updated the way colours are computed and checked in TestSceneUserDimB…
Uncomfy Oct 13, 2024
b62a044
Added forgotten Dispose's
Uncomfy Oct 13, 2024
983243f
Moved BeatmapBackground creation into a virtual function to help with…
Uncomfy Oct 13, 2024
892bd41
Created TestBeatmapBackground class and moved colour computations int…
Uncomfy Oct 14, 2024
20c1141
Added an interface to simplify access to current dimmable object in B…
Uncomfy Oct 14, 2024
cf88f97
Formatting fixes
Uncomfy Oct 14, 2024
eada120
Get DrawColour directly from object drawing the BG in TestSceneUserDi…
Uncomfy Oct 14, 2024
a4a43a9
Changed the way Background.Sprite is replaced by BeatmapBackground
Uncomfy Oct 14, 2024
0312490
Changed the way Background.bufferedContainer is replaced by BeatmapBa…
Uncomfy Oct 14, 2024
ec22de7
Added test to verify that both dimming handlers work as intended
Uncomfy Oct 14, 2024
0712230
Made TestSceneUserDimBackgrounds compute current colour using Colored…
Uncomfy Oct 16, 2024
356a0f3
Added Background colour to visual settings
Uncomfy Oct 16, 2024
62567f2
Changed BackgroundColour localisable string into BackgroundDimColour
Uncomfy Oct 16, 2024
c8d4dac
Changed the way ParentDrawColour is computed
Uncomfy Oct 16, 2024
fbe05ea
Updated the source of DrawColour when computing expected/target colour
Uncomfy Oct 17, 2024
65d9dbe
Formatting fixes
Uncomfy Oct 17, 2024
f04c4b0
Removed dependency on osuTK
Uncomfy Oct 21, 2024
96b7d97
Preload BeatmapBackground shader
Uncomfy Oct 22, 2024
c5867b6
Small formatting fixes
Uncomfy Oct 20, 2024
93c3fac
Test if BufferedContainer's framebuffer is redrawn on dim changes
Uncomfy Oct 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
281 changes: 265 additions & 16 deletions osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions osu.Game/Configuration/OsuConfigManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ protected override void InitialiseDefaults()
// Gameplay
SetDefault(OsuSetting.PositionalHitsoundsLevel, 0.2f, 0, 1);
SetDefault(OsuSetting.DimLevel, 0.7, 0, 1, 0.01);
SetDefault(OsuSetting.DimColour, 0, 0, 1, 0.01);
SetDefault(OsuSetting.BlurLevel, 0, 0, 1, 0.01);
SetDefault(OsuSetting.LightenDuringBreaks, true);

Expand Down Expand Up @@ -329,6 +330,7 @@ public enum OsuSetting
AutoCursorSize,
GameplayCursorDuringTouch,
DimLevel,
DimColour,
BlurLevel,
EditorDim,
LightenDuringBreaks,
Expand Down
32 changes: 18 additions & 14 deletions osu.Game/Graphics/Backgrounds/Background.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,7 @@ public Background(string textureName = @"")
this.textureName = textureName;
RelativeSizeAxes = Axes.Both;

AddInternal(Sprite = new Sprite
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
FillMode = FillMode.Fill,
});
AddInternal(Sprite = CreateSprite());
}

[BackgroundDependencyLoader]
Expand All @@ -47,24 +41,34 @@ private void load(LargeTextureStore textures)
Sprite.Texture = textures.Get(textureName);
}

protected virtual Sprite CreateSprite() => new Sprite
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
FillMode = FillMode.Fill,
};

protected virtual BufferedContainer CreateBufferedContainer() => new BufferedContainer(cachedFrameBuffer: true)
{
RelativeSizeAxes = Axes.Both,
RedrawOnScale = false,
Child = Sprite
};

public Vector2 BlurSigma => Vector2.Divide(bufferedContainer?.BlurSigma ?? Vector2.Zero, blurScale);

/// <summary>
/// Smoothly adjusts <see cref="IBufferedContainer.BlurSigma"/> over time.
/// </summary>
/// <returns>A <see cref="TransformSequence{T}"/> to which further transforms can be added.</returns>
public void BlurTo(Vector2 newBlurSigma, double duration = 0, Easing easing = Easing.None)
public virtual void BlurTo(Vector2 newBlurSigma, double duration = 0, Easing easing = Easing.None)
{
if (bufferedContainer == null && newBlurSigma != Vector2.Zero)
{
RemoveInternal(Sprite, false);

AddInternal(bufferedContainer = new BufferedContainer(cachedFrameBuffer: true)
{
RelativeSizeAxes = Axes.Both,
RedrawOnScale = false,
Child = Sprite
});
AddInternal(bufferedContainer = CreateBufferedContainer());
}

if (bufferedContainer != null)
Expand Down
265 changes: 265 additions & 0 deletions osu.Game/Graphics/Backgrounds/BeatmapBackground.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,60 @@

#nullable disable

using System.Runtime.InteropServices;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Rendering;
using osu.Framework.Graphics.Shaders;
using osu.Framework.Graphics.Shaders.Types;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Game.Beatmaps;

namespace osu.Game.Graphics.Backgrounds
{
/// <summary>
/// A background which offers dimming using a custom shader with ability to change dim colour.
/// </summary>
public partial class BeatmapBackground : Background
{
public readonly WorkingBeatmap Beatmap;

private readonly string fallbackTextureName;

protected BeatmapBackgroundSprite ColouredDimmableSprite { get; private set; }

protected DimmableBufferedContainer ColouredDimmableBufferedContainer;

private Colour4 dimColour;

private float dimLevel;

public Colour4 DimColour
{
get => dimColour;
set => ColouredDimmable.DimColour = dimColour = value;
}

public float DimLevel
{
get => dimLevel;
set => ColouredDimmable.DimLevel = dimLevel = value;
}

/// <summary>
/// A drawable that currently handles dimming.
/// </summary>
protected IColouredDimmable ColouredDimmable => ColouredDimmableBufferedContainer != null ? ColouredDimmableBufferedContainer : ColouredDimmableSprite;

public BeatmapBackground(WorkingBeatmap beatmap, string fallbackTextureName = @"Backgrounds/bg1")
{
Beatmap = beatmap;
this.fallbackTextureName = fallbackTextureName;

DimColour = Colour4.Black;
DimLevel = 0.0f;
}

[BackgroundDependencyLoader]
Expand All @@ -27,6 +65,29 @@ private void load(LargeTextureStore textures)
Sprite.Texture = Beatmap?.GetBackground() ?? textures.Get(fallbackTextureName);
}

protected override Sprite CreateSprite() => ColouredDimmableSprite = new BeatmapBackgroundSprite
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
FillMode = FillMode.Fill,
};

protected override BufferedContainer CreateBufferedContainer()
{
ColouredDimmableSprite.DimColour = Colour4.Black;
ColouredDimmableSprite.DimLevel = 0.0f;

return ColouredDimmableBufferedContainer = new DimmableBufferedContainer(cachedFrameBuffer: true)
{
RelativeSizeAxes = Axes.Both,
RedrawOnScale = false,
Child = Sprite,
DimColour = DimColour,
DimLevel = DimLevel
};
}

public override bool Equals(Background other)
{
if (ReferenceEquals(null, other)) return false;
Expand All @@ -35,5 +96,209 @@ public override bool Equals(Background other)
return other.GetType() == GetType()
&& ((BeatmapBackground)other).Beatmap == Beatmap;
}

public interface IColouredDimmable : IDrawable
{
Colour4 DimColour { get; set; }

float DimLevel { get; set; }
}

public partial class DimmableBufferedContainer : BufferedContainer, IColouredDimmable
{
private Colour4 dimColour;

private float dimLevel;

public Colour4 DimColour
{
get => dimColour;
set
{
dimColour = value;
Invalidate(Invalidation.DrawNode);
}
}

public float DimLevel
{
get => dimLevel;
set
{
dimLevel = value;
Invalidate(Invalidation.DrawNode);
}
}

public DimmableBufferedContainer(RenderBufferFormat[] formats = null, bool pixelSnapping = false, bool cachedFrameBuffer = false)
: base(formats, pixelSnapping, cachedFrameBuffer)
{
DimColour = Colour4.Black;
DimLevel = 0.0f;
}

[BackgroundDependencyLoader]
private void load(ShaderManager shaders)
{
TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, "BeatmapBackground");
Copy link
Member

@frenzibyte frenzibyte Oct 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't TextureShader responsible for rendering the framebuffer texture of the buffered container? If so, isn't that pointless? Isn't it enough to apply the custom "BeatmapBackground" shader on the underlying beatmap background sprite? Would that cause issues with blurring? I can't imagine it will.

In other words, do Sprite.TextureShader = "BeatmapBackground" instead (alongside the extra logic for the uniform buffer). That will save exposing TextureShader framework-side and also possibly "save" shader computation because the overall effect will become buffered (don't quote me on this).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but it will require the redraw of the framebuffer texture on each DimLevel change, which will also force the blur to be redrawn, and I feared that this might introduce unnecessary overhead during (for example) breaks, if player has background blur enabled. If that is not a concern - yeah, keeping the custom shader only on the sprite makes complete sense.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, it's pointless, at least in the current implementation.
Changing the dim parameters requires DrawNode invalidation, which forces the framebuffer to be redrawn, so there are no benefits. This does mean that DimLevel transitions are way more expensive now (at least on my rather old and slow hardware).

Here are the results of my benchmarks for average frametime during different parts of the beatmap.
With user blur set to 0%:

Gameplay Breaks Fade in Fade out
Baseline 4.25 ms 4.29 ms 4.42 ms 4.08 ms
Coloured dim 4.26 ms 4.33 ms 4.92 ms 4.55 ms
Relative increase 0.2% 0.9% 11.3% 11.5%

With user blur set to 100%:

Gameplay Breaks Fade in Fade out
Baseline 4.20 ms 4.24 ms 4.39 ms 4.02 ms
Coloured dim 4.22 ms 4.30 ms 5.18 ms 4.66 ms
Relative increase 0.5% 1.4% 18% 15.9%

"Baseline" is osu! 2024.1009.1
"Coloured dim" is commit c5867b6

Details about the benchmark

Made a map with 64 gameplay sections and 63 breaks for this benchmark, total duration is 3:09.

"Fade in" refers to frames rendered during gameplay-to-break transition, "Fade out" refers to break-to-gameplay transition.
"Gameplay" and "Breaks" columns are just for sanity-checking, as they should be same.

Total amount of recorded frames:

Blur Gameplay Breaks Fade in Fade out
Baseline 0% 12068 8176 11018 12069
Baseline 100% 12193 8253 11089 12236
Coloured dim 0% 12090 8105 9810 10689
Coloured dim 100% 12154 8137 9448 10512

osu! settings:

  • Compiled in release mode
  • Single-threaded mode
  • "OpenGL (Experimental)" renderer
  • Windowed screen mode
  • Resolution: 1564x964
  • Frame limiter: Basically unlimited

System info:

  • OS: Kubuntu 24.04.1 LTS x86_64
  • CPU: Intel i3-6100U @ 2.300GHz
  • GPU: Intel HD Graphics 520
  • RAM: 19907MiB

So I guess now there are two ways to go - ditch the shader replacement on the BufferedContainer and have slower transitions, or modify the BufferedContainer somehow to avoid redrawing the framebuffer when shader parameters are changed.

Which one is more preferable?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough, this is a bit of a pesky situation. You can control whether the framebuffer gets redrawn by overriding GetDrawVersion() and make that point to a custom invalidation ID that is normally incremented but not when dim properties are changed. See Path in osu!framework, it might actually be doing exactly what you're looking for.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a test case for it, but solving it requires a change in the framework: ppy/osu-framework#6404

With that change applied benchmarks look a lot better.

With user blur set to 0%:

Implementation Gameplay Breaks Fade in Fade out
Baseline 4.15 ms 4.20 ms 4.34 ms 3.99 ms
Coloured dim 4.19 ms 4.23 ms 4.39 ms 4.00 ms
Relative increase 1.0% 0.7% 1.2% 0.3%

With user blur set to 100%:

Implementation Gameplay Breaks Fade in Fade out
Baseline 4.11 ms 4.16 ms 4.31 ms 3.93 ms
Coloured dim 4.05 ms 4.11 ms 4.25 ms 3.85 ms
Relative increase -1.5% -1.2% -1.4% -2.0%

"Baseline" is osu! 2024.1009.1
"Coloured dim" is commit 93c3fac with ppy/osu-framework#6404 applied

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think these benchmark numbers represent the true impact because as far as I read you're measuring over a long period of time, while when the blur re-renders it's going to be very detrimental only to the local frames (so the overall "increase" is much smaller than the actual perceived difference to the user).

Of course, as long as the numbers are going down this doesn't matter so much, so carry on I guess.

A better way of benchmarking would be to force the redraw every frame then just take a reading of the average frame time.

Copy link
Author

@Uncomfy Uncomfy Nov 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've double checked, and without ppy/osu-framework#6404 my implementation triggered a blur redraw on every frame during transition, but didn't affect frames during gameplay or break.
This means that the stats computed in the "Fade in" and "Fade out" columns only take into account frames where redraw happened.

A better way of benchmarking would be to force the redraw every frame then just take a reading of the average frame time.

My goal was to show that extra redraws were triggered and that it affected the performance during specific parts of the beatmap, so I couldn't use this approach

}

protected override DrawNode CreateDrawNode() => new DimmableBufferedContainerDrawNode(this, SharedData);

protected class DimmableBufferedContainerDrawNode : BufferedContainerDrawNode
{
public new DimmableBufferedContainer Source => (DimmableBufferedContainer)base.Source;

public DimmableBufferedContainerDrawNode(DimmableBufferedContainer source, BufferedContainerDrawNodeSharedData sharedData)
: base(source, sharedData)
{
}

private Colour4 dimColour;

private float dimLevel;

public override void ApplyState()
{
base.ApplyState();

dimColour = Source.DimColour;
dimLevel = Source.DimLevel;
}

private IUniformBuffer<BeatmapBackgroundParameters> beatmapBackgroundParametersBuffer;

protected override void BindUniformResources(IShader shader, IRenderer renderer)
{
beatmapBackgroundParametersBuffer ??= renderer.CreateUniformBuffer<BeatmapBackgroundParameters>();

beatmapBackgroundParametersBuffer.Data = beatmapBackgroundParametersBuffer.Data with
{
DimColour = new UniformVector4
{
X = dimColour.R,
Y = dimColour.G,
Z = dimColour.B,
W = dimColour.A
},
DimLevel = dimLevel,
};

shader.BindUniformBlock("m_BeatmapBackgroundParameters", beatmapBackgroundParametersBuffer);
}

protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
beatmapBackgroundParametersBuffer?.Dispose();
}

[StructLayout(LayoutKind.Sequential, Pack = 1)]
private record struct BeatmapBackgroundParameters
{
public UniformVector4 DimColour;
public UniformFloat DimLevel;
private readonly UniformPadding12 pad1;
}
}
}

public partial class BeatmapBackgroundSprite : Sprite, IColouredDimmable
{
private Colour4 dimColour;

private float dimLevel;

public Colour4 DimColour
{
get => dimColour;
set
{
dimColour = value;
Invalidate(Invalidation.DrawNode);
}
}

public float DimLevel
{
get => dimLevel;
set
{
dimLevel = value;
Invalidate(Invalidation.DrawNode);
}
}

public BeatmapBackgroundSprite()
{
DimColour = Colour4.Black;
DimLevel = 0.0f;
}

[BackgroundDependencyLoader]
private void load(ShaderManager shaders)
{
TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, "BeatmapBackground");
}

protected override DrawNode CreateDrawNode() => new BeatmapBackgroundSpriteDrawNode(this);

public class BeatmapBackgroundSpriteDrawNode : SpriteDrawNode
{
public new BeatmapBackgroundSprite Source => (BeatmapBackgroundSprite)base.Source;

public BeatmapBackgroundSpriteDrawNode(BeatmapBackgroundSprite source)
: base(source)
{
}

private Colour4 dimColour;

private float dimLevel;

public override void ApplyState()
{
base.ApplyState();

dimColour = Source.DimColour;
dimLevel = Source.DimLevel;
}

private IUniformBuffer<BeatmapBackgroundParameters> beatmapBackgroundParametersBuffer;

protected override void BindUniformResources(IShader shader, IRenderer renderer)
{
beatmapBackgroundParametersBuffer ??= renderer.CreateUniformBuffer<BeatmapBackgroundParameters>();

beatmapBackgroundParametersBuffer.Data = beatmapBackgroundParametersBuffer.Data with
{
DimColour = new UniformVector4
{
X = dimColour.R,
Y = dimColour.G,
Z = dimColour.B,
W = dimColour.A
},
DimLevel = dimLevel,
};

shader.BindUniformBlock("m_BeatmapBackgroundParameters", beatmapBackgroundParametersBuffer);
}

protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
beatmapBackgroundParametersBuffer?.Dispose();
}

[StructLayout(LayoutKind.Sequential, Pack = 1)]
private record struct BeatmapBackgroundParameters
{
public UniformVector4 DimColour;
public UniformFloat DimLevel;
private readonly UniformPadding12 pad1;
}
}
}
}
}
2 changes: 1 addition & 1 deletion osu.Game/Graphics/Containers/UserDimContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public abstract partial class UserDimContainer : Container
/// <summary>
/// Whether the content of this container is currently being displayed.
/// </summary>
public bool ContentDisplayed { get; private set; }
public bool ContentDisplayed { get; protected set; }

protected Bindable<double> UserDimLevel { get; private set; } = null!;

Expand Down
5 changes: 5 additions & 0 deletions osu.Game/Localisation/GameplaySettingsStrings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ public static class GameplaySettingsStrings
/// </summary>
public static LocalisableString BackgroundBlur => new TranslatableString(getKey(@"blur"), @"Background blur");

/// <summary>
/// "Background dim colour"
/// </summary>
public static LocalisableString BackgroundDimColour => new TranslatableString(getKey(@"dim_colour"), @"Background dim colour");

/// <summary>
/// "Lighten playfield during breaks"
/// </summary>
Expand Down
Loading