背景
上一篇文章中我們簡單體驗了一下Rajawali的基本功能,現在我們來具體看一下這些物體是如何封裝,最終調用GLES 繪製的。上一篇中通過閱讀代碼我們發現Rajawali的繪製也是類似於Surface和Renderer的機制,Surface用於最終的呈現,Renderer用於渲染圖形。
實現
Surface
ISurface
這個接口定義了Surface中的基本功能,實現了它纔算是一個基本的Surface。我們看看Surface需要哪些基本方法。
/**
* 抗鋸齒選項
*/
public static enum ANTI_ALIASING_CONFIG {
NONE, MULTISAMPLING, COVERAGE;
public static ANTI_ALIASING_CONFIG fromInteger(int i) {
switch (i) {
case 0:
return NONE;
case 1:
return MULTISAMPLING;
case 2:
return COVERAGE;
}
return NONE;
}
}
/**
* The renderer only renders
* when the surface is created, or when {@link #requestRenderUpdate()} is called.
*/
public final static int RENDERMODE_WHEN_DIRTY = 0;
/**
* The renderer is called
* continuously to re-render the scene.
*/
public final static int RENDERMODE_CONTINUOUSLY = 1;
/**
* 設置幀率
*/
public void setFrameRate(double rate);
/**
* 獲取渲染模式
*/
public int getRenderMode();
/**
* 設置渲染模式
*/
public void setRenderMode(int mode);
/**
* 使能多重採樣
* Must be called before {@link #setSurfaceRenderer(ISurfaceRenderer)}.
*/
public void setAntiAliasingMode(ANTI_ALIASING_CONFIG config);
/**
* 設置採樣次數,多重採樣開啓時生效
*/
public void setSampleCount(int count);
/**
* 設置render
*/
public void setSurfaceRenderer(ISurfaceRenderer renderer) throws IllegalStateException ;
/**
* render請求生效時調用
*/
public void requestRenderUpdate();
TextureView
這是我們真正的Sureface,它並不是Android原生的TextureView,而是繼承了原生TextureView並實現了ISurface。這個類的代碼比較長,不過我們可以發現其中包含了很多內部類,現在我們簡單梳理一下這些內部類的功能。
GLThreadManager 通過判斷標誌及使用notifyAll進行線程調度
GLThread GL線程,用於委託render進行繪製,同步相關在GLThreadManager中完成
在run方法中執行guardedRun()方法,guardedRun()循環判斷同步條件後執行如下代碼
...
try {
mEglHelper.start();
} catch (RuntimeException t) {
sGLThreadManager.releaseEglContextLocked(this);
throw t;
}
mHaveEglContext = true;
createEglContext = true;
sGLThreadManager.notifyAll();
...
其中mEglHelper.start()的作用是初始化了如下OpenGL變量
EGL10 mEgl;
EGLDisplay mEglDisplay;
EGLSurface mEglSurface;
EGLConfig mEglConfig;
EGLContext mEglContext;
EglHelper
ComponentSizeChooser 設置使用的rgba 深度,模板的大小
BaseConfigChooser
DefaultWindowSurfaceFactory EGLSurfaces的工廠類,提供了生成和銷燬的方法
DefaultContextFactory EGLContext的工廠類,提供了生成和銷燬的方法
RendererDelegate render委託類,將幀率,抗鋸齒,以及關聯的TextureView設置給render
我們自定義的TextureView類就是靠上面這些類來實現上述具體的功能。
瞭解了內部類的基本作用,我們開始看TextureView的主要功能。
public TextureView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
applyAttributes(context, attrs);
}
構造函數中,我們將幀率等屬性進行賦值。注意獲取屬性列表的方式:
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.TextureView);
下面看一下onAttachedToWindow
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (LOG_ATTACH_DETACH) {
Log.d(TAG, "onAttachedToWindow reattach =" + mDetached);
}
if (mDetached && (mRendererDelegate != null)) {
int renderMode = RENDERMODE_CONTINUOUSLY;
if (mGLThread != null) {
renderMode = mGLThread.getRenderMode();
}
mGLThread = new GLThread(mThisWeakRef);
if (renderMode != RENDERMODE_CONTINUOUSLY) {
mGLThread.setRenderMode(renderMode);
}
mGLThread.start();
}
mDetached = false;
}
此處執行我們上文提到過的GLThread進行GL繪製,可見Surface中更新UI並不是在主線程,而是在GL線程中進行。
Renderer
ISurfaceRenderer
同理,renderer也有個接口,作爲renderer必須實現這個接口。
/**
* Fetch the current target frame rate in frames per second.
*
* @return {@code double} The target frame rate.
*/
public double getFrameRate();
/**
* Sets the target frame rate in frames per second.
*
* @param rate {@code int} The target rate.
*/
public void setFrameRate(int rate);
/**
* Sets the target frame rate in frames per second.
*
* @param rate {@code double} The target rate.
*/
public void setFrameRate(double rate);
/**
* Called to inform the renderer of the multisampling configuration on this surface.
*
* @param config {@link ISurface.ANTI_ALIASING_CONFIG} The desired anti aliasing configuration.
*/
public void setAntiAliasingMode(ISurface.ANTI_ALIASING_CONFIG config);
/**
* Sets the {@link ISurface} which this implementation will be rendering on.
*
* @param surface {@link ISurface} The rendering surface.
*/
public void setRenderSurface(ISurface surface);
/**
* Called when the renderer should pause all of its rendering activities, such as frame draw requests.
*/
public void onPause();
/**
* Called when the renderer should continue all of its rendering activities, such as frame draw requests.
*/
public void onResume();
/**
* This corresponds to {@link TextureView.SurfaceTextureListener#onSurfaceTextureAvailable(SurfaceTexture, int, int)}
* and {@link GLSurfaceView.Renderer#onSurfaceCreated(GL10, EGLConfig)}. Unused parameters are passed as null or -1.
*
* @param config {@link EGLConfig config}. This is used if the surface is {@link GL10} type (SurfaceView).
* @param gl {@link GL10} for rendering.
* @param width {@code width} The surface width in pixels.
* @param height {@code height} The surface height in pixels.
*/
public void onRenderSurfaceCreated(EGLConfig config, GL10 gl, int width, int height);
/**
* Called when the rendering surface has been destroyed, such as the view being detached from the window.
*
* @param surface {@link SurfaceTexture} The texture which was being rendered to.
*/
public void onRenderSurfaceDestroyed(SurfaceTexture surface);
/**
* This corresponds to {@link TextureView.SurfaceTextureListener#onSurfaceTextureSizeChanged(SurfaceTexture, int, int)}
* and {@link GLSurfaceView.Renderer#onSurfaceChanged(GL10, int, int)}.
*
* @param gl {@link GL10} for rendering.
* @param width {@code width} The surface width in pixels.
* @param height {@code height} The surface height in pixels.
*/
public void onRenderSurfaceSizeChanged(GL10 gl, int width, int height);
/**
* Called when the renderer should draw its next frame.
*
* @param gl {@link GL10} for rendering.
*/
public void onRenderFrame(GL10 gl);
/**
* NOTE: Only relevant when rendering a live wallpaper.
*
* Called to inform you of the wallpaper's offsets changing within its contain, corresponding to the container's
* call to WallpaperManager.setWallpaperOffsets().
*
* @param xOffset
* @param yOffset
* @param xOffsetStep
* @param yOffsetStep
* @param xPixelOffset
* @param yPixelOffset
*/
public void onOffsetsChanged(float xOffset, float yOffset, float xOffsetStep,
float yOffsetStep, int xPixelOffset, int yPixelOffset);
/**
* Called as the user performs touch-screen interaction with the window that is currently showing this wallpaper.
* Note that the events you receive here are driven by the actual application the user is interacting with,
* so if it is slow you will get fewer move events.
*
* @param event {@link MotionEvent} The touch event.
*/
public void onTouchEvent(MotionEvent event);
上面一堆主要做了以下幾件事
1.採樣率設置
2.抗鋸齒設置
3.surface相關回調方法
*surface創建
*surface銷燬
*surface大小發生改變
4.繪製下一幀回掉
5.外部容器位置變化回掉
6.Touch事件回調
Renderer
Renderer是一個實現了ISurfaceRenderer的抽象類,其中void initScene()方法需要我們自己實現,功能是初始化擺放我們的各種模型。
private Scene mCurrentScene;
//場景列表
protected final List<Scene> mScenes; //List of all scenes this renderer is aware of.
//目標列表
protected final List<RenderTarget> mRenderTargets; //List of all render targets this renderer is aware of.
//添加場景內容任務
private final Queue<AFrameTask> mFrameTaskQueue;
private final SparseArray<ModelRunnable> mLoaderThreads;
private final SparseArray<IAsyncLoaderCallback> mLoaderCallbacks;
構造方法
public Renderer(Context context, boolean registerForResources) {
RajLog.i("Rajawali | Bombshell | v1.1 Development ");
RajLog.i("THIS IS A DEV BRANCH CONTAINING SIGNIFICANT CHANGES. PLEASE REFER TO CHANGELOG.md FOR MORE INFORMATION.");
mHaveRegisteredForResources = registerForResources;
mContext = context;
RawShaderLoader.mContext = new WeakReference<>(context);
mFrameRate = getRefreshRate();
mScenes = Collections.synchronizedList(new CopyOnWriteArrayList<Scene>());
mRenderTargets = Collections.synchronizedList(new CopyOnWriteArrayList<RenderTarget>());
mFrameTaskQueue = new LinkedList<>();
mSceneCachingEnabled = true;
mSceneInitialized = false;
mLoaderThreads = new SparseArray<>();
mLoaderCallbacks = new SparseArray<>();
final Scene defaultScene = getNewDefaultScene();
mScenes.add(defaultScene);
mCurrentScene = defaultScene;
// Make sure we use the default viewport size initially
clearOverrideViewportDimensions();
// Make sure we have a texture manager
mTextureManager = TextureManager.getInstance();
mTextureManager.setContext(getContext());
// Make sure we have a material manager
mMaterialManager = MaterialManager.getInstance();
mMaterialManager.setContext(getContext());
// We are registering now
if (registerForResources) {
mTextureManager.registerRenderer(this);
mMaterialManager.registerRenderer(this);
}
}
開啓渲染
此處開啓RequestRenderTask來執行渲染操作,幀率就是RequestRenderTask執行的頻率。
public void startRendering() {
RajLog.d("startRendering()");
if (!mSceneInitialized) {
return;
}
mRenderStartTime = System.nanoTime();
mLastRender = mRenderStartTime;
if (mTimer != null) return;
mTimer = Executors.newScheduledThreadPool(1);
mTimer.scheduleAtFixedRate(new RequestRenderTask(), 0, (long) (1000 / mFrameRate), TimeUnit.MILLISECONDS);
}
下面看一下RequestRenderTask裏做了什麼
private class RequestRenderTask implements Runnable {
public void run() {
if (mSurface != null) {
mSurface.requestRenderUpdate();
}
}
}
我們發現這裏又回到了Isurface中的方法requestRenderUpdate()。那麼,我們的場景是如何傳入surface中的呢?好像並沒有直接傳入場景數據的方法,我們需要再往下看。
onRenderFrame
//輪一遍mFrameTaskQueue中的任務
performFrameTasks(); //Execute any pending frame tasks
...
onRender(elapsedRenderTime, deltaTime);
++mFrameCount;
if (mFrameCount % 50 == 0) {
...
if (mFPSUpdateListener != null)
mFPSUpdateListener.onFPSUpdate(mLastMeasuredFPS); //Update the FPS listener
}
onRender
protected void onRender(final long ellapsedRealtime, final double deltaTime) {
render(ellapsedRealtime, deltaTime);
}
找到render方法
protected void render(final long ellapsedRealtime, final double deltaTime) {
mCurrentScene.render(ellapsedRealtime, deltaTime, mCurrentRenderTarget);
}
可見,最終的渲染其實是調用了mCurrentScene的render方法,參數中mCurrentRenderTarget正是我們場景中的模型。
Scene
我們發現render中真實調用的其實是Scene中的render方法,現在我們來看看這裏做了什麼。此處開始真正調用Opengl的API來對我們傳入數據進行渲染。這裏先檢查有沒有新的模型或者其他物體添加,如果有的話在render線程中同步添加,之後,分別處理每個列表,調用物體本身的render方法進行繪製,這些方法就直接調用Opengl API進行繪製。
public void render(long ellapsedTime, double deltaTime, RenderTarget renderTarget) {
render(ellapsedTime, deltaTime, renderTarget, null);
}
public void render(long ellapsedTime, double deltaTime, RenderTarget renderTarget, Material sceneMaterial) {
if (mPickerInfo != null) {
最終調用ObjectColorPicker的靜態方法pickObject(pickerInfo),查看是否有模型被選中
}
performFrameTasks(); //如果有新的物體,同步添加
synchronized (mFrameTaskQueue) {
爲表面材料設置光照
}
synchronized (mNextSkyboxLock) {
設置天空盒
}
synchronized (mNextCameraLock) {
設置Camera切換
}
int clearMask = mAlwaysClearColorBuffer? GLES20.GL_COLOR_BUFFER_BIT : 0;
...
if (mEnableDepthBuffer) {
設置Opengl深度緩存
}
if (mAntiAliasingConfig.equals(ISurface.ANTI_ALIASING_CONFIG.COVERAGE)) {
clearMask |= GL_COVERAGE_BUFFER_BIT_NV;
}
GLES20.glClear(clearMask);
final int preCount = mPreCallbacks.size();
if (preCount > 0) {
執行onPreFrame回調
}
// Update all registered animations
synchronized (mAnimations) {
執行動畫
}
//更新Camera的MVP矩陣
// We are beginning the render process so we need to update the camera matrix before fetching its values
mCamera.onRecalculateModelMatrix(null);
// Get the view and projection matrices in advance
mVMatrix = mCamera.getViewMatrix();
mPMatrix = mCamera.getProjectionMatrix();
// Pre-multiply View and Projection matrices once for speed
mVPMatrix.setAll(mPMatrix).multiply(mVMatrix);
mInvVPMatrix.setAll(mVPMatrix).inverse();
mCamera.updateFrustum(mInvVPMatrix); // Update frustum plane
// 更新光源的模型矩陣
synchronized (mLights) {
final int numLights = mLights.size();
for (int i = 0; i < numLights; ++i) {
mLights.get(i).onRecalculateModelMatrix(null);
}
}
// Execute onPreDraw callbacks
// We explicitly break out the steps here to help the compiler optimize
final int preDrawCount = mPreDrawCallbacks.size();
if (preDrawCount > 0) {
執行onPreFrame回調
}
if (mSkybox != null) {
繪製天空盒
}
if(sceneMaterial != null) {
綁定場景的材質
}
synchronized (mChildren) {
爲每個模型傳入Camera以及模型矩陣,視圖矩陣,投影矩陣,場景的材質
繪製每個模型,此處調用Object3D類中的render方法現實
}
if (mDisplaySceneGraph) {
//繪製場景圖,mDisplaySceneGraph一般爲false
mSceneGraph.displayGraph(mCamera, mVPMatrix, mPMatrix, mVMatrix);
}
if(sceneMaterial != null) {
sceneMaterial.unbindTextures();
}
synchronized (mPlugins) {
執行Plugins的render方法
}
if(renderTarget != null) {
renderTarget.unbind();
}
// Execute onPostFrame callbacks
// We explicitly break out the steps here to help the compiler optimize
final int postCount = mPostCallbacks.size();
if (postCount > 0) {
執行onPostFrame回調
}
}
回頭來看Scane,每個renderer中都包含一個Scene mCurrentScene變量,用來標識當前的場景。下面我們就來看一下Scene中做了些什麼。下面我們先大概看看其中的變量。
首先,有一個renderer的引用,用來記錄對應的renderer。
protected Renderer mRenderer;
接下來時四個矩陣,物體繪製時需要用到這些矩陣
protected Matrix4 mVMatrix = new Matrix4();
protected Matrix4 mPMatrix = new Matrix4();
protected Matrix4 mVPMatrix = new Matrix4();
protected Matrix4 mInvVPMatrix = new Matrix4();
之後定義了天空盒和霧霾參數
protected volatile ColorPickerInfo mPickerInfo;
再之後就是我們在前文中向場景中添加的各種對象的列表
private final List<Object3D> mChildren;
private final List<ASceneFrameCallback> mPreCallbacks;
private final List<ASceneFrameCallback> mPreDrawCallbacks;
private final List<ASceneFrameCallback> mPostCallbacks;
private final List<Animation> mAnimations;
private final List<IRendererPlugin> mPlugins;
private final List<ALight> mLights;
最後當然還少不了攝像機
protected Camera mCamera;
以上是目前能大概知道意思的成員變量,可以推測Scene的主要作用是用於管理和繪製各種模型,結合上文分析我們可以印證renderer中真正執行Opengl繪製的功能,其實是在Scene中。
AFrameTask
AFrameTask是Scane頻繁出現的一個類,下面我們看看它的用途。
我們發現在Scene中操作模型,光等對象時,都先New一個AFrameTask,之後返回internalOfferTask(task);
看來要搞清楚Scene的工作,必須先搞明白這個的原理。
AFrameTask的代碼很簡單,就是一個實現了Runnable的類。
/**
* Adds a task to the frame task queue.
*
* @param task AFrameTask to be added.
* @return boolean True on successful addition to queue.
*/
private boolean internalOfferTask(AFrameTask task) {
synchronized (mFrameTaskQueue) {
return mFrameTaskQueue.offer(task);
}
}
可見這裏是用來同步添加物體的。
private void performFrameTasks() {
synchronized (mFrameTaskQueue) {
//Fetch the first task
AFrameTask task = mFrameTaskQueue.poll();
while (task != null) {
task.run();
//Retrieve the next task
task = mFrameTaskQueue.poll();
}
}
}
在這裏依次執行表中的每個任務,並刪除相應節點。這個方法在render方法的開始調用,也就是說在執行render方法時,先檢查有沒有新添加的物體或者回到接口等,如果有的話,先同步將它們添加,再做下一步操作。