content: /*
   * Copyright (C) 2011 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  
  package android.filterfw.core;
  
  import android.annotation.UnsupportedAppUsage;
  import android.filterfw.core.Frame;
  import android.filterfw.core.FrameFormat;
  import android.filterfw.core.FrameManager;
  import android.filterfw.core.NativeFrame;
  import android.filterfw.core.StopWatchMap;
  import android.graphics.Bitmap;
  import android.opengl.GLES20;
  import android.graphics.Rect;
  
  import java.nio.ByteBuffer;
  
  class GLFrameTimer {
  
      private static StopWatchMap mTimer = null;
  
      public static StopWatchMap get() {
          if (mTimer == null) {
              mTimer = new StopWatchMap();
          }
          return mTimer;
      }
  
  }
  
  /**
   * @hide
   */
  public class GLFrame extends Frame {
  
      // GL-related binding types
      public final static int EXISTING_TEXTURE_BINDING = 100;
      public final static int EXISTING_FBO_BINDING     = 101;
      public final static int NEW_TEXTURE_BINDING      = 102; // TODO: REMOVE THIS
      public final static int NEW_FBO_BINDING          = 103; // TODO: REMOVE THIS
      public final static int EXTERNAL_TEXTURE         = 104;
  
      private int glFrameId = -1;
  
      /**
       * Flag whether we own the texture or not. If we do not, we must be careful when caching or
       * storing the frame, as the user may delete, and regenerate it.
       */
      private boolean mOwnsTexture = true;
  
      /**
       * Keep a reference to the GL environment, so that it does not get deallocated while there
       * are still frames living in it.
       */
      private GLEnvironment mGLEnvironment;
  
      GLFrame(FrameFormat format, FrameManager frameManager) {
          super(format, frameManager);
      }
  
      GLFrame(FrameFormat format, FrameManager frameManager, int bindingType, long bindingId) {
          super(format, frameManager, bindingType, bindingId);
      }
  
      void init(GLEnvironment glEnv) {
          FrameFormat format = getFormat();
          mGLEnvironment = glEnv;
  
          // Check that we have a valid format
          if (format.getBytesPerSample() != 4) {
              throw new IllegalArgumentException("GL frames must have 4 bytes per sample!");
          } else if (format.getDimensionCount() != 2) {
              throw new IllegalArgumentException("GL frames must be 2-dimensional!");
          } else if (getFormat().getSize() < 0) {
              throw new IllegalArgumentException("Initializing GL frame with zero size!");
          }
  
          // Create correct frame
          int bindingType = getBindingType();
          boolean reusable = true;
          if (bindingType == Frame.NO_BINDING) {
              initNew(false);
          } else if (bindingType == EXTERNAL_TEXTURE) {
              initNew(true);
              reusable = false;
          } else if (bindingType == EXISTING_TEXTURE_BINDING) {
              initWithTexture((int)getBindingId());
          } else if (bindingType == EXISTING_FBO_BINDING) {
              initWithFbo((int)getBindingId());
          } else if (bindingType == NEW_TEXTURE_BINDING) {
              initWithTexture((int)getBindingId());
          } else if (bindingType == NEW_FBO_BINDING) {
              initWithFbo((int)getBindingId());
          } else {
              throw new RuntimeException("Attempting to create GL frame with unknown binding type "
                  + bindingType + "!");
          }
          setReusable(reusable);
      }
  
      private void initNew(boolean isExternal) {
          if (isExternal) {
              if (!nativeAllocateExternal(mGLEnvironment)) {
                  throw new RuntimeException("Could not allocate external GL frame!");
              }
          } else {
              if (!nativeAllocate(mGLEnvironment, getFormat().getWidth(), getFormat().getHeight())) {
                  throw new RuntimeException("Could not allocate GL frame!");
              }
          }
      }
  
      private void initWithTexture(int texId) {
          int width = getFormat().getWidth();
          int height = getFormat().getHeight();
          if (!nativeAllocateWithTexture(mGLEnvironment, texId, width, height)) {
              throw new RuntimeException("Could not allocate texture backed GL frame!");
          }
          mOwnsTexture = false;
          markReadOnly();
      }
  
      private void initWithFbo(int fboId) {
          int width = getFormat().getWidth();
          int height = getFormat().getHeight();
          if (!nativeAllocateWithFbo(mGLEnvironment, fboId, width, height)) {
              throw new RuntimeException("Could not allocate FBO backed GL frame!");
          }
      }
  
      void flushGPU(String message) {
          StopWatchMap timer = GLFrameTimer.get();
          if (timer.LOG_MFF_RUNNING_TIMES) {
            timer.start("glFinish " + message);
            GLES20.glFinish();
            timer.stop("glFinish " + message);
          }
      }
  
      @Override
      protected synchronized boolean hasNativeAllocation() {
          return glFrameId != -1;
      }
  
      @Override
      protected synchronized void releaseNativeAllocation() {
          nativeDeallocate();
          glFrameId = -1;
      }
  
      public GLEnvironment getGLEnvironment() {
          return mGLEnvironment;
      }
  
      @Override
      public Object getObjectValue() {
          assertGLEnvValid();
          return ByteBuffer.wrap(getNativeData());
      }
  
      @Override
      public void setInts(int[] ints) {
          assertFrameMutable();
          assertGLEnvValid();
          if (!setNativeInts(ints)) {
              throw new RuntimeException("Could not set int values for GL frame!");
          }
      }
  
      @Override
      public int[] getInts() {
          assertGLEnvValid();
          flushGPU("getInts");
          return getNativeInts();
      }
  
      @Override
      public void setFloats(float[] floats) {
          assertFrameMutable();
          assertGLEnvValid();
          if (!setNativeFloats(floats)) {
              throw new RuntimeException("Could not set int values for GL frame!");
          }
      }
  
      @Override
      public float[] getFloats() {
          assertGLEnvValid();
          flushGPU("getFloats");
          return getNativeFloats();
      }
  
      @Override
      public void setData(ByteBuffer buffer, int offset, int length) {
          assertFrameMutable();
          assertGLEnvValid();
          byte[] bytes = buffer.array();
          if (getFormat().getSize() != bytes.length) {
              throw new RuntimeException("Data size in setData does not match GL frame size!");
          } else if (!setNativeData(bytes, offset, length)) {
              throw new RuntimeException("Could not set GL frame data!");
          }
      }
  
      @Override
      public ByteBuffer getData() {
          assertGLEnvValid();
          flushGPU("getData");
          return ByteBuffer.wrap(getNativeData());
      }
  
      @Override
      @UnsupportedAppUsage
      public void setBitmap(Bitmap bitmap) {
          assertFrameMutable();
          assertGLEnvValid();
          if (getFormat().getWidth()  != bitmap.getWidth() ||
              getFormat().getHeight() != bitmap.getHeight()) {
              throw new RuntimeException("Bitmap dimensions do not match GL frame dimensions!");
          } else {
              Bitmap rgbaBitmap = convertBitmapToRGBA(bitmap);
              if (!setNativeBitmap(rgbaBitmap, rgbaBitmap.getByteCount())) {
                  throw new RuntimeException("Could not set GL frame bitmap data!");
              }
          }
      }
  
      @Override
      public Bitmap getBitmap() {
          assertGLEnvValid();
          flushGPU("getBitmap");
          Bitmap result = Bitmap.createBitmap(getFormat().getWidth(),
                                              getFormat().getHeight(),
                                              Bitmap.Config.ARGB_8888);
          if (!getNativeBitmap(result)) {
              throw new RuntimeException("Could not get bitmap data from GL frame!");
          }
          return result;
      }
  
      @Override
      public void setDataFromFrame(Frame frame) {
          assertGLEnvValid();
  
          // Make sure frame fits
          if (getFormat().getSize() < frame.getFormat().getSize()) {
              throw new RuntimeException(
                  "Attempting to assign frame of size " + frame.getFormat().getSize() + " to " +
                  "smaller GL frame of size " + getFormat().getSize() + "!");
          }
  
          // Invoke optimized implementations if possible
          if (frame instanceof NativeFrame) {
              nativeCopyFromNative((NativeFrame)frame);
          } else if (frame instanceof GLFrame) {
              nativeCopyFromGL((GLFrame)frame);
          } else if (frame instanceof SimpleFrame) {
              setObjectValue(frame.getObjectValue());
          } else {
              super.setDataFromFrame(frame);
          }
      }
  
      public void setViewport(int x, int y, int width, int height) {
          assertFrameMutable();
          setNativeViewport(x, y, width, height);
      }
  
      public void setViewport(Rect rect) {
          assertFrameMutable();
          setNativeViewport(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
      }
  
      @UnsupportedAppUsage
      public void generateMipMap() {
          assertFrameMutable();
          assertGLEnvValid();
          if (!generateNativeMipMap()) {
              throw new RuntimeException("Could not generate mip-map for GL frame!");
          }
      }
  
      @UnsupportedAppUsage
      public void setTextureParameter(int param, int value) {
          assertFrameMutable();
          assertGLEnvValid();
          if (!setNativeTextureParam(param, value)) {
              throw new RuntimeException("Could not set texture value " + param + " = " + value + " " +
                                         "for GLFrame!");
          }
      }
  
      @UnsupportedAppUsage
      public int getTextureId() {
          return getNativeTextureId();
      }
  
      public int getFboId() {
          return getNativeFboId();
      }
  
      public void focus() {
          if (!nativeFocus()) {
              throw new RuntimeException("Could not focus on GLFrame for drawing!");
          }
      }
  
      @Override
      public String toString() {
          return "GLFrame id: " + glFrameId + " (" + getFormat() + ") with texture ID "
              + getTextureId() + ", FBO ID " + getFboId();
      }
  
      @Override
      protected void reset(FrameFormat newFormat) {
          if (!nativeResetParams()) {
              throw new RuntimeException("Could not reset GLFrame texture parameters!");
          }
          super.reset(newFormat);
      }
  
      @Override
      protected void onFrameStore() {
          if (!mOwnsTexture) {
              // Detach texture from FBO in case user manipulates it.
              nativeDetachTexFromFbo();
          }
      }
  
      @Override
      protected void onFrameFetch() {
          if (!mOwnsTexture) {
              // Reattach texture to FBO when using frame again. This may reallocate the texture
              // in case it has become invalid.
              nativeReattachTexToFbo();
          }
      }
  
      private void assertGLEnvValid() {
          if (!mGLEnvironment.isContextActive()) {
              if (GLEnvironment.isAnyContextActive()) {
                  throw new RuntimeException("Attempting to access " + this + " with foreign GL " +
                      "context active!");
              } else {
                  throw new RuntimeException("Attempting to access " + this + " with no GL context " +
                      " active!");
              }
          }
      }
  
      static {
          System.loadLibrary("filterfw");
      }
  
      private native boolean nativeAllocate(GLEnvironment env, int width, int height);
  
      private native boolean nativeAllocateWithTexture(GLEnvironment env,
                                                 int textureId,
                                                 int width,
                                                 int height);
  
      private native boolean nativeAllocateWithFbo(GLEnvironment env,
                                             int fboId,
                                             int width,
                                             int height);
  
      private native boolean nativeAllocateExternal(GLEnvironment env);
  
      private native boolean nativeDeallocate();
  
      private native boolean setNativeData(byte[] data, int offset, int length);
  
      private native byte[] getNativeData();
  
      private native boolean setNativeInts(int[] ints);
  
      private native boolean setNativeFloats(float[] floats);
  
      private native int[] getNativeInts();
  
      private native float[] getNativeFloats();
  
      private native boolean setNativeBitmap(Bitmap bitmap, int size);
  
      private native boolean getNativeBitmap(Bitmap bitmap);
  
      private native boolean setNativeViewport(int x, int y, int width, int height);
  
      private native int getNativeTextureId();
  
      private native int getNativeFboId();
  
      private native boolean generateNativeMipMap();
  
      private native boolean setNativeTextureParam(int param, int value);
  
      private native boolean nativeResetParams();
  
      private native boolean nativeCopyFromNative(NativeFrame frame);
  
      private native boolean nativeCopyFromGL(GLFrame frame);
  
      private native boolean nativeFocus();
  
      private native boolean nativeReattachTexToFbo();
  
      private native boolean nativeDetachTexFromFbo();
  }
  