1 module prova.graphics.rendertarget;
2 
3 import prova.assets;
4 import prova.attachables;
5 import prova.graphics;
6 import prova.math;
7 import std.typecons;
8 
9 ///
10 class RenderTarget
11 {
12   private static uint currentFrameBuffer = -1;
13 
14   ///
15   SpriteBatch spriteBatch;
16   ///
17   protected Matrix projection;
18 
19   private GraphicsContext _context;
20   private uint renderBufferId;
21   private uint _frameBufferId;
22   private Texture _texture;
23   private int _width;
24   private int _height;
25   private bool begun;
26 
27   ///
28   this(GraphicsContext context, int width, int height)
29   {
30     _context = context;
31     spriteBatch = new SpriteBatch(context);
32     _texture = new Texture(width, height);
33     _width = width;
34     _height = height;
35 
36     createFrameBuffer();
37     createRenderBuffer();
38   }
39 
40   private void createFrameBuffer()
41   {
42     glGenFramebuffers(1, &_frameBufferId);
43     bindFrameBuffer();
44 
45     // attach texture to the frame buffer
46     glFramebufferTexture2D(
47       GL_FRAMEBUFFER,
48       GL_COLOR_ATTACHMENT0,
49       GL_TEXTURE_2D,
50       _texture.id,
51       0
52     );
53 
54     glDepthFunc(GL_LEQUAL);
55     glEnable(GL_BLEND);
56     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
57     glViewport(0, 0, width, height);
58   }
59 
60   private void createRenderBuffer()
61   {
62     glGenRenderbuffers(1, &renderBufferId);
63     glBindRenderbuffer(GL_RENDERBUFFER, renderBufferId);
64     glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, width, height);
65 
66     bindFrameBuffer();
67 
68     glFramebufferRenderbuffer(
69       GL_FRAMEBUFFER,
70       GL_DEPTH_ATTACHMENT,
71       GL_RENDERBUFFER,
72       renderBufferId
73     );
74   }
75 
76   ///
77   @property GraphicsContext context()
78   {
79     return _context;
80   }
81 
82   ///
83   void resize(int width, int height)
84   {
85     _width = width;
86     _height = height;
87 
88     texture.recreate(null, width, height);
89 
90     glBindRenderbuffer(GL_RENDERBUFFER, renderBufferId);
91     glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, width, height);
92 
93     // update settings
94     glViewport(0, 0, width, height);
95   }
96 
97   ///
98   @property int width()
99   {
100     return _width;
101   }
102 
103   ///
104   @property int height()
105   {
106     return _height;
107   }
108 
109   ///
110   @property Texture texture()
111   {
112     return _texture;
113   }
114 
115   ///
116   @property uint frameBufferId()
117   {
118     return _frameBufferId;
119   }
120 
121   package(prova) void bindFrameBuffer()
122   {
123     bindFrameBuffer(_frameBufferId);
124   }
125 
126   /// Updates projection and prepares batches
127   void begin(Matrix projection)
128   {
129     assert(!begun, "RenderTarget should not already be started");
130 
131     spriteBatch.begin(this, projection);
132 
133     this.projection = projection;
134     begun = true;
135   }
136 
137   /// Finishes the batch
138   void end()
139   {
140     assert(begun, "RenderTarget not ready, call begin(Matrix projection)");
141 
142     spriteBatch.end();
143     begun = false;
144   }
145 
146   /// Draws mesh using a flat shader
147   void drawMesh(Mesh mesh, Matrix transform, DrawMode mode, Color color)
148   {
149     assert(begun, "RenderTarget not ready, call begin(Matrix projection)");
150 
151     context.flatShader.setMatrix("transform", projection * transform);
152     context.flatShader.setVector4("color", color);
153     context.flatShader.drawMesh(mesh, this, mode);
154   }
155 
156   ///
157   void drawSprite(Sprite sprite, Matrix transform)
158   {
159     spriteBatch.batchSprite(sprite, transform);
160   }
161 
162   ///
163   void drawSprite(AnimatedSprite sprite, Matrix transform)
164   {
165     spriteBatch.batchSprite(sprite, transform);
166   }
167 
168   ///
169   void clear()
170   {
171     bindFrameBuffer();
172 
173     glClearDepth(1);
174     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
175   }
176 
177   package(prova) static void bindFrameBuffer(uint id)
178   {
179     if(id == currentFrameBuffer)
180       return;
181 
182     glBindFramebuffer(GL_FRAMEBUFFER, id);
183     currentFrameBuffer = id;
184   }
185 
186   ~this()
187   {
188     glDeleteFramebuffers(1, &_frameBufferId);
189     glDeleteRenderbuffers(1, &renderBufferId);
190   }
191 }