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 System;
5using osu.Framework.Allocation;
6using osu.Framework.Graphics.Colour;
7using osu.Framework.Graphics.Containers;
8using osu.Framework.Graphics.OpenGL;
9using osu.Framework.Graphics.OpenGL.Vertices;
10using osu.Framework.Graphics.Primitives;
11using osu.Framework.Graphics.Shaders;
12
13namespace osu.Framework.Graphics.Sprites
14{
15 /// <summary>
16 /// A view that displays the contents of a <see cref="BufferedContainer{T}"/>.
17 /// </summary>
18 public class BufferedContainerView<T> : Drawable, ITexturedShaderDrawable
19 where T : Drawable
20 {
21 public IShader TextureShader { get; private set; }
22 public IShader RoundedTextureShader { get; private set; }
23
24 private BufferedContainer<T> container;
25 private BufferedDrawNodeSharedData sharedData;
26
27 internal BufferedContainerView(BufferedContainer<T> container, BufferedDrawNodeSharedData sharedData)
28 {
29 this.container = container;
30 this.sharedData = sharedData;
31
32 container.OnDispose += removeContainer;
33 }
34
35 [BackgroundDependencyLoader]
36 private void load(ShaderManager shaders)
37 {
38 TextureShader = shaders?.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE);
39 RoundedTextureShader = shaders?.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED);
40 }
41
42 protected override DrawNode CreateDrawNode() => new BufferSpriteDrawNode(this);
43
44 private bool synchronisedDrawQuad;
45
46 /// <summary>
47 /// Whether this <see cref="BufferedContainerView{T}"/> should be drawn using the original <see cref="BufferedContainer{T}"/>'s draw quad.
48 /// </summary>
49 /// <remarks>
50 /// This can be useful to display the <see cref="BufferedContainer{T}"/> as an overlay on top of itself.
51 /// </remarks>
52 public bool SynchronisedDrawQuad
53 {
54 get => synchronisedDrawQuad;
55 set
56 {
57 if (value == synchronisedDrawQuad)
58 return;
59
60 synchronisedDrawQuad = value;
61
62 Invalidate(Invalidation.DrawNode);
63 }
64 }
65
66 private bool displayOriginalEffects;
67
68 /// <summary>
69 /// Whether the effects drawn by the <see cref="BufferedContainer{T}"/> should also be drawn for this view.
70 /// </summary>
71 public bool DisplayOriginalEffects
72 {
73 get => displayOriginalEffects;
74 set
75 {
76 if (displayOriginalEffects == value)
77 return;
78
79 displayOriginalEffects = value;
80
81 Invalidate(Invalidation.DrawNode);
82 }
83 }
84
85 private void removeContainer()
86 {
87 if (container == null)
88 return;
89
90 container.OnDispose -= removeContainer;
91
92 container = null;
93 sharedData = null;
94
95 Invalidate(Invalidation.DrawNode);
96 }
97
98 protected override void Dispose(bool isDisposing)
99 {
100 base.Dispose(isDisposing);
101
102 removeContainer();
103 }
104
105 private class BufferSpriteDrawNode : TexturedShaderDrawNode
106 {
107 protected new BufferedContainerView<T> Source => (BufferedContainerView<T>)base.Source;
108
109 private Quad screenSpaceDrawQuad;
110 private BufferedDrawNodeSharedData shared;
111 private bool displayOriginalEffects;
112
113 private bool sourceDrawsOriginal;
114 private ColourInfo sourceEffectColour;
115 private BlendingParameters sourceEffectBlending;
116 private EffectPlacement sourceEffectPlacement;
117
118 public BufferSpriteDrawNode(BufferedContainerView<T> source)
119 : base(source)
120 {
121 }
122
123 public override void ApplyState()
124 {
125 base.ApplyState();
126
127 screenSpaceDrawQuad = Source.synchronisedDrawQuad ? Source.container.ScreenSpaceDrawQuad : Source.ScreenSpaceDrawQuad;
128 shared = Source.sharedData;
129
130 displayOriginalEffects = Source.displayOriginalEffects;
131 sourceDrawsOriginal = Source.container.DrawOriginal;
132 sourceEffectColour = Source.container.EffectColour;
133 sourceEffectBlending = Source.container.DrawEffectBlending;
134 sourceEffectPlacement = Source.container.EffectPlacement;
135 }
136
137 public override void Draw(Action<TexturedVertex2D> vertexAction)
138 {
139 base.Draw(vertexAction);
140
141 if (shared?.MainBuffer?.Texture?.Available != true || shared.DrawVersion == -1)
142 return;
143
144 Shader.Bind();
145
146 if (sourceEffectPlacement == EffectPlacement.InFront)
147 drawMainBuffer(vertexAction);
148
149 drawEffectBuffer(vertexAction);
150
151 if (sourceEffectPlacement == EffectPlacement.Behind)
152 drawMainBuffer(vertexAction);
153
154 Shader.Unbind();
155 }
156
157 private void drawMainBuffer(Action<TexturedVertex2D> vertexAction)
158 {
159 // If the original was drawn, draw it.
160 // Otherwise, if an effect will also not be drawn then we still need to display something - the original.
161 // Keep in mind that the effect MAY be the original itself, but is drawn through drawEffectBuffer().
162 if (!sourceDrawsOriginal && shouldDrawEffectBuffer)
163 return;
164
165 GLWrapper.SetBlend(DrawColourInfo.Blending);
166 DrawFrameBuffer(shared.MainBuffer, screenSpaceDrawQuad, DrawColourInfo.Colour, vertexAction);
167 }
168
169 private void drawEffectBuffer(Action<TexturedVertex2D> vertexAction)
170 {
171 if (!shouldDrawEffectBuffer)
172 return;
173
174 GLWrapper.SetBlend(sourceEffectBlending);
175 ColourInfo finalEffectColour = DrawColourInfo.Colour;
176 finalEffectColour.ApplyChild(sourceEffectColour);
177
178 DrawFrameBuffer(shared.CurrentEffectBuffer, screenSpaceDrawQuad, DrawColourInfo.Colour, vertexAction);
179 }
180
181 /// <summary>
182 /// Whether the source's current effect buffer should be drawn.
183 /// This is true if we explicitly want to draw it or if no effects were drawn by the source. In the case that no effects were drawn by the source,
184 /// the current effect buffer will be the main buffer, and what will be drawn is the main buffer with the effect blending applied.
185 /// </summary>
186 private bool shouldDrawEffectBuffer => displayOriginalEffects || shared.CurrentEffectBuffer == shared.MainBuffer;
187 }
188 }
189}