SDK、NDK 和 CMake 都是使用 Android Studio 的 SDK Manager 下载的。
使用 Android Studio 创建Native C++项目。具体可参考 Android Studio 中集成 OpenCV (Java 和 NDK 均可,不使用Manager) 中的 创建 Native C++ 项目。
这里创建的项目名称为OpenCVNDK(是的,不是OpenGL)。在根据下面的NDK(C++)接口配置和Java接口配置后,项目的整体文件目录如下所示。这里将使用NDK(C++)版本画一个长方形,用Java版本画一个三角形,并可在同一个画面显示。
这里将创建一个基于C++版本的渲染器(cpp_renderer.h和cpp_renderer.cpp),以及JNI调用接口(cpp_renderer_interface.cpp)。这里将用其画一个长方形。
在 CMakeList.txt 中设置OpenGL库文件(NDK中自带),以及将C++项目编译成动态库(cpp_renderer.so),如下所示:
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.4.1)
# ====================================================================================
set(CMAKE_VERBOSE_MAKEFILE on)
include_directories(include)
# ===== OPENGL =====
set(OPENGL_LIB GLESv2)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11 -fexceptions -frtti")
# ====================================================================================
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
add_library( # Sets the name of the library.
cpp_renderer
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
cpp_renderer_interface.cpp
cpp_renderer.cpp)
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log )
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.
cpp_renderer
# Links the target library to the log library
# included in the NDK.
# ===========
${OPENGL_LIB}
# ===========
${log-lib} )
首先是cpp_renderer.h文件
#ifndef OPENCVNDK_CPP_RENDERER_H
#define OPENCVNDK_CPP_RENDERER_H
#include
class cpp_renderer {
public:
// Initialize shaders and GL buffers
void init();
// Render the OpenGL buffers using the shader
void draw();
private:
GLuint mVertexBuffer;
GLuint mIndexBuffer;
GLuint mProgram;
GLint mVertexAttribPos;
unsigned int mElementCount = 0;
};
#endif //OPENCVNDK_CPP_RENDERER_H
其次是cpp_renderer.cpp
#include "cpp_renderer.h"
#include
#include
#include
// datas to be drawn
// no need to import whole glm for simple example
struct Vertex {
float x, y, z;
};
const Vertex QUAD[4] = {
{-0.0f, -0.0f,0.0f},
{0.7f, -0.0f, 0.0f},
{-0.0f, 0.7f, 0.0f},
{0.7f, 0.7f, 0.0f}
};
const unsigned int ORDER[6] = {0, 1, 2, 3, 2, 1};
// for shader loading
#define LOG_TAG "GLES C++"
#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#define ALOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
bool checkGlError(const char *funcName) {
GLint err = glGetError();
if(err != GL_NO_ERROR) {
ALOGE("GL error after %s(): 0x%08x\n", funcName, err);
return true;
}
return false;
}
GLuint createShader(GLenum shaderType, const char* src) {
GLuint shader = glCreateShader(shaderType);
if(!shader) {
checkGlError("glCreateShader");
return 0;
}
glShaderSource(shader, 1, &src, NULL);
GLint compiled = GL_FALSE;
glCompileShader(shader);
glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
if(!compiled) {
GLint infoLogLen = 0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLen);
if(infoLogLen > 0) {
GLchar* infoLog = (GLchar*)malloc(infoLogLen);
if(infoLog) {
glGetShaderInfoLog(shader, infoLogLen, NULL, infoLog);
ALOGE("Could not compile %s shader:\n%s\n",
shaderType == GL_VERTEX_SHADER ? "vertex" : "fragment",
infoLog);
free(infoLog);
}
}
glDeleteShader(shader);
return 0;
}
return shader;
}
GLuint createProgram(const char* vtxSrc, const char* fragSrc) {
GLuint vtxShader = 0;
GLuint fragShader = 0;
GLuint program = 0;
GLint linked = GL_FALSE;
vtxShader = createShader(GL_VERTEX_SHADER, vtxSrc);
if(!vtxShader)
goto exit;
fragShader = createShader(GL_FRAGMENT_SHADER, fragSrc);
if(!fragShader)
goto exit;
program = glCreateProgram();
if(!program) {
checkGlError("glCreateProgram");
goto exit;
}
glAttachShader(program, vtxShader);
glAttachShader(program, fragShader);
glLinkProgram(program);
glGetProgramiv(program, GL_LINK_STATUS, &linked);
if(!linked) {
ALOGE("Could not link program");
GLint infoLogLen = 0;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLen);
if(infoLogLen) {
GLchar* infoLog = (GLchar*)malloc(infoLogLen);
if(infoLog) {
glGetProgramInfoLog(program, infoLogLen, NULL, infoLog);
ALOGE("Could not link program:\n%s\n", infoLog);
free(infoLog);
}
}
glDeleteProgram(program);
program = 0;
}
exit:
glDeleteShader(vtxShader);
glDeleteShader(fragShader);
return program;
}
// renderer
void cpp_renderer::draw() {
glUseProgram(mProgram);
glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer);
glEnableVertexAttribArray(mVertexAttribPos);
glVertexAttribPointer(mVertexAttribPos, 3, GL_FLOAT, GL_FALSE, 0, (const GLvoid*)0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBuffer);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (void*)0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
void cpp_renderer::init() {
glGenBuffers(1, &mVertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer);
std::vector<Vertex> testVertices(QUAD, QUAD + 4);
glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * testVertices.size(), &testVertices[0], GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glGenBuffers(1, &mIndexBuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBuffer);
std::vector<unsigned int> testOrder(ORDER, ORDER + 6);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned)*testOrder.size(), &testOrder[0], GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
static const char VERTEX_SHADER[] =
"precision highp float;\n"
"attribute vec4 inPosition;\n"
"void main(){\n"
" gl_Position = inPosition;\n"
"}\n";
static const char FRAGMENT_SHADER[] =
"precision highp float;\n"
"void main() {\n"
" gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
"}\n";
mProgram = createProgram(VERTEX_SHADER, FRAGMENT_SHADER);
mVertexAttribPos = glGetAttribLocation(mProgram, "inPosition");
}
最后是JNI接口文件 cpp_renderer_interface.cpp。需要注意的是函数名称:
Java_com_casmc_opencvndk_MyCppRenderer__1init
这里Java_com_casmc_opencvndk为项目包的名称,MyCppRenderer 为调用该接口的Java文件名称,_1init 为调用的函数名称,这里多一个“_1”表示下划线。
//
// Created by 朱晓阳 on 7/19/20.
//
#include
#include
#include "cpp_renderer.h"
std::unique_ptr<cpp_renderer> mRenderer;
extern "C" {
JNIEXPORT void JNICALL
Java_com_casmc_opencvndk_MyCppRenderer__1init(JNIEnv *env, jobject instance) {
mRenderer->init();
}
JNIEXPORT void JNICALL
Java_com_casmc_opencvndk_MyCppRenderer__1draw(JNIEnv * env, jobject instance) {
mRenderer->draw();
}
// Create renderer instance
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
mRenderer = std::unique_ptr<cpp_renderer> {new cpp_renderer{}};
JNIEnv* env;
if(vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
return -1;
}
return JNI_VERSION_1_6;
}
}
这里一共有4个文件,包括主页面(MainActivity)、渲染视图(MyGLSurfaceView)、Java渲染器(MyGLRenderer)和 NDK(C++)渲染器调用(MyCppRenderer)。这里是用其画一个三角形。
package com.casmc.opencvndk;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
public class MyGLRenderer implements GLSurfaceView.Renderer {
final private Triangle mTriangle;
final private MyCppRenderer mCppRenderer;
MyGLRenderer() {
mTriangle = new Triangle();
mCppRenderer = new MyCppRenderer();
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig eglConfig) {
gl.glClearColor(1.0f, 0.0f, 1.0f, 1.0f);
mTriangle.init();
mCppRenderer.init();
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
gl.glViewport(0, 0, width, height);
}
@Override
public void onDrawFrame(GL10 gl) {
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
mTriangle.draw();
mCppRenderer.draw();
}
static int loadShader(int type, String shaderCode) {
int shader = GLES20.glCreateShader(type);
GLES20.glShaderSource(shader, shaderCode);
GLES20.glCompileShader(shader);
return shader;
}
private static class Triangle {
private FloatBuffer vertexBuffer;
private int mProgram;
private int mPositionHandle;
private int mColorHandle;
private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;
private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex
private final String vertexShaderCode =
"attribute vec4 vPosition;" + "void main() {" + "gl_Position = vPosition;" + "}";
private final String fragmentShaderCode =
"precision mediump float;" + "uniform vec4 vColor;" + "void main() {" + "gl_FragColor = vColor;" + "}";
static final int COORDS_PER_VERTEX = 3;
static float triangleCoords[] = {
0.0f, 0.122008459f, 0.0f,
-0.7f, -0.711004243f, 0.0f,
0.1f, -0.711004243f, 0.0f
};
float color[] = {0.63671875f, 0.76953125f, 0.22265625f, 1.0f};
public Triangle() {
ByteBuffer bb = ByteBuffer.allocateDirect(
triangleCoords.length *4);
bb.order(ByteOrder.nativeOrder());
vertexBuffer = bb.asFloatBuffer();
vertexBuffer.put(triangleCoords);
vertexBuffer.position(0);
}
public void init() {
int vertexShader = MyGLRenderer.loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
int fragmentShader = MyGLRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
mProgram = GLES20.glCreateProgram();
GLES20.glAttachShader(mProgram, vertexShader);
GLES20.glAttachShader(mProgram, fragmentShader);
GLES20.glLinkProgram(mProgram);
}
public void draw() {
GLES20.glUseProgram(mProgram);
mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
GLES20.glEnableVertexAttribArray(mPositionHandle);
GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
GLES20.GL_FLOAT, false,
vertexStride, vertexBuffer);
mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");
GLES20.glUniform4fv(mColorHandle, 1, color, 0);
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);
GLES20.glDisableVertexAttribArray(mPositionHandle);
}
}
}
package com.casmc.opencvndk;
public class MyCppRenderer {
void draw() {
_draw();
}
void init() {
_init();
}
private native void _init();
private native void _draw();
// Used to load the 'cpp_renderer' library on application startup.
static {
System.loadLibrary("cpp_renderer");
}
}
package com.casmc.opencvndk;
import android.content.Context;
import android.opengl.GLSurfaceView;
public class MyGLSurfaceView extends GLSurfaceView{
private final MyGLRenderer myGLRenderer;
public MyGLSurfaceView(Context context) {
super(context);
setEGLContextClientVersion(2);
myGLRenderer = new MyGLRenderer();
setRenderer(myGLRenderer);
}
}
package com.casmc.opencvndk;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
public MyGLSurfaceView myGLSurfaceView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
myGLSurfaceView = new MyGLSurfaceView(this);
setContentView(myGLSurfaceView);
}
@Override
protected void onPause() {
super.onPause();
myGLSurfaceView.onPause();
}
@Override
protected void onResume() {
super.onResume();
myGLSurfaceView.onResume();
}
JavaCppGLES