// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Allocation; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.OpenGL; using osu.Framework.Graphics.OpenGL.Vertices; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shaders; namespace osu.Framework.Graphics.Sprites { /// /// A view that displays the contents of a . /// public class BufferedContainerView : Drawable, ITexturedShaderDrawable where T : Drawable { public IShader TextureShader { get; private set; } public IShader RoundedTextureShader { get; private set; } private BufferedContainer container; private BufferedDrawNodeSharedData sharedData; internal BufferedContainerView(BufferedContainer container, BufferedDrawNodeSharedData sharedData) { this.container = container; this.sharedData = sharedData; container.OnDispose += removeContainer; } [BackgroundDependencyLoader] private void load(ShaderManager shaders) { TextureShader = shaders?.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE); RoundedTextureShader = shaders?.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED); } protected override DrawNode CreateDrawNode() => new BufferSpriteDrawNode(this); private bool synchronisedDrawQuad; /// /// Whether this should be drawn using the original 's draw quad. /// /// /// This can be useful to display the as an overlay on top of itself. /// public bool SynchronisedDrawQuad { get => synchronisedDrawQuad; set { if (value == synchronisedDrawQuad) return; synchronisedDrawQuad = value; Invalidate(Invalidation.DrawNode); } } private bool displayOriginalEffects; /// /// Whether the effects drawn by the should also be drawn for this view. /// public bool DisplayOriginalEffects { get => displayOriginalEffects; set { if (displayOriginalEffects == value) return; displayOriginalEffects = value; Invalidate(Invalidation.DrawNode); } } private void removeContainer() { if (container == null) return; container.OnDispose -= removeContainer; container = null; sharedData = null; Invalidate(Invalidation.DrawNode); } protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); removeContainer(); } private class BufferSpriteDrawNode : TexturedShaderDrawNode { protected new BufferedContainerView Source => (BufferedContainerView)base.Source; private Quad screenSpaceDrawQuad; private BufferedDrawNodeSharedData shared; private bool displayOriginalEffects; private bool sourceDrawsOriginal; private ColourInfo sourceEffectColour; private BlendingParameters sourceEffectBlending; private EffectPlacement sourceEffectPlacement; public BufferSpriteDrawNode(BufferedContainerView source) : base(source) { } public override void ApplyState() { base.ApplyState(); screenSpaceDrawQuad = Source.synchronisedDrawQuad ? Source.container.ScreenSpaceDrawQuad : Source.ScreenSpaceDrawQuad; shared = Source.sharedData; displayOriginalEffects = Source.displayOriginalEffects; sourceDrawsOriginal = Source.container.DrawOriginal; sourceEffectColour = Source.container.EffectColour; sourceEffectBlending = Source.container.DrawEffectBlending; sourceEffectPlacement = Source.container.EffectPlacement; } public override void Draw(Action vertexAction) { base.Draw(vertexAction); if (shared?.MainBuffer?.Texture?.Available != true || shared.DrawVersion == -1) return; Shader.Bind(); if (sourceEffectPlacement == EffectPlacement.InFront) drawMainBuffer(vertexAction); drawEffectBuffer(vertexAction); if (sourceEffectPlacement == EffectPlacement.Behind) drawMainBuffer(vertexAction); Shader.Unbind(); } private void drawMainBuffer(Action vertexAction) { // If the original was drawn, draw it. // Otherwise, if an effect will also not be drawn then we still need to display something - the original. // Keep in mind that the effect MAY be the original itself, but is drawn through drawEffectBuffer(). if (!sourceDrawsOriginal && shouldDrawEffectBuffer) return; GLWrapper.SetBlend(DrawColourInfo.Blending); DrawFrameBuffer(shared.MainBuffer, screenSpaceDrawQuad, DrawColourInfo.Colour, vertexAction); } private void drawEffectBuffer(Action vertexAction) { if (!shouldDrawEffectBuffer) return; GLWrapper.SetBlend(sourceEffectBlending); ColourInfo finalEffectColour = DrawColourInfo.Colour; finalEffectColour.ApplyChild(sourceEffectColour); DrawFrameBuffer(shared.CurrentEffectBuffer, screenSpaceDrawQuad, DrawColourInfo.Colour, vertexAction); } /// /// Whether the source's current effect buffer should be drawn. /// 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, /// the current effect buffer will be the main buffer, and what will be drawn is the main buffer with the effect blending applied. /// private bool shouldDrawEffectBuffer => displayOriginalEffects || shared.CurrentEffectBuffer == shared.MainBuffer; } } }