A game framework written with osu! in mind.
1// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
2// See the LICENCE file in the repository root for full licence text.
3
4using osuTK;
5using osuTK.Graphics;
6using osuTK.Graphics.ES30;
7using osu.Framework.Allocation;
8using osu.Framework.Graphics.Colour;
9using osu.Framework.Graphics.Primitives;
10using osu.Framework.Graphics.Shaders;
11using osu.Framework.Utils;
12using osu.Framework.Graphics.Sprites;
13using osu.Framework.Layout;
14
15namespace osu.Framework.Graphics.Containers
16{
17 /// <summary>
18 /// A container that renders its children to an internal framebuffer, and then
19 /// blits the framebuffer to the screen, instead of directly rendering the children
20 /// to the screen. This allows otherwise impossible effects to be applied to the
21 /// appearance of the container at the cost of performance. Such effects include
22 /// uniform fading of children, blur, and other post-processing effects.
23 /// If all children are of a specific non-<see cref="Drawable"/> type, use the
24 /// generic version <see cref="BufferedContainer{T}"/>.
25 /// </summary>
26 public class BufferedContainer : BufferedContainer<Drawable>
27 {
28 /// <inheritdoc />
29 public BufferedContainer(RenderbufferInternalFormat[] formats = null, bool pixelSnapping = false)
30 : base(formats, pixelSnapping)
31 {
32 }
33 }
34
35 /// <summary>
36 /// A container that renders its children to an internal framebuffer, and then
37 /// blits the framebuffer to the screen, instead of directly rendering the children
38 /// to the screen. This allows otherwise impossible effects to be applied to the
39 /// appearance of the container at the cost of performance. Such effects include
40 /// uniform fading of children, blur, and other post-processing effects.
41 /// </summary>
42 public partial class BufferedContainer<T> : Container<T>, IBufferedContainer, IBufferedDrawable
43 where T : Drawable
44 {
45 private bool drawOriginal;
46
47 /// <summary>
48 /// If true the original buffered children will be drawn a second time on top of any effect (e.g. blur).
49 /// </summary>
50 public bool DrawOriginal
51 {
52 get => drawOriginal;
53 set
54 {
55 if (drawOriginal == value)
56 return;
57
58 drawOriginal = value;
59 ForceRedraw();
60 }
61 }
62
63 private Vector2 blurSigma = Vector2.Zero;
64
65 /// <summary>
66 /// Controls the amount of blurring in two orthogonal directions (X and Y if
67 /// <see cref="BlurRotation"/> is zero).
68 /// Blur is parametrized by a gaussian image filter. This property controls
69 /// the standard deviation (sigma) of the gaussian kernel.
70 /// </summary>
71 public Vector2 BlurSigma
72 {
73 get => blurSigma;
74 set
75 {
76 if (blurSigma == value)
77 return;
78
79 blurSigma = value;
80 ForceRedraw();
81 }
82 }
83
84 private float blurRotation;
85
86 /// <summary>
87 /// Rotates the blur kernel clockwise. In degrees. Has no effect if
88 /// <see cref="BlurSigma"/> has the same magnitude in both directions.
89 /// </summary>
90 public float BlurRotation
91 {
92 get => blurRotation;
93 set
94 {
95 if (blurRotation == value)
96 return;
97
98 blurRotation = value;
99 ForceRedraw();
100 }
101 }
102
103 private ColourInfo effectColour = Color4.White;
104
105 /// <summary>
106 /// The multiplicative colour of drawn buffered object after applying all effects (e.g. blur). Default is <see cref="Color4.White"/>.
107 /// Does not affect the original which is drawn when <see cref="DrawOriginal"/> is true.
108 /// </summary>
109 public ColourInfo EffectColour
110 {
111 get => effectColour;
112 set
113 {
114 if (effectColour.Equals(value))
115 return;
116
117 effectColour = value;
118 Invalidate(Invalidation.DrawNode);
119 }
120 }
121
122 private BlendingParameters effectBlending = BlendingParameters.Inherit;
123
124 /// <summary>
125 /// The <see cref="BlendingParameters"/> to use after applying all effects. Default is <see cref="BlendingType.Inherit"/>.
126 /// <see cref="BlendingType.Inherit"/> inherits the blending mode of the original, i.e. <see cref="Drawable.Blending"/> is used.
127 /// Does not affect the original which is drawn when <see cref="DrawOriginal"/> is true.
128 /// </summary>
129 public BlendingParameters EffectBlending
130 {
131 get => effectBlending;
132 set
133 {
134 if (effectBlending == value)
135 return;
136
137 effectBlending = value;
138 Invalidate(Invalidation.DrawNode);
139 }
140 }
141
142 private EffectPlacement effectPlacement;
143
144 /// <summary>
145 /// Whether the buffered effect should be drawn behind or in front of the original.
146 /// Behind by default. Does not have any effect if <see cref="DrawOriginal"/> is false.
147 /// </summary>
148 public EffectPlacement EffectPlacement
149 {
150 get => effectPlacement;
151 set
152 {
153 if (effectPlacement == value)
154 return;
155
156 effectPlacement = value;
157 Invalidate(Invalidation.DrawNode);
158 }
159 }
160
161 private Color4 backgroundColour = new Color4(0, 0, 0, 0);
162
163 /// <summary>
164 /// The background colour of the framebuffer. Transparent black by default.
165 /// </summary>
166 public Color4 BackgroundColour
167 {
168 get => backgroundColour;
169 set
170 {
171 if (backgroundColour == value)
172 return;
173
174 backgroundColour = value;
175 ForceRedraw();
176 }
177 }
178
179 private Vector2 frameBufferScale = Vector2.One;
180
181 public Vector2 FrameBufferScale
182 {
183 get => frameBufferScale;
184 set
185 {
186 if (frameBufferScale == value)
187 return;
188
189 frameBufferScale = value;
190 ForceRedraw();
191 }
192 }
193
194 /// <summary>
195 /// Whether the rendered framebuffer shall be cached until <see cref="ForceRedraw"/> is called
196 /// or the size of the container (i.e. framebuffer) changes.
197 /// If false, then the framebuffer is re-rendered before it is blitted to the screen; equivalent
198 /// to calling <see cref="ForceRedraw"/> every frame.
199 /// </summary>
200 public bool CacheDrawnFrameBuffer;
201
202 private bool redrawOnScale = true;
203
204 /// <summary>
205 /// Whether to redraw this <see cref="BufferedContainer"/> when the draw scale changes.
206 /// </summary>
207 public bool RedrawOnScale
208 {
209 get => redrawOnScale;
210 set
211 {
212 if (redrawOnScale == value)
213 return;
214
215 redrawOnScale = value;
216 screenSpaceSizeBacking?.Invalidate();
217 }
218 }
219
220 /// <summary>
221 /// Forces a redraw of the framebuffer before it is blitted the next time.
222 /// Only relevant if <see cref="CacheDrawnFrameBuffer"/> is true.
223 /// </summary>
224 public void ForceRedraw() => Invalidate(Invalidation.DrawNode);
225
226 /// <summary>
227 /// In order to signal the draw thread to re-draw the buffered container we version it.
228 /// Our own version (update) keeps track of which version we are on, whereas the
229 /// drawVersion keeps track of the version the draw thread is on.
230 /// When forcing a redraw we increment updateVersion, pass it into each new drawnode
231 /// and the draw thread will realize its drawVersion is lagging behind, thus redrawing.
232 /// </summary>
233 private long updateVersion;
234
235 public IShader TextureShader { get; private set; }
236
237 public IShader RoundedTextureShader { get; private set; }
238
239 private IShader blurShader;
240
241 private readonly BufferedContainerDrawNodeSharedData sharedData;
242
243 /// <summary>
244 /// Constructs an empty buffered container.
245 /// </summary>
246 /// <param name="formats">The render buffer formats attached to the frame buffers of this <see cref="BufferedContainer"/>.</param>
247 /// <param name="pixelSnapping">Whether the frame buffer position should be snapped to the nearest pixel when blitting.
248 /// This amounts to setting the texture filtering mode to "nearest".</param>
249 public BufferedContainer(RenderbufferInternalFormat[] formats = null, bool pixelSnapping = false)
250 {
251 sharedData = new BufferedContainerDrawNodeSharedData(formats, pixelSnapping);
252
253 AddLayout(screenSpaceSizeBacking);
254 }
255
256 [BackgroundDependencyLoader]
257 private void load(ShaderManager shaders)
258 {
259 TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE);
260 RoundedTextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED);
261 blurShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.BLUR);
262 }
263
264 protected override DrawNode CreateDrawNode() => new BufferedContainerDrawNode(this, sharedData);
265
266 public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds)
267 {
268 var result = base.UpdateSubTreeMasking(source, maskingBounds);
269
270 childrenUpdateVersion = updateVersion;
271
272 return result;
273 }
274
275 protected override RectangleF ComputeChildMaskingBounds(RectangleF maskingBounds) => ScreenSpaceDrawQuad.AABBFloat; // Make sure children never get masked away
276
277 private Vector2 lastScreenSpaceSize;
278
279 // We actually only care about Invalidation.MiscGeometry | Invalidation.DrawInfo
280 private readonly LayoutValue screenSpaceSizeBacking = new LayoutValue(Invalidation.Presence | Invalidation.RequiredParentSizeToFit | Invalidation.DrawInfo);
281
282 protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source)
283 {
284 var result = base.OnInvalidate(invalidation, source);
285
286 if ((invalidation & Invalidation.DrawNode) > 0)
287 {
288 ++updateVersion;
289 result = true;
290 }
291
292 return result;
293 }
294
295 private long childrenUpdateVersion = -1;
296 protected override bool RequiresChildrenUpdate => base.RequiresChildrenUpdate && childrenUpdateVersion != updateVersion;
297
298 protected override void Update()
299 {
300 base.Update();
301
302 // Invalidate drawn frame buffer every frame.
303 if (!CacheDrawnFrameBuffer)
304 ForceRedraw();
305 else if (!screenSpaceSizeBacking.IsValid)
306 {
307 Vector2 drawSize = ScreenSpaceDrawQuad.AABBFloat.Size;
308
309 if (!RedrawOnScale)
310 {
311 Matrix3 scaleMatrix = Matrix3.CreateScale(DrawInfo.MatrixInverse.ExtractScale());
312 Vector2Extensions.Transform(ref drawSize, ref scaleMatrix, out drawSize);
313 }
314
315 if (!Precision.AlmostEquals(lastScreenSpaceSize, drawSize))
316 {
317 ++updateVersion;
318 lastScreenSpaceSize = drawSize;
319 }
320
321 screenSpaceSizeBacking.Validate();
322 }
323 }
324
325 /// <summary>
326 /// The blending which <see cref="BufferedContainerDrawNode"/> uses for the effect.
327 /// </summary>
328 public BlendingParameters DrawEffectBlending
329 {
330 get
331 {
332 BlendingParameters blending = EffectBlending;
333
334 blending.CopyFromParent(Blending);
335 blending.ApplyDefaultToInherited();
336
337 return blending;
338 }
339 }
340
341 /// <summary>
342 /// Creates a view which can be added to a container to display the content of this <see cref="BufferedContainer{T}"/>.
343 /// </summary>
344 /// <returns>The view.</returns>
345 public BufferedContainerView<T> CreateView() => new BufferedContainerView<T>(this, sharedData);
346
347 public DrawColourInfo? FrameBufferDrawColour => base.DrawColourInfo;
348
349 // Children should not receive the true colour to avoid colour doubling when the frame-buffers are rendered to the back-buffer.
350 public override DrawColourInfo DrawColourInfo
351 {
352 get
353 {
354 // Todo: This is incorrect.
355 var blending = Blending;
356 blending.ApplyDefaultToInherited();
357
358 return new DrawColourInfo(Color4.White, blending);
359 }
360 }
361
362 protected override void Dispose(bool isDisposing)
363 {
364 base.Dispose(isDisposing);
365
366 sharedData.Dispose();
367 }
368 }
369
370 public enum EffectPlacement
371 {
372 Behind,
373 InFront,
374 }
375}