1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 18package android.filterpacks.ui; 19 20import android.filterfw.core.Filter; 21import android.filterfw.core.FilterContext; 22import android.filterfw.core.FilterSurfaceView; 23import android.filterfw.core.Frame; 24import android.filterfw.core.FrameFormat; 25import android.filterfw.core.GenerateFieldPort; 26import android.filterfw.core.GenerateFinalPort; 27import android.filterfw.core.GLEnvironment; 28import android.filterfw.core.GLFrame; 29import android.filterfw.core.MutableFrameFormat; 30import android.filterfw.core.ShaderProgram; 31import android.filterfw.format.ImageFormat; 32 33import android.view.SurfaceHolder; 34 35import android.util.Log; 36 37/** 38 * @hide 39 */ 40public class SurfaceRenderFilter extends Filter implements SurfaceHolder.Callback { 41 42 private final int RENDERMODE_STRETCH = 0; 43 private final int RENDERMODE_FIT = 1; 44 private final int RENDERMODE_FILL_CROP = 2; 45 46 /** Required. Sets the destination filter surface view for this 47 * node. 48 */ 49 @GenerateFinalPort(name = "surfaceView") 50 private FilterSurfaceView mSurfaceView; 51 52 /** Optional. Control how the incoming frames are rendered onto the 53 * output. Default is FIT. 54 * RENDERMODE_STRETCH: Just fill the output surfaceView. 55 * RENDERMODE_FIT: Keep aspect ratio and fit without cropping. May 56 * have black bars. 57 * RENDERMODE_FILL_CROP: Keep aspect ratio and fit without black 58 * bars. May crop. 59 */ 60 @GenerateFieldPort(name = "renderMode", hasDefault = true) 61 private String mRenderModeString; 62 63 private boolean mIsBound = false; 64 65 private ShaderProgram mProgram; 66 private GLFrame mScreen; 67 private int mRenderMode = RENDERMODE_FIT; 68 private float mAspectRatio = 1.f; 69 70 private int mScreenWidth; 71 private int mScreenHeight; 72 73 private boolean mLogVerbose; 74 private static final String TAG = "SurfaceRenderFilter"; 75 76 public SurfaceRenderFilter(String name) { 77 super(name); 78 79 mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE); 80 } 81 82 @Override 83 public void setupPorts() { 84 // Make sure we have a SurfaceView 85 if (mSurfaceView == null) { 86 throw new RuntimeException("NULL SurfaceView passed to SurfaceRenderFilter"); 87 } 88 89 // Add input port 90 addMaskedInputPort("frame", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); 91 } 92 93 public void updateRenderMode() { 94 if (mRenderModeString != null) { 95 if (mRenderModeString.equals("stretch")) { 96 mRenderMode = RENDERMODE_STRETCH; 97 } else if (mRenderModeString.equals("fit")) { 98 mRenderMode = RENDERMODE_FIT; 99 } else if (mRenderModeString.equals("fill_crop")) { 100 mRenderMode = RENDERMODE_FILL_CROP; 101 } else { 102 throw new RuntimeException("Unknown render mode '" + mRenderModeString + "'!"); 103 } 104 } 105 updateTargetRect(); 106 } 107 108 @Override 109 public void prepare(FilterContext context) { 110 // Create identity shader to render, and make sure to render upside-down, as textures 111 // are stored internally bottom-to-top. 112 mProgram = ShaderProgram.createIdentity(context); 113 mProgram.setSourceRect(0, 1, 1, -1); 114 mProgram.setClearsOutput(true); 115 mProgram.setClearColor(0.0f, 0.0f, 0.0f); 116 117 updateRenderMode(); 118 119 // Create a frame representing the screen 120 MutableFrameFormat screenFormat = ImageFormat.create(mSurfaceView.getWidth(), 121 mSurfaceView.getHeight(), 122 ImageFormat.COLORSPACE_RGBA, 123 FrameFormat.TARGET_GPU); 124 mScreen = (GLFrame)context.getFrameManager().newBoundFrame(screenFormat, 125 GLFrame.EXISTING_FBO_BINDING, 126 0); 127 } 128 129 @Override 130 public void open(FilterContext context) { 131 // Bind surface view to us. This will emit a surfaceCreated and surfaceChanged call that 132 // will update our screen width and height. 133 mSurfaceView.unbind(); 134 mSurfaceView.bindToListener(this, context.getGLEnvironment()); 135 } 136 137 @Override 138 public void process(FilterContext context) { 139 // Make sure we are bound to a surface before rendering 140 if (!mIsBound) { 141 Log.w("SurfaceRenderFilter", 142 this + ": Ignoring frame as there is no surface to render to!"); 143 return; 144 } 145 146 if (mLogVerbose) Log.v(TAG, "Starting frame processing"); 147 148 GLEnvironment glEnv = mSurfaceView.getGLEnv(); 149 if (glEnv != context.getGLEnvironment()) { 150 throw new RuntimeException("Surface created under different GLEnvironment!"); 151 } 152 153 154 // Get input frame 155 Frame input = pullInput("frame"); 156 boolean createdFrame = false; 157 158 float currentAspectRatio = (float)input.getFormat().getWidth() / input.getFormat().getHeight(); 159 if (currentAspectRatio != mAspectRatio) { 160 if (mLogVerbose) Log.v(TAG, "New aspect ratio: " + currentAspectRatio +", previously: " + mAspectRatio); 161 mAspectRatio = currentAspectRatio; 162 updateTargetRect(); 163 } 164 165 // See if we need to copy to GPU 166 Frame gpuFrame = null; 167 if (mLogVerbose) Log.v("SurfaceRenderFilter", "Got input format: " + input.getFormat()); 168 int target = input.getFormat().getTarget(); 169 if (target != FrameFormat.TARGET_GPU) { 170 gpuFrame = context.getFrameManager().duplicateFrameToTarget(input, 171 FrameFormat.TARGET_GPU); 172 createdFrame = true; 173 } else { 174 gpuFrame = input; 175 } 176 177 // Activate our surface 178 glEnv.activateSurfaceWithId(mSurfaceView.getSurfaceId()); 179 180 // Process 181 mProgram.process(gpuFrame, mScreen); 182 183 // And swap buffers 184 glEnv.swapBuffers(); 185 186 if (createdFrame) { 187 gpuFrame.release(); 188 } 189 } 190 191 @Override 192 public void fieldPortValueUpdated(String name, FilterContext context) { 193 updateTargetRect(); 194 } 195 196 @Override 197 public void close(FilterContext context) { 198 mSurfaceView.unbind(); 199 } 200 201 @Override 202 public void tearDown(FilterContext context) { 203 if (mScreen != null) { 204 mScreen.release(); 205 } 206 } 207 208 @Override 209 public synchronized void surfaceCreated(SurfaceHolder holder) { 210 mIsBound = true; 211 } 212 213 @Override 214 public synchronized void surfaceChanged(SurfaceHolder holder, 215 int format, 216 int width, 217 int height) { 218 // If the screen is null, we do not care about surface changes (yet). Once we have a 219 // screen object, we need to keep track of these changes. 220 if (mScreen != null) { 221 mScreenWidth = width; 222 mScreenHeight = height; 223 mScreen.setViewport(0, 0, mScreenWidth, mScreenHeight); 224 updateTargetRect(); 225 } 226 } 227 228 @Override 229 public synchronized void surfaceDestroyed(SurfaceHolder holder) { 230 mIsBound = false; 231 } 232 233 private void updateTargetRect() { 234 if (mScreenWidth > 0 && mScreenHeight > 0 && mProgram != null) { 235 float screenAspectRatio = (float)mScreenWidth / mScreenHeight; 236 float relativeAspectRatio = screenAspectRatio / mAspectRatio; 237 238 switch (mRenderMode) { 239 case RENDERMODE_STRETCH: 240 mProgram.setTargetRect(0, 0, 1, 1); 241 break; 242 case RENDERMODE_FIT: 243 if (relativeAspectRatio > 1.0f) { 244 // Screen is wider than the camera, scale down X 245 mProgram.setTargetRect(0.5f - 0.5f / relativeAspectRatio, 0.0f, 246 1.0f / relativeAspectRatio, 1.0f); 247 } else { 248 // Screen is taller than the camera, scale down Y 249 mProgram.setTargetRect(0.0f, 0.5f - 0.5f * relativeAspectRatio, 250 1.0f, relativeAspectRatio); 251 } 252 break; 253 case RENDERMODE_FILL_CROP: 254 if (relativeAspectRatio > 1) { 255 // Screen is wider than the camera, crop in Y 256 mProgram.setTargetRect(0.0f, 0.5f - 0.5f * relativeAspectRatio, 257 1.0f, relativeAspectRatio); 258 } else { 259 // Screen is taller than the camera, crop in X 260 mProgram.setTargetRect(0.5f - 0.5f / relativeAspectRatio, 0.0f, 261 1.0f / relativeAspectRatio, 1.0f); 262 } 263 break; 264 } 265 } 266 } 267} 268