A game framework written with osu! in mind.
at master 375 lines 13 kB view raw
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}