Android view 渲染到GL,Android OpenGL ES从白痴到入门(四):离屏渲染(Pbuffer)

注:涉及太专业的知识请自行保留怀疑态度!

一本正经的胡说八道

上一节我们只是把情丝斩断了,还是没偷偷摸摸的干点见不得人的事,这节我们就来吧!

首先,我们来看EGL创建EGLSurface有三个方法:eglCreateWindowSurface()、eglCreatePbufferSurface()和eglCreatePixmapSurface()。这三者有什么不同呢?

WindowSurface

顾名思义WindowSurface是和窗口相关的,也就是在屏幕上的一块显示区的封装,渲染后即显示在界面上。

PbufferSurface

在显存中开辟一个空间,将渲染后的数据(帧)存放在这里。

PixmapSurface

以位图的形式存放在内存中,据说各平台的支持不是很好。

做离屏渲染我们可以选择PbufferSurface或者PixmapSurface(其实WindowSurface也是可以的),什么?你说FBO(Frame Buffer Object),这种高深的学问身为小菜鸡的我根本就不懂好吗(帧缓存对象FBO是对帧缓存的封装,性能要优于Pbuffer但并非可以完全替代Pbuffer)。

OpenGL操作的最终目标实际上是帧缓存(Frame Buffer)后面的各种表现形式则是EGL对Frame Buffer的封装,这么理解会不会好点?

8793f0fbd1e6?mtype=group

代码整理

这节的知识相对简单,我们先把之前的代码整理一下:

新建GLSurface类,对EGLSurface进行封装

将GLRenderer类改为抽象类,继承于Thread

GLRenderer添加一个阻塞队列(消息队列),用于交互和解耦

GLRenderer添加一个Event内部类

GLRenderer中添加生命周期,将渲染之类的具体工作放到实现类中

添加ShaderUtil类,将着色器创建的代码封装到这个类中

GLSurface.java

public class GLSurface {

public static final int TYPE_WINDOW_SURFACE = 0;

public static final int TYPE_PBUFFER_SURFACE = 1;

public static final int TYPE_PIXMAP_SURFACE = 2;

protected final int type;

protected Object surface; // 显示控件(支持SurfaceView、SurfaceHolder、Surface和SurfaceTexture)

protected EGLSurface eglSurface = EGL14.EGL_NO_SURFACE;

protected Viewport viewport = new Viewport();

public GLSurface(int width, int height) {

setViewport(0, 0, width, height);

surface = null;

type = TYPE_PBUFFER_SURFACE;

}

public GLSurface(Surface surface, int width, int height) {

this(surface,0,0,width,height);

}

public GLSurface(Surface surface, int x, int y, int width, int height) {

setViewport(x, y, width, height);

this.surface = surface;

type = TYPE_WINDOW_SURFACE;

}

public void setViewport(int x, int y, int width, int height){

viewport.x = x;

viewport.y = y;

viewport.width = width;

viewport.height = height;

}

public void setViewport(Viewport viewport){

this.viewport = viewport;

}

public Viewport getViewport(){

return viewport;

}

public static class Viewport{

public int x;

public int y;

public int width;

public int height;

}

}

GLRenderer.java

public abstract class GLRenderer extends Thread {

private static final String TAG = "GLThread";

private EGLConfig eglConfig = null;

private EGLDisplay eglDisplay = EGL14.EGL_NO_DISPLAY;

private EGLContext eglContext = EGL14.EGL_NO_CONTEXT;

private ArrayBlockingQueue eventQueue;

private final List outputSurfaces;

private boolean rendering;

private boolean isRelease;

public GLRenderer() {

setName("GLRenderer-" + getId());

outputSurfaces = new ArrayList<>();

rendering = false;

isRelease = false;

eventQueue = new ArrayBlockingQueue<>(100);

}

private boolean makeOutputSurface(GLSurface surface) {

// 创建Surface缓存

try {

switch (surface.type) {

case GLSurface.TYPE_WINDOW_SURFACE: {

final int[] attributes = {EGL14.EGL_NONE};

// 创建失败时返回EGL14.EGL_NO_SURFACE

surface.eglSurface = EGL14.eglCreateWindowSurface(eglDisplay, eglConfig, surface.surface, attributes, 0);

break;

}

case GLSurface.TYPE_PBUFFER_SURFACE: {

final int[] attributes = {

EGL14.EGL_WIDTH, surface.viewport.width,

EGL14.EGL_HEIGHT, surface.viewport.height,

EGL14.EGL_NONE};

// 创建失败时返回EGL14.EGL_NO_SURFACE

surface.eglSurface = EGL14.eglCreatePbufferSurface(eglDisplay, eglConfig, attributes, 0);

break;

}

case GLSurface.TYPE_PIXMAP_SURFACE: {

Log.w(TAG, "nonsupport pixmap surface");

return false;

}

default:

Log.w(TAG, "surface type error " + surface.type);

return false;

}

} catch (Exception e) {

Log.w(TAG, "can't create eglSurface");

surface.eglSurface = EGL_NO_SURFACE;

return false;

}

return true;

}

public void addSurface(@NonNull final GLSurface surface){

Event event = new Event(Event.ADD_SURFACE);

event.param = surface;

if(!eventQueue.offer(event))

Log.e(TAG,"queue full");

}

public void removeSurface(@NonNull final GLSurface surface){

Event event = new Event(Event.REMOVE_SURFACE);

event.param = surface;

if(!eventQueue.offer(event))

Log.e(TAG,"queue full");

}

/**

* 开始渲染

* 启动线程并等待初始化完毕

*/

public void startRender(){

if(!eventQueue.offer(new Event(Event.START_RENDER)))

Log.e(TAG,"queue full");

if(getState()==State.NEW) {

super.start(); // 启动渲染线程

}

}

public void stopRender(){

if(!eventQueue.offer(new Event(Event.STOP_RENDER)))

Log.e(TAG,"queue full");

}

public boolean postRunnable(@NonNull Runnable runnable){

Event event = new Event(Event.RUNNABLE);

event.param = runnable;

if(!eventQueue.offer(event)) {

Log.e(TAG, "queue full");

return false;

}

return true;

}

@Override

public void start() {

Log.w(TAG,"Don't call this function");

}

public void requestRender(){

eventQueue.offer(new Event(Event.REQ_RENDER));

}

/**

* 创建OpenGL环境

*/

private void createGL() {

// 获取显示设备(默认的显示设备)

eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);

// 初始化

int[] version = new int[2];

if (!EGL14.eglInitialize(eglDisplay, version, 0, version, 1)) {

throw new RuntimeException("EGL error " + EGL14.eglGetError());

}

// 获取FrameBuffer格式和能力

int[] configAttribs = {

EGL14.EGL_BUFFER_SIZE, 32,

EGL14.EGL_ALPHA_SIZE, 8,

EGL14.EGL_BLUE_SIZE, 8,

EGL14.EGL_GREEN_SIZE, 8,

EGL14.EGL_RED_SIZE, 8,

EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,

EGL14.EGL_SURFACE_TYPE, EGL14.EGL_WINDOW_BIT,

EGL14.EGL_NONE

};

int[] numConfigs = new int[1];

EGLConfig[] configs = new EGLConfig[1];

if (!EGL14.eglChooseConfig(eglDisplay, configAttribs, 0, configs, 0, configs.length, numConfigs, 0)) {

throw new RuntimeException("EGL error " + EGL14.eglGetError());

}

eglConfig = configs[0];

// 创建OpenGL上下文(可以先不设置EGLSurface,但EGLContext必须创建,

// 因为后面调用GLES方法基本都要依赖于EGLContext)

int[] contextAttribs = {

EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,

EGL14.EGL_NONE

};

eglContext = EGL14.eglCreateContext(eglDisplay, eglConfig, EGL14.EGL_NO_CONTEXT, contextAttribs, 0);

if (eglContext == EGL14.EGL_NO_CONTEXT) {

throw new RuntimeException("EGL error " + EGL14.eglGetError());

}

// 设置默认的上下文环境和输出缓冲区(小米4上如果不设置有效的eglSurface后面创建着色器会失败,可以先创建一个默认的eglSurface)

//EGL14.eglMakeCurrent(eglDisplay, surface.eglSurface, surface.eglSurface, eglContext);

EGL14.eglMakeCurrent(eglDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, eglContext);

}

/**

* 销毁OpenGL环境

*/

private void destroyGL() {

EGL14.eglDestroyContext(eglDisplay, eglContext);

eglContext = EGL14.EGL_NO_CONTEXT;

eglDisplay = EGL14.EGL_NO_DISPLAY;

}

/**

* 渲染到各个eglSurface

*/

private void render(){

// 渲染(绘制)

for(GLSurface output:outputSurfaces){

if(output.eglSurface== EGL_NO_SURFACE) {

if(!makeOutputSurface(output))

continue;

}

// 设置当前的上下文环境和输出缓冲区

EGL14.eglMakeCurrent(eglDisplay, output.eglSurface, output.eglSurface, eglContext);

// 设置视窗大小及位置

GLES20.glViewport(output.viewport.x, output.viewport.y, output.viewport.width, output.viewport.height);

// 绘制

onDrawFrame(output);

// 交换显存(将surface显存和显示器的显存交换)

EGL14.eglSwapBuffers(eglDisplay, output.eglSurface);

}

}

@Override

public void run() {

Event event;

Log.d(TAG,getName()+": render create");

createGL();

onCreated();

// 渲染

while(!isRelease){

try {

event = eventQueue.take();

switch(event.event){

case Event.ADD_SURFACE: {

// 创建eglSurface

GLSurface surface = (GLSurface)event.param;

Log.d(TAG,"add:"+surface);

makeOutputSurface(surface);

outputSurfaces.add(surface);

break;

}

case Event.REMOVE_SURFACE: {

GLSurface surface = (GLSurface)event.param;

Log.d(TAG,"remove:"+surface);

EGL14.eglDestroySurface(eglDisplay, surface.eglSurface);

outputSurfaces.remove(surface);

break;

}

case Event.START_RENDER:

rendering = true;

break;

case Event.REQ_RENDER: // 渲染

if(rendering) {

onUpdate();

render(); // 如果surface缓存没有释放(被消费)那么这里将卡住

}

break;

case Event.STOP_RENDER:

rendering = false;

break;

case Event.RUNNABLE:

((Runnable)event.param).run();

break;

case Event.RELEASE:

isRelease = true;

break;

default:

Log.e(TAG,"event error: "+event);

break;

}

} catch (InterruptedException e) {

e.printStackTrace();

}

}

// 回调

onDestroy();

// 销毁eglSurface

for(GLSurface outputSurface:outputSurfaces){

EGL14.eglDestroySurface(eglDisplay, outputSurface.eglSurface);

outputSurface.eglSurface = EGL_NO_SURFACE;

}

destroyGL();

eventQueue.clear();

Log.d(TAG,getName()+": render release");

}

/**

* 退出OpenGL渲染并释放资源

* 这里先将渲染器释放(renderer)再退出looper,因为renderer里面可能持有这个looper的handler,

* 先退出looper再释放renderer可能会报一些警告信息(sending message to a Handler on a dead thread)

*/

public void release(){

if(eventQueue.offer(new Event(Event.RELEASE))){

// 等待线程结束,如果不等待,在快速开关的时候可能会导致资源竞争(如竞争摄像头)

// 但这样操作可能会引起界面卡顿,择优取舍

while (isAlive()){

try {

this.join(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}

/**

* 当创建完基本的OpenGL环境后调用此方法,可以在这里初始化纹理之类的东西

*/

public abstract void onCreated();

/**

* 在渲染之前调用,用于更新纹理数据。渲染一帧调用一次

*/

public abstract void onUpdate();

/**

* 绘制渲染,每次绘制都会调用,一帧数据可能调用多次(不同是输出缓存)

* @param outputSurface 输出缓存位置surface

*/

public abstract void onDrawFrame(GLSurface outputSurface);

/**

* 当渲染器销毁前调用,用户回收释放资源

*/

public abstract void onDestroy();

private static String getEGLErrorString() {

return GLUtils.getEGLErrorString(EGL14.eglGetError());

}

private static class Event {

static final int ADD_SURFACE = 1; // 添加输出的surface

static final int REMOVE_SURFACE = 2; // 移除输出的surface

static final int START_RENDER = 3; // 开始渲染

static final int REQ_RENDER = 4; // 请求渲染

static final int STOP_RENDER = 5; // 结束渲染

static final int RUNNABLE = 6; //

static final int RELEASE = 7; // 释放渲染器

final int event;

Object param;

Event(int event) {

this.event = event;

}

}

}

ShaderUtil.java

public class ShaderUtil {

/**

* 加载制定shader的方法

* @param shaderType shader的类型 GLES20.GL_VERTEX_SHADER GLES20.GL_FRAGMENT_SHADER

* @param source shader的脚本字符串

* @return 着色器id

*/

private static int loadShader(int shaderType,String source) {

// 创建一个新shader

int shader = GLES20.glCreateShader(shaderType);

// 若创建成功则加载shader

if (shader != 0) {

//加载shader的源代码

GLES20.glShaderSource(shader, source);

//编译shader

GLES20.glCompileShader(shader);

//存放编译成功shader数量的数组

int[] compiled = new int[1];

//获取Shader的编译情况

GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);

if (compiled[0] == 0) {//若编译失败则显示错误日志并删除此shader

Log.e("ES20_ERROR", "Could not compile shader " + shaderType + ":");

Log.e("ES20_ERROR", GLES20.glGetShaderInfoLog(shader));

GLES20.glDeleteShader(shader);

shader = 0;

}

}

return shader;

}

/**

* 创建shader程序的方法

*/

public static int createProgram(String vertexSource, String fragmentSource) {

//加载顶点着色器

int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);

if (vertexShader == 0) {

return 0;

}

//加载片元着色器

int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);

if (pixelShader == 0) {

return 0;

}

//创建程序

int program = GLES20.glCreateProgram();

//若程序创建成功则向程序中加入顶点着色器与片元着色器

if (program != 0) {

//向程序中加入顶点着色器

GLES20.glAttachShader(program, vertexShader);

checkGlError("glAttachShader");

//向程序中加入片元着色器

GLES20.glAttachShader(program, pixelShader);

checkGlError("glAttachShader");

//链接程序

GLES20.glLinkProgram(program);

//存放链接成功program数量的数组

int[] linkStatus = new int[1];

//获取program的链接情况

GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);

//若链接失败则报错并删除程序

if (linkStatus[0] != GLES20.GL_TRUE) {

Log.e("ES20_ERROR", "Could not link program: ");

Log.e("ES20_ERROR", GLES20.glGetProgramInfoLog(program));

GLES20.glDeleteProgram(program);

program = 0;

}

}

return program;

}

//检查每一步操作是否有错误的方法

public static void checkGlError(String op) {

int error;

while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {

Log.e("ES20_ERROR", op + ": glError " + error);

throw new RuntimeException(op + ": glError " + error);

}

}

//从sh脚本中加载shader内容的方法

public static String loadFromAssetsFile(String fname, Resources r) {

String result = null;

try {

InputStream in = r.getAssets().open(fname);

int ch = 0;

ByteArrayOutputStream baos = new ByteArrayOutputStream();

while ((ch = in.read()) != -1) {

baos.write(ch);

}

byte[] buff = baos.toByteArray();

baos.close();

in.close();

result = new String(buff, "UTF-8");

result = result.replaceAll("\\r\\n", "\n");

} catch (Exception e) {

e.printStackTrace();

}

return result;

}

}

离屏渲染关键部分就是在makeOutputSurface()方法中的GLSurface.TYPE_PBUFFER_SURFACE这个case里面,仅9行代码,简单吧(指定大小,使用EGL14.eglCreatePbufferSurface()创建)

测试

工程整理完毕,添加一些测试代码

新建TestRenderer继承于GLRenderer,在onCreated()中创建着色器和顶点,在onDrawFrame()中进行绘制

TestRenderer.java

public class TestRenderer extends GLRenderer {

private static final String TAG = "TestRenderer";

private int program;

private int vPosition;

private int uColor;

private FloatBuffer vertices;

/**

* 获取图形的顶点

* 特别提示:由于不同平台字节顺序不同数据单元不是字节的一定要经过ByteBuffer

* 转换,关键是要通过ByteOrder设置nativeOrder(),否则有可能会出问题

*

* @return 顶点Buffer

*/

private FloatBuffer getVertices() {

float vertices[] = {

0.0f, 0.5f,

-0.5f, -0.5f,

0.5f, -0.5f,

};

// 创建顶点坐标数据缓冲

// vertices.length*4是因为一个float占四个字节

ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4);

vbb.order(ByteOrder.nativeOrder()); //设置字节顺序

FloatBuffer vertexBuf = vbb.asFloatBuffer(); //转换为Float型缓冲

vertexBuf.put(vertices); //向缓冲区中放入顶点坐标数据

vertexBuf.position(0); //设置缓冲区起始位置

return vertexBuf;

}

@Override

public void onCreated() {

//基于顶点着色器与片元着色器创建程序

program = createProgram(verticesShader, fragmentShader);

// 获取着色器中的属性引用id(传入的字符串就是我们着色器脚本中的属性名)

vPosition = GLES20.glGetAttribLocation(program, "vPosition");

uColor = GLES20.glGetUniformLocation(program, "uColor");

vertices = getVertices();

}

@Override

public void onUpdate() {

}

@Override

public void onDrawFrame(GLSurface surface) {

// 设置clear color颜色RGBA(这里仅仅是设置清屏时GLES20.glClear()用的颜色值而不是执行清屏)

GLES20.glClearColor(1.0f, 0, 0, 1.0f);

// 清除深度缓冲与颜色缓冲(清屏,否则会出现绘制之外的区域花屏)

GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);

// 使用某套shader程序

GLES20.glUseProgram(program);

// 为画笔指定顶点位置数据(vPosition)

GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 0, vertices);

// 允许顶点位置数据数组

GLES20.glEnableVertexAttribArray(vPosition);

// 设置属性uColor(颜色 索引,R,G,B,A)

GLES20.glUniform4f(uColor, 0.0f, 1.0f, 0.0f, 1.0f);

// 绘制

GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 3);

}

@Override

public void onDestroy() {

}

// 顶点着色器的脚本

private static final String verticesShader

= "attribute vec2 vPosition; \n" // 顶点位置属性vPosition

+ "void main(){ \n"

+ " gl_Position = vec4(vPosition,0,1);\n" // 确定顶点位置

+ "}";

// 片元着色器的脚本

private static final String fragmentShader

= "precision mediump float; \n" // 声明float类型的精度为中等(精度越高越耗资源)

+ "uniform vec4 uColor; \n" // uniform的属性uColor

+ "void main(){ \n"

+ " gl_FragColor = uColor; \n" // 给此片元的填充色

+ "}";

}

在activity_main.xml添加一个ImageView用于显示渲染结果

activity_main.xml

android:layout_width="match_parent"

android:layout_height="match_parent"

android:orientation="vertical">

android:id="@+id/iv_main_image"

android:layout_width="50dp"

android:layout_height="50dp"

android:layout_gravity="center"

android:contentDescription="@string/app_name" />

android:id="@+id/sv_main_demo"

android:layout_width="match_parent"

android:layout_height="match_parent" />

修改MainActivity

在onCreate()中创建一个PbufferSurface,加入渲染器,启动渲染,取回像素数据(要在OpenGL线程)转换成Bitmap设置给ImageView

MainActivity.java

public class MainActivity extends Activity {

private TestRenderer glRenderer;

private ImageView imageIv;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

SurfaceView sv = (SurfaceView)findViewById(R.id.sv_main_demo);

imageIv = (ImageView)findViewById(R.id.iv_main_image);

glRenderer = new TestRenderer();

GLSurface glPbufferSurface = new GLSurface(512,512);

glRenderer.addSurface(glPbufferSurface);

glRenderer.startRender();

glRenderer.requestRender();

glRenderer.postRunnable(new Runnable() {

@Override

public void run() {

IntBuffer ib = IntBuffer.allocate(512 * 512);

GLES20.glReadPixels(0, 0, 512, 512, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, ib);

final Bitmap bitmap = frameToBitmap(512, 512, ib);

new Handler(Looper.getMainLooper()).post(new Runnable() {

@Override

public void run() {

imageIv.setImageBitmap(bitmap);

}

});

}

});

sv.getHolder().addCallback(new SurfaceHolder.Callback() {

@Override

public void surfaceCreated(SurfaceHolder surfaceHolder) {

}

@Override

public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) {

GLSurface glWindowSurface = new GLSurface(surfaceHolder.getSurface(),width,height);

glRenderer.addSurface(glWindowSurface);

glRenderer.requestRender();

}

@Override

public void surfaceDestroyed(SurfaceHolder surfaceHolder) {

}

});

}

@Override

protected void onDestroy() {

glRenderer.release();

glRenderer = null;

super.onDestroy();

}

/**

* 将数据转换成bitmap(OpenGL和Android的Bitmap色彩空间不一致,这里需要做转换)

*

* @param width 图像宽度

* @param height 图像高度

* @param ib 图像数据

* @return bitmap

*/

private static Bitmap frameToBitmap(int width, int height, IntBuffer ib) {

int pixs[] = ib.array();

// 扫描转置(OpenGl:左上->右下 Bitmap:左下->右上)

for (int y = 0; y < height / 2; y++) {

for (int x = 0; x < width; x++) {

int pos1 = y * width + x;

int pos2 = (height - 1 - y) * width + x;

int tmp = pixs[pos1];

pixs[pos1] = (pixs[pos2] & 0xFF00FF00) | ((pixs[pos2] >> 16) & 0xff) | ((pixs[pos2] << 16) & 0x00ff0000); // ABGR->ARGB

pixs[pos2] = (tmp & 0xFF00FF00) | ((tmp >> 16) & 0xff) | ((tmp << 16) & 0x00ff0000);

}

}

if (height % 2 == 1) { // 中间一行

for (int x = 0; x < width; x++) {

int pos = (height / 2 + 1) * width + x;

pixs[pos] = (pixs[pos] & 0xFF00FF00) | ((pixs[pos] >> 16) & 0xff) | ((pixs[pos] << 16) & 0x00ff0000);

}

}

return Bitmap.createBitmap(pixs, width, height, Bitmap.Config.ARGB_8888);

}

}

运行一下,这里我们就能看到两个三角形了,顶部的小三角形就是在后台渲染的图像了

8793f0fbd1e6?mtype=group

源码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值