Project

General

Profile

Download (17.1 KB) Statistics
| Branch: | Revision:

library / src / main / java / org / distorted / library / DistortedFramebuffer.java @ 8337fa41

1
///////////////////////////////////////////////////////////////////////////////////////////////////
2
// Copyright 2016 Leszek Koltunski                                                               //
3
//                                                                                               //
4
// This file is part of Distorted.                                                               //
5
//                                                                                               //
6
// Distorted is free software: you can redistribute it and/or modify                             //
7
// it under the terms of the GNU General Public License as published by                          //
8
// the Free Software Foundation, either version 2 of the License, or                             //
9
// (at your option) any later version.                                                           //
10
//                                                                                               //
11
// Distorted is distributed in the hope that it will be useful,                                  //
12
// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
13
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
14
// GNU General Public License for more details.                                                  //
15
//                                                                                               //
16
// You should have received a copy of the GNU General Public License                             //
17
// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
18
///////////////////////////////////////////////////////////////////////////////////////////////////
19

    
20
package org.distorted.library;
21

    
22
import android.opengl.GLES20;
23
import android.opengl.Matrix;
24

    
25
import java.util.Iterator;
26
import java.util.LinkedList;
27

    
28
///////////////////////////////////////////////////////////////////////////////////////////////////
29
/**
30
 * Class which represents a OpenGL Framebuffer object.
31
 * <p>
32
 * User is able to create either Framebuffers from objects already constructed outside
33
 * of the library (the first constructor; primary use case: the screen) or an offscreen
34
 * FBOs.
35
 * <p>
36
 * Keep all objects created in a static LinkedList. The point: we need to be able to mark
37
 * Framebuffers for deletion, and delete all marked objects later at a convenient time (that's
38
 * because we can only delete from a thread that holds the OpenGL context so here we provide a
39
 * framework where one is able to mark for deletion at any time and actual deletion takes place
40
 * on the next render).
41
 */
42
public class DistortedFramebuffer
43
  {
44
  private static final int FAILED_TO_CREATE = -1;
45
  private static final int NOT_CREATED_YET  = -2;
46
  private static final int DONT_CREATE      = -3;
47

    
48
  private static boolean mListMarked = false;
49
  private static LinkedList<DistortedFramebuffer> mList = new LinkedList<>();
50

    
51
  private int[] colorIds = new int[1];
52
  private int[] depthIds = new int[1];
53
  private int[] fboIds   = new int[1];
54

    
55
  private boolean mMarked;
56
  private boolean mDepthWanted;
57

    
58
  // Projection stuff
59
  private float mX, mY, mFOV;
60
  int mWidth,mHeight,mDepth;
61
  float mDistance;
62
  float[] mProjectionMatrix;
63

    
64
///////////////////////////////////////////////////////////////////////////////////////////////////
65
// Must be called from a thread holding OpenGL Context
66

    
67
  void createFBO()
68
    {
69
    if( colorIds[0]==NOT_CREATED_YET )
70
      {
71
      GLES20.glGenTextures(1, colorIds, 0);
72
      GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, colorIds[0]);
73
      GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT);
74
      GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT);
75
      GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
76
      GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
77
      GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, mWidth, mHeight, 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
78

    
79
      GLES20.glGenFramebuffers(1, fboIds, 0);
80
      GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fboIds[0]);
81
      GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, colorIds[0], 0);
82

    
83
      checkStatus("color");
84

    
85
      mList.add(this);
86
      }
87

    
88
    if(  mDepthWanted && depthIds[0]==NOT_CREATED_YET ) // we need to create a new DEPTH attachment
89
      {
90
      GLES20.glGenTextures(1, depthIds, 0);
91
      GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, depthIds[0]);
92
      GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT);
93
      GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT);
94
      GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
95
      GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);
96
      GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_DEPTH_COMPONENT, mWidth, mHeight, 0, GLES20.GL_DEPTH_COMPONENT, GLES20.GL_UNSIGNED_SHORT, null);
97

    
98
      GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fboIds[0]);
99
      GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_DEPTH_ATTACHMENT, GLES20.GL_TEXTURE_2D, depthIds[0], 0);
100

    
101
      checkStatus("depth");
102
      }
103
    if( !mDepthWanted && depthIds[0]!=NOT_CREATED_YET ) // we need to detach and destroy the DEPTH attachment.
104
      {
105
      GLES20.glDeleteTextures(1, depthIds, 0);
106
      depthIds[0]=NOT_CREATED_YET;
107
      }
108
    }
109

    
110
///////////////////////////////////////////////////////////////////////////////////////////////////
111

    
112
  private boolean checkStatus(String message)
113
    {
114
    int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER);
115

    
116
    if(status != GLES20.GL_FRAMEBUFFER_COMPLETE)
117
      {
118
      android.util.Log.e("DistortedFramebuffer", "FRAMEBUFFER INCOMPLETE, "+message+" error="+status);
119

    
120
      GLES20.glDeleteTextures(1, colorIds, 0);
121
      GLES20.glDeleteTextures(1, depthIds, 0);
122
      GLES20.glDeleteFramebuffers(1, fboIds, 0);
123
      fboIds[0]   = 0;
124
      colorIds[0] = FAILED_TO_CREATE;
125
      depthIds[0] = FAILED_TO_CREATE;
126

    
127
      return false;
128
      }
129

    
130
    return true;
131
    }
132

    
133
///////////////////////////////////////////////////////////////////////////////////////////////////
134
// Must be called from a thread holding OpenGL Context
135

    
136
  private void deleteFBO()
137
    {
138
    if( colorIds[0]>=0 )
139
      {
140
      //android.util.Log.e("FBO", "deleting ("+mWidth+","+mHeight+") "+fboIds[0]);
141

    
142
      if( depthIds[0]>=0 )
143
        {
144
        GLES20.glDeleteTextures(1, depthIds, 0);
145
        depthIds[0]=NOT_CREATED_YET;
146
        }
147

    
148
      GLES20.glDeleteTextures(1, colorIds, 0);
149
      colorIds[0] = NOT_CREATED_YET;
150

    
151
      GLES20.glDeleteFramebuffers(1, fboIds, 0);
152
      fboIds[0] = 0;
153
      }
154

    
155
    mMarked = false;
156
    }
157

    
158
///////////////////////////////////////////////////////////////////////////////////////////////////
159

    
160
  void reset()
161
    {
162
    if( colorIds[0]!=DONT_CREATE )
163
      colorIds[0] = NOT_CREATED_YET;
164

    
165
    if( mDepthWanted ) depthIds[0] = NOT_CREATED_YET;
166
    }
167

    
168
///////////////////////////////////////////////////////////////////////////////////////////////////
169

    
170
  void setAsOutput()
171
    {
172
    GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fboIds[0]);
173

    
174
    if( depthIds[0]!=NOT_CREATED_YET )
175
      {
176
      GLES20.glEnable(GLES20.GL_DEPTH_TEST);
177
      GLES20.glDepthMask(true);
178
      }
179
    else
180
      {
181
      GLES20.glDisable(GLES20.GL_DEPTH_TEST);
182
      GLES20.glDepthMask(false);
183
      }
184
    }
185

    
186
///////////////////////////////////////////////////////////////////////////////////////////////////
187

    
188
  void setAsInput()
189
    {
190
    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, colorIds[0]);
191
    }
192

    
193
///////////////////////////////////////////////////////////////////////////////////////////////////
194

    
195
  private void createProjection()
196
    {
197
    if( mWidth>0 && mHeight>1 )
198
      {
199
      if( mFOV>0.0f )  // perspective projection
200
        {
201
        float left   = (-mX-mWidth /2.0f)/mHeight;
202
        float right  = (-mX+mWidth /2.0f)/mHeight;
203
        float bottom = (-mY-mHeight/2.0f)/mHeight;
204
        float top    = (-mY+mHeight/2.0f)/mHeight;
205
        float near   = (top-bottom) / (2.0f*(float)Math.tan(mFOV*Math.PI/360));
206
        mDistance    = mHeight*near/(top-bottom);
207
        float far    = 2*mDistance-near;
208
        mDepth       = (int)((far-near)/2);
209

    
210
        Matrix.frustumM(mProjectionMatrix, 0, left, right, bottom, top, near, far);
211
        }
212
      else             // parallel projection
213
        {
214
        float left   = -mX-mWidth /2.0f;
215
        float right  = -mX+mWidth /2.0f;
216
        float bottom = -mY-mHeight/2.0f;
217
        float top    = -mY+mHeight/2.0f;
218
        float near   = (mWidth+mHeight)/2;
219
        mDistance    = 2*near;
220
        float far    = 3*near;
221
        mDepth       = (int)near;
222

    
223
        Matrix.orthoM(mProjectionMatrix, 0, left, right, bottom, top, near, far);
224
        }
225
      }
226
    }
227

    
228
///////////////////////////////////////////////////////////////////////////////////////////////////
229

    
230
  static synchronized void onDestroy()
231
    {
232
    // There are issues with this. Namely, if one
233
    // 1. creates a DObjectTree (somewhere else than onSurfaceCreated of constructor so it does not get re-created on re-launch)
234
    // 2. exits the app (here mList would be cleared)
235
    // 3. re-launches the app
236
    // 4. deletes some nodes
237
    // then the underlying Framebuffers will never be deleted!
238

    
239
    mListMarked = false;
240
    mList.clear();
241
    }
242

    
243
///////////////////////////////////////////////////////////////////////////////////////////////////
244
// must be called form a thread holding OpenGL Context
245

    
246
  static synchronized void deleteAllMarked()
247
    {
248
    if( mListMarked )
249
      {
250
      DistortedFramebuffer tmp;
251
      Iterator<DistortedFramebuffer> iterator = mList.iterator();
252

    
253
      while(iterator.hasNext())
254
        {
255
        tmp = iterator.next();
256

    
257
        if( tmp.mMarked )
258
          {
259
          tmp.deleteFBO();
260
          iterator.remove();
261
          }
262
        }
263

    
264
      mListMarked = false;
265
      }
266
    }
267

    
268
///////////////////////////////////////////////////////////////////////////////////////////////////
269
// PUBLIC API
270
///////////////////////////////////////////////////////////////////////////////////////////////////
271
/**
272
 * Create a new offscreen Framebuffer.
273
 *
274
 * @param width Width of the COLOR attachment.
275
 * @param height Height of the COLOR attachment.
276
 */
277
  @SuppressWarnings("unused")
278
  public DistortedFramebuffer(int width, int height)
279
    {
280
    mProjectionMatrix = new float[16];
281

    
282
    mHeight     = height;
283
    mWidth      = width;
284
    mFOV        = 60.0f;
285
    mX          = 0.0f;
286
    mY          = 0.0f;
287
    mMarked     = false;
288
    mDepthWanted= false;
289

    
290
    fboIds[0]  =-1;
291
    colorIds[0]= NOT_CREATED_YET;
292
    depthIds[0]= NOT_CREATED_YET;
293

    
294
    createProjection();
295
    }
296

    
297
///////////////////////////////////////////////////////////////////////////////////////////////////
298
/**
299
 * Create a new Framebuffer from an already created OpenGL Framebuffer.
300
 * <p>
301
 * Has to be followed by a 'resize()' to set the size.
302
 *
303
 * @param fbo the ID of a OpenGL Framebuffer object. Typically 0 (the screen)
304
 */
305
  public DistortedFramebuffer(int fbo)
306
    {
307
    mProjectionMatrix = new float[16];
308

    
309
    mFOV        = 60.0f;
310
    mX          = 0.0f;
311
    mY          = 0.0f;
312
    mMarked     = false;
313
    mDepthWanted= true;
314

    
315
    fboIds[0]  = fbo;
316
    colorIds[0]= DONT_CREATE;
317
    depthIds[0]= DONT_CREATE;
318
    }
319

    
320
///////////////////////////////////////////////////////////////////////////////////////////////////
321
/**
322
 * Create a new DEPTH buffer and attach it or (param=false) detach an existing DEPTh attachment and destroy it.
323
 *
324
 * @param hasDepthAttachment <bold>true</bold> if we want to attach a new DEPTH buffer to the FBO.<br>
325
 *                           <bold>false</bold> if we want to detach the DEPTH attachment.
326
 */
327
  public void setDepthAttachment(boolean hasDepthAttachment)
328
    {
329
    mDepthWanted = hasDepthAttachment;
330
    }
331

    
332
///////////////////////////////////////////////////////////////////////////////////////////////////
333

    
334
/**
335
 * Draw the (texture,mesh,effects) object to the Framebuffer.
336
 * <p>
337
 * Must be called from a thread holding OpenGL Context.
338
 *
339
 * @param tex input Texture to use.
340
 * @param mesh Class descendant from MeshObject
341
 * @param effects The DistortedEffects to use when rendering
342
 * @param time Current time, in milliseconds.
343
 */
344
  public void renderTo(DistortedTexture tex, MeshObject mesh, DistortedEffects effects, long time)
345
    {
346
    tex.createTexture();
347
    tex.setAsInput();
348
    createFBO();
349
    setAsOutput();
350
    effects.drawPriv(tex.mHalfX, tex.mHalfY, mesh, this, time);
351
    DistortedFramebuffer.deleteAllMarked();
352
    DistortedTexture.deleteAllMarked();
353
    }
354

    
355
///////////////////////////////////////////////////////////////////////////////////////////////////
356
/**
357
 * Draw the (framebuffer,mesh,effects) object to the Framebuffer.
358
 * <p>
359
 * Must be called from a thread holding OpenGL Context.
360
 *
361
 * @param fbo The Framebuffer (previously created with the first constructor, drawing FROM the screen
362
 *            is unsupported!) whose COLOR attachment 0 will be used as input texture.
363
 *            Please note that rendering from an FBO to itself is unsupported by OpenGL!
364
 * @param mesh Class descendant from MeshObject
365
 * @param effects The DistortedEffects to use when rendering
366
 * @param time Current time, in milliseconds.
367
 */
368
  public void renderTo(DistortedFramebuffer fbo, MeshObject mesh, DistortedEffects effects, long time)
369
    {
370
    fbo.createFBO();
371

    
372
    if( fbo.colorIds[0]>0 )    // fbo created with the first constructor
373
      {
374
      createFBO();
375
      setAsOutput();
376
      GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, fbo.colorIds[0]);
377
      effects.drawPriv(fbo.mWidth/2, fbo.mHeight/2, mesh, this, time);
378
      DistortedFramebuffer.deleteAllMarked();
379
      DistortedTexture.deleteAllMarked();
380
      }
381
    }
382

    
383
///////////////////////////////////////////////////////////////////////////////////////////////////
384
/**
385
 * Draws the Tree, and all its children, to the Framebuffer.
386
 * <p>
387
 * Must be called from a thread holding OpenGL Context.
388
 *
389
 * @param dt DistortedTree to render.
390
 * @param time Current time, in milliseconds. This will be passed to all the Effects stored in the Tree.
391
 */
392
  public void renderTo(DistortedTree dt, long time)
393
    {
394
    createFBO();
395
    setAsOutput();
396
    dt.drawRecursive(time,this);
397
    DistortedFramebuffer.deleteAllMarked();
398
    DistortedTexture.deleteAllMarked();
399
    }
400

    
401
///////////////////////////////////////////////////////////////////////////////////////////////////
402
/**
403
 * Mark the underlying OpenGL object for deletion. Actual deletion will take place on the next render.
404
 */
405
  public void markForDeletion()
406
    {
407
    //android.util.Log.e("FBO", "marking for deletion ("+mWidth+","+mHeight+") "+fboIds[0]);
408

    
409
    mListMarked = true;
410
    mMarked     = true;
411
    }
412

    
413
///////////////////////////////////////////////////////////////////////////////////////////////////
414
/**
415
 * Create new Projection matrix.
416
 *
417
 * @param fov Vertical 'field of view' of the Projection frustrum (in degrees).
418
 * @param x X-coordinate of the point at which our camera looks at. 0 is the center.
419
 * @param y Y-coordinate of the point at which our camera looks at. 0 is the center.
420
 */
421
  public void setProjection(float fov, float x, float y)
422
    {
423
    mFOV = fov;
424
    mX   = x;
425
    mY   = y;
426

    
427
    createProjection();
428
    }
429

    
430
///////////////////////////////////////////////////////////////////////////////////////////////////
431
/**
432
 * Resize the underlying Framebuffer.
433
 *
434
 * As the Framebuffer is not created until the first render, typical usage of this API is actually
435
 * to set the size of an not-yet-created Framebuffer of an object that has been created with the
436
 * second constructor.
437
 * <p>
438
 * Fully creating an object, rendering to it, then resizing mid-render is also possible. Actual
439
 * resize takes place on the next render.
440
 *
441
 * @param width The new width.
442
 * @param height The new height.
443
 */
444
  public void resize(int width, int height)
445
    {
446
    if( mWidth!=width || mHeight!=height )
447
      {
448
      mWidth = width;
449
      mHeight= height;
450

    
451
      createProjection();
452

    
453
      if( colorIds[0]>0 ) markForDeletion();
454
      }
455
    }
456

    
457
///////////////////////////////////////////////////////////////////////////////////////////////////
458
/**
459
 * Return the ID of the Texture (COLOR attachment 0) that's backing this FBO.
460
 * <p>
461
 * Catch: this will only work if the library has had time to actually create the texture. Remember
462
 * that the texture gets created only on first render, thus creating a Texture object and immediately
463
 * calling this method will return an invalid (negative) result.
464
 *
465
 * @return If there was not a single render between creation of the Object and calling this method on
466
 *         it, return a negative value. Otherwise, return ID of COLOR attachment 0.
467
 */
468
  public int getTextureID()
469
    {
470
    return colorIds[0];
471
    }
472

    
473
///////////////////////////////////////////////////////////////////////////////////////////////////
474
/**
475
 * Return true if the FBO contains a DEPTH attachment.
476
 *
477
 * @return <bold>true</bold> if the FBO contains a DEPTH attachment.
478
 */
479
  public boolean hasDepth()
480
    {
481
    return mDepthWanted;
482
    }
483
  }
(3-3/16)