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 System.Collections.Generic;
6using osu.Framework.Graphics.OpenGL.Textures;
7using osu.Framework.Graphics.Textures;
8using osuTK;
9using osuTK.Graphics.ES30;
10
11namespace osu.Framework.Graphics.OpenGL.Buffers
12{
13 public class FrameBuffer : IDisposable
14 {
15 private int frameBuffer = -1;
16
17 public TextureGL Texture { get; private set; }
18
19 private readonly List<RenderBuffer> attachedRenderBuffers = new List<RenderBuffer>();
20
21 private bool isInitialised;
22
23 private readonly All filteringMode;
24 private readonly RenderbufferInternalFormat[] renderBufferFormats;
25
26 public FrameBuffer(RenderbufferInternalFormat[] renderBufferFormats = null, All filteringMode = All.Linear)
27 {
28 this.renderBufferFormats = renderBufferFormats;
29 this.filteringMode = filteringMode;
30 }
31
32 private Vector2 size = Vector2.One;
33
34 /// <summary>
35 /// Sets the size of the texture of this frame buffer.
36 /// </summary>
37 public Vector2 Size
38 {
39 get => size;
40 set
41 {
42 if (value == size)
43 return;
44
45 size = value;
46
47 if (isInitialised)
48 {
49 Texture.Width = (int)Math.Ceiling(size.X);
50 Texture.Height = (int)Math.Ceiling(size.Y);
51
52 Texture.SetData(new TextureUpload());
53 Texture.Upload();
54 }
55 }
56 }
57
58 private void initialise()
59 {
60 frameBuffer = GL.GenFramebuffer();
61 Texture = new FrameBufferTexture(Size, filteringMode);
62
63 GLWrapper.BindFrameBuffer(frameBuffer);
64
65 GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, TextureTarget2d.Texture2D, Texture.TextureId, 0);
66 GLWrapper.BindTexture(null);
67
68 if (renderBufferFormats != null)
69 {
70 foreach (var format in renderBufferFormats)
71 attachedRenderBuffers.Add(new RenderBuffer(format));
72 }
73 }
74
75 /// <summary>
76 /// Binds the framebuffer.
77 /// <para>Does not clear the buffer or reset the viewport/ortho.</para>
78 /// </summary>
79 public void Bind()
80 {
81 if (!isInitialised)
82 {
83 initialise();
84 isInitialised = true;
85 }
86 else
87 {
88 // Buffer is bound during initialisation
89 GLWrapper.BindFrameBuffer(frameBuffer);
90 }
91
92 foreach (var buffer in attachedRenderBuffers)
93 buffer.Bind(Size);
94 }
95
96 /// <summary>
97 /// Unbinds the framebuffer.
98 /// </summary>
99 public void Unbind()
100 {
101 // See: https://community.arm.com/developer/tools-software/graphics/b/blog/posts/mali-performance-2-how-to-correctly-handle-framebuffers
102 // Unbinding renderbuffers causes an invalidation of the relevant attachment of this framebuffer on embedded devices, causing the renderbuffers to remain transient.
103 // This must be done _before_ the framebuffer is flushed via the framebuffer unbind process, otherwise the renderbuffer may be copied to system memory.
104 foreach (var buffer in attachedRenderBuffers)
105 buffer.Unbind();
106
107 GLWrapper.UnbindFrameBuffer(frameBuffer);
108 }
109
110 #region Disposal
111
112 ~FrameBuffer()
113 {
114 GLWrapper.ScheduleDisposal(() => Dispose(false));
115 }
116
117 public void Dispose()
118 {
119 Dispose(true);
120 GC.SuppressFinalize(this);
121 }
122
123 private bool isDisposed;
124
125 protected virtual void Dispose(bool disposing)
126 {
127 if (isDisposed)
128 return;
129
130 if (isInitialised)
131 {
132 Texture?.Dispose();
133 Texture = null;
134
135 GLWrapper.DeleteFrameBuffer(frameBuffer);
136
137 foreach (var buffer in attachedRenderBuffers)
138 buffer.Dispose();
139 }
140
141 isDisposed = true;
142 }
143
144 #endregion
145
146 private class FrameBufferTexture : TextureGLSingle
147 {
148 public FrameBufferTexture(Vector2 size, All filteringMode = All.Linear)
149 : base((int)Math.Ceiling(size.X), (int)Math.Ceiling(size.Y), true, filteringMode)
150 {
151 BypassTextureUploadQueueing = true;
152
153 SetData(new TextureUpload());
154 Upload();
155 }
156
157 public override int Width
158 {
159 get => base.Width;
160 set => base.Width = Math.Clamp(value, 1, GLWrapper.MaxTextureSize);
161 }
162
163 public override int Height
164 {
165 get => base.Height;
166 set => base.Height = Math.Clamp(value, 1, GLWrapper.MaxTextureSize);
167 }
168 }
169 }
170}