Project

General

Profile

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

library / src / main / java / org / distorted / library / DistortedFramebuffer.java @ b33f6f0a

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
  private int mTexWidth, mTexHeight;
59

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

    
66
///////////////////////////////////////////////////////////////////////////////////////////////////
67
// Must be called from a thread holding OpenGL Context
68

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

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

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

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

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

    
109
///////////////////////////////////////////////////////////////////////////////////////////////////
110

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

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

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

    
126
      return false;
127
      }
128

    
129
    return true;
130
    }
131

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

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

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

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

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

    
154
    mMarked = false;
155
    }
156

    
157
///////////////////////////////////////////////////////////////////////////////////////////////////
158

    
159
  void setAsOutput()
160
    {
161
    GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fboIds[0]);
162

    
163
    if( depthIds[0]!=NOT_CREATED_YET )
164
      {
165
      GLES20.glEnable(GLES20.GL_DEPTH_TEST);
166
      GLES20.glDepthMask(true);
167
      }
168
    else
169
      {
170
      GLES20.glDisable(GLES20.GL_DEPTH_TEST);
171
      GLES20.glDepthMask(false);
172
      }
173
    }
174

    
175
///////////////////////////////////////////////////////////////////////////////////////////////////
176

    
177
  void setAsInput()
178
    {
179
    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, colorIds[0]);
180
    }
181

    
182
///////////////////////////////////////////////////////////////////////////////////////////////////
183

    
184
  private void createProjection()
185
    {
186
    if( mWidth>0 && mHeight>1 )
187
      {
188
      if( mFOV>0.0f )  // perspective projection
189
        {
190
        float left   = (-mX-mWidth /2.0f)/mHeight;
191
        float right  = (-mX+mWidth /2.0f)/mHeight;
192
        float bottom = (-mY-mHeight/2.0f)/mHeight;
193
        float top    = (-mY+mHeight/2.0f)/mHeight;
194
        float near   = (top-bottom) / (2.0f*(float)Math.tan(mFOV*Math.PI/360));
195
        mDistance    = mHeight*near/(top-bottom);
196
        float far    = 2*mDistance-near;
197
        mDepth       = (int)((far-near)/2);
198

    
199
        Matrix.frustumM(mProjectionMatrix, 0, left, right, bottom, top, near, far);
200
        }
201
      else             // parallel projection
202
        {
203
        float left   = -mX-mWidth /2.0f;
204
        float right  = -mX+mWidth /2.0f;
205
        float bottom = -mY-mHeight/2.0f;
206
        float top    = -mY+mHeight/2.0f;
207
        float near   = (mWidth+mHeight)/2;
208
        mDistance    = 2*near;
209
        float far    = 3*near;
210
        mDepth       = (int)near;
211

    
212
        Matrix.orthoM(mProjectionMatrix, 0, left, right, bottom, top, near, far);
213
        }
214
      }
215
    }
216

    
217
///////////////////////////////////////////////////////////////////////////////////////////////////
218

    
219
  static synchronized void onDestroy()
220
    {
221
    for( DistortedFramebuffer fbo : mList)
222
      {
223
      if( fbo.colorIds[0]!=DONT_CREATE ) fbo.colorIds[0] = NOT_CREATED_YET;
224
      if( fbo.mDepthWanted             ) fbo.depthIds[0] = NOT_CREATED_YET;
225
      }
226
    }
227

    
228
///////////////////////////////////////////////////////////////////////////////////////////////////
229
// must be called form a thread holding OpenGL Context
230

    
231
  private static synchronized void deleteAllMarked()
232
    {
233
    if( mListMarked )
234
      {
235
      DistortedFramebuffer tmp;
236
      Iterator<DistortedFramebuffer> iterator = mList.iterator();
237

    
238
      while(iterator.hasNext())
239
        {
240
        tmp = iterator.next();
241

    
242
        if( tmp.mMarked )
243
          {
244
          tmp.deleteFBO();
245
          iterator.remove();
246
          }
247
        }
248

    
249
      mListMarked = false;
250
      }
251
    }
252

    
253
///////////////////////////////////////////////////////////////////////////////////////////////////
254
// if new size fits into the size of the underlying Texture, just change the projection without
255
// reallocating the Texture. Otherwise, we need to reallocate.
256
//
257
// Must be called form a thread holding the OpenGL context.
258

    
259
  void resizeFast(int width, int height)
260
    {
261
    if( mWidth!=width || mHeight!=height )
262
      {
263
      mWidth = width;
264
      mHeight= height;
265

    
266
      createProjection();
267

    
268
      if( mWidth>mTexWidth || mHeight>mTexHeight )
269
        {
270
        mTexWidth = mWidth;
271
        mTexHeight= mHeight;
272
        deleteFBO();
273
        }
274
      }
275

    
276
    createFBO();
277
    }
278

    
279
///////////////////////////////////////////////////////////////////////////////////////////////////
280
// PUBLIC API
281
///////////////////////////////////////////////////////////////////////////////////////////////////
282
/**
283
 * Create a new offscreen Framebuffer.
284
 *
285
 * @param width Width of the COLOR attachment.
286
 * @param height Height of the COLOR attachment.
287
 */
288
  @SuppressWarnings("unused")
289
  public DistortedFramebuffer(int width, int height)
290
    {
291
    mProjectionMatrix = new float[16];
292

    
293
    mHeight     = height;
294
    mWidth      = width;
295
    mTexHeight  = height;
296
    mTexWidth   = width;
297
    mFOV        = 60.0f;
298
    mX          = 0.0f;
299
    mY          = 0.0f;
300
    mMarked     = false;
301
    mDepthWanted= false;
302

    
303
    fboIds[0]  =-1;
304
    colorIds[0]= NOT_CREATED_YET;
305
    depthIds[0]= NOT_CREATED_YET;
306

    
307
    createProjection();
308

    
309
    mList.add(this);
310
    }
311

    
312
///////////////////////////////////////////////////////////////////////////////////////////////////
313
/**
314
 * Create a new Framebuffer from an already created OpenGL Framebuffer.
315
 * <p>
316
 * Has to be followed by a 'resize()' to set the size.
317
 *
318
 * @param fbo the ID of a OpenGL Framebuffer object. Typically 0 (the screen)
319
 */
320
  public DistortedFramebuffer(int fbo)
321
    {
322
    mProjectionMatrix = new float[16];
323

    
324
    mFOV        = 60.0f;
325
    mX          = 0.0f;
326
    mY          = 0.0f;
327
    mMarked     = false;
328
    mDepthWanted= true;
329

    
330
    fboIds[0]  = fbo;
331
    colorIds[0]= DONT_CREATE;
332
    depthIds[0]= DONT_CREATE;
333
    }
334

    
335
///////////////////////////////////////////////////////////////////////////////////////////////////
336
/**
337
 * Create a new DEPTH buffer and attach it or (param=false) detach an existing DEPTh attachment and destroy it.
338
 *
339
 * @param hasDepthAttachment <bold>true</bold> if we want to attach a new DEPTH buffer to the FBO.<br>
340
 *                           <bold>false</bold> if we want to detach the DEPTH attachment.
341
 */
342
  public void setDepthAttachment(boolean hasDepthAttachment)
343
    {
344
    mDepthWanted = hasDepthAttachment;
345
    }
346

    
347
///////////////////////////////////////////////////////////////////////////////////////////////////
348

    
349
/**
350
 * Draw the (texture,mesh,effects) object to the Framebuffer.
351
 * <p>
352
 * Must be called from a thread holding OpenGL Context.
353
 *
354
 * @param tex input Texture to use.
355
 * @param mesh Class descendant from MeshObject
356
 * @param effects The DistortedEffects to use when rendering
357
 * @param time Current time, in milliseconds.
358
 */
359
  public void renderTo(DistortedTexture tex, MeshObject mesh, DistortedEffects effects, long time)
360
    {
361
    tex.createTexture();
362
    DistortedFramebuffer.deleteAllMarked();
363
    DistortedTexture.deleteAllMarked();
364
    createFBO();
365
    tex.setAsInput();
366
    setAsOutput();
367
    effects.drawPriv(tex.mHalfX, tex.mHalfY, mesh, this, time);
368
    }
369

    
370
///////////////////////////////////////////////////////////////////////////////////////////////////
371
/**
372
 * Draw the (framebuffer,mesh,effects) object to the Framebuffer.
373
 * <p>
374
 * Must be called from a thread holding OpenGL Context.
375
 *
376
 * @param fbo The Framebuffer (previously created with the first constructor, drawing FROM the screen
377
 *            is unsupported!) whose COLOR attachment 0 will be used as input texture.
378
 *            Please note that rendering from an FBO to itself is unsupported by OpenGL!
379
 * @param mesh Class descendant from MeshObject
380
 * @param effects The DistortedEffects to use when rendering
381
 * @param time Current time, in milliseconds.
382
 */
383
  public void renderTo(DistortedFramebuffer fbo, MeshObject mesh, DistortedEffects effects, long time)
384
    {
385
    fbo.createFBO();
386

    
387
    if( fbo.colorIds[0]>0 )    // fbo created with the first constructor
388
      {
389
      DistortedFramebuffer.deleteAllMarked();
390
      DistortedTexture.deleteAllMarked();
391
      createFBO();
392
      setAsOutput();
393
      GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, fbo.colorIds[0]);
394
      effects.drawPriv(fbo.mWidth/2, fbo.mHeight/2, mesh, this, time);
395
      }
396
    }
397

    
398
///////////////////////////////////////////////////////////////////////////////////////////////////
399
/**
400
 * Draws the Tree, and all its children, to the Framebuffer.
401
 * <p>
402
 * Must be called from a thread holding OpenGL Context.
403
 *
404
 * @param dt DistortedTree to render.
405
 * @param time Current time, in milliseconds. This will be passed to all the Effects stored in the Tree.
406
 */
407
  public void renderTo(DistortedTree dt, long time)
408
    {
409
    DistortedFramebuffer.deleteAllMarked();
410
    DistortedTexture.deleteAllMarked();
411
    createFBO();
412
    setAsOutput();
413
    dt.drawRecursive(time,this);
414
    }
415

    
416
///////////////////////////////////////////////////////////////////////////////////////////////////
417
/**
418
 * Mark the underlying OpenGL object for deletion. Actual deletion will take place on the next render.
419
 */
420
  public void markForDeletion()
421
    {
422
    //android.util.Log.e("FBO", "marking for deletion ("+mWidth+","+mHeight+") "+fboIds[0]);
423

    
424
    mListMarked = true;
425
    mMarked     = true;
426
    }
427

    
428
///////////////////////////////////////////////////////////////////////////////////////////////////
429
/**
430
 * Create new Projection matrix.
431
 *
432
 * @param fov Vertical 'field of view' of the Projection frustrum (in degrees).
433
 * @param x X-coordinate of the point at which our camera looks at. 0 is the center.
434
 * @param y Y-coordinate of the point at which our camera looks at. 0 is the center.
435
 */
436
  public void setProjection(float fov, float x, float y)
437
    {
438
    mFOV = fov;
439
    mX   = x;
440
    mY   = y;
441

    
442
    createProjection();
443
    }
444

    
445
///////////////////////////////////////////////////////////////////////////////////////////////////
446
/**
447
 * Resize the underlying Framebuffer.
448
 *
449
 * As the Framebuffer is not created until the first render, typical usage of this API is actually
450
 * to set the size of an not-yet-created Framebuffer of an object that has been created with the
451
 * second constructor.
452
 * <p>
453
 * Fully creating an object, rendering to it, then resizing mid-render is also possible. Actual
454
 * resize takes place on the next render.
455
 *
456
 * @param width The new width.
457
 * @param height The new height.
458
 */
459
  public void resize(int width, int height)
460
    {
461
    if( mWidth!=width || mHeight!=height )
462
      {
463
      mWidth    = width;
464
      mHeight   = height;
465
      mTexWidth = width;
466
      mTexHeight= height;
467

    
468
      createProjection();
469

    
470
      if( colorIds[0]>0 ) markForDeletion();
471
      }
472
    }
473

    
474
///////////////////////////////////////////////////////////////////////////////////////////////////
475
/**
476
 * Return the ID of the Texture (COLOR attachment 0) that's backing this FBO.
477
 * <p>
478
 * Catch: this will only work if the library has had time to actually create the texture. Remember
479
 * that the texture gets created only on first render, thus creating a Texture object and immediately
480
 * calling this method will return an invalid (negative) result.
481
 *
482
 * @return If there was not a single render between creation of the Object and calling this method on
483
 *         it, return a negative value. Otherwise, return ID of COLOR attachment 0.
484
 */
485
  public int getTextureID()
486
    {
487
    return colorIds[0];
488
    }
489

    
490
///////////////////////////////////////////////////////////////////////////////////////////////////
491
/**
492
 * Return true if the FBO contains a DEPTH attachment.
493
 *
494
 * @return <bold>true</bold> if the FBO contains a DEPTH attachment.
495
 */
496
  public boolean hasDepth()
497
    {
498
    return mDepthWanted;
499
    }
500
  }
(3-3/16)