OpenGLES入門筆記:Rajawali學習(2)場景繪製基本流程

背景

上一篇文章中我們簡單體驗了一下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方法時,先檢查有沒有新添加的物體或者回到接口等,如果有的話,先同步將它們添加,再做下一步操作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章