安卓OpenGLES環境搭建(十)
前言
前面學習了opengl es的基礎知識,包括GLSL語言,常用函式等等,由於opengl es是基於誇平臺的api設計,它本身不提供上下文管理,視窗管理,這些交給具體的裝置廠商。在安卓平臺這些是由EGL庫實現的,接下來我們就學習安卓平臺如何搭建opengl es的環境;安卓平臺的EGL庫分為java層,在com.media.opengl_es包下;native層的EGL庫則需要引入標頭檔案
#include <EGL/egl.h>
#include <EGL/eglext.h>
opengl es系列文章
opengl es之-基礎概念(一)
opengl es之-GLSL語言(二)
opengl es之-GLSL語言(三)
opengl es之-常用函式介紹(四)
opengl es之-安卓平臺在圖片上畫對角線和截圖(十一)
目標
1、GLSurfaceView搭建opengl es環境
2、SurfaceView搭建opengl es環境
3、TextureView搭建opengl es環境
4、利用上面搭建的環境渲染一張圖片到螢幕上
GLSurfaceView、SurfaceView、TextureView區別
1、SurfaceView,它繼承自類View,因此它本質上是一個View。但與普通View不同的是,它有自己的Surface(所有的普通View是共享一個Surface的,該Surface由他們的共同父類DecorView提供),利用這個Surface,我們可以進行單獨的渲染,並將最終的渲染結果與DecorView合併最終輸出到螢幕上。流程如下:
1561205389814.jpg
這裡的Surface其實就是前面章節所講的frame buffer和繫結了frame buffer的 render buffer的封裝體,所以在安卓平臺利用opengl es並不需要我們再去通過
glGenframebuffers()和glGenRenderbuffers()函式建立FBO和RBO了,因為SurfaceView已經為我們封裝好了。
2、GLSurfaceView整合於SurfaceView,它具有SurfaceView的全部特性,內部實現了EGL管理和一個獨立的渲染執行緒,而SurfaceView卻需要我們自己實現EGL和渲染執行緒
3、TextureView繼承於View,它內部沒有Surface,但是有一個SurfaceTexture,該SurfaceTexture可以作為EGL的引數來建立Surface,SurfaceTexture就相當於frame buffer。使用流程和SurfaceView一致。
GLSurfaceView搭建Opengl es環境
通過前面的學習,我們可以知道,使用GLSurfaceView搭建opengl es環境是最簡單的,因為不需要我們自己實現EGL,和對渲染執行緒的管理了,下面來看看如何使用GLSurfaceView
1、像建立普通View一樣建立GLSurfaceView
int h = PixelUtil.dp2px(this,200);
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT,h);
lp.addRule(RelativeLayout.BELOW, R.id.id_btn3);
lp.topMargin = PixelUtil.dp2px(this,20);
lp.leftMargin = PixelUtil.dp2px(this,20);
lp.rightMargin = PixelUtil.dp2px(this,20);
glSurfaceView = new MyGLSurfaceView(this);
contentLayout.addView(glSurfaceView);
glSurfaceView.setLayoutParams(lp);
MyGLSurfaceView是GLSurfaceView的子類
public class MyGLSurfaceView extends GLSurfaceView {
// 先儲存要顯示的紋理
private Bitmap mBitmap;
private int mWidth;
private int mHeight;
// 頂點座標
private ByteBuffer vbuffer;
// 紋理座標
private ByteBuffer fbuffer;
// 1、初始化GLSurfaceView,包括呼叫setRenderer()設定GLSurfaceView.Renderer物件
// setRenderer()將會建立一個渲染執行緒
public MyGLSurfaceView(Context context) {
super(context);
initGLESContext();
}
public MyGLSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
initGLESContext();
}
/** 遇到問題:Opengl es函式無法工作,由於沒有指定opengl es的版本
* 解決方案:設定正確的opengl es版本
**/
private void initGLESContext() {
// 設定版本,必須要
setEGLContextClientVersion(2);
GLRGBRender render = new GLRGBRender();
setRenderer(render);
setRenderMode(RENDERMODE_WHEN_DIRTY); // 預設是連續渲染模式
}
......
}
注意initGLESContext()函式
要呼叫setEGLContextClientVersion(2);設定opengl es的版本
呼叫setRenderer(render);指定GLSurfaceView.Renderer介面的實現者,點進去可以看到此函式呼叫後GLSurfaceView內部的渲染執行緒GLThread將自動啟用
public void setRenderer(Renderer renderer) {
checkRenderThreadState();
if (mEGLConfigChooser == null) {
mEGLConfigChooser = new SimpleEGLConfigChooser(true);
}
if (mEGLContextFactory == null) {
mEGLContextFactory = new DefaultContextFactory();
}
if (mEGLWindowSurfaceFactory == null) {
mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory();
}
mRenderer = renderer;
mGLThread = new GLThread(mThisWeakRef);
mGLThread.start();
}
那麼有人可能會想,還沒開始繪製,渲染執行緒就空跑起來了?那豈不是很浪費啊,對的,但是我們可以通過如下函式指定該執行緒的行為
setRenderMode(RENDERMODE_WHEN_DIRTY); // 預設是連續渲染模式
RENDERMODE_CONTINUOUSLY:
預設情況下,渲染執行緒每隔16ms自動發出一次渲染請求。
RENDERMODE_WHEN_DIRTY:
則表示渲染請求由使用者呼叫requestRender();函式後觸發;備註:GLSurfaceView建立成果後也會觸發幾次渲染請求
2、實現GLSurfaceView.Renderer介面
前面提到了setRenderer(render)函式,這裡的render就是實現了GLSurfaceView.Renderer介面的類,該介面告訴了我們EGL環境、Surface的準備情況,我們的opengl es繪製指令要在該介面中去實現,先看下該介面的介紹
public interface Renderer {
....
void onSurfaceCreated(GL10 gl, EGLConfig config);
void onSurfaceChanged(GL10 gl, int width, int height);
void onDrawFrame(GL10 gl);
....
}
void onSurfaceCreated(GL10 gl, EGLConfig config):
代表GLSurfaceView內部的Surface已經建立好,EGL環境也準備就緒,同時我們可以在該回撥中重新配置EGLConfig,回撥結束後將使用新的EGLConfig配置的EGL環境,當然也可以不做處理使用預設配置。
void onSurfaceChanged(GL10 gl, int width, int height);
當GLSurfaceView的大小改變時往往會伴隨著內部Surface大小的改變,此時該回撥會被呼叫,GLSurfaceView首次初始化時該函式也會被執行
void onDrawFrame(GL10 gl);
如果前面是預設的RENDERMODE_WHEN_DIRTY渲染模式,那麼此函式每隔16ms執行一次;如果是RENDERMODE_WHEN_DIRTY渲染模式,那麼此函式
在GLSurfaceView的requestRender();函式呼叫後被呼叫
opengl es函式指令應該在該函式中去執行
@Override
public void onDrawFrame(GL10 gl) {
MLog.log("onDrawFrame thread " + Thread.currentThread());
........
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP,0,4);
}
這裡只是講解opengl es的環境搭建,就不貼出具體的opengl es的程式碼了,具體可以參考Demo中的MyGLSurfaceView類
備註:它是在渲染執行緒中被呼叫
3、GLSurfaceView釋放
使用完畢後還要對GLSurfaceView進行釋放,那麼釋放就跟隨Activity的生命週期了
至此,GLSurfaceView環境的搭建就講解完畢了
SurfaceView搭建opengl es環境
SurfaceView與GLSurfaceView一樣,它內部會自動建立一個Surface作為opengl es的渲染緩衝區。區別就是不提供EGL的實現和渲染執行緒的管理,所以要使用SurfaceView搭建Opengl es環境我們得自己實現EGL和渲染執行緒。
1、實現EGL環境
整個EGL環境的實現有如下幾個很重要的類:
EGLDisplay:可以理解為要繪製的地方的一個抽象
EGLConfig:它是EGL上下文的配置引數,比如RGBA的位寬等等
EGLContext:代表EGL上下文
EGLSurface:opengl es渲染結果的緩衝區;opengl es通過它呈現到螢幕上或者實現離屏渲染
有人可能會問SurfaceView中的Surface是不是就是這裡的EGLSurface,答案是。不過通過EGLSurface來操作渲染結果更加靈活,所以下面都是用EGLSurface來操作渲染結果;
EGL環境的搭建步驟:
-建立 EGLDisplay
private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY;
private void initEGLDisplay() {
// 用於繪製的地方的一個抽閒
mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
MLog.log("eglGetDisplay fail");
}
// 初始化EGLDisplay,還可以在這個函式裡面獲取版本號
boolean ret = EGL14.eglInitialize(mEGLDisplay,null,0,null,0);
if (!ret) {
MLog.log("eglInitialize fail");
}
}
eglGetDisplay()函式選擇EGLDisplay型別,選擇好了之後,在呼叫eglInitialize()進行初始化
-建立EGLConfig配置
// 定義 EGLConfig 屬性配置,這裡定義了紅、綠、藍、透明度、深度、模板緩衝的位數,屬性配置陣列要以EGL14.EGL_NONE結尾
private static final int[] EGL_CONFIG = {
EGL14.EGL_RED_SIZE, 8,
EGL14.EGL_GREEN_SIZE, 8,
EGL14.EGL_BLUE_SIZE, 8,
EGL14.EGL_ALPHA_SIZE, 8,
EGL14.EGL_DEPTH_SIZE, 16,
EGL14.EGL_STENCIL_SIZE, 0,
EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
EGL14.EGL_NONE,
};
private EGLConfig mEGLConfig;
private void initEGLConfig() {
// 所有符合配置的 EGLConfig 個數
int[] numConfigs = new int[1];
// 所有符合配置的 EGLConfig
EGLConfig[] configs = new EGLConfig[1];
// 會獲取所有滿足 EGL_CONFIG 的 config,然後取第一個
EGL14.eglChooseConfig(mEGLDisplay, EGL_CONFIG, 0, configs, 0, configs.length, numConfigs, 0);
mEGLConfig = configs[0];
}
eglChooseConfig()函式將選擇一個滿足配置的EGLconfig
-建立上下文EGLContext
private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT;
private static final int[] EGLCONTEXT_ATTRIBUTE = {
EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
EGL14.EGL_NONE,
};
private void initEGLContext() {
// 建立上下文
mEGLContext = EGL14.eglCreateContext(mEGLDisplay, mEGLConfig, EGL14.EGL_NO_CONTEXT, EGLCONTEXT_ATTRIBUTE, 0);
if (mEGLContext == EGL14.EGL_NO_CONTEXT) {
MLog.log("eglCreateContext fail");
}
}
-建立EGLSurface
EGLSurface分兩種,一種是將渲染結果呈現到螢幕上;另外一種是將渲染結果作為離線快取不呈現到螢幕上,所以建立方法也不一樣;
第一種:
public void createWindowSurface(Object surface) {
if (mEGLSurface != EGL14.EGL_NO_SURFACE) {
throw new IllegalStateException("surface already created");
}
mEGLSurface = mEglContext.createWindowSurface(surface);
}
這裡的surface變數為SurfaceTexture型別和Surface型別都可以。所以這裡可以明白我們其實可以再次建立一個EGLSurface類操作SurfaceView中的Surface的路徑了吧,沒錯,就是通過該方法
第二種:
public void createOffscreenSurface(int width, int height) {
if (mEGLSurface != EGL14.EGL_NO_SURFACE) {
throw new IllegalStateException("surface already created");
}
mEGLSurface = mEglContext.createOffscreenSurface(width, height);
mWidth = width;
mHeight = height;
}
建立離線渲染的EGLSurface,與前面不同的是,這裡並不需要surface,只需要指定寬高即可。
至此EGL環境搭建流程全部完畢,具體可以參考
參考 google的示例程式碼 grafika 地址:https://github.com/google/grafika/
2、實現渲染執行緒
這裡用java的Thread類實現,通過繼承Thread類
private class RenderThread extends Thread implements SurfaceHolder.Callback {
......
}
這裡先講一下SurfaceHolder.Callback這個介面,它代表了SurfaceView內部的那個Surface從建立到變化到銷燬的整個生命週期,如下:
public interface Callback {
public void surfaceCreated(SurfaceHolder holder);
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height);
public void surfaceDestroyed(SurfaceHolder holder);
}
這幾個回撥函式意義我就不一一講解了,看名字也可以知道。這裡我只是講一下為什麼自己實現的渲染執行緒要實現這個介面呢?其實不一定要渲染執行緒實現,也可以在MySurfaceView類實現,只要實現該介面即可。實現的目的在於:
-我們可通過該Surface對整個EGL的生命週期進行管理
-拿到該Surface後,我們可以把它作為EGLSurface的引數來建立一個EGL物件,這樣他們是共享同一個渲染緩衝區的,有利於節約記憶體。
接下來回到渲染執行緒的實現。其實渲染執行緒主要的工作就是呼叫opengl es的指令進行渲染,然後將渲染結果根據需求是呈現到螢幕上還是離線放到渲染緩衝區
我們這裡實現的非常簡單,渲染執行緒開啟後呼叫通過ondraw()函式呼叫一次opengl es指令然後就關閉該執行緒(具體實現可以參考GLSurfaceView)
首先看一下渲染執行緒實現的SurfaceHolder.Callback介面
@Override
public void surfaceCreated(SurfaceHolder holder) {
MLog.log("surfaceCreated 建立了");
synchronized (mLock) {
mSurfaceTexture = holder.getSurface();
mLock.notify();
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
MLog.log("surfaceChanged 建立了");
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
MLog.log("surfaceDestroyed 建立了");
}
這些介面都是在UI執行緒呼叫的,因為我們的渲染執行緒執行順序可能在這個函式之前,為了保證渲染執行緒中EGLSurface 拿到的Surface不為空,這裡用鎖和條件變數方式實現,如上
然後是渲染執行緒
@Override
public void run() {
Surface st = null;
mRun = true;
while (true) {
// 等待Surface的Surface建立成功
synchronized (mLock) {
while (mRun && (st = mSurfaceTexture) == null) {
try {
// 阻塞當前執行緒,直到在其它執行緒呼叫mLock.notifyAll()/mLock.notify()函式
MLog.log("texutre 還未建立,等待");
mLock.wait();
} catch (InterruptedException ie) {
ie.printStackTrace();
}
if (!mRun) {
break;
}
}
}
MLog.log("開始渲染 ");
// 獲取到了SurfaceTexture,那麼開始做渲染工作
mGLcontext = new GLContext();
mSurface = new GLSurface(mGLcontext);
/** 遇到問題:
* 奔潰:
* eglCreateWindowSurface: native_window_api_connect (win=0x75d7e8d010) failed (0xffffffed) (already connected to another API?)
* 解決方案:
* 因為SurfaceTexture還未與前面的EGLContext解綁就又被繫結到其它EGLContext,導致奔潰。原因就是下面渲染結束後沒有跳出迴圈;在while語句最後新增
* break;語句
* */
mSurface.createWindowSurface(st);
mSurface.makeCurrentReadFrom(mSurface);
onSurfaceCreated();
onDraw();
/** 遇到問題:渲染結果沒有成功顯示到螢幕上
* 解決方案:因為下面提前釋放了SurfaceTexture導致的問題。不應該在這裡釋放
* */
// 渲染結束 進行相關釋放工作
// st.release();
MLog.log("渲染結束");
finishRender = true;
break;
}
}
可以看到,渲染執行緒在收到SurfaceView的Surface被初始化後的鎖通知前一直休眠,直到通知到達才開始EGL環境的初始化
接下來初始化EGL,
接下來ondraw()函式呼叫
那麼opengl es的指令就可以像GLSurfaceView那樣寫在ondraw函式中了。
private void onDraw() {
....
// 必須要有,否則渲染結果不會呈現到螢幕上
mSurface.swapBuffers();
}
注意:前面我們使用GLSurfaceView時是沒有呼叫swapBuffers()這個函式的,沒錯,因為GLSurfaceView內部自動呼叫了,但是這裡我們要自己呼叫,最終的渲染結果才會呈現到螢幕上。
至此整個SurfaceView搭建opengl es環境流程就完了。
TextureView搭建opengl es環境
TextureView和SurfaceView不同的是,它內部沒有Surface,但是有SurfaceTexture,前面我們知道以SurfaceTexture作為引數可以建立EGLSurface,同樣需要我們自己實現EGL管理和渲染執行緒的管理。
其實EGL的實現和渲染執行緒的實現和SurfaceView差不多,這裡只是講一下不同的地方
渲染執行緒實現SurfaceTextureListener介面,該介面如下:
public static interface SurfaceTextureListener {
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height);
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height);
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface);
public void onSurfaceTextureUpdated(SurfaceTexture surface);
}
咋一看是不是與SurfaceView的SurfaceHolder.Callback介面神似,沒錯,該介面就是TextureView內部的SurfaceTexture的生命週期。那後面的渲染執行緒我就不在講解了,依然是又渲染執行緒實現SurfaceTextureListener介面,
GLSurfaceView、SurfaceView、TextureView區別總結:
1、我們可以知道GLSurfaceView實現opengl es是最簡單的了,我們只需要呼叫opengl es 函式指令即可,其它都交給GLSurfaceView來實現
2、SurfaceView和TextureView實現opengl es環境流程其實大體一樣,至於他們效率上的區別這裡就不做討論了,因為筆者也沒有認真去研究。後面研究透了在單獨來寫
3、GLSurfaceView內部實現的是渲染到螢幕上的EGLSurface,所以這也是它的缺點
渲染一張圖片到螢幕上
前面opengl es的環境搭建好了,那麼接下來我們做點正事了。如何渲染一張圖片到螢幕上,這裡就涉及到opengl es的使用流程了,前面的opengl es基礎文字中我們知道,opengl es的渲染管線分為七個步驟,但實際上提供給app的只有其中幾個步驟,下面一一道來
1、搭建opengl es環境
參考前面,這裡用前面TextureView搭建的環境為例來實現
2、初始化頂點座標和著色器程式
頂點座標的初始化可以在onSurfaceCreated()回撥中進行,當然你也可以在onDraw()回撥中,都可以。
// 頂點座標
private static final float verdata[] = {
-1.0f,-1.0f, // 左下角
1.0f,-1.0f, // 右下角
-1.0f,1.0f, // 左上角
1.0f,1.0f // 右上角
};
// 紋理座標
private static final float texdata[] = {
0.0f,1.0f, // 左下角
1.0f,1.0f, // 右上角
0.0f,0.0f, // 左上角
1.0f,0.0f, // 右上角
};
前面我們的opengl es基礎文章中,我們知道,渲染管線中有一步驟是要確定頂點座標,紋理座標,頂點座標規則和紋理座標
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
// 初始化著色器程式
mprogram = new GLProgram(vString,fString);
mprogram.useprogram()
// 初始化頂點座標和紋理座標v
vbuffer = ByteBuffer.allocateDirect(verdata.length * 4);
vbuffer.order(ByteOrder.nativeOrder())
.asFloatBuffer().put(verdata)
.position(0);
fbuffer = ByteBuffer.allocateDirect(texdata.length * 4);
fbuffer.order(ByteOrder.nativeOrder())
.asFloatBuffer().put(texdata)
.position(0);
}
這裡要說明的就是,java中是大端序,而opengl es中資料是小端序,所以這裡
order(ByteOrder.nativeOrder())將大端序轉換成小端序。
GLProgram是我封裝的專門用於處理著色器程式的類,這裡將頂點著色器和片段著色器程式碼作為引數建立一個著色器程式
其實著色器程式的原始碼載入,編譯,連線,載入程式有一個固定的流程,所以就可以封裝了,這裡不在多講,具體實現可以參考專案程式碼。
3、將頂點座標,紋理座標傳遞給opengl es
GLES20.glViewport(0,0,width,height);
GLES20.glClearColor(1.0f,0,0,1.0f);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
// 為著色器程式賦值
mprogram.useprogram();
int position = mprogram.attributeLocationForname("position");
int texcoord = mprogram.attributeLocationForname("texcoord");
int texture = mprogram.uniformaLocationForname("texture");
GLES20.glVertexAttribPointer(position,2,GLES20.GL_FLOAT,false,0,vbuffer);
GLES20.glEnableVertexAttribArray(position);
GLES20.glVertexAttribPointer(texcoord,2,GLES20.GL_FLOAT,false,0,fbuffer);
GLES20.glEnableVertexAttribArray(texcoord);
MLog.log("position " + position + " texcoord " + texcoord + " texture " + texture);
注意:使用著色器程式之前,一定要先呼叫著色器程式
mprogram.useprogram();
4、上傳紋理
// 開始上傳紋理
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
// 設定紋理引數
IntBuffer texIntbuffer = IntBuffer.allocate(1);
GLES20.glGenTextures(1,texIntbuffer);
texture = texIntbuffer.get(0);
if (texture == 0) {
MLog.log("glGenTextures fail 0");
}
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,texture);
// 設定紋理引數
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,texture);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MIN_FILTER,GLES20.GL_NEAREST);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MAG_FILTER,GLES20.GL_NEAREST);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_WRAP_S,GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_WRAP_T,GLES20.GL_CLAMP_TO_EDGE);
// 第二個引數和前面用glActiveTexture()函式啟用的紋理單元編號要一致,這樣opengl es才知道用哪個紋理單元物件 去處理紋理
GLES20.glUniform1i(texture,0);
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D,0,mBitmap,0);
注意的地方就是:
-上傳紋理之前一定要先呼叫glActiveTexture()啟用當前單元;呼叫glBindTexture()繫結當前texture id
-呼叫GLES20.glUniform1i(texture,0);將當前紋理單元id傳遞給片元著色器中紋理變數
-GLUtils.texImage2D(GLES20.GL_TEXTURE_2D,0,mBitmap,0);是google提供的java層的上傳bitmap到opengl es的工具,opengl es api中沒有該函式,但是最終是呼叫glTexImage2D()函式
這裡講一下該函式。它使用GL_RGBA作為glTexImage2d()的internalformat和format引數,使用GL_UNSIGNED_BYTE作為type引數。所以如果傳遞的bitmap不是RGBA格式,這裡會出錯,那麼就可以用
public static void texImage2D(int target, int level, int internalformat,
Bitmap bitmap, int type, int border);這個函式了
指定bitmap對應的畫素格式和type了
5、渲染並將渲染結果呈現到螢幕上
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP,0,4);
// 必須要有,否則渲染結果不會呈現到螢幕上
mSurface.swapBuffers();
以上就是如何基於TextureView用opengl es渲染一張圖片到螢幕上
專案地址
https://github.com/nldzsz/opengles-android
1、GLSurfaceView搭建opengl es環境請參考MyGLSurfaceView類
2、SurfaceView搭建opengl es環境請參考MySurfaceView類
3、TextureView搭建opengl es環境請參考MyTextureView類
4、opengl es渲染一張圖片到螢幕上請參考MySurfaceView和MyTextureView
相關文章
- 安卓開發環境搭建安卓開發環境
- win7下搭建opengles2.0程式設計環境Win7程式設計
- 安卓自動化打包環境搭建安卓
- 十、.net core(.NET 6)搭建ElasticSearch(ES)系列之Java環境搭建和Node.js環境搭建ElasticsearchJavaNode.js
- 安卓開發入門(一)開發環境搭建安卓開發環境
- 安卓開發環境搭建之最新版(So Easy!)安卓開發環境
- 安卓SDK環境配置安卓
- 環境搭建
- windows環境下Django環境搭建WindowsDjango
- react環境搭建React
- LNMP 環境搭建LNMP
- 搭建Java環境Java
- Vagrant 環境搭建
- Flutter環境搭建Flutter
- swoft 環境搭建
- OpenGL 環境搭建
- 搭建gym環境
- 搭建lnmp環境LNMP
- Angular環境搭建Angular
- JDK環境搭建JDK
- keil環境搭建
- Dubbo環境搭建
- mac搭建環境Mac
- FNA 環境搭建
- FNA環境搭建
- Maven 環境搭建Maven
- spark環境搭建Spark
- Hive環境搭建Hive
- centosLAMP環境搭建CentOSSLAMLAMP
- lnmp環境搭建LNMP
- ZooKeeper環境搭建
- lnamp環境搭建
- java 環境 搭建Java
- MAVEN環境搭建Maven
- gogs環境搭建Go
- App環境搭建APP
- 十分鐘上手-搭建vue開發環境(新手教程)Vue開發環境
- Windows環境下的Nginx環境搭建WindowsNginx