The original article was first published on the wechat official account Byteflow

What is a OpenGLES

OpenGLES is a subset of OpenGL, which is a cross-programming language and cross-platform programming interface specification. It is mainly applied to Embedded devices, such as mobile phones and tablets. Defined and promoted by Khronos, a graphics hardware and software industry association that focuses on open standards in graphics and multimedia.

OpenGLES 3.0 features

OpenGLES 3.0 is actually an extended version of OpenGLES 2.0, backward compatible with OpenGLES 2.0, but not compatible with OpenGLES 1.0.

Major new features in OpenGLES 3.0

texture

  • SRGB texture and frame buffer – allows applications to perform gamma-corrected rendering.
  • 2D Texture array – Holds a set of 2D texture targets.
  • 3 d texture. Some OpenGL ES 2.0 implementations support 3D textures through extensions, while OpenGL ES3.0 makes this mandatory.
  • Depth texture and Shadow Comparison – Enables depth buffers stored in textures.
  • Seamless cube diagram. In OpenGL ES 3.0, cube graphs can be sampled such as filtered to use data from adjacent faces and remove artifacts at joints.
  • Floating point texture.

shader

  • Binary program files. In OpenGL ES 3.0, fully linked binary program files can be saved in offline binary format, with no linking step required at runtime. This helps reduce application load time.
  • Non square matrix. Support for new matrix types beyond square matrices, and added related unified calls to the API to support the loading of these matrices.
  • All integers are supported. Supports integer (and unsigned integer) scalar and vector types and full integer operations.
  • Plane/smooth interpolation program. Interpolation programs in OpenGL ES 3.0 can be explicitly declared as flat or smooth coloring.
  • Unified variable block. Uniform variable values can be combined into blocks of uniform variables. Uniform variable blocks can be loaded more efficiently and can be shared among multiple shader programs.
  • Layout qualifiers. Vertex shader inputs can be declared with layout qualifiers to explicitly bind positions in the shader source code without calling the API.

geometry

  • Transform feedback. You can capture the output of the vertex shader in a buffer object.
  • Boolean occlusion query. An application can query whether any pixel of a draw call (or a set of draws) passes the depth test.
  • Instance rendering. Effectively render objects that contain similar geometry but have different properties (such as change matrix, color, or size).

Buffer object

  • Uniform variable buffer object. Provides an efficient way to store/bind large blocks of uniform variables. system
  • VAO vertex array object. Provides efficient ways to bind and switch between vertex array states.
  • Sampler object. Separate the sampler state (texture loop mode and filter) from the texture object.
  • Synchronize objects. Provides a mechanism for applications to check that a set of operations have completed execution on the GPU.
  • Pixel buffer object. Enables applications to perform asynchronous data transfers to pixel operations and texture transfer operations.
  • Copy between buffer objects. Provides a mechanism to efficiently transfer data from one buffer object to another without CPU intervention.

The frame buffer

  • Multiple Render targets (MRT). Allows applications to render to multiple color buffers simultaneously.
  • Multisample render buffer. Enables applications to render to an off-screen frame buffer with multi-sample anti-aliasing capabilities.
  • Frame buffer invalid message.

OpenGLES 3.0 shader language specification changes

OpenGLES 3.0 shader script

#version 300 es                          
layout(location = 0) in vec4 vPosition;  
void main()                              
{                                        
   gl_Position = vPosition;              
}                                        
Copy the code

OpenGLES 3.0 uses the in and out keywords instead of the attribute and varying, Layout keywords to specify the location of the attributes in the script.

GLfloatVVertices [] = {0.0 f, f, 0.5 0.0 f to 0.5 f to 0.5 f to 0.0 f to 0.5 f to 0.5 f to 0.0 f to 0.0 f to 1.0 f to 0.0 f to 0.5 f to 0.5 f to 0.0 f to 0.5 f, 0.5 f, 0.0 f}; // The first argument is the location value glVertexAttribPointer (0, 3, GL_FLOAT, GL_FALSE, 0, vVertices);Copy the code

The original 2.0 assignment is:

PositionLoc = glGetAttribLocation (program,"vPosition"); glVertexAttribPointer(positionLoc, 3, GL_FLOAT, GL_FALSE, 0, vVertices );Copy the code

Draw a triangle

Steps for drawing a triangle:

  1. Create an OpenGLES environment (a context object that can be created with GLSurfaceView);
  2. Compile and link shader programs;
  3. Specifies a shader program that assigns values to variables in the shader program.
  4. To draw.

Build OpenGLES environment based on GLSurfaceView

Simple custom GLSurfaceView.

package com.byteflow.app;

import android.content.Context;
import android.opengl.GLSurfaceView;
import android.util.AttributeSet;
import android.util.Log;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

public class MyGLSurfaceView extends GLSurfaceView {
    private static final String TAG = "MyGLSurfaceView";

    public static final int IMAGE_FORMAT_RGBA = 0x01;
    public static final int IMAGE_FORMAT_NV21 = 0x02;
    public static final int IMAGE_FORMAT_NV12 = 0x03;
    public static final int IMAGE_FORMAT_I420 = 0x04;

    private MyGLRender mGLRender;
    private MyNativeRender mNativeRender;

    public MyGLSurfaceView(Context context) {
        this(context, null);
    }

    public MyGLSurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.setEGLContextClientVersion(3);
        mNativeRender = new MyNativeRender();
        mGLRender = new MyGLRender(mNativeRender);
        setRenderer(mGLRender);
        setRenderMode(RENDERMODE_CONTINUOUSLY);
    }

    public MyNativeRender getNativeRender(a) {
        return mNativeRender;
    }

    public static class MyGLRender implements GLSurfaceView.Renderer {
        private MyNativeRender mNativeRender;

        MyGLRender(MyNativeRender myNativeRender) {
            mNativeRender = myNativeRender;
        }

        @Override
        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
            Log.d(TAG, "onSurfaceCreated() called with: gl = [" + gl + "], config = [" + config + "]");
            mNativeRender.native_OnSurfaceCreated();

        }

        @Override
        public void onSurfaceChanged(GL10 gl, int width, int height) {
            Log.d(TAG, "onSurfaceChanged() called with: gl = [" + gl + "], width = [" + width + "], height = [" + height + "]");
            mNativeRender.native_OnSurfaceChanged(width, height);

        }

        @Override
        public void onDrawFrame(GL10 gl) {
            Log.d(TAG, "onDrawFrame() called with: gl = [" + gl + "]"); mNativeRender.native_OnDrawFrame(); }}}Copy the code

JNI class.

package com.byteflow.app;

public class MyNativeRender {
    static {
        System.loadLibrary("native-render");
    }

    public native void native_OnInit(a);

    public native void native_OnUnInit(a);

    public native void native_SetImageData(int format, int width, int height, byte[] bytes);

    public native void native_OnSurfaceCreated(a);

    public native void native_OnSurfaceChanged(int width, int height);

    public native void native_OnDrawFrame(a);
}
Copy the code

Native layer simply implements JNI.

//
// Created by ByteFlow on 2019/7/9.
//
#include "util/LogUtil.h"
#include <MyGLRenderContext.h>
#include "jni.h"

#define NATIVE_RENDER_CLASS_NAME "com/byteflow/app/MyNativeRender"

#ifdef __cplusplus
extern "C" {
#endif
/* * Class: com_byteflow_app_MyNativeRender * Method: native_OnInit * Signature: ()V */
JNIEXPORT void JNICALL native_OnInit(JNIEnv *env, jobject instance)
{
	MyGLRenderContext::GetInstance();

}

/* * Class: com_byteflow_app_MyNativeRender * Method: native_OnUnInit * Signature: ()V */
JNIEXPORT void JNICALL native_OnUnInit(JNIEnv *env, jobject instance)
{
	MyGLRenderContext::DestroyInstance();
}

/* * Class: com_byteflow_app_MyNativeRender * Method: native_SetImageData * Signature: (III[B)V */
JNIEXPORT void JNICALL native_SetImageData
(JNIEnv *env, jobject instance, jint format, jint width, jint height, jbyteArray imageData)
{
	int len = env->GetArrayLength (imageData);
	uint8_t* buf = new uint8_t[len];
	env->GetByteArrayRegion(imageData, 0, len, reinterpret_cast<jbyte*>(buf));
	MyGLRenderContext::GetInstance()->SetImageData(format, width, height, buf);
	delete[] buf;
	env->DeleteLocalRef(imageData);
}

/* * Class: com_byteflow_app_MyNativeRender * Method: native_OnSurfaceCreated * Signature: ()V */
JNIEXPORT void JNICALL native_OnSurfaceCreated(JNIEnv *env, jobject instance)
{
	MyGLRenderContext::GetInstance()->OnSurfaceCreated();
}

/* * Class: com_byteflow_app_MyNativeRender * Method: native_OnSurfaceChanged * Signature: (II)V */
JNIEXPORT void JNICALL native_OnSurfaceChanged
(JNIEnv *env, jobject instance, jint width, jint height)
{
	MyGLRenderContext::GetInstance()->OnSurfaceChanged(width, height);

}

/* * Class: com_byteflow_app_MyNativeRender * Method: native_OnDrawFrame * Signature: ()V */
JNIEXPORT void JNICALL native_OnDrawFrame(JNIEnv *env, jobject instance)
{
	MyGLRenderContext::GetInstance()->OnDrawFrame();

}

#ifdef __cplusplus
}
#endif

static JNINativeMethod g_RenderMethods[] = {
		{"native_OnInit"."()V",       (void *)(native_OnInit)},
		{"native_OnUnInit"."()V",       (void *)(native_OnUnInit)},
		{"native_SetImageData"."(III[B)V",  (void *)(native_SetImageData)},
		{"native_OnSurfaceCreated"."()V",       (void *)(native_OnSurfaceCreated)},
		{"native_OnSurfaceChanged"."(II)V",     (void *)(native_OnSurfaceChanged)},
		{"native_OnDrawFrame"."()V",       (void *)(native_OnDrawFrame)},
};

static int RegisterNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int methodNum)
{
	LOGCATE("RegisterNativeMethods");
	jclass clazz = env->FindClass(className);
	if (clazz == NULL)
	{
		LOGCATE("RegisterNativeMethods fail. clazz == NULL");
		return JNI_FALSE;
	}
	if (env->RegisterNatives(clazz, methods, methodNum) < 0)
	{
		LOGCATE("RegisterNativeMethods fail");
		return JNI_FALSE;
	}
	return JNI_TRUE;
}

static void UnregisterNativeMethods(JNIEnv *env, const char *className)
{
	LOGCATE("UnregisterNativeMethods");
	jclass clazz = env->FindClass(className);
	if (clazz == NULL)
	{
		LOGCATE("UnregisterNativeMethods fail. clazz == NULL");
		return;
	}
	if(env ! =NULL) { env->UnregisterNatives(clazz); }}// call this func when loading lib
extern "C" jint JNI_OnLoad(JavaVM *jvm, void *p)
{
	LOGCATE("===== JNI_OnLoad =====");
	jint jniRet = JNI_ERR;
	JNIEnv *env = NULL;
	if (jvm->GetEnv((void**) (&env), JNI_VERSION_1_6) ! = JNI_OK) {return jniRet;
	}

	jint regRet = RegisterNativeMethods(env, NATIVE_RENDER_CLASS_NAME, g_RenderMethods,
										sizeof(g_RenderMethods) /
										sizeof(g_RenderMethods[0]));
	if(regRet ! = JNI_TRUE) {return JNI_ERR;
	}

	return JNI_VERSION_1_6;
}

extern "C" void JNI_OnUnload(JavaVM *jvm, void *p)
{
	JNIEnv *env = NULL;
	if (jvm->GetEnv((void**) (&env), JNI_VERSION_1_6) ! = JNI_OK) {return;
	}

	UnregisterNativeMethods(env, NATIVE_RENDER_CLASS_NAME);
}

Copy the code

Native layer’s Render encapsulates the class, and other samples are implemented in this class.

//
// Created by ByteFlow on 2019/7/9.
//

#include <TriangleSample.h>
#include "MyGLRenderContext.h"
#include "LogUtil.h"

MyGLRenderContext* MyGLRenderContext::m_pContext = nullptr;

MyGLRenderContext::MyGLRenderContext()
{

}

MyGLRenderContext::~MyGLRenderContext()
{

}

void MyGLRenderContext::SetImageData(int format, int width, int height, uint8_t *pData)
{
	LOGCATE("MyGLRenderContext::SetImageData format=%d, width=%d, height=%d, pData=%p", format, width, height, pData);
	NativeImage nativeImage;
	nativeImage.format = format;
	nativeImage.width = width;
	nativeImage.height = height;
	nativeImage.ppPlane[0] = pData;

	switch (format)
	{
		case IMAGE_FORMAT_NV12:
		case IMAGE_FORMAT_NV21:
			nativeImage.ppPlane[1] = nativeImage.ppPlane[0] + width * height;
			break;
		case IMAGE_FORMAT_I420:
			nativeImage.ppPlane[1] = nativeImage.ppPlane[0] + width * height;
			nativeImage.ppPlane[2] = nativeImage.ppPlane[1] + width * height / 4;
			break;
		default:
			break;
	}

	//m_TextureMapSample->LoadImage(&nativeImage);

}

void MyGLRenderContext::OnSurfaceCreated()
{
	LOGCATE("MyGLRenderContext::OnSurfaceCreated");
	glClearColor(1.0 f.1.0 f.0.5 f.1.0 f);
	m_Sample.Init();
}

void MyGLRenderContext::OnSurfaceChanged(int width, int height)
{
	LOGCATE("MyGLRenderContext::OnSurfaceChanged [w, h] = [%d, %d]", width, height);
	glViewport(0.0, width, height);
}

void MyGLRenderContext::OnDrawFrame()
{
	LOGCATE("MyGLRenderContext::OnDrawFrame");
	glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);

    m_Sample.Draw();


}

MyGLRenderContext *MyGLRenderContext::GetInstance()
{
	LOGCATE("MyGLRenderContext::GetInstance");
	if (m_pContext == nullptr)
	{
		m_pContext = new MyGLRenderContext();
	}
	return m_pContext;
}

void MyGLRenderContext::DestroyInstance()
{
	LOGCATE("MyGLRenderContext::DestroyInstance");
	if (m_pContext)
	{
		delete m_pContext;
		m_pContext = nullptr; }}Copy the code

Compile the link shader program to draw

Triangle drawing implementation class.

//
// Created by ByteFlow on 2019/7/9.
//

#include "TriangleSample.h"
#include ".. /util/GLUtils.h"
#include ".. /util/LogUtil.h"

TriangleSample::TriangleSample()
{

}

TriangleSample::~TriangleSample()
{
	if(m_ProgramObj) { glDeleteProgram(m_ProgramObj); }}void TriangleSample::Init()
{
	char vShaderStr[] =
			"#version 300 es \n"
			"layout(location = 0) in vec4 vPosition; \n"
			"void main() \n"
			"{ \n"
			" gl_Position = vPosition; \n"
			"} \n";

	char fShaderStr[] =
			"#version 300 es \n"
			"precision mediump float; \n"
			"out vec4 fragColor; \n"
			"void main() \n"
			"{ \n"
			"FragColor = vec4 (1.0, 0.0, 0.0, 1.0); \n"
			"} \n";

	m_ProgramObj = GLUtils::CreateProgram(vShaderStr, fShaderStr, m_VertexShader, m_FragmentShader);

}

void TriangleSample::Draw()
{
	LOGCATE("TriangleSample::Draw");
	GLfloat vVertices[] = {
			0.0 f.0.5 f.0.0 f.0.5 f.0.5 f.0.0 f.0.5 f.0.5 f.0.0 f};if(m_ProgramObj == 0)
		return;

	// Use the program object
	glUseProgram (m_ProgramObj);

	// Load the vertex data
	glVertexAttribPointer (0.3, GL_FLOAT, GL_FALSE, 0, vVertices );
	glEnableVertexAttribArray (0);

	glDrawArrays (GL_TRIANGLES, 0.3);

}

Copy the code

Classes that compile and link shader programs

#include "GLUtils.h"
#include "LogUtil.h"
#include <stdlib.h>

GLuint GLUtils::LoadShader(GLenum shaderType, const char *pSource)
{
    GLuint shader = 0;
        shader = glCreateShader(shaderType);
        if (shader)
        {
            glShaderSource(shader, 1, &pSource, NULL);
            glCompileShader(shader);
            GLint compiled = 0;
            glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
            if(! compiled) { GLint infoLen =0;
                glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
                if (infoLen)
                {
                    char* buf = (char*) malloc((size_t)infoLen);
                    if (buf)
                    {
                        glGetShaderInfoLog(shader, infoLen, NULL, buf);
                        LOGCATE("GLUtils::LoadShader Could not compile shader %d:\n%s\n", shaderType, buf);
                        free(buf);
                    }
                    glDeleteShader(shader);
                    shader = 0; }}}return shader;
}

GLuint GLUtils::CreateProgram(const char *pVertexShaderSource, const char *pFragShaderSource, GLuint &vertexShaderHandle, GLuint &fragShaderHandle)
{
    GLuint program = 0;
        vertexShaderHandle = LoadShader(GL_VERTEX_SHADER, pVertexShaderSource);
        if(! vertexShaderHandle)return program;

        fragShaderHandle = LoadShader(GL_FRAGMENT_SHADER, pFragShaderSource);
        if(! fragShaderHandle)return program;

        program = glCreateProgram();
        if (program)
        {
            glAttachShader(program, vertexShaderHandle);
            CheckGLError("glAttachShader");
            glAttachShader(program, fragShaderHandle);
            CheckGLError("glAttachShader");
            glLinkProgram(program);
            GLint linkStatus = GL_FALSE;
            glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);

            glDetachShader(program, vertexShaderHandle);
            glDeleteShader(vertexShaderHandle);
            vertexShaderHandle = 0;
            glDetachShader(program, fragShaderHandle);
            glDeleteShader(fragShaderHandle);
            fragShaderHandle = 0;
            if(linkStatus ! = GL_TRUE) { GLint bufLength =0;
                glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength);
                if (bufLength)
                {
                    char* buf = (char*) malloc((size_t)bufLength);
                    if (buf)
                    {
                        glGetProgramInfoLog(program, bufLength, NULL, buf);
                        LOGCATE("GLUtils::CreateProgram Could not link program:\n%s\n", buf);
                        free(buf);
                    }
                }
                glDeleteProgram(program);
                program = 0;
            }
        }
    LOGCATE("GLUtils::CreateProgram program = %d", program);
	return program;
}

void GLUtils::DeleteProgram(GLuint &program)
{
    LOGCATE("GLUtils::DeleteProgram");
    if (program)
    {
        glUseProgram(0);
        glDeleteProgram(program);
        program = 0; }}void GLUtils::CheckGLError(const char *pGLOperation)
{
    for (GLint error = glGetError(); error; error = glGetError())
    {
        LOGCATE("GLUtils::CheckGLError GL Operation %s() glError (0x%x)\n", pGLOperation, error); }}Copy the code

Init function to compile link shader program m_ProgramObj, which vertex shader script:

#version 300 es                          
layout(location = 0) in vec4 vPosition;  
void main()                              
{                                        
   gl_Position = vPosition;              
}                                        
Copy the code

Slice shader script:

#version 300 es                              
precision mediump float;                     
out vec4 fragColor;                          
void main() {fragColor = vec4 (1.0, 0.0, 0.0, 1.0); // Fill the triangle area with red}Copy the code

Specify a shader program in the Draw function, assign values to variables in the shader program, pass in vertex coordinates, and then Draw triangles.

Implementation code path: Android_OpenGLES_3_0

reference

3.0 programming guide https://book.douban.com/subject/26414014/ https://zh.wikipedia.org/wiki/OpenGL_ES OpenGLES OpenGLES wikipedia

Contact and exchange