├── .gitignore
├── .idea
├── compiler.xml
├── copyright
│ └── profiles_settings.xml
├── gradle.xml
├── misc.xml
├── modules.xml
└── runConfigurations.xml
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── dandy
│ │ └── demo
│ │ └── gltextureviewdemo
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── assets
│ │ └── GLESDemo
│ │ │ └── SimpleTriangle
│ │ │ ├── gles_triangle.frag
│ │ │ ├── gles_triangle.mat
│ │ │ └── gles_triangle.vert
│ ├── java
│ │ └── com
│ │ │ └── dandy
│ │ │ ├── demo
│ │ │ └── gltextureviewdemo
│ │ │ │ └── MainActivity.java
│ │ │ ├── gldemo
│ │ │ └── glestextureview
│ │ │ │ ├── DemoGlesTextureView.java
│ │ │ │ ├── GLESRendererImpl.java
│ │ │ │ ├── MatrixAid.java
│ │ │ │ └── Triangle.java
│ │ │ ├── helper
│ │ │ ├── android
│ │ │ │ ├── FileHelper.java
│ │ │ │ ├── LogHelper.java
│ │ │ │ └── SDCardHelper.java
│ │ │ ├── gles
│ │ │ │ ├── CommonUtils.java
│ │ │ │ ├── IGLESRenderer.java
│ │ │ │ ├── MaterialAider.java
│ │ │ │ └── ShaderHelper.java
│ │ │ └── java
│ │ │ │ ├── JFileHelper.java
│ │ │ │ └── PendingThreadAider.java
│ │ │ └── module
│ │ │ └── gles
│ │ │ └── textureview
│ │ │ ├── GLESTVThread.java
│ │ │ └── GLESTextureView.java
│ └── res
│ │ ├── layout
│ │ └── activity_main.xml
│ │ ├── mipmap-hdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxxhdpi
│ │ └── ic_launcher.png
│ │ ├── values-w820dp
│ │ └── dimens.xml
│ │ └── values
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── dandy
│ └── demo
│ └── gltextureviewdemo
│ └── ExampleUnitTest.java
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 | .externalNativeBuild
10 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
17 |
22 |
27 |
54 |
56 |
42 | * 43 | *44 | */ 45 | private void initVertexData() { 46 | // 顶点坐标数据的初始化 47 | mVertexCount = 3; 48 | final float UNIT_SIZE = 0.2f; 49 | float vertices[] = new float[]// 首先将顶点此属性数据一次存放入数组,这里是顶点坐标 50 | { // 51 | -4 * UNIT_SIZE, 0, 0, // 第1个顶点的XYZ坐标值 52 | 0, -4 * UNIT_SIZE, 0, // // 第2个顶点的XYZ坐标值 53 | 4 * UNIT_SIZE, 0, 0// // 第3个顶点的XYZ坐标值 54 | }; 55 | ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4);// 开辟对应容量的缓冲 56 | vbb.order(ByteOrder.nativeOrder());// 设置字节顺序为本地操作系统顺序 57 | mVertexBuffer = vbb.asFloatBuffer();// 浮点型缓冲 58 | mVertexBuffer.put(vertices);// 将数组中的顶点数据送入缓冲 59 | mVertexBuffer.position(0);// 设置缓冲的其实位置 60 | 61 | float colors[] = new float[]// 62 | { // 63 | 1, 1, 1, 0, // 64 | 0, 0, 1, 0, // 65 | 0, 1, 0, 0// 66 | }; 67 | ByteBuffer cbb = ByteBuffer.allocateDirect(colors.length * 4); 68 | cbb.order(ByteOrder.nativeOrder()); 69 | mColorBuffer = cbb.asFloatBuffer(); 70 | mColorBuffer.put(colors); 71 | mColorBuffer.position(0); 72 | } 73 | 74 | /** 75 | *
76 | * 77 | *78 | */ 79 | private void initShader() { 80 | MaterialAider mat = new MaterialAider(mContext); 81 | mat.setMaterialName("GLESDemo/SimpleTriangle/gles_triangle.mat"); 82 | mProgram = ShaderHelper.getProgramFromAsset(mContext, mat); 83 | // 获取程序中顶点位置属性引用id 84 | maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition"); 85 | // 获取程序中顶点颜色属性引用id 86 | maColorHandle = GLES20.glGetAttribLocation(mProgram, "aColor"); 87 | // 获取程序中总变换矩阵引用id 88 | muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix"); 89 | } 90 | 91 | /** 92 | *
93 | * 94 | *95 | */ 96 | public void onDrawSelf() { 97 | LogHelper.d("Triangle", "onDrawSelf"); 98 | // 制定使用某套shader程序 99 | GLES20.glUseProgram(mProgram); 100 | // 初始化变换矩阵 101 | Matrix.setRotateM(mMatrix.mMMatrix, 0, 0, 0, 1, 0); 102 | // 设置沿Z轴正向位移1 103 | Matrix.translateM(mMatrix.mMMatrix, 0, 0, 0, 1); 104 | // 设置绕x轴旋转 105 | Matrix.rotateM(mMatrix.mMMatrix, 0, 30, 1, 0, 0); 106 | // 107 | GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mMatrix.getFianlMatrix(mMatrix.mMMatrix), 0); 108 | // 主要分为三步,1.获取着色器对应属性变量的引用 109 | // 2.通过引用将缓存中的数据传入管线 110 | // 3.启用传入的数据 111 | // 为画笔指定顶点位置数据 112 | // glVertexAttribPointer将顶点坐标数据以及顶点颜色数据传送进渲染管线,以备渲染时在顶点着色器中使用 113 | GLES20.glVertexAttribPointer(// 将顶点位置数据传送进渲染管线 114 | maPositionHandle, // 顶点位置属性引用 115 | 3, // 每顶点一组的数据个数(这里是X、Y、Z坐标,所以是3) 116 | GLES20.GL_FLOAT, // 数据类型 117 | false, // 是否格式化 118 | 3 * 4, // 每组数据的尺寸,这里每组3个浮点数值(XYZ坐标),每个浮点数4个字节所以是3*4=12个字节 119 | mVertexBuffer// 存放了数据的缓存 120 | ); 121 | GLES20.glVertexAttribPointer(maColorHandle, 4, GLES20.GL_FLOAT, false, 4 * 4, mColorBuffer); 122 | // 允许顶点位置数据数组 123 | // glEnableVertexAttribArray启用顶点位置数据和顶点颜色数据 124 | GLES20.glEnableVertexAttribArray(maPositionHandle); 125 | GLES20.glEnableVertexAttribArray(maColorHandle); 126 | // 绘制三角形 127 | GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, mVertexCount); 128 | } 129 | 130 | /** 131 | *
132 | * 133 | *134 | * 135 | * @param width 136 | * @param height 137 | */ 138 | public void onSurfaceChanged(int width, int height) { 139 | // 计算GLSurfaceView的宽高比 140 | float ratio = (float) width / height; 141 | // 调用此方法计算产生透视投影矩阵 142 | Matrix.frustumM(mMatrix.mProjMatrix, // 存储生成矩阵元素的float[]类型数组 143 | 0, // 填充起始偏移量 144 | -ratio, ratio, // near面的left、right 145 | -1, 1, // near面的bottom、top 146 | 1, 10// near面、far面与视点的距离 147 | ); 148 | // 调用此方法产生摄像机9参数位置矩阵 149 | Matrix.setLookAtM(mMatrix.mVMatrix, // 存储生成矩阵元素的float[]类型数组 150 | 0, // 填充起始偏移量 151 | 0, 0, 3, // 摄像机位置的XYZ坐标 152 | 0f, 0f, 0f, // 观察目标点XYZ坐标 153 | 0.0f, 1.0f, 0.0f// up向量在XYZ轴上的分量 154 | ); 155 | 156 | } 157 | 158 | } 159 | -------------------------------------------------------------------------------- /app/src/main/java/com/dandy/helper/android/FileHelper.java: -------------------------------------------------------------------------------- 1 | package com.dandy.helper.android; 2 | 3 | import java.io.File; 4 | import java.io.FileNotFoundException; 5 | import java.io.FileOutputStream; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | 9 | import android.content.Context; 10 | import android.graphics.Bitmap; 11 | 12 | import com.dandy.helper.java.JFileHelper; 13 | 14 | /** 15 | * 文件帮助类,和IO帮助类 16 | * 17 | * @author dengchukun 2016年11月18日 18 | */ 19 | public class FileHelper { 20 | 21 | private static final String TAG = FileHelper.class.getSimpleName(); 22 | 23 | /** 24 | * 从assets目录下拷贝整个文件夹,不管是文件夹还是文件都能拷贝 25 | * 26 | * @param context 27 | * 上下文 28 | * @param rootDirFullPath 29 | * 文件目录,要拷贝的目录如assets目录下有一个SBClock文件夹:SBClock 30 | * @param targetDirFullPath 31 | * 目标文件夹位置如:/sdcrad/SBClock 32 | */ 33 | public static boolean copyFolderFromAssets(Context context, String rootDirFullPath, String targetDirFullPath) { 34 | LogHelper.d(TAG, "copyFolderFromAssets " + "rootDirFullPath-" + rootDirFullPath + " targetDirFullPath-" + targetDirFullPath); 35 | File file = new File(targetDirFullPath); 36 | if (file.exists()) { 37 | LogHelper.d(TAG, LogHelper.getThreadName() + "file exists"); 38 | } else { 39 | boolean success = file.mkdirs(); 40 | LogHelper.d(TAG, "copyFolderFromAssets mkdir status: " + success + " isSDCardExist()-" + SDCardHelper.isSDCardExist()); 41 | if (!success) { 42 | return false; 43 | } 44 | } 45 | try { 46 | String[] listFiles = context.getAssets().list(rootDirFullPath);// 遍历该目录下的文件和文件夹 47 | for (String string : listFiles) {// 看起子目录是文件还是文件夹,这里只好用.做区分了 48 | LogHelper.d(TAG, "name-" + rootDirFullPath + "/" + string); 49 | if (isFileByName(string)) {// 文件 50 | copyFileFromAssets(context, rootDirFullPath + "/" + string, targetDirFullPath + "/" + string); 51 | } else {// 文件夹 52 | String childRootDirFullPath = rootDirFullPath + "/" + string; 53 | String childTargetDirFullPath = targetDirFullPath + "/" + string; 54 | new File(childTargetDirFullPath).mkdirs(); 55 | copyFolderFromAssets(context, childRootDirFullPath, childTargetDirFullPath); 56 | } 57 | } 58 | return true; 59 | } catch (IOException e) { 60 | LogHelper.d(TAG, LogHelper.getThreadName() + "IOException-" + e.getMessage()); 61 | e.printStackTrace(); 62 | return false; 63 | } 64 | } 65 | 66 | private static boolean isFileByName(String string) { 67 | if (string.contains(".")) { 68 | return true; 69 | } 70 | return false; 71 | } 72 | 73 | /** 74 | * 从assets目录下拷贝文件 75 | * 76 | * @param context 77 | * 上下文 78 | * @param assetsFilePath 79 | * 文件的路径名如:SBClock/0001cuteowl/cuteowl_dot.png 80 | * @param targetFileFullPath 81 | * 目标文件路径如:/sdcard/SBClock/0001cuteowl/cuteowl_dot.png 82 | */ 83 | public static void copyFileFromAssets(Context context, String assetsFilePath, String targetFileFullPath) { 84 | LogHelper.d(TAG, LogHelper.getThreadName()); 85 | InputStream assestsFileImputStream; 86 | try { 87 | assestsFileImputStream = context.getAssets().open(assetsFilePath); 88 | JFileHelper.copyFile(assestsFileImputStream, targetFileFullPath); 89 | } catch (IOException e) { 90 | LogHelper.d(TAG, LogHelper.getThreadName() + "IOException-" + e.getMessage()); 91 | e.printStackTrace(); 92 | } 93 | } 94 | 95 | /** 96 | * 从assets目录下获取文本文件内容 97 | * 98 | * @param context 99 | * 上下文 100 | * @param fileAssetPath 101 | * 文本文件路径 102 | * @return 103 | */ 104 | public static String getFileContentFromAsset(Context context, String fileAssetPath) { 105 | InputStream ins; 106 | try { 107 | ins = context.getAssets().open(fileAssetPath); 108 | byte[] contentByte = new byte[ins.available()]; 109 | ins.read(contentByte); 110 | return new String(contentByte); 111 | } catch (Exception e1) { 112 | e1.printStackTrace(); 113 | } 114 | return ""; 115 | } 116 | 117 | public static InputStream getInputStreamFromAsset(Context context, String fileAssetPath) { 118 | LogHelper.d(TAG, LogHelper.getThreadName()); 119 | InputStream ins = null; 120 | try { 121 | ins = context.getAssets().open(fileAssetPath); 122 | } catch (IOException e) { 123 | e.printStackTrace(); 124 | LogHelper.d(TAG, LogHelper.getThreadName() + " e=" + e.getMessage()); 125 | } 126 | return ins; 127 | } 128 | 129 | /** 130 | *
131 | * 保存一张图片 132 | *133 | * 134 | * @param bitmap 135 | * @param path 136 | * @param needRecycleBitmap 137 | */ 138 | public static void saveImage(Bitmap bitmap, String path, boolean needRecycleBitmap) { 139 | FileOutputStream fos = null; 140 | try { 141 | fos = new FileOutputStream(path);// 注意app的sdcard读写权限问题 142 | } catch (FileNotFoundException e) { 143 | e.printStackTrace(); 144 | LogHelper.d(TAG, LogHelper.getThreadName() + " FileNotFoundException e=" + e.getMessage()); 145 | } 146 | bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);// 压缩成png,100%显示效果 147 | try { 148 | fos.flush(); 149 | } catch (IOException e) { 150 | e.printStackTrace(); 151 | } 152 | if (needRecycleBitmap && !bitmap.isRecycled()) { 153 | bitmap.recycle(); 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /app/src/main/java/com/dandy/helper/android/LogHelper.java: -------------------------------------------------------------------------------- 1 | package com.dandy.helper.android; 2 | 3 | import android.app.ActivityManager; 4 | import android.content.Context; 5 | import android.util.Log; 6 | import android.widget.Toast; 7 | 8 | /** 9 | * 打印帮助类 10 | * 11 | * @author dengchukun 12 | * 13 | */ 14 | public class LogHelper { 15 | private static final String ROOT_TAG = "dengck"; 16 | private static boolean sIsLogDebug = true; 17 | private static String sRootTag = ROOT_TAG; 18 | private static boolean sIsToastDebug = true; 19 | 20 | /** 21 | * 打印log详细信息 相见LogDemo类和MainActivity类 最好是每个方法中都调用此方法 22 | */ 23 | public static void d(String tag, String content) { 24 | if (sIsLogDebug) { 25 | Log.d(sRootTag + "_" + tag, content); 26 | } 27 | } 28 | 29 | // class DetailLogDemo 30 | /** 31 | * 打印一段字符串 32 | * 33 | * @param content 34 | */ 35 | public static void printLog(String content) { 36 | Log.d(sRootTag, content); 37 | } 38 | 39 | /** 40 | * 打印线程名称 41 | */ 42 | public static void printProcessName(Context context, String content) { 43 | int pid = android.os.Process.myPid(); 44 | ActivityManager mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); 45 | for (ActivityManager.RunningAppProcessInfo appProcess : mActivityManager.getRunningAppProcesses()) { 46 | if (appProcess.pid == pid) { 47 | LogHelper.printLog(content + appProcess.processName); 48 | } 49 | } 50 | } 51 | 52 | /** 53 | * 得到调用此方法的线程的线程名 54 | * 55 | * @return 56 | */ 57 | public static String getThreadName() { 58 | if (!sIsLogDebug) { 59 | return ""; 60 | } 61 | StringBuffer sb = new StringBuffer(); 62 | try { 63 | sb.append(Thread.currentThread().getName()); 64 | sb.append("-> "); 65 | sb.append(Thread.currentThread().getStackTrace()[3].getMethodName()); 66 | sb.append("()"); 67 | sb.append(" "); 68 | } catch (Exception e) { 69 | } 70 | return sb.toString(); 71 | } 72 | 73 | public static boolean isLogDebug() { 74 | return sIsLogDebug; 75 | } 76 | 77 | public static void setLogDebug(boolean isLogDebug) { 78 | sIsLogDebug = isLogDebug; 79 | } 80 | 81 | public static String getRootTag() { 82 | return sRootTag; 83 | } 84 | 85 | public static void setRootTag(String rootTag) { 86 | sRootTag = rootTag; 87 | } 88 | 89 | public static void showToast(Context context, String content) { 90 | if (sIsToastDebug) { 91 | Toast.makeText(context, content, Toast.LENGTH_SHORT).show(); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /app/src/main/java/com/dandy/helper/android/SDCardHelper.java: -------------------------------------------------------------------------------- 1 | package com.dandy.helper.android; 2 | 3 | import android.os.Environment; 4 | import android.os.StatFs; 5 | 6 | public class SDCardHelper { 7 | 8 | /** 9 | * 得到SD卡的目录路径 10 | */ 11 | public static String getSDCardDirPath() { 12 | return Environment.getExternalStorageDirectory().getAbsolutePath(); 13 | } 14 | 15 | /** 16 | * 判断SD卡是否可用 17 | * 18 | * @return 19 | */ 20 | public static boolean isSDCardExist() { 21 | if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { 22 | return true; 23 | } 24 | return false; 25 | } 26 | 27 | /** 28 | * 获取SD卡的剩余容量 单位byte 29 | * 30 | * @return 31 | */ 32 | @SuppressWarnings("deprecation") 33 | public static long getSDCardAllSize() { 34 | if (isSDCardExist()) { 35 | StatFs stat = new StatFs(getSDCardDirPath()); 36 | // 获取空闲的数据块的数量 37 | long availableBlocks = (long) stat.getAvailableBlocks() - 4; 38 | // 获取单个数据块的大小(byte) 39 | long freeBlocks = stat.getAvailableBlocks(); 40 | return freeBlocks * availableBlocks; 41 | } 42 | return 0; 43 | } 44 | 45 | /** 46 | * 获取指定路径所在空间的剩余可用容量字节数,单位byte 47 | * 48 | * @param filePath 49 | * @return 容量字节 SDCard可用空间,内部存储可用空间 50 | */ 51 | @SuppressWarnings("deprecation") 52 | public static long getFreeBytes(String filePath) { 53 | // 如果是sd卡的下的路径,则获取sd卡可用容量 54 | if (filePath.startsWith(getSDCardDirPath())) { 55 | filePath = getSDCardDirPath(); 56 | } else {// 如果是内部存储的路径,则获取内存存储的可用容量 57 | filePath = Environment.getDataDirectory().getAbsolutePath(); 58 | } 59 | StatFs stat = new StatFs(filePath); 60 | long availableBlocks = (long) stat.getAvailableBlocks() - 4; 61 | return stat.getBlockSize() * availableBlocks; 62 | } 63 | 64 | /** 65 | * 获取系统存储路径 66 | * 67 | * @return 68 | */ 69 | public static String getRootDirectoryPath() { 70 | return Environment.getRootDirectory().getAbsolutePath(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/src/main/java/com/dandy/helper/gles/CommonUtils.java: -------------------------------------------------------------------------------- 1 | package com.dandy.helper.gles; 2 | 3 | import com.dandy.helper.android.LogHelper; 4 | 5 | import android.app.ActivityManager; 6 | import android.content.Context; 7 | import android.content.pm.ConfigurationInfo; 8 | import android.opengl.GLES20; 9 | 10 | /** 11 | * 12 | * @author dengchukun 2016年11月23日 13 | */ 14 | public class CommonUtils { 15 | 16 | private static final String TAG = CommonUtils.class.getSimpleName(); 17 | 18 | /** 19 | *
20 | * 检查设备是否支持OpenGL ES 2.0 21 | * check whether the device support OpenGL ES 2.0 22 | *23 | * 24 | * @param context 25 | * @return 26 | */ 27 | public static boolean isSupportEs2(Context context) { 28 | ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); 29 | ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo(); 30 | boolean supportsEs2 = configurationInfo.reqGlEsVersion >= 0x20000; 31 | LogHelper.d(TAG, LogHelper.getThreadName() + " supportsEs2=" + supportsEs2); 32 | return supportsEs2; 33 | } 34 | 35 | /** 36 | * 检查每一步操作是否有错误的方法 37 | * 38 | * @param op 39 | * TAG 40 | */ 41 | public static void checkGlError(String op) { 42 | int error; 43 | while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) { 44 | LogHelper.d("ES20_ERROR", op + ": glError " + error); 45 | throw new RuntimeException(op + ": glError " + error); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/dandy/helper/gles/IGLESRenderer.java: -------------------------------------------------------------------------------- 1 | package com.dandy.helper.gles; 2 | 3 | /** 4 | *
5 | * GLES里用到的用于渲染的一个接口。 6 | * 如果是GLSurfaceView要用到,则其对应的GLSurfaceView.Renderer可以来调用IGLESRenderer的实现类来实现逻辑 7 | * 如果是TextureView要用到,则使用自定义的一个线程里调用IGLESRenderer的实现类来做一个类似于GLSurfaceView.Renderer的操作 8 | * 所以IGLESRenderer中的方法都要在GL线程里运行(TextureView创建一个线程,把它当做一个GL线程) 9 | *10 | * 11 | * @author flycatdeng 12 | * 13 | */ 14 | public interface IGLESRenderer { 15 | /** 16 | *
17 | * Surface创建好之后 18 | *19 | */ 20 | public void onSurfaceCreated(); 21 | 22 | /** 23 | *
24 | * 界面大小有更改 25 | *26 | * 27 | * @param width 28 | * @param height 29 | */ 30 | public void onSurfaceChanged(int width, int height); 31 | 32 | /** 33 | *
34 | * 绘制每一帧 35 | *36 | */ 37 | public void onDrawFrame(); 38 | 39 | /** 40 | *
41 | * Activity的onResume时的操作 42 | *43 | */ 44 | public void onResume(); 45 | 46 | /** 47 | *
48 | * Activity的onPause时的操作 49 | *50 | */ 51 | public void onPause(); 52 | 53 | /** 54 | *
55 | * Activity的onDestroy时的操作 56 | *57 | */ 58 | public void onDestroy(); 59 | 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/com/dandy/helper/gles/MaterialAider.java: -------------------------------------------------------------------------------- 1 | package com.dandy.helper.gles; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.util.HashMap; 6 | import java.util.Properties; 7 | 8 | import android.content.Context; 9 | import android.text.TextUtils; 10 | 11 | import com.dandy.helper.android.FileHelper; 12 | import com.dandy.helper.android.LogHelper; 13 | 14 | /** 15 | * need the key word new to create a MaterialAid object 16 | * 17 | * @author dandy 18 | * 19 | */ 20 | public class MaterialAider { 21 | private static final String TAG = MaterialAider.class.getSimpleName(); 22 | private String mMaterialName = "origin.mat"; 23 | private String mMaterialDirectory = ""; 24 | private Properties property = new Properties(); 25 | private Context mContext; 26 | private HashMap
38 | * the default material file will not be changed until we invoke setMaterialName
39 | *
40 | * @param context
41 | */
42 | public MaterialAider(Context context) {
43 | mContext = context;
44 | // try {
45 | // property.load(FileHelper.getInputStreamFromAsset(mContext, mMaterialName));
46 | // } catch (IOException e) {
47 | // e.printStackTrace();
48 | // }
49 | }
50 |
51 | /**
52 | * set the material file we want to use
53 | *
54 | * @param name
55 | */
56 | public void setMaterialName(String name) {
57 | LogHelper.d(TAG, LogHelper.getThreadName() + " name-" + name + " mContext=" + mContext);
58 | mMaterialName = name;
59 | try {
60 | property.load(FileHelper.getInputStreamFromAsset(mContext, mMaterialName));
61 | int lastSep = name.lastIndexOf(File.separator);
62 | if (lastSep != -1) {
63 | mMaterialDirectory = name.substring(0, lastSep);
64 | }
65 | LogHelper.d(TAG, LogHelper.getThreadName() + " name-" + name + " mMaterialDirectory=" + mMaterialDirectory);
66 | } catch (IOException e) {
67 | e.printStackTrace();
68 | LogHelper.d(TAG, LogHelper.getThreadName() + " e=" + e.getMessage());
69 | }
70 | }
71 |
72 | /**
73 | * get the material file name under assets
74 | *
75 | * @return
76 | */
77 | public String getMaterialName() {
78 | return mMaterialName;
79 | }
80 |
81 | /**
82 | * get the vertex shader file name in the material file
83 | *
84 | * @return
85 | */
86 | public String getMaterialVertexName() {
87 | String vertexFile = property.getProperty(Key.VERTEX_FILE);
88 | // LogHelper.d(TAG, LogHelper.getThreadName() + " vertexFile-" + vertexFile);
89 | if (TextUtils.isEmpty(mMaterialDirectory)) {
90 | return vertexFile;
91 | }
92 | return mMaterialDirectory + File.separator + vertexFile;
93 | }
94 |
95 | /**
96 | * get the fragment shader file name in the material file
97 | *
98 | * @return
99 | */
100 | public String getMaterialFragmentName() {
101 | String fragmentFile = property.getProperty(Key.FRAGMENT_FILE);
102 | // LogHelper.d(TAG, LogHelper.getThreadName() + " fragmentFile-" + fragmentFile);
103 | if (TextUtils.isEmpty(mMaterialDirectory)) {
104 | return fragmentFile;
105 | }
106 | return mMaterialDirectory + File.separator + fragmentFile;
107 | }
108 |
109 | /**
110 | * get the version of the material file
111 | *
112 | * @return
113 | */
114 | public String getMaterialVersion() {
115 | String version = property.getProperty(Key.VERSION);
116 | // LogHelper.d(TAG, LogHelper.getThreadName() + " version-" + version);
117 | return version;
118 | }
119 |
120 | public void setMaterialProperty(String key, float value) {
121 | if (key.startsWith("u")) {// uniform
122 | mMaterialUniformMap.put(key, String.valueOf(value));
123 | } else if (key.startsWith("a")) {// attribute
124 |
125 | }
126 | }
127 |
128 | public HashMap
274 | * 在某个大的文件夹中删除指定名称的所有文件,
275 | * 例如这个文件夹下不同的子文件夹下都有一个叫a.txt的文件,那么此时可以用这个方法来删除这个a.txt
276 | *
277 | *
278 | * @param targetFolderFullPath
279 | * 目标文件夹
280 | * @param fileSimpleName
281 | * 要删除的文件的名称(不要全路劲)
282 | */
283 | public static void deleteAppointedFilesInDirectory(String targetFolderFullPath, String fileSimpleName) {
284 | File file = new File(targetFolderFullPath);
285 | if (!file.exists()) {// 文件夹不存在,不用查找
286 | LogHelper.d(TAG, LogHelper.getThreadName() + "file does not exist");
287 | return;
288 | }
289 | String[] files = file.list();
290 | File temp = null;
291 | if (files == null || files.length == 0) {// 文件夹下没有子文件或子文件夹,不用查找
292 | LogHelper.d(TAG, LogHelper.getThreadName() + "files.length == 0");
293 | return;
294 | }
295 | for (int i = 0; i < files.length; i++) {
296 | if (!files[i].equals(fileSimpleName)) {// 如果你的文件名或者文件夹的名称都和目标名称不一致了,那这个就不用判断了,直接判断下一个
297 | continue;
298 | }
299 | if (targetFolderFullPath.endsWith(File.separator)) {
300 | temp = new File(targetFolderFullPath + files[i]);
301 | } else {
302 | temp = new File(targetFolderFullPath + File.separator + files[i]);
303 | }
304 | if (temp.isFile()) {// 是文件,而且名称相同,删除
305 | temp.delete();
306 | // deleteAppointedFile(targetFolderFullPath + "/" + (temp.getName()).toString());
307 | }
308 | if (temp.isDirectory()) {// 如果是子文件夹
309 | deleteAppointedFilesInDirectory(targetFolderFullPath + "/" + files[i], fileSimpleName);
310 | }
311 | }
312 | }
313 |
314 | /**
315 | *
316 | * 在某个大的文件夹中删除指定名称的所有文件夹,
317 | * 例如这个文件夹下不同的子文件夹下都有一个叫.svn的文件夹,那么此时可以用这个方法来删除这个.svn夹
318 | *
319 | *
320 | * @param targetFolderFullPath
321 | * 目标文件夹
322 | * @param directorySimpleName
323 | * 要删除的文件夹的名称(不要全路劲)
324 | */
325 | public static void deleteAppointedDirectorysInDirectory(String targetFolderFullPath, String directorySimpleName) {
326 | File file = new File(targetFolderFullPath);
327 | if (file.getName().equals(directorySimpleName)) {// 如果该文件夹已经是要删除的文件夹名称了,直接删除这个文件夹
328 | LogHelper.d(TAG, LogHelper.getThreadName() + " file.getName()=" + file.getName() + " directorySimpleName=" + directorySimpleName);
329 | deleteFolder(targetFolderFullPath);
330 | return;
331 | }
332 | String[] files = file.list();
333 | File temp = null;
334 | if (files == null || files.length == 0) {// 文件夹下没有子文件或子文件夹,不用查找
335 | LogHelper.d(TAG, LogHelper.getThreadName() + "files.length == 0");
336 | return;
337 | }
338 | for (int i = 0; i < files.length; i++) {
339 | if (!files[i].equals(directorySimpleName)) {// 如果你的文件名或者文件夹的名称都和目标名称不一致了,那这个就不用判断了,直接判断下一个
340 | continue;
341 | }
342 | if (targetFolderFullPath.endsWith(File.separator)) {
343 | temp = new File(targetFolderFullPath + files[i]);
344 | } else {
345 | temp = new File(targetFolderFullPath + File.separator + files[i]);
346 | }
347 | if (temp.isFile()) {// 是文件,而且名称相同,删除
348 | continue;
349 | }
350 | if (temp.isDirectory()) {// 如果是子文件夹
351 | deleteAppointedDirectorysInDirectory(targetFolderFullPath + File.separator + files[i], directorySimpleName);
352 | }
353 | }
354 | }
355 |
356 | /**
357 | * 删除某个文件夹
358 | *
359 | * @param targetFolderFullPath
360 | * 要删除的文件夹的路径
361 | * @return
362 | */
363 | public static boolean deleteFolder(String targetFolderFullPath) {
364 | LogHelper.d(TAG, LogHelper.getThreadName() + "targetFolderFullPath-" + targetFolderFullPath);
365 | File file = new File(targetFolderFullPath);
366 | if (!file.exists()) {
367 | LogHelper.d(TAG, LogHelper.getThreadName() + "file does not exist");
368 | return true;
369 | }
370 | String[] files = file.list();
371 | File temp = null;
372 | if (files == null) {
373 | LogHelper.d(TAG, LogHelper.getThreadName() + "files == null");
374 | return true;
375 | }
376 | if (files.length == 0) {
377 | LogHelper.d(TAG, LogHelper.getThreadName() + "files.length == 0");
378 | boolean success = file.delete();
379 | return success;
380 | }
381 | for (int i = 0; i < files.length; i++) {
382 | if (targetFolderFullPath.endsWith(File.separator)) {
383 | temp = new File(targetFolderFullPath + files[i]);
384 | } else {
385 | temp = new File(targetFolderFullPath + File.separator + files[i]);
386 | }
387 | if (temp.isFile()) {
388 | deleteFile(targetFolderFullPath + File.separator + (temp.getName()).toString());
389 | }
390 | if (temp.isDirectory()) {// 如果是子文件夹
391 | deleteFolder(targetFolderFullPath + File.separator + files[i]);
392 | }
393 | }
394 | boolean success = file.delete();
395 | return success;
396 | }
397 |
398 | /**
399 | * 删除某个文件
400 | *
401 | * @param targetFileFullPath
402 | * 要删除的文件的路径
403 | */
404 | public static void deleteFile(String targetFileFullPath) {
405 | File file = new File(targetFileFullPath);
406 | file.delete();
407 | }
408 | }
409 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dandy/helper/java/PendingThreadAider.java:
--------------------------------------------------------------------------------
1 | package com.dandy.helper.java;
2 |
3 | import java.util.LinkedList;
4 |
5 | /**
6 | * 若在某些条件下才能运行的一些行为,提前运行则失去了意义,所以此时可以将这些事件保存起来,等条件满足之后再全部按顺序运行
7 | *
8 | * @author flycatdeng
9 | *
10 | */
11 | public class PendingThreadAider {
12 | LinkedList
18 | * TextureView中要用的GLThread
19 | * 一个很好的EGL相关学习参考
20 | * 一般在Android中使用OpenGL ES,总是会从GLSurfaceView和Renderer开始,只需要提供一个合适的SurfaceHolder,就可以完成整个环境初始化,并进行绘制。
21 | * GLSurfaceView和Renderer事实上只是在本文描述的基础上封装了一些便利的功能,便于开发者开发,比如渲染同步、状态控制、主(渲染)循环等
22 | *
23 | *
24 | * @author flycatdeng
25 | */
26 | class GLESTVThread extends Thread {
27 | private static final String TAG = "GLESTVThread";
28 | private SurfaceTexture mSurfaceTexture;
29 | private EGL10 mEgl;
30 | private EGLDisplay mEglDisplay = EGL10.EGL_NO_DISPLAY;// 显示设备
31 | private EGLSurface mEglSurface = EGL10.EGL_NO_SURFACE;
32 | private EGLContext mEglContext = EGL10.EGL_NO_CONTEXT;
33 | // private GL mGL;
34 | private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
35 | private static final int EGL_OPENGL_ES2_BIT = 4;
36 | private IGLESRenderer mRenderer;
37 | private PendingThreadAider mPendingThreadAider = new PendingThreadAider();
38 | private boolean mNeedRenderring = true;
39 | private Object LOCK = new Object();
40 | private boolean mIsPaused = false;
41 |
42 | public GLESTVThread(SurfaceTexture surface, IGLESRenderer renderer) {
43 | mSurfaceTexture = surface;
44 | mRenderer = renderer;
45 | }
46 |
47 | @Override
48 | public void run() {
49 | LogHelper.d(TAG, LogHelper.getThreadName());
50 | initGLESContext();
51 | mRenderer.onSurfaceCreated();
52 | while (mNeedRenderring) {
53 | mPendingThreadAider.runPendings();// 执行未执行的,或要执行的事件。(后期可以开放以便模仿GLSurfaceView的queueEvent(Runnable r))
54 | mRenderer.onDrawFrame();// 绘制
55 | // 一帧完成之后,调用eglSwapBuffers(EGLDisplay dpy, EGLContext ctx)来显示
56 | mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);// 这一句不能少啊,少了就GG了,一片空白
57 | // 1.凡是onPause都要停止,2.如果是onResume的状态,如果是循环刷新则会继续下一次循环,否则会暂停等待调用requestRender()
58 | if (mIsPaused) {
59 | pauseWhile();
60 | } else if (mRendererMode == GLESTextureView.RENDERMODE_WHEN_DIRTY) {
61 | pauseWhile();
62 | }
63 | }
64 | destoryGLESContext();
65 | }
66 |
67 | private void initGLESContext() {
68 | LogHelper.d(TAG, LogHelper.getThreadName());
69 | mEgl = (EGL10) EGLContext.getEGL();
70 | mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);// 获取显示设备
71 | if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
72 | throw new RuntimeException("eglGetdisplay failed : " + GLUtils.getEGLErrorString(mEgl.eglGetError()));
73 | }
74 |
75 | int[] version = new int[2];
76 | if (!mEgl.eglInitialize(mEglDisplay, version)) {// //version中存放EGL 版本号,int[0]为主版本号,int[1]为子版本号
77 | throw new RuntimeException("eglInitialize failed : " + GLUtils.getEGLErrorString(mEgl.eglGetError()));
78 | }
79 |
80 | // 构造需要的特性列表
81 | int[] configAttribs = { //
82 | EGL10.EGL_BUFFER_SIZE, 32,//
83 | EGL10.EGL_ALPHA_SIZE, 8, // 指定Alpha大小,以下四项实际上指定了像素格式
84 | EGL10.EGL_BLUE_SIZE, 8, // 指定B大小
85 | EGL10.EGL_GREEN_SIZE, 8,// 指定G大小
86 | EGL10.EGL_RED_SIZE, 8,// 指定RGB中的R大小(bits)
87 | EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,// 指定渲染api类别,这里或者是硬编码的4,或者是EGL14.EGL_OPENGL_ES2_BIT
88 | EGL10.EGL_SURFACE_TYPE, EGL10.EGL_WINDOW_BIT, EGL10.EGL_NONE// 总是以EGL10.EGL_NONE结尾
89 | };
90 |
91 | int[] numConfigs = new int[1];
92 | EGLConfig[] configs = new EGLConfig[1];
93 | // eglChooseConfig(display, attributes, configs, num, configNum);
94 | // 用于获取满足attributes的所有config,参数1、2其意明显,参数3用于存放输出的configs,参数4指定最多输出多少个config,参数5由EGL系统写入,表明满足attributes的config一共有多少个
95 | if (!mEgl.eglChooseConfig(mEglDisplay, configAttribs, configs, 1, numConfigs)) {// 获取所有满足attributes的configs,并选择一个
96 | throw new RuntimeException("eglChooseConfig failed : " + GLUtils.getEGLErrorString(mEgl.eglGetError()));
97 | }
98 |
99 | int[] contextAttribs = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE};// attrib_list,目前可用属性只有EGL_CONTEXT_CLIENT_VERSION, 1代表OpenGL ES 1.x,
100 | // 2代表2.0。同样在Android4.2之前,没有EGL_CONTEXT_CLIENT_VERSION这个属性,只能使用硬编码0x3098代替
101 | mEglContext = mEgl.eglCreateContext(mEglDisplay, configs[0], EGL10.EGL_NO_CONTEXT, // share_context,是否有context共享,共享的contxt之间亦共享所有数据。EGL_NO_CONTEXT代表不共享
102 | contextAttribs);// 创建context
103 | mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, configs[0], mSurfaceTexture,// 负责对Android Surface的管理
104 | null// Surface属性
105 | );// 获取显存,create a new EGL window surface
106 | if (mEglSurface == EGL10.EGL_NO_SURFACE || mEglContext == EGL10.EGL_NO_CONTEXT) {
107 | int error = mEgl.eglGetError();
108 | if (error == EGL10.EGL_BAD_NATIVE_WINDOW) {
109 | throw new RuntimeException("eglCreateWindowSurface returned EGL_BAD_NATIVE_WINDOW. ");
110 | }
111 | throw new RuntimeException("eglCreateWindowSurface failed : " + GLUtils.getEGLErrorString(mEgl.eglGetError()));
112 | }
113 |
114 | if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {// 设置为当前的渲染环境
115 | throw new RuntimeException("eglMakeCurrent failed : " + GLUtils.getEGLErrorString(mEgl.eglGetError()));
116 | }
117 | // mGL = mEglContext.getGL();
118 | }
119 |
120 | private void pauseWhile() {
121 | synchronized (LOCK) {
122 | try {
123 | LOCK.wait();
124 | } catch (InterruptedException e) {
125 | e.printStackTrace();
126 | }
127 | }
128 | }
129 |
130 | private void destoryGLESContext() {
131 | LogHelper.d(TAG, LogHelper.getThreadName());
132 | mEgl.eglDestroyContext(mEglDisplay, mEglContext);
133 | mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
134 | mEglContext = EGL10.EGL_NO_CONTEXT;
135 | mEglSurface = EGL10.EGL_NO_SURFACE;
136 | }
137 |
138 | public void onPause() {
139 | mRenderer.onPause();
140 | mIsPaused = true;
141 | }
142 |
143 | public void onResume() {
144 | mRenderer.onResume();
145 | mIsPaused = false;
146 | requestRender();
147 | }
148 |
149 | public void onSurfaceChanged(final int width, final int height) {
150 | mPendingThreadAider.addToPending(new Runnable() {// 在GL线程中执行
151 |
152 | @Override
153 | public void run() {
154 | mRenderer.onSurfaceChanged(width, height);
155 | }
156 | });
157 | }
158 |
159 | private int mRendererMode = GLESTextureView.RENDERMODE_CONTINUOUSLY;
160 |
161 | public void setRenderMode(int mode) {
162 | mRendererMode = mode;
163 | }
164 |
165 | public void requestRender() {
166 | synchronized (LOCK) {
167 | LOCK.notifyAll();
168 | }
169 | }
170 |
171 | public void onDestroy() {
172 | mNeedRenderring = false;
173 | mRenderer.onDestroy();
174 | destoryGLESContext();
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dandy/module/gles/textureview/GLESTextureView.java:
--------------------------------------------------------------------------------
1 | package com.dandy.module.gles.textureview;
2 |
3 | import android.content.Context;
4 | import android.graphics.SurfaceTexture;
5 | import android.util.AttributeSet;
6 | import android.view.TextureView;
7 |
8 | import com.dandy.helper.gles.IGLESRenderer;
9 |
10 | /**
11 | *
12 | * 一个类似于GLSurfaceView的TextureView,用于显示opengl
13 | * {@link #setRenderer(IGLESRenderer)}类似于GLSurfaceView的setRenderer(Renderer)
14 | * {{@link #setRenderMode(int)}类似于GLSurfaceView的setRenderMode(int)
15 | * 详细调用可模仿com.dandy.gldemo.glestextureview.DemoGlesTextureView
16 | * 没事可看参照1>或者参照2
17 | *
18 | *
19 | * @author flycatdeng
20 | *
21 | */
22 | public class GLESTextureView extends TextureView implements TextureView.SurfaceTextureListener {
23 | public final static int RENDERMODE_WHEN_DIRTY = 0;
24 | public final static int RENDERMODE_CONTINUOUSLY = 1;
25 | private GLESTVThread mGLThread;
26 | private IGLESRenderer mRenderer;
27 | private int mRendererMode = RENDERMODE_CONTINUOUSLY;
28 |
29 | public GLESTextureView(Context context) {
30 | super(context);
31 | init(context);
32 | }
33 |
34 | public GLESTextureView(Context context, AttributeSet attrs) {
35 | super(context, attrs);
36 | init(context);
37 | }
38 |
39 | /**
40 | *
41 | * 类似于GLSurfaceView的setRenderer
42 | *
43 | */
44 | public void setRenderer(IGLESRenderer renderer) {
45 | mRenderer = renderer;
46 | }
47 |
48 | /**
49 | *
50 | * 类似于GLSurfaceView的setRenderMode
51 | * 渲染模式,是循环刷新,还是请求的时候刷新
52 | *
53 | */
54 | public void setRenderMode(int mode) {
55 | mRendererMode = mode;
56 | }
57 |
58 | /**
59 | * Request that the renderer render a frame. This method is typically used when the render mode has been set to {@link #RENDERMODE_WHEN_DIRTY}, so
60 | * that frames are only rendered on demand. May be called from any thread. Must not be called before a renderer has been set.
61 | */
62 | public void requestRender() {
63 | if (mRendererMode != RENDERMODE_WHEN_DIRTY) {
64 | return;
65 | }
66 | mGLThread.requestRender();
67 | }
68 |
69 | private void init(Context context) {
70 | setSurfaceTextureListener(this);
71 | }
72 |
73 | @Override
74 | public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
75 | mGLThread = new GLESTVThread(surface, mRenderer);// 创建一个线程,作为GL线程
76 | mGLThread.setRenderMode(mRendererMode);
77 | mGLThread.start();
78 | mGLThread.onSurfaceChanged(width, height);
79 | }
80 |
81 | @Override
82 | public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
83 | return false;
84 | }
85 |
86 | @Override
87 | public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
88 | mGLThread.onSurfaceChanged(width, height);
89 | }
90 |
91 | @Override
92 | public void onSurfaceTextureUpdated(SurfaceTexture surface) {
93 |
94 | }
95 |
96 | public void onResume() {
97 | if (mGLThread != null) {
98 | mGLThread.onResume();
99 | }
100 | }
101 |
102 | public void onPause() {
103 | if (mGLThread != null) {
104 | mGLThread.onPause();
105 | }
106 | }
107 |
108 | public void onDestroy() {
109 | if (mGLThread != null) {
110 | mGLThread.onDestroy();
111 | }
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |