├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── sample ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ └── sample.mp4 │ ├── java │ └── jp │ │ └── wasabeef │ │ └── roundedtextureview │ │ ├── GLRoundedGeometry.java │ │ ├── GLTextureView.java │ │ ├── MainActivity.java │ │ ├── MultiSampleEGLConfigChooser.java │ │ └── RoundedTextureView.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 └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X files 2 | .DS_Store 3 | Thumbs.db 4 | 5 | # gradle files 6 | .gradle 7 | 8 | # Intellij project files 9 | .idea 10 | *.iml 11 | 12 | # generated files 13 | bin/ 14 | gen/ 15 | obj/ 16 | apk/ 17 | target/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | com_crashlytics_export_strings.xml 27 | crashlytics-build.properties 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RoundedTextureView 2 | 3 | Sample Android code that displays video with rounded corners. 4 | 5 | Use: 6 | - TextureView (GLTextureView) 7 | - OpenGL ES 2.0 8 | 9 | 10 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.0.0-beta6' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | } 19 | } 20 | 21 | task clean(type: Delete) { 22 | delete rootProject.buildDir 23 | } 24 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasabeef/android-RoundedTextureView/d234fe99ec208600efd569e6613a9ea9d98e3b15/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Dec 28 10:00:20 PST 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.11-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /sample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.2" 6 | 7 | defaultConfig { 8 | applicationId "jp.wasabeef.roundedtextureview" 9 | minSdkVersion 14 10 | targetSdkVersion 23 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | testCompile 'junit:junit:4.12' 25 | compile 'com.android.support:appcompat-v7:23.2.0' 26 | } 27 | -------------------------------------------------------------------------------- /sample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 14 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /sample/src/main/assets/sample.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasabeef/android-RoundedTextureView/d234fe99ec208600efd569e6613a9ea9d98e3b15/sample/src/main/assets/sample.mp4 -------------------------------------------------------------------------------- /sample/src/main/java/jp/wasabeef/roundedtextureview/GLRoundedGeometry.java: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.roundedtextureview; 2 | 3 | import android.graphics.Point; 4 | import android.graphics.RectF; 5 | import android.support.annotation.NonNull; 6 | 7 | /** 8 | * Changed https://github.com/fabrantes/videoroundedcorners 9 | * 10 | * Created by Daichi Furiya on 2015/10/09. 11 | */ 12 | public class GLRoundedGeometry { 13 | 14 | private float[] leftTop = new float[2]; 15 | private float[] leftBottom = new float[2]; 16 | private float[] topLeft = new float[2]; 17 | private float[] topRight = new float[2]; 18 | private float[] rightTop = new float[2]; 19 | private float[] rightBottom = new float[2]; 20 | private float[] bottomLeft = new float[2]; 21 | private float[] bottomRight = new float[2]; 22 | 23 | private float[] innerTopLeft = new float[2]; 24 | private float[] innerTopRight = new float[2]; 25 | private float[] innerBottomRight = new float[2]; 26 | private float[] innerBottomLeft = new float[2]; 27 | 28 | private float[] topLeftRadius = new float[2]; 29 | private float[] topRightRadius = new float[2]; 30 | private float[] bottomRightRadius = new float[2]; 31 | private float[] bottomLeftRadius = new float[2]; 32 | 33 | public GeometryArrays generateVertexData(RectF radius, RectF viewPortGLBounds, 34 | Point viewPortPxSize) { 35 | return generateVertexData(radius, viewPortGLBounds, viewPortPxSize, 0f); 36 | } 37 | 38 | /** 39 | * Generates a {@link GeometryArrays} object with arrays containing the resulting geometry 40 | * vertices and the corresponding triangle indexes. 41 | * 42 | * @param radius the corner radius of each corner. left is topLeft, top is topRight, right is 43 | * rightBottom and bottom is leftBottom. 44 | * @param viewPortGLBounds the bounds of the GL viewport in GL scalar units. 45 | * @param viewPortPxSize the size of the view port in pixels. 46 | * @param z the z coordinate for the z-plane geometry. 47 | * @return an object with the resulting geometry. 48 | */ 49 | public GeometryArrays generateVertexData(RectF radius, RectF viewPortGLBounds, 50 | Point viewPortPxSize, float z) { 51 | final float x0 = viewPortGLBounds.left; 52 | final float x1 = viewPortGLBounds.right; 53 | final float y0 = viewPortGLBounds.bottom; 54 | final float y1 = viewPortGLBounds.top; 55 | 56 | final float leftTopRadius = radius.left; 57 | final float rightTopRadius = radius.top; 58 | final float rightBottomRadius = radius.right; 59 | final float leftBottomRadius = radius.bottom; 60 | 61 | topLeftRadius[0] = leftTopRadius / viewPortPxSize.x * viewPortGLBounds.width(); 62 | topLeftRadius[1] = leftTopRadius / viewPortPxSize.y * -viewPortGLBounds.height(); 63 | topRightRadius[0] = rightTopRadius / viewPortPxSize.x * viewPortGLBounds.width(); 64 | topRightRadius[1] = rightTopRadius / viewPortPxSize.y * -viewPortGLBounds.height(); 65 | bottomRightRadius[0] = rightBottomRadius / viewPortPxSize.x * viewPortGLBounds.width(); 66 | bottomRightRadius[1] = rightBottomRadius / viewPortPxSize.y * -viewPortGLBounds.height(); 67 | bottomLeftRadius[0] = leftBottomRadius / viewPortPxSize.x * viewPortGLBounds.width(); 68 | bottomLeftRadius[1] = leftBottomRadius / viewPortPxSize.y * -viewPortGLBounds.height(); 69 | 70 | leftTop[0] = x0; 71 | leftTop[1] = y1 - topLeftRadius[1]; 72 | leftBottom[0] = x0; 73 | leftBottom[1] = y0 + bottomLeftRadius[1]; 74 | topLeft[0] = x0 + topLeftRadius[0]; 75 | topLeft[1] = y1; 76 | topRight[0] = x1 - topRightRadius[0]; 77 | topRight[1] = y1; 78 | rightTop[0] = x1; 79 | rightTop[1] = y1 - topRightRadius[1]; 80 | rightBottom[0] = x1; 81 | rightBottom[1] = y0 + bottomRightRadius[1]; 82 | bottomLeft[0] = x0 + bottomLeftRadius[0]; 83 | bottomLeft[1] = y0; 84 | bottomRight[0] = x1 - bottomRightRadius[0]; 85 | bottomRight[1] = y0; 86 | 87 | innerTopLeft[0] = topLeft[0]; 88 | innerTopLeft[1] = leftTop[1]; 89 | innerTopRight[0] = topRight[0]; 90 | innerTopRight[1] = rightTop[1]; 91 | innerBottomLeft[0] = bottomLeft[0]; 92 | innerBottomLeft[1] = leftBottom[1]; 93 | innerBottomRight[0] = bottomRight[0]; 94 | innerBottomRight[1] = rightBottom[1]; 95 | 96 | // Each vertex has 5 floats (xyz + uv) 97 | // 5 squares (each has 4 vertices) 98 | // 4 rounded corners (each has X triangles, each triangle has 3 vertices) 99 | final int trianglesPerCorner = 6; 100 | final int floatsPerRoundedCorner = (trianglesPerCorner + 2) * 5; 101 | final int floatsPerSquare = 4 * 5; 102 | final int shortsPerTriangle = 3; 103 | final int shortsPerSquare = 2 * shortsPerTriangle; 104 | final int verticesSize = 5 * floatsPerSquare + 4 * floatsPerRoundedCorner; 105 | final int indicesSize = 5 * shortsPerSquare + 4 * trianglesPerCorner * shortsPerTriangle; 106 | final float[] vertices = new float[verticesSize]; 107 | final short[] indices = new short[indicesSize]; 108 | final GeometryArrays geoArrays = new GeometryArrays(vertices, indices); 109 | 110 | // Inner center rect 111 | addRect(geoArrays, new float[][] { 112 | innerTopLeft, innerTopRight, innerBottomLeft, innerBottomRight 113 | }, viewPortGLBounds, z); 114 | geoArrays.verticesOffset += floatsPerSquare; 115 | geoArrays.indicesOffset += shortsPerSquare; 116 | 117 | // Left rect 118 | addRect(geoArrays, new float[][] { 119 | leftTop, innerTopLeft, leftBottom, innerBottomLeft 120 | }, viewPortGLBounds, z); 121 | geoArrays.verticesOffset += floatsPerSquare; 122 | geoArrays.indicesOffset += shortsPerSquare; 123 | 124 | // Right rect 125 | addRect(geoArrays, new float[][] { 126 | innerTopRight, rightTop, innerBottomRight, rightBottom 127 | }, viewPortGLBounds, z); 128 | geoArrays.verticesOffset += floatsPerSquare; 129 | geoArrays.indicesOffset += shortsPerSquare; 130 | 131 | // Top rect 132 | addRect(geoArrays, new float[][] { 133 | topLeft, innerTopLeft, topRight, innerTopRight 134 | }, viewPortGLBounds, z); 135 | geoArrays.verticesOffset += floatsPerSquare; 136 | geoArrays.indicesOffset += shortsPerSquare; 137 | 138 | // Bottom rect 139 | addRect(geoArrays, new float[][] { 140 | innerBottomLeft, bottomLeft, innerBottomRight, bottomRight 141 | }, viewPortGLBounds, z); 142 | geoArrays.verticesOffset += floatsPerSquare; 143 | geoArrays.indicesOffset += shortsPerSquare; 144 | 145 | // These assume uniform corners (i.e. same radius on both axis) 146 | // Top left corner 147 | addRoundedCorner(geoArrays, innerTopLeft, topLeftRadius, (float) Math.PI, 148 | (float) (Math.PI / 2.0), trianglesPerCorner, viewPortGLBounds, z); 149 | geoArrays.verticesOffset += floatsPerRoundedCorner; 150 | geoArrays.indicesOffset += trianglesPerCorner * shortsPerTriangle; 151 | 152 | // Top right corner 153 | addRoundedCorner(geoArrays, innerTopRight, topRightRadius, (float) (Math.PI / 2), 0f, 154 | trianglesPerCorner, viewPortGLBounds, z); 155 | geoArrays.verticesOffset += floatsPerRoundedCorner; 156 | geoArrays.indicesOffset += trianglesPerCorner * shortsPerTriangle; 157 | 158 | // Bottom right corner 159 | addRoundedCorner(geoArrays, innerBottomRight, bottomRightRadius, (float) (Math.PI * 3.0 / 2.0), 160 | (float) Math.PI * 2, trianglesPerCorner, viewPortGLBounds, z); 161 | geoArrays.verticesOffset += floatsPerRoundedCorner; 162 | geoArrays.indicesOffset += trianglesPerCorner * shortsPerTriangle; 163 | 164 | // Bottom left corner 165 | addRoundedCorner(geoArrays, innerBottomLeft, bottomLeftRadius, (float) Math.PI, 166 | (float) (Math.PI * 3.0 / 2.0), trianglesPerCorner, viewPortGLBounds, z); 167 | 168 | return new GeometryArrays(vertices, indices); 169 | } 170 | 171 | /** 172 | * Adds the vertices of a rectangle defined by 4 corner points. The array of vertices passed 173 | * in must have the required length to add the geometry points (5 floats for each vertex). Also 174 | * the coordinates of the rect corners should already be in the view port space. 175 | * 176 | * @param geoArrays an object containing the vertex and index data arrays and their current 177 | * offsets. 178 | * @param rectPoints an array of corner points defining the rectangle. index 0 is the x 179 | * coordinate and index 1 the y coordinate. 180 | * @param viewPort the bounds of the current GL viewport, this is used to calculate the texture 181 | * mapping. 182 | * @param z the z coordinate. 183 | */ 184 | private void addRect(@NonNull GeometryArrays geoArrays, @NonNull float[][] rectPoints, 185 | @NonNull RectF viewPort, float z) { 186 | final float[] vertices = geoArrays.triangleVertices; 187 | final short[] indices = geoArrays.triangleIndices; 188 | final int indicesOffset = geoArrays.indicesOffset; 189 | final int verticesOffset = geoArrays.verticesOffset; 190 | int rectPointIdx = 0; 191 | for (final float[] rectPoint : rectPoints) { 192 | // 5 values [xyzuv] per vertex 193 | final int currentVertexOffset = verticesOffset + rectPointIdx * 5; 194 | 195 | // XYZ (vertex space coordinates 196 | vertices[currentVertexOffset] = rectPoint[0]; 197 | vertices[currentVertexOffset + 1] = rectPoint[1]; 198 | vertices[currentVertexOffset + 2] = z; 199 | 200 | // UV (texture mapping) 201 | vertices[currentVertexOffset + 3] = (rectPoint[0] - viewPort.left) / viewPort.width(); 202 | vertices[currentVertexOffset + 4] = (rectPoint[1] - viewPort.bottom) / -viewPort.height(); 203 | 204 | rectPointIdx++; 205 | } 206 | 207 | // Index our triangles -- tell where each triangle vertex is 208 | final int initialIdx = verticesOffset / 5; 209 | indices[indicesOffset] = (short) (initialIdx); 210 | indices[indicesOffset + 1] = (short) (initialIdx + 1); 211 | indices[indicesOffset + 2] = (short) (initialIdx + 2); 212 | indices[indicesOffset + 3] = (short) (initialIdx + 1); 213 | indices[indicesOffset + 4] = (short) (initialIdx + 2); 214 | indices[indicesOffset + 5] = (short) (initialIdx + 3); 215 | } 216 | 217 | /** 218 | * Adds the vertices of a number of triangles to form a rounded corner. The triangles start at 219 | * some center point and will sweep from a given initial angle up to a final one. The size of 220 | * the triangles is defined by the radius. 221 | * 222 | * The array of vertices passed in must have the required length to add the geometry points 223 | * (5 floats for each vertex). Also the coordinates of the rect corners should already be in 224 | * the view port space. 225 | * 226 | * @param geoArrays an object containing the vertex and index data arrays and their current 227 | * offsets. 228 | * @param center the center point where all triangles will start. 229 | * @param radius the desired radius in the x and y axis, in viewport dimensions. 230 | * @param rads0 the initial angle. 231 | * @param rads1 the final angle. 232 | * @param triangles the amount of triangles to create. 233 | * @param viewPort the bounds of the current GL viewport, this is used to calculate the texture 234 | * mapping. 235 | * @param z the z coordinate. 236 | */ 237 | private void addRoundedCorner(@NonNull GeometryArrays geoArrays, @NonNull float[] center, 238 | float[] radius, float rads0, float rads1, int triangles, @NonNull RectF viewPort, float z) { 239 | final float[] vertices = geoArrays.triangleVertices; 240 | final short[] indices = geoArrays.triangleIndices; 241 | final int verticesOffset = geoArrays.verticesOffset; 242 | final int indicesOffset = geoArrays.indicesOffset; 243 | for (int i = 0; i < triangles; i++) { 244 | // final int currentOffset = verticesOffset + i * 15 /* each triangle is 3 * xyzuv */; 245 | final int currentOffset = verticesOffset + i * 5 + (i > 0 ? 2 * 5 : 0); 246 | final float rads = rads0 + (rads1 - rads0) * (i / (float) triangles); 247 | final float radsNext = rads0 + (rads1 - rads0) * ((i + 1) / (float) triangles); 248 | final int triangleEdge2Offset; 249 | 250 | if (i == 0) { 251 | // XYZUV - center point 252 | vertices[currentOffset] = center[0]; 253 | vertices[currentOffset + 1] = center[1]; 254 | vertices[currentOffset + 2] = z; 255 | vertices[currentOffset + 3] = (vertices[currentOffset] - viewPort.left) / viewPort.width(); 256 | vertices[currentOffset + 4] = 257 | (vertices[currentOffset + 1] - viewPort.bottom) / -viewPort.height(); 258 | 259 | // XYZUV - triangle edge 1 260 | vertices[currentOffset + 5] = center[0] + radius[0] * (float) Math.cos(rads); 261 | vertices[currentOffset + 6] = center[1] + radius[1] * (float) Math.sin(rads); 262 | vertices[currentOffset + 7] = z; 263 | vertices[currentOffset + 8] = 264 | (vertices[currentOffset + 5] - viewPort.left) / viewPort.width(); 265 | vertices[currentOffset + 9] = 266 | (vertices[currentOffset + 6] - viewPort.bottom) / -viewPort.height(); 267 | 268 | triangleEdge2Offset = 10; 269 | } else { 270 | triangleEdge2Offset = 0; 271 | } 272 | 273 | // XYZUV - triangle edge 2 274 | final int edge2Offset = currentOffset + triangleEdge2Offset; 275 | vertices[edge2Offset] = center[0] + radius[0] * (float) Math.cos(radsNext); 276 | vertices[edge2Offset + 1] = center[1] + radius[1] * (float) Math.sin(radsNext); 277 | vertices[edge2Offset + 2] = z; 278 | vertices[edge2Offset + 3] = (vertices[edge2Offset] - viewPort.left) / viewPort.width(); 279 | vertices[edge2Offset + 4] = 280 | (vertices[edge2Offset + 1] - viewPort.bottom) / -viewPort.height(); 281 | 282 | // Index our triangles -- tell where each triangle vertex is 283 | final int initialIdx = verticesOffset / 5; 284 | indices[indicesOffset + i * 3] = (short) (initialIdx); 285 | indices[indicesOffset + i * 3 + 1] = (short) (initialIdx + i + 1); 286 | indices[indicesOffset + i * 3 + 2] = (short) (initialIdx + i + 2); 287 | } 288 | } 289 | 290 | public static class GeometryArrays { 291 | public float[] triangleVertices; 292 | public short[] triangleIndices; 293 | public int verticesOffset = 0; 294 | public int indicesOffset = 0; 295 | 296 | public GeometryArrays(@NonNull float[] vertices, @NonNull short[] indices) { 297 | triangleVertices = vertices; 298 | triangleIndices = indices; 299 | } 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /sample/src/main/java/jp/wasabeef/roundedtextureview/GLTextureView.java: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.roundedtextureview; 2 | 3 | import android.content.Context; 4 | import android.graphics.SurfaceTexture; 5 | import android.opengl.GLDebugHelper; 6 | import android.util.AttributeSet; 7 | import android.util.Log; 8 | import android.view.TextureView; 9 | import android.view.View; 10 | import java.io.Writer; 11 | import java.lang.ref.WeakReference; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | import javax.microedition.khronos.egl.EGL10; 15 | import javax.microedition.khronos.egl.EGL11; 16 | import javax.microedition.khronos.egl.EGLConfig; 17 | import javax.microedition.khronos.egl.EGLContext; 18 | import javax.microedition.khronos.egl.EGLDisplay; 19 | import javax.microedition.khronos.egl.EGLSurface; 20 | import javax.microedition.khronos.opengles.GL; 21 | import javax.microedition.khronos.opengles.GL10; 22 | 23 | /** 24 | * See {@link android.opengl.GLSurfaceView} 25 | * 26 | * Created by Daichi Furiya on 2015/10/09. 27 | */ 28 | public class GLTextureView extends TextureView 29 | implements TextureView.SurfaceTextureListener, View.OnLayoutChangeListener { 30 | 31 | private final static String TAG = GLTextureView.class.getSimpleName(); 32 | 33 | private final static boolean LOG_ATTACH_DETACH = false; 34 | private final static boolean LOG_THREADS = false; 35 | private final static boolean LOG_PAUSE_RESUME = false; 36 | private final static boolean LOG_SURFACE = false; 37 | private final static boolean LOG_RENDERER = false; 38 | private final static boolean LOG_RENDERER_DRAW_FRAME = false; 39 | private final static boolean LOG_EGL = false; 40 | 41 | /** 42 | * The renderer only renders 43 | * when the surface is created, or when {@link #requestRender} is called. 44 | * 45 | * @see #getRenderMode() 46 | * @see #setRenderMode(int) 47 | * @see #requestRender() 48 | */ 49 | public final static int RENDERMODE_WHEN_DIRTY = 0; 50 | /** 51 | * The renderer is called 52 | * continuously to re-render the scene. 53 | * 54 | * @see #getRenderMode() 55 | * @see #setRenderMode(int) 56 | */ 57 | public final static int RENDERMODE_CONTINUOUSLY = 1; 58 | 59 | /** 60 | * Check glError() after every GL call and throw an exception if glError indicates 61 | * that an error has occurred. This can be used to help track down which OpenGL ES call 62 | * is causing an error. 63 | * 64 | * @see #getDebugFlags 65 | * @see #setDebugFlags 66 | */ 67 | public final static int DEBUG_CHECK_GL_ERROR = 1; 68 | 69 | /** 70 | * Log GL calls to the system log at "verbose" level with tag "GLTextureView". 71 | * 72 | * @see #getDebugFlags 73 | * @see #setDebugFlags 74 | */ 75 | public final static int DEBUG_LOG_GL_CALLS = 2; 76 | 77 | /** 78 | * Standard View constructor. In order to render something, you 79 | * must call {@link #setRenderer} to register a renderer. 80 | */ 81 | public GLTextureView(Context context) { 82 | super(context); 83 | init(); 84 | } 85 | 86 | /** 87 | * Standard View constructor. In order to render something, you 88 | * must call {@link #setRenderer} to register a renderer. 89 | */ 90 | public GLTextureView(Context context, AttributeSet attrs) { 91 | super(context, attrs); 92 | init(); 93 | } 94 | 95 | @Override protected void finalize() throws Throwable { 96 | try { 97 | if (glThread != null) { 98 | // GLThread may still be running if this view was never 99 | // attached to a window. 100 | glThread.requestExitAndWait(); 101 | } 102 | } finally { 103 | super.finalize(); 104 | } 105 | } 106 | 107 | private void init() { 108 | setSurfaceTextureListener(this); 109 | } 110 | 111 | /** 112 | * Set the glWrapper. If the glWrapper is not null, its 113 | * {@link GLWrapper#wrap(GL)} method is called 114 | * whenever a surface is created. A GLWrapper can be used to wrap 115 | * the GL object that's passed to the renderer. Wrapping a GL 116 | * object enables examining and modifying the behavior of the 117 | * GL calls made by the renderer. 118 | *

119 | * Wrapping is typically used for debugging purposes. 120 | *

121 | * The default value is null. 122 | * 123 | * @param glWrapper the new GLWrapper 124 | */ 125 | public void setGLWrapper(GLWrapper glWrapper) { 126 | this.glWrapper = glWrapper; 127 | } 128 | 129 | /** 130 | * Set the debug flags to a new value. The value is 131 | * constructed by OR-together zero or more 132 | * of the DEBUG_CHECK_* constants. The debug flags take effect 133 | * whenever a surface is created. The default value is zero. 134 | * 135 | * @param debugFlags the new debug flags 136 | * @see #DEBUG_CHECK_GL_ERROR 137 | * @see #DEBUG_LOG_GL_CALLS 138 | */ 139 | public void setDebugFlags(int debugFlags) { 140 | this.debugFlags = debugFlags; 141 | } 142 | 143 | /** 144 | * Get the current value of the debug flags. 145 | * 146 | * @return the current value of the debug flags. 147 | */ 148 | public int getDebugFlags() { 149 | return debugFlags; 150 | } 151 | 152 | /** 153 | * Control whether the EGL context is preserved when the GLTextureView is paused and 154 | * resumed. 155 | *

156 | * If set to true, then the EGL context may be preserved when the GLTextureView is paused. 157 | * Whether the EGL context is actually preserved or not depends upon whether the 158 | * Android device that the program is running on can support an arbitrary number of EGL 159 | * contexts or not. Devices that can only support a limited number of EGL contexts must 160 | * release the EGL context in order to allow multiple applications to share the GPU. 161 | *

162 | * If set to false, the EGL context will be released when the GLTextureView is paused, 163 | * and recreated when the GLTextureView is resumed. 164 | *

165 | * 166 | * The default is false. 167 | * 168 | * @param preserveOnPause preserve the EGL context when paused 169 | */ 170 | public void setPreserveEGLContextOnPause(boolean preserveOnPause) { 171 | preserveEGLContextOnPause = preserveOnPause; 172 | } 173 | 174 | /** 175 | * @return true if the EGL context will be preserved when paused 176 | */ 177 | public boolean getPreserveEGLContextOnPause() { 178 | return preserveEGLContextOnPause; 179 | } 180 | 181 | /** 182 | * Set the renderer associated with this view. Also starts the thread that 183 | * will call the renderer, which in turn causes the rendering to start. 184 | *

This method should be called once and only once in the life-cycle of 185 | * a GLTextureView. 186 | *

The following GLTextureView methods can only be called before 187 | * setRenderer is called: 188 | *

193 | *

194 | * The following GLTextureView methods can only be called after 195 | * setRenderer is called: 196 | *

204 | * 205 | * @param renderer the renderer to use to perform OpenGL drawing. 206 | */ 207 | public void setRenderer(Renderer renderer) { 208 | checkRenderThreadState(); 209 | if (eglConfigChooser == null) { 210 | eglConfigChooser = new SimpleEGLConfigChooser(true); 211 | } 212 | if (eglContextFactory == null) { 213 | eglContextFactory = new DefaultContextFactory(); 214 | } 215 | if (eglWindowSurfaceFactory == null) { 216 | eglWindowSurfaceFactory = new DefaultWindowSurfaceFactory(); 217 | } 218 | this.renderer = renderer; 219 | glThread = new GLThread(mThisWeakRef); 220 | glThread.start(); 221 | } 222 | 223 | /** 224 | * Install a custom EGLContextFactory. 225 | *

If this method is 226 | * called, it must be called before {@link #setRenderer(Renderer)} 227 | * is called. 228 | *

229 | * If this method is not called, then by default 230 | * a context will be created with no shared context and 231 | * with a null attribute list. 232 | */ 233 | public void setEGLContextFactory(EGLContextFactory factory) { 234 | checkRenderThreadState(); 235 | eglContextFactory = factory; 236 | } 237 | 238 | /** 239 | * Install a custom EGLWindowSurfaceFactory. 240 | *

If this method is 241 | * called, it must be called before {@link #setRenderer(Renderer)} 242 | * is called. 243 | *

244 | * If this method is not called, then by default 245 | * a window surface will be created with a null attribute list. 246 | */ 247 | public void setEGLWindowSurfaceFactory(EGLWindowSurfaceFactory factory) { 248 | checkRenderThreadState(); 249 | eglWindowSurfaceFactory = factory; 250 | } 251 | 252 | /** 253 | * Install a custom EGLConfigChooser. 254 | *

If this method is 255 | * called, it must be called before {@link #setRenderer(Renderer)} 256 | * is called. 257 | *

258 | * If no setEGLConfigChooser method is called, then by default the 259 | * view will choose an EGLConfig that is compatible with the current 260 | * android.view.Surface, with a depth buffer depth of 261 | * at least 16 bits. 262 | */ 263 | public void setEGLConfigChooser(EGLConfigChooser configChooser) { 264 | checkRenderThreadState(); 265 | eglConfigChooser = configChooser; 266 | } 267 | 268 | /** 269 | * Install a config chooser which will choose a config 270 | * as close to 16-bit RGB as possible, with or without an optional depth 271 | * buffer as close to 16-bits as possible. 272 | *

If this method is 273 | * called, it must be called before {@link #setRenderer(Renderer)} 274 | * is called. 275 | *

276 | * If no setEGLConfigChooser method is called, then by default the 277 | * view will choose an RGB_888 surface with a depth buffer depth of 278 | * at least 16 bits. 279 | */ 280 | public void setEGLConfigChooser(boolean needDepth) { 281 | setEGLConfigChooser(new SimpleEGLConfigChooser(needDepth)); 282 | } 283 | 284 | /** 285 | * Install a config chooser which will choose a config 286 | * with at least the specified depthSize and stencilSize, 287 | * and exactly the specified redSize, greenSize, blueSize and alphaSize. 288 | *

If this method is 289 | * called, it must be called before {@link #setRenderer(Renderer)} 290 | * is called. 291 | *

292 | * If no setEGLConfigChooser method is called, then by default the 293 | * view will choose an RGB_888 surface with a depth buffer depth of 294 | * at least 16 bits. 295 | */ 296 | public void setEGLConfigChooser(int redSize, int greenSize, int blueSize, int alphaSize, 297 | int depthSize, int stencilSize) { 298 | setEGLConfigChooser( 299 | new ComponentSizeChooser(redSize, greenSize, blueSize, alphaSize, depthSize, stencilSize)); 300 | } 301 | 302 | /** 303 | * Inform the default EGLContextFactory and default EGLConfigChooser 304 | * which EGLContext client version to pick. 305 | *

Use this method to create an OpenGL ES 2.0-compatible context. 306 | * Example: 307 | *

 308 |    * public MyView(Context context) {
 309 |    * super(context);
 310 |    * setEGLContextClientVersion(2); // Pick an OpenGL ES 2.0 context.
 311 |    * setRenderer(new MyRenderer());
 312 |    * }
 313 |    * 
314 | *

Note: Activities which require OpenGL ES 2.0 should indicate this by 315 | * setting @lt;uses-feature android:glEsVersion="0x00020000" /> in the activity's 316 | * AndroidManifest.xml file. 317 | *

If this method is called, it must be called before {@link #setRenderer(Renderer)} 318 | * is called. 319 | *

This method only affects the behavior of the default EGLContexFactory and the 320 | * default EGLConfigChooser. If 321 | * {@link #setEGLContextFactory(EGLContextFactory)} has been called, then the supplied 322 | * EGLContextFactory is responsible for creating an OpenGL ES 2.0-compatible context. 323 | * If 324 | * {@link #setEGLConfigChooser(EGLConfigChooser)} has been called, then the supplied 325 | * EGLConfigChooser is responsible for choosing an OpenGL ES 2.0-compatible config. 326 | * 327 | * @param version The EGLContext client version to choose. Use 2 for OpenGL ES 2.0 328 | */ 329 | public void setEGLContextClientVersion(int version) { 330 | checkRenderThreadState(); 331 | eglContextClientVersion = version; 332 | } 333 | 334 | /** 335 | * Set the rendering mode. When renderMode is 336 | * RENDERMODE_CONTINUOUSLY, the renderer is called 337 | * repeatedly to re-render the scene. When renderMode 338 | * is RENDERMODE_WHEN_DIRTY, the renderer only rendered when the surface 339 | * is created, or when {@link #requestRender} is called. Defaults to RENDERMODE_CONTINUOUSLY. 340 | *

341 | * Using RENDERMODE_WHEN_DIRTY can improve battery life and overall system performance 342 | * by allowing the GPU and CPU to idle when the view does not need to be updated. 343 | *

344 | * This method can only be called after {@link #setRenderer(Renderer)} 345 | * 346 | * @param renderMode one of the RENDERMODE_X constants 347 | * @see #RENDERMODE_CONTINUOUSLY 348 | * @see #RENDERMODE_WHEN_DIRTY 349 | */ 350 | public void setRenderMode(int renderMode) { 351 | glThread.setRenderMode(renderMode); 352 | } 353 | 354 | /** 355 | * Get the current rendering mode. May be called 356 | * from any thread. Must not be called before a renderer has been set. 357 | * 358 | * @return the current rendering mode. 359 | * @see #RENDERMODE_CONTINUOUSLY 360 | * @see #RENDERMODE_WHEN_DIRTY 361 | */ 362 | public int getRenderMode() { 363 | return glThread.getRenderMode(); 364 | } 365 | 366 | /** 367 | * Request that the renderer render a frame. 368 | * This method is typically used when the render mode has been set to 369 | * {@link #RENDERMODE_WHEN_DIRTY}, so that frames are only rendered on demand. 370 | * May be called 371 | * from any thread. Must not be called before a renderer has been set. 372 | */ 373 | public void requestRender() { 374 | glThread.requestRender(); 375 | } 376 | 377 | /** 378 | * This method is part of the SurfaceHolder.Callback interface, and is 379 | * not normally called or subclassed by clients of GLTextureView. 380 | */ 381 | public void surfaceCreated(SurfaceTexture texture) { 382 | glThread.surfaceCreated(); 383 | } 384 | 385 | /** 386 | * This method is part of the SurfaceHolder.Callback interface, and is 387 | * not normally called or subclassed by clients of GLTextureView. 388 | */ 389 | public void surfaceDestroyed(SurfaceTexture texture) { 390 | // Surface will be destroyed when we return 391 | glThread.surfaceDestroyed(); 392 | } 393 | 394 | /** 395 | * This method is part of the SurfaceHolder.Callback interface, and is 396 | * not normally called or subclassed by clients of GLTextureView. 397 | */ 398 | public void surfaceChanged(SurfaceTexture texture, int format, int w, int h) { 399 | glThread.onWindowResize(w, h); 400 | } 401 | 402 | /** 403 | * Inform the view that the activity is paused. The owner of this view must 404 | * call this method when the activity is paused. Calling this method will 405 | * pause the rendering thread. 406 | * Must not be called before a renderer has been set. 407 | */ 408 | public void onPause() { 409 | glThread.onPause(); 410 | } 411 | 412 | /** 413 | * Inform the view that the activity is resumed. The owner of this view must 414 | * call this method when the activity is resumed. Calling this method will 415 | * recreate the OpenGL display and resume the rendering 416 | * thread. 417 | * Must not be called before a renderer has been set. 418 | */ 419 | public void onResume() { 420 | glThread.onResume(); 421 | } 422 | 423 | /** 424 | * Queue a runnable to be run on the GL rendering thread. This can be used 425 | * to communicate with the Renderer on the rendering thread. 426 | * Must not be called before a renderer has been set. 427 | * 428 | * @param r the runnable to be run on the GL rendering thread. 429 | */ 430 | public void queueEvent(Runnable r) { 431 | glThread.queueEvent(r); 432 | } 433 | 434 | /** 435 | * This method is used as part of the View class and is not normally 436 | * called or subclassed by clients of GLTextureView. 437 | */ 438 | @Override protected void onAttachedToWindow() { 439 | super.onAttachedToWindow(); 440 | if (LOG_ATTACH_DETACH) { 441 | Log.d(TAG, "onAttachedToWindow reattach =" + detached); 442 | } 443 | if (detached && (renderer != null)) { 444 | int renderMode = RENDERMODE_CONTINUOUSLY; 445 | if (glThread != null) { 446 | renderMode = glThread.getRenderMode(); 447 | } 448 | glThread = new GLThread(mThisWeakRef); 449 | if (renderMode != RENDERMODE_CONTINUOUSLY) { 450 | glThread.setRenderMode(renderMode); 451 | } 452 | glThread.start(); 453 | } 454 | detached = false; 455 | } 456 | 457 | /** 458 | * This method is used as part of the View class and is not normally 459 | * called or subclassed by clients of GLTextureView. 460 | * Must not be called before a renderer has been set. 461 | */ 462 | @Override protected void onDetachedFromWindow() { 463 | if (LOG_ATTACH_DETACH) { 464 | Log.d(TAG, "onDetachedFromWindow"); 465 | } 466 | if (glThread != null) { 467 | glThread.requestExitAndWait(); 468 | } 469 | detached = true; 470 | super.onDetachedFromWindow(); 471 | } 472 | 473 | public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, 474 | int oldTop, int oldRight, int oldBottom) { 475 | surfaceChanged(getSurfaceTexture(), 0, right - left, bottom - top); 476 | } 477 | 478 | public void addSurfaceTextureListener(SurfaceTextureListener listener) { 479 | surfaceTextureListeners.add(listener); 480 | } 481 | 482 | public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { 483 | surfaceCreated(surface); 484 | surfaceChanged(surface, 0, width, height); 485 | 486 | for (SurfaceTextureListener l : surfaceTextureListeners) { 487 | l.onSurfaceTextureAvailable(surface, width, height); 488 | } 489 | } 490 | 491 | public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { 492 | surfaceChanged(surface, 0, width, height); 493 | 494 | for (SurfaceTextureListener l : surfaceTextureListeners) { 495 | l.onSurfaceTextureSizeChanged(surface, width, height); 496 | } 497 | } 498 | 499 | public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { 500 | surfaceDestroyed(surface); 501 | 502 | for (SurfaceTextureListener l : surfaceTextureListeners) { 503 | l.onSurfaceTextureDestroyed(surface); 504 | } 505 | 506 | return true; 507 | } 508 | 509 | public void onSurfaceTextureUpdated(SurfaceTexture surface) { 510 | requestRender(); 511 | 512 | for (SurfaceTextureListener l : surfaceTextureListeners) { 513 | l.onSurfaceTextureUpdated(surface); 514 | } 515 | } 516 | 517 | // ---------------------------------------------------------------------- 518 | 519 | /** 520 | * An interface used to wrap a GL interface. 521 | *

Typically 522 | * used for implementing debugging and tracing on top of the default 523 | * GL interface. You would typically use this by creating your own class 524 | * that implemented all the GL methods by delegating to another GL instance. 525 | * Then you could add your own behavior before or after calling the 526 | * delegate. All the GLWrapper would do was instantiate and return the 527 | * wrapper GL instance: 528 | *

 529 |    * class MyGLWrapper implements GLWrapper {
 530 |    * GL wrap(GL gl) {
 531 |    * return new MyGLImplementation(gl);
 532 |    * }
 533 |    * static class MyGLImplementation implements GL,GL10,GL11,... {
 534 |    * ...
 535 |    * }
 536 |    * }
 537 |    * 
538 | * 539 | * @see #setGLWrapper(GLWrapper) 540 | */ 541 | public interface GLWrapper { 542 | /** 543 | * Wraps a gl interface in another gl interface. 544 | * 545 | * @param gl a GL interface that is to be wrapped. 546 | * @return either the input argument or another GL object that wraps the input argument. 547 | */ 548 | GL wrap(GL gl); 549 | } 550 | 551 | /** 552 | * A generic renderer interface. 553 | *

554 | * The renderer is responsible for making OpenGL calls to render a frame. 555 | *

556 | * GLTextureView clients typically create their own classes that implement 557 | * this interface, and then call {@link GLTextureView#setRenderer} to 558 | * register the renderer with the GLTextureView. 559 | *

560 | * 561 | *

562 | *

Developer Guides

563 | *

For more information about how to use OpenGL, read the 564 | * OpenGL developer guide.

565 | *
566 | * 567 | *

Threading

568 | * The renderer will be called on a separate thread, so that rendering 569 | * performance is decoupled from the UI thread. Clients typically need to 570 | * communicate with the renderer from the UI thread, because that's where 571 | * input events are received. Clients can communicate using any of the 572 | * standard Java techniques for cross-thread communication, or they can 573 | * use the {@link GLTextureView#queueEvent(Runnable)} convenience method. 574 | *

575 | *

EGL Context Lost

576 | * There are situations where the EGL rendering context will be lost. This 577 | * typically happens when device wakes up after going to sleep. When 578 | * the EGL context is lost, all OpenGL resources (such as textures) that are 579 | * associated with that context will be automatically deleted. In order to 580 | * keep rendering correctly, a renderer must recreate any lost resources 581 | * that it still needs. The {@link #onSurfaceCreated(GL10, 582 | * EGLConfig)} method 583 | * is a convenient place to do this. 584 | * 585 | * @see #setRenderer(Renderer) 586 | */ 587 | public interface Renderer { 588 | /** 589 | * Called when the surface is created or recreated. 590 | *

591 | * Called when the rendering thread 592 | * starts and whenever the EGL context is lost. The EGL context will typically 593 | * be lost when the Android device awakes after going to sleep. 594 | *

595 | * Since this method is called at the beginning of rendering, as well as 596 | * every time the EGL context is lost, this method is a convenient place to put 597 | * code to create resources that need to be created when the rendering 598 | * starts, and that need to be recreated when the EGL context is lost. 599 | * Textures are an example of a resource that you might want to create 600 | * here. 601 | *

602 | * Note that when the EGL context is lost, all OpenGL resources associated 603 | * with that context will be automatically deleted. You do not need to call 604 | * the corresponding "glDelete" methods such as glDeleteTextures to 605 | * manually delete these lost resources. 606 | *

607 | * 608 | * @param gl the GL interface. Use instanceof to 609 | * test if the interface supports GL11 or higher interfaces. 610 | * @param config the EGLConfig of the created surface. Can be used 611 | * to create matching pbuffers. 612 | */ 613 | void onSurfaceCreated(GL10 gl, EGLConfig config); 614 | 615 | /** 616 | * Called when the surface changed size. 617 | *

618 | * Called after the surface is created and whenever 619 | * the OpenGL ES surface size changes. 620 | *

621 | * Typically you will set your viewport here. If your camera 622 | * is fixed then you could also set your projection matrix here: 623 | *

 624 |      * void onSurfaceChanged(GL10 gl, int width, int height) {
 625 |      * gl.glViewport(0, 0, width, height);
 626 |      * // for a fixed camera, set the projection too
 627 |      * float ratio = (float) width / height;
 628 |      * gl.glMatrixMode(GL10.GL_PROJECTION);
 629 |      * gl.glLoadIdentity();
 630 |      * gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);
 631 |      * }
 632 |      * 
633 | * 634 | * @param gl the GL interface. Use instanceof to 635 | * test if the interface supports GL11 or higher interfaces. 636 | */ 637 | void onSurfaceChanged(GL10 gl, int width, int height); 638 | 639 | /** 640 | * Called to draw the current frame. 641 | *

642 | * This method is responsible for drawing the current frame. 643 | *

644 | * The implementation of this method typically looks like this: 645 | *

 646 |      * void onDrawFrame(GL10 gl) {
 647 |      * gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
 648 |      * //... other gl calls to render the scene ...
 649 |      * }
 650 |      * 
651 | * 652 | * @param gl the GL interface. Use instanceof to 653 | * test if the interface supports GL11 or higher interfaces. 654 | */ 655 | void onDrawFrame(GL10 gl); 656 | } 657 | 658 | /** 659 | * An interface for customizing the eglCreateContext and eglDestroyContext calls. 660 | *

661 | * This interface must be implemented by clients wishing to call 662 | * {@link GLTextureView#setEGLContextFactory(EGLContextFactory)} 663 | */ 664 | public interface EGLContextFactory { 665 | EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig); 666 | 667 | void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context); 668 | } 669 | 670 | private class DefaultContextFactory implements EGLContextFactory { 671 | private int EGL_CONTEXT_CLIENT_VERSION = 0x3098; 672 | 673 | public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig config) { 674 | int[] attrib_list = { 675 | EGL_CONTEXT_CLIENT_VERSION, eglContextClientVersion, EGL10.EGL_NONE 676 | }; 677 | 678 | return egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT, 679 | eglContextClientVersion != 0 ? attrib_list : null); 680 | } 681 | 682 | public void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context) { 683 | if (!egl.eglDestroyContext(display, context)) { 684 | Log.e("DefaultContextFactory", "display:" + display + " context: " + context); 685 | if (LOG_THREADS) { 686 | Log.i("DefaultContextFactory", "tid=" + Thread.currentThread().getId()); 687 | } 688 | EglHelper.throwEglException("eglDestroyContex", egl.eglGetError()); 689 | } 690 | } 691 | } 692 | 693 | /** 694 | * An interface for customizing the eglCreateWindowSurface and eglDestroySurface calls. 695 | *

696 | * This interface must be implemented by clients wishing to call 697 | * {@link GLTextureView#setEGLWindowSurfaceFactory(EGLWindowSurfaceFactory)} 698 | */ 699 | public interface EGLWindowSurfaceFactory { 700 | /** 701 | * @return null if the surface cannot be constructed. 702 | */ 703 | EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display, EGLConfig config, 704 | Object nativeWindow); 705 | 706 | void destroySurface(EGL10 egl, EGLDisplay display, EGLSurface surface); 707 | } 708 | 709 | private static class DefaultWindowSurfaceFactory implements EGLWindowSurfaceFactory { 710 | 711 | public EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display, EGLConfig config, 712 | Object nativeWindow) { 713 | EGLSurface result = null; 714 | try { 715 | result = egl.eglCreateWindowSurface(display, config, nativeWindow, null); 716 | } catch (IllegalArgumentException e) { 717 | // This exception indicates that the surface flinger surface 718 | // is not valid. This can happen if the surface flinger surface has 719 | // been torn down, but the application has not yet been 720 | // notified via SurfaceHolder.Callback.surfaceDestroyed. 721 | // In theory the application should be notified first, 722 | // but in practice sometimes it is not. See b/4588890 723 | Log.e(TAG, "eglCreateWindowSurface", e); 724 | } 725 | return result; 726 | } 727 | 728 | public void destroySurface(EGL10 egl, EGLDisplay display, EGLSurface surface) { 729 | egl.eglDestroySurface(display, surface); 730 | } 731 | } 732 | 733 | /** 734 | * An interface for choosing an EGLConfig configuration from a list of 735 | * potential configurations. 736 | *

737 | * This interface must be implemented by clients wishing to call 738 | * {@link GLTextureView#setEGLConfigChooser(EGLConfigChooser)} 739 | */ 740 | public interface EGLConfigChooser { 741 | /** 742 | * Choose a configuration from the list. Implementors typically 743 | * implement this method by calling 744 | * {@link EGL10#eglChooseConfig} and iterating through the results. Please consult the 745 | * EGL specification available from The Khronos Group to learn how to call eglChooseConfig. 746 | * 747 | * @param egl the EGL10 for the current display. 748 | * @param display the current display. 749 | * @return the chosen configuration. 750 | */ 751 | EGLConfig chooseConfig(EGL10 egl, EGLDisplay display); 752 | } 753 | 754 | private abstract class BaseConfigChooser implements EGLConfigChooser { 755 | public BaseConfigChooser(int[] configSpec) { 756 | mConfigSpec = filterConfigSpec(configSpec); 757 | } 758 | 759 | public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { 760 | int[] num_config = new int[1]; 761 | if (!egl.eglChooseConfig(display, mConfigSpec, null, 0, num_config)) { 762 | throw new IllegalArgumentException("eglChooseConfig failed"); 763 | } 764 | 765 | int numConfigs = num_config[0]; 766 | 767 | if (numConfigs <= 0) { 768 | throw new IllegalArgumentException("No configs match configSpec"); 769 | } 770 | 771 | EGLConfig[] configs = new EGLConfig[numConfigs]; 772 | if (!egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs, num_config)) { 773 | throw new IllegalArgumentException("eglChooseConfig#2 failed"); 774 | } 775 | EGLConfig config = chooseConfig(egl, display, configs); 776 | if (config == null) { 777 | throw new IllegalArgumentException("No config chosen"); 778 | } 779 | return config; 780 | } 781 | 782 | abstract EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs); 783 | 784 | protected int[] mConfigSpec; 785 | 786 | private int[] filterConfigSpec(int[] configSpec) { 787 | if (eglContextClientVersion != 2) { 788 | return configSpec; 789 | } 790 | /* We know none of the subclasses define EGL_RENDERABLE_TYPE. 791 | * And we know the configSpec is well formed. 792 | */ 793 | int len = configSpec.length; 794 | int[] newConfigSpec = new int[len + 2]; 795 | System.arraycopy(configSpec, 0, newConfigSpec, 0, len - 1); 796 | newConfigSpec[len - 1] = EGL10.EGL_RENDERABLE_TYPE; 797 | newConfigSpec[len] = 0x0004; /* EGL_OPENGL_ES2_BIT */ 798 | newConfigSpec[len + 1] = EGL10.EGL_NONE; 799 | return newConfigSpec; 800 | } 801 | } 802 | 803 | /** 804 | * Choose a configuration with exactly the specified r,g,b,a sizes, 805 | * and at least the specified depth and stencil sizes. 806 | */ 807 | private class ComponentSizeChooser extends BaseConfigChooser { 808 | public ComponentSizeChooser(int redSize, int greenSize, int blueSize, int alphaSize, 809 | int depthSize, int stencilSize) { 810 | super(new int[] { 811 | EGL10.EGL_RED_SIZE, redSize, EGL10.EGL_GREEN_SIZE, greenSize, EGL10.EGL_BLUE_SIZE, 812 | blueSize, EGL10.EGL_ALPHA_SIZE, alphaSize, EGL10.EGL_DEPTH_SIZE, depthSize, 813 | EGL10.EGL_STENCIL_SIZE, stencilSize, EGL10.EGL_NONE 814 | }); 815 | value = new int[1]; 816 | this.redSize = redSize; 817 | this.greenSize = greenSize; 818 | this.blueSize = blueSize; 819 | this.alphaSize = alphaSize; 820 | this.depthSize = depthSize; 821 | this.stencilSize = stencilSize; 822 | } 823 | 824 | @Override public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs) { 825 | for (EGLConfig config : configs) { 826 | int d = findConfigAttrib(egl, display, config, EGL10.EGL_DEPTH_SIZE, 0); 827 | int s = findConfigAttrib(egl, display, config, EGL10.EGL_STENCIL_SIZE, 0); 828 | if ((d >= depthSize) && (s >= stencilSize)) { 829 | int r = findConfigAttrib(egl, display, config, EGL10.EGL_RED_SIZE, 0); 830 | int g = findConfigAttrib(egl, display, config, EGL10.EGL_GREEN_SIZE, 0); 831 | int b = findConfigAttrib(egl, display, config, EGL10.EGL_BLUE_SIZE, 0); 832 | int a = findConfigAttrib(egl, display, config, EGL10.EGL_ALPHA_SIZE, 0); 833 | if ((r == redSize) && (g == greenSize) && (b == blueSize) && (a == alphaSize)) { 834 | return config; 835 | } 836 | } 837 | } 838 | return null; 839 | } 840 | 841 | private int findConfigAttrib(EGL10 egl, EGLDisplay display, EGLConfig config, int attribute, 842 | int defaultValue) { 843 | 844 | if (egl.eglGetConfigAttrib(display, config, attribute, value)) { 845 | return value[0]; 846 | } 847 | return defaultValue; 848 | } 849 | 850 | private int[] value; 851 | // Subclasses can adjust these values: 852 | protected int redSize; 853 | protected int greenSize; 854 | protected int blueSize; 855 | protected int alphaSize; 856 | protected int depthSize; 857 | protected int stencilSize; 858 | } 859 | 860 | /** 861 | * This class will choose a RGB_888 surface with 862 | * or without a depth buffer. 863 | */ 864 | private class SimpleEGLConfigChooser extends ComponentSizeChooser { 865 | public SimpleEGLConfigChooser(boolean withDepthBuffer) { 866 | super(8, 8, 8, 0, withDepthBuffer ? 16 : 0, 0); 867 | } 868 | } 869 | 870 | /** 871 | * An EGL helper class. 872 | */ 873 | 874 | private static class EglHelper { 875 | public EglHelper(WeakReference glTextureViewWeakReference) { 876 | this.glTextureViewWeakRef = glTextureViewWeakReference; 877 | } 878 | 879 | /** 880 | * Initialize EGL for a given configuration spec. 881 | */ 882 | public void start() { 883 | if (LOG_EGL) { 884 | Log.w("EglHelper", "start() tid=" + Thread.currentThread().getId()); 885 | } 886 | /* 887 | * Get an EGL instance 888 | */ 889 | egl = (EGL10) EGLContext.getEGL(); 890 | 891 | /* 892 | * Get to the default display. 893 | */ 894 | eglDisplay = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); 895 | 896 | if (eglDisplay == EGL10.EGL_NO_DISPLAY) { 897 | throw new RuntimeException("eglGetDisplay failed"); 898 | } 899 | 900 | /* 901 | * We can now initialize EGL for that display 902 | */ 903 | int[] version = new int[2]; 904 | if (!egl.eglInitialize(eglDisplay, version)) { 905 | throw new RuntimeException("eglInitialize failed"); 906 | } 907 | GLTextureView view = glTextureViewWeakRef.get(); 908 | if (view == null) { 909 | eglConfig = null; 910 | eglContext = null; 911 | } else { 912 | eglConfig = view.eglConfigChooser.chooseConfig(egl, eglDisplay); 913 | 914 | /* 915 | * Create an EGL context. We want to do this as rarely as we can, because an 916 | * EGL context is a somewhat heavy object. 917 | */ 918 | eglContext = view.eglContextFactory.createContext(egl, eglDisplay, eglConfig); 919 | } 920 | if (eglContext == null || eglContext == EGL10.EGL_NO_CONTEXT) { 921 | eglContext = null; 922 | throwEglException("createContext"); 923 | } 924 | if (LOG_EGL) { 925 | Log.w("EglHelper", 926 | "createContext " + eglContext + " tid=" + Thread.currentThread().getId()); 927 | } 928 | 929 | eglSurface = null; 930 | } 931 | 932 | /** 933 | * Create an egl surface for the current SurfaceHolder surface. If a surface 934 | * already exists, destroy it before creating the new surface. 935 | * 936 | * @return true if the surface was created successfully. 937 | */ 938 | public boolean createSurface() { 939 | if (LOG_EGL) { 940 | Log.w("EglHelper", "createSurface() tid=" + Thread.currentThread().getId()); 941 | } 942 | /* 943 | * Check preconditions. 944 | */ 945 | if (egl == null) { 946 | throw new RuntimeException("egl not initialized"); 947 | } 948 | if (eglDisplay == null) { 949 | throw new RuntimeException("eglDisplay not initialized"); 950 | } 951 | if (eglConfig == null) { 952 | throw new RuntimeException("eglConfig not initialized"); 953 | } 954 | 955 | /* 956 | * The window size has changed, so we need to create a new 957 | * surface. 958 | */ 959 | destroySurfaceImp(); 960 | 961 | /* 962 | * Create an EGL surface we can render into. 963 | */ 964 | GLTextureView view = glTextureViewWeakRef.get(); 965 | if (view != null) { 966 | eglSurface = view.eglWindowSurfaceFactory.createWindowSurface(egl, eglDisplay, eglConfig, 967 | view.getSurfaceTexture()); 968 | } else { 969 | eglSurface = null; 970 | } 971 | 972 | if (eglSurface == null || eglSurface == EGL10.EGL_NO_SURFACE) { 973 | int error = egl.eglGetError(); 974 | if (error == EGL10.EGL_BAD_NATIVE_WINDOW) { 975 | Log.e("EglHelper", "createWindowSurface returned EGL_BAD_NATIVE_WINDOW."); 976 | } 977 | return false; 978 | } 979 | 980 | /* 981 | * Before we can issue GL commands, we need to make sure 982 | * the context is current and bound to a surface. 983 | */ 984 | if (!egl.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) { 985 | /* 986 | * Could not make the context current, probably because the underlying 987 | * TextureView surface has been destroyed. 988 | */ 989 | logEglErrorAsWarning("EGLHelper", "eglMakeCurrent", egl.eglGetError()); 990 | return false; 991 | } 992 | 993 | return true; 994 | } 995 | 996 | /** 997 | * Create a GL object for the current EGL context. 998 | */ 999 | GL createGL() { 1000 | 1001 | GL gl = eglContext.getGL(); 1002 | GLTextureView view = glTextureViewWeakRef.get(); 1003 | if (view != null) { 1004 | if (view.glWrapper != null) { 1005 | gl = view.glWrapper.wrap(gl); 1006 | } 1007 | 1008 | if ((view.debugFlags & (DEBUG_CHECK_GL_ERROR | DEBUG_LOG_GL_CALLS)) != 0) { 1009 | int configFlags = 0; 1010 | Writer log = null; 1011 | if ((view.debugFlags & DEBUG_CHECK_GL_ERROR) != 0) { 1012 | configFlags |= GLDebugHelper.CONFIG_CHECK_GL_ERROR; 1013 | } 1014 | if ((view.debugFlags & DEBUG_LOG_GL_CALLS) != 0) { 1015 | log = new LogWriter(); 1016 | } 1017 | gl = GLDebugHelper.wrap(gl, configFlags, log); 1018 | } 1019 | } 1020 | return gl; 1021 | } 1022 | 1023 | /** 1024 | * Display the current render surface. 1025 | * 1026 | * @return the EGL error code from eglSwapBuffers. 1027 | */ 1028 | public int swap() { 1029 | if (!egl.eglSwapBuffers(eglDisplay, eglSurface)) { 1030 | return egl.eglGetError(); 1031 | } 1032 | return EGL10.EGL_SUCCESS; 1033 | } 1034 | 1035 | public void destroySurface() { 1036 | if (LOG_EGL) { 1037 | Log.w("EglHelper", "destroySurface() tid=" + Thread.currentThread().getId()); 1038 | } 1039 | destroySurfaceImp(); 1040 | } 1041 | 1042 | private void destroySurfaceImp() { 1043 | if (eglSurface != null && eglSurface != EGL10.EGL_NO_SURFACE) { 1044 | egl.eglMakeCurrent(eglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, 1045 | EGL10.EGL_NO_CONTEXT); 1046 | GLTextureView view = glTextureViewWeakRef.get(); 1047 | if (view != null) { 1048 | view.eglWindowSurfaceFactory.destroySurface(egl, eglDisplay, eglSurface); 1049 | } 1050 | eglSurface = null; 1051 | } 1052 | } 1053 | 1054 | public void finish() { 1055 | if (LOG_EGL) { 1056 | Log.w("EglHelper", "finish() tid=" + Thread.currentThread().getId()); 1057 | } 1058 | if (eglContext != null) { 1059 | GLTextureView view = glTextureViewWeakRef.get(); 1060 | if (view != null) { 1061 | view.eglContextFactory.destroyContext(egl, eglDisplay, eglContext); 1062 | } 1063 | eglContext = null; 1064 | } 1065 | if (eglDisplay != null) { 1066 | egl.eglTerminate(eglDisplay); 1067 | eglDisplay = null; 1068 | } 1069 | } 1070 | 1071 | private void throwEglException(String function) { 1072 | throwEglException(function, egl.eglGetError()); 1073 | } 1074 | 1075 | public static void throwEglException(String function, int error) { 1076 | String message = formatEglError(function, error); 1077 | if (LOG_THREADS) { 1078 | Log.e("EglHelper", 1079 | "throwEglException tid=" + Thread.currentThread().getId() + " " + message); 1080 | } 1081 | throw new RuntimeException(message); 1082 | } 1083 | 1084 | public static void logEglErrorAsWarning(String tag, String function, int error) { 1085 | Log.w(tag, formatEglError(function, error)); 1086 | } 1087 | 1088 | public static String formatEglError(String function, int error) { 1089 | return function + " failed: " + error; 1090 | } 1091 | 1092 | private WeakReference glTextureViewWeakRef; 1093 | EGL10 egl; 1094 | EGLDisplay eglDisplay; 1095 | EGLSurface eglSurface; 1096 | EGLConfig eglConfig; 1097 | EGLContext eglContext; 1098 | } 1099 | 1100 | /** 1101 | * A generic GL Thread. Takes care of initializing EGL and GL. Delegates 1102 | * to a Renderer instance to do the actual drawing. Can be configured to 1103 | * render continuously or on request. 1104 | * 1105 | * All potentially blocking synchronization is done through the 1106 | * glThreadManager object. This avoids multiple-lock ordering issues. 1107 | */ 1108 | static class GLThread extends Thread { 1109 | GLThread(WeakReference glTextureViewWeakRef) { 1110 | super(); 1111 | width = 0; 1112 | height = 0; 1113 | requestRender = true; 1114 | renderMode = RENDERMODE_CONTINUOUSLY; 1115 | this.glTextureViewWeakRef = glTextureViewWeakRef; 1116 | } 1117 | 1118 | @Override public void run() { 1119 | setName("GLThread " + getId()); 1120 | if (LOG_THREADS) { 1121 | Log.i("GLThread", "starting tid=" + getId()); 1122 | } 1123 | 1124 | try { 1125 | guardedRun(); 1126 | } catch (InterruptedException e) { 1127 | // fall thru and exit normally 1128 | } finally { 1129 | glThreadManager.threadExiting(this); 1130 | } 1131 | } 1132 | 1133 | /* 1134 | * This private method should only be called inside a 1135 | * synchronized(glThreadManager) block. 1136 | */ 1137 | private void stopEglSurfaceLocked() { 1138 | if (haveEglSurface) { 1139 | haveEglSurface = false; 1140 | eglHelper.destroySurface(); 1141 | } 1142 | } 1143 | 1144 | /* 1145 | * This private method should only be called inside a 1146 | * synchronized(glThreadManager) block. 1147 | */ 1148 | private void stopEglContextLocked() { 1149 | if (haveEglContext) { 1150 | eglHelper.finish(); 1151 | haveEglContext = false; 1152 | glThreadManager.releaseEglContextLocked(this); 1153 | } 1154 | } 1155 | 1156 | private void guardedRun() throws InterruptedException { 1157 | eglHelper = new EglHelper(glTextureViewWeakRef); 1158 | haveEglContext = false; 1159 | haveEglSurface = false; 1160 | try { 1161 | GL10 gl = null; 1162 | boolean createEglContext = false; 1163 | boolean createEglSurface = false; 1164 | boolean createGlInterface = false; 1165 | boolean lostEglContext = false; 1166 | boolean sizeChanged = false; 1167 | boolean wantRenderNotification = false; 1168 | boolean doRenderNotification = false; 1169 | boolean askedToReleaseEglContext = false; 1170 | int w = 0; 1171 | int h = 0; 1172 | Runnable event = null; 1173 | 1174 | while (true) { 1175 | synchronized (glThreadManager) { 1176 | while (true) { 1177 | if (shouldExit) { 1178 | return; 1179 | } 1180 | 1181 | if (!eventQueue.isEmpty()) { 1182 | event = eventQueue.remove(0); 1183 | break; 1184 | } 1185 | 1186 | // Update the pause state. 1187 | boolean pausing = false; 1188 | if (paused != requestPaused) { 1189 | pausing = requestPaused; 1190 | paused = requestPaused; 1191 | glThreadManager.notifyAll(); 1192 | if (LOG_PAUSE_RESUME) { 1193 | Log.i("GLThread", "paused is now " + paused + " tid=" + getId()); 1194 | } 1195 | } 1196 | 1197 | // Do we need to give up the EGL context? 1198 | if (shouldReleaseEglContext) { 1199 | if (LOG_SURFACE) { 1200 | Log.i("GLThread", "releasing EGL context because asked to tid=" + getId()); 1201 | } 1202 | stopEglSurfaceLocked(); 1203 | stopEglContextLocked(); 1204 | shouldReleaseEglContext = false; 1205 | askedToReleaseEglContext = true; 1206 | } 1207 | 1208 | // Have we lost the EGL context? 1209 | if (lostEglContext) { 1210 | stopEglSurfaceLocked(); 1211 | stopEglContextLocked(); 1212 | lostEglContext = false; 1213 | } 1214 | 1215 | // When pausing, release the EGL surface: 1216 | if (pausing && haveEglSurface) { 1217 | if (LOG_SURFACE) { 1218 | Log.i("GLThread", "releasing EGL surface because paused tid=" + getId()); 1219 | } 1220 | stopEglSurfaceLocked(); 1221 | } 1222 | 1223 | // When pausing, optionally release the EGL Context: 1224 | if (pausing && haveEglContext) { 1225 | GLTextureView view = glTextureViewWeakRef.get(); 1226 | boolean preserveEglContextOnPause = 1227 | view == null ? false : view.preserveEGLContextOnPause; 1228 | if (!preserveEglContextOnPause 1229 | || glThreadManager.shouldReleaseEGLContextWhenPausing()) { 1230 | stopEglContextLocked(); 1231 | if (LOG_SURFACE) { 1232 | Log.i("GLThread", "releasing EGL context because paused tid=" + getId()); 1233 | } 1234 | } 1235 | } 1236 | 1237 | // When pausing, optionally terminate EGL: 1238 | if (pausing) { 1239 | if (glThreadManager.shouldTerminateEGLWhenPausing()) { 1240 | eglHelper.finish(); 1241 | if (LOG_SURFACE) { 1242 | Log.i("GLThread", "terminating EGL because paused tid=" + getId()); 1243 | } 1244 | } 1245 | } 1246 | 1247 | // Have we lost the TextureView surface? 1248 | if ((!hasSurface) && (!waitingForSurface)) { 1249 | if (LOG_SURFACE) { 1250 | Log.i("GLThread", "noticed textureView surface lost tid=" + getId()); 1251 | } 1252 | if (haveEglSurface) { 1253 | stopEglSurfaceLocked(); 1254 | } 1255 | waitingForSurface = true; 1256 | surfaceIsBad = false; 1257 | glThreadManager.notifyAll(); 1258 | } 1259 | 1260 | // Have we acquired the surface view surface? 1261 | if (hasSurface && waitingForSurface) { 1262 | if (LOG_SURFACE) { 1263 | Log.i("GLThread", "noticed textureView surface acquired tid=" + getId()); 1264 | } 1265 | waitingForSurface = false; 1266 | glThreadManager.notifyAll(); 1267 | } 1268 | 1269 | if (doRenderNotification) { 1270 | if (LOG_SURFACE) { 1271 | Log.i("GLThread", "sending render notification tid=" + getId()); 1272 | } 1273 | wantRenderNotification = false; 1274 | doRenderNotification = false; 1275 | renderComplete = true; 1276 | glThreadManager.notifyAll(); 1277 | } 1278 | 1279 | // Ready to draw? 1280 | if (readyToDraw()) { 1281 | 1282 | // If we don't have an EGL context, try to acquire one. 1283 | if (!haveEglContext) { 1284 | if (askedToReleaseEglContext) { 1285 | askedToReleaseEglContext = false; 1286 | } else if (glThreadManager.tryAcquireEglContextLocked(this)) { 1287 | try { 1288 | eglHelper.start(); 1289 | } catch (RuntimeException t) { 1290 | glThreadManager.releaseEglContextLocked(this); 1291 | throw t; 1292 | } 1293 | haveEglContext = true; 1294 | createEglContext = true; 1295 | 1296 | glThreadManager.notifyAll(); 1297 | } 1298 | } 1299 | 1300 | if (haveEglContext && !haveEglSurface) { 1301 | haveEglSurface = true; 1302 | createEglSurface = true; 1303 | createGlInterface = true; 1304 | sizeChanged = true; 1305 | } 1306 | 1307 | if (haveEglSurface) { 1308 | if (this.sizeChanged) { 1309 | sizeChanged = true; 1310 | w = width; 1311 | h = height; 1312 | wantRenderNotification = true; 1313 | if (LOG_SURFACE) { 1314 | Log.i("GLThread", "noticing that we want render notification tid=" + getId()); 1315 | } 1316 | 1317 | // Destroy and recreate the EGL surface. 1318 | createEglSurface = true; 1319 | 1320 | this.sizeChanged = false; 1321 | } 1322 | requestRender = false; 1323 | glThreadManager.notifyAll(); 1324 | break; 1325 | } 1326 | } 1327 | 1328 | // By design, this is the only place in a GLThread thread where we wait(). 1329 | if (LOG_THREADS) { 1330 | Log.i("GLThread", "waiting tid=" + getId() + " haveEglContext: " + haveEglContext 1331 | + " haveEglSurface: " + haveEglSurface + " paused: " + paused + " hasSurface: " 1332 | + hasSurface + " surfaceIsBad: " + surfaceIsBad + " waitingForSurface: " 1333 | + waitingForSurface + " width: " + width + " height: " + height 1334 | + " requestRender: " + requestRender + " renderMode: " + renderMode); 1335 | } 1336 | glThreadManager.wait(); 1337 | } 1338 | } // end of synchronized(glThreadManager) 1339 | 1340 | if (event != null) { 1341 | event.run(); 1342 | event = null; 1343 | continue; 1344 | } 1345 | 1346 | if (createEglSurface) { 1347 | if (LOG_SURFACE) { 1348 | Log.w("GLThread", "egl createSurface"); 1349 | } 1350 | if (!eglHelper.createSurface()) { 1351 | synchronized (glThreadManager) { 1352 | surfaceIsBad = true; 1353 | glThreadManager.notifyAll(); 1354 | } 1355 | continue; 1356 | } 1357 | createEglSurface = false; 1358 | } 1359 | 1360 | if (createGlInterface) { 1361 | gl = (GL10) eglHelper.createGL(); 1362 | 1363 | glThreadManager.checkGLDriver(gl); 1364 | createGlInterface = false; 1365 | } 1366 | 1367 | if (createEglContext) { 1368 | if (LOG_RENDERER) { 1369 | Log.w("GLThread", "onSurfaceCreated"); 1370 | } 1371 | GLTextureView view = glTextureViewWeakRef.get(); 1372 | if (view != null) { 1373 | view.renderer.onSurfaceCreated(gl, eglHelper.eglConfig); 1374 | } 1375 | createEglContext = false; 1376 | } 1377 | 1378 | if (sizeChanged) { 1379 | if (LOG_RENDERER) { 1380 | Log.w("GLThread", "onSurfaceChanged(" + w + ", " + h + ")"); 1381 | } 1382 | GLTextureView view = glTextureViewWeakRef.get(); 1383 | if (view != null) { 1384 | view.renderer.onSurfaceChanged(gl, w, h); 1385 | } 1386 | sizeChanged = false; 1387 | } 1388 | 1389 | if (LOG_RENDERER_DRAW_FRAME) { 1390 | Log.w("GLThread", "onDrawFrame tid=" + getId()); 1391 | } 1392 | { 1393 | GLTextureView view = glTextureViewWeakRef.get(); 1394 | if (view != null) { 1395 | view.renderer.onDrawFrame(gl); 1396 | } 1397 | } 1398 | int swapError = eglHelper.swap(); 1399 | switch (swapError) { 1400 | case EGL10.EGL_SUCCESS: 1401 | break; 1402 | case EGL11.EGL_CONTEXT_LOST: 1403 | if (LOG_SURFACE) { 1404 | Log.i("GLThread", "egl context lost tid=" + getId()); 1405 | } 1406 | lostEglContext = true; 1407 | break; 1408 | default: 1409 | // Other errors typically mean that the current surface is bad, 1410 | // probably because the TextureView surface has been destroyed, 1411 | // but we haven't been notified yet. 1412 | // Log the error to help developers understand why rendering stopped. 1413 | EglHelper.logEglErrorAsWarning("GLThread", "eglSwapBuffers", swapError); 1414 | 1415 | synchronized (glThreadManager) { 1416 | surfaceIsBad = true; 1417 | glThreadManager.notifyAll(); 1418 | } 1419 | break; 1420 | } 1421 | 1422 | if (wantRenderNotification) { 1423 | doRenderNotification = true; 1424 | } 1425 | } 1426 | } finally { 1427 | /* 1428 | * clean-up everything... 1429 | */ 1430 | synchronized (glThreadManager) { 1431 | stopEglSurfaceLocked(); 1432 | stopEglContextLocked(); 1433 | } 1434 | } 1435 | } 1436 | 1437 | public boolean ableToDraw() { 1438 | return haveEglContext && haveEglSurface && readyToDraw(); 1439 | } 1440 | 1441 | private boolean readyToDraw() { 1442 | return (!paused) && hasSurface && (!surfaceIsBad) && (width > 0) && (height > 0) && ( 1443 | requestRender || (renderMode == RENDERMODE_CONTINUOUSLY)); 1444 | } 1445 | 1446 | public void setRenderMode(int renderMode) { 1447 | if (!((RENDERMODE_WHEN_DIRTY <= renderMode) && (renderMode <= RENDERMODE_CONTINUOUSLY))) { 1448 | throw new IllegalArgumentException("renderMode"); 1449 | } 1450 | synchronized (glThreadManager) { 1451 | this.renderMode = renderMode; 1452 | glThreadManager.notifyAll(); 1453 | } 1454 | } 1455 | 1456 | public int getRenderMode() { 1457 | synchronized (glThreadManager) { 1458 | return renderMode; 1459 | } 1460 | } 1461 | 1462 | public void requestRender() { 1463 | synchronized (glThreadManager) { 1464 | requestRender = true; 1465 | glThreadManager.notifyAll(); 1466 | } 1467 | } 1468 | 1469 | public void surfaceCreated() { 1470 | synchronized (glThreadManager) { 1471 | if (LOG_THREADS) { 1472 | Log.i("GLThread", "surfaceCreated tid=" + getId()); 1473 | } 1474 | hasSurface = true; 1475 | glThreadManager.notifyAll(); 1476 | while ((waitingForSurface) && (!exited)) { 1477 | try { 1478 | glThreadManager.wait(); 1479 | } catch (InterruptedException e) { 1480 | Thread.currentThread().interrupt(); 1481 | } 1482 | } 1483 | } 1484 | } 1485 | 1486 | public void surfaceDestroyed() { 1487 | synchronized (glThreadManager) { 1488 | if (LOG_THREADS) { 1489 | Log.i("GLThread", "surfaceDestroyed tid=" + getId()); 1490 | } 1491 | hasSurface = false; 1492 | glThreadManager.notifyAll(); 1493 | while ((!waitingForSurface) && (!exited)) { 1494 | try { 1495 | glThreadManager.wait(); 1496 | } catch (InterruptedException e) { 1497 | Thread.currentThread().interrupt(); 1498 | } 1499 | } 1500 | } 1501 | } 1502 | 1503 | public void onPause() { 1504 | synchronized (glThreadManager) { 1505 | if (LOG_PAUSE_RESUME) { 1506 | Log.i("GLThread", "onPause tid=" + getId()); 1507 | } 1508 | requestPaused = true; 1509 | glThreadManager.notifyAll(); 1510 | while ((!exited) && (!paused)) { 1511 | if (LOG_PAUSE_RESUME) { 1512 | Log.i("Main thread", "onPause waiting for paused."); 1513 | } 1514 | try { 1515 | glThreadManager.wait(); 1516 | } catch (InterruptedException ex) { 1517 | Thread.currentThread().interrupt(); 1518 | } 1519 | } 1520 | } 1521 | } 1522 | 1523 | public void onResume() { 1524 | synchronized (glThreadManager) { 1525 | if (LOG_PAUSE_RESUME) { 1526 | Log.i("GLThread", "onResume tid=" + getId()); 1527 | } 1528 | requestPaused = false; 1529 | requestRender = true; 1530 | renderComplete = false; 1531 | glThreadManager.notifyAll(); 1532 | while ((!exited) && paused && (!renderComplete)) { 1533 | if (LOG_PAUSE_RESUME) { 1534 | Log.i("Main thread", "onResume waiting for !paused."); 1535 | } 1536 | try { 1537 | glThreadManager.wait(); 1538 | } catch (InterruptedException ex) { 1539 | Thread.currentThread().interrupt(); 1540 | } 1541 | } 1542 | } 1543 | } 1544 | 1545 | public void onWindowResize(int w, int h) { 1546 | synchronized (glThreadManager) { 1547 | width = w; 1548 | height = h; 1549 | sizeChanged = true; 1550 | requestRender = true; 1551 | renderComplete = false; 1552 | glThreadManager.notifyAll(); 1553 | 1554 | // Wait for thread to react to resize and render a frame 1555 | while (!exited && !paused && !renderComplete && ableToDraw()) { 1556 | if (LOG_SURFACE) { 1557 | Log.i("Main thread", "onWindowResize waiting for render complete from tid=" + getId()); 1558 | } 1559 | try { 1560 | glThreadManager.wait(); 1561 | } catch (InterruptedException ex) { 1562 | Thread.currentThread().interrupt(); 1563 | } 1564 | } 1565 | } 1566 | } 1567 | 1568 | public void requestExitAndWait() { 1569 | // don't call this from GLThread thread or it is a guaranteed 1570 | // deadlock! 1571 | synchronized (glThreadManager) { 1572 | shouldExit = true; 1573 | glThreadManager.notifyAll(); 1574 | while (!exited) { 1575 | try { 1576 | glThreadManager.wait(); 1577 | } catch (InterruptedException ex) { 1578 | Thread.currentThread().interrupt(); 1579 | } 1580 | } 1581 | } 1582 | } 1583 | 1584 | public void requestReleaseEglContextLocked() { 1585 | shouldReleaseEglContext = true; 1586 | glThreadManager.notifyAll(); 1587 | } 1588 | 1589 | /** 1590 | * Queue an "event" to be run on the GL rendering thread. 1591 | * 1592 | * @param r the runnable to be run on the GL rendering thread. 1593 | */ 1594 | public void queueEvent(Runnable r) { 1595 | if (r == null) { 1596 | throw new IllegalArgumentException("r must not be null"); 1597 | } 1598 | synchronized (glThreadManager) { 1599 | eventQueue.add(r); 1600 | glThreadManager.notifyAll(); 1601 | } 1602 | } 1603 | 1604 | // Once the thread is started, all accesses to the following member 1605 | // variables are protected by the glThreadManager monitor 1606 | private boolean shouldExit; 1607 | private boolean exited; 1608 | private boolean requestPaused; 1609 | private boolean paused; 1610 | private boolean hasSurface; 1611 | private boolean surfaceIsBad; 1612 | private boolean waitingForSurface; 1613 | private boolean haveEglContext; 1614 | private boolean haveEglSurface; 1615 | private boolean shouldReleaseEglContext; 1616 | private int width; 1617 | private int height; 1618 | private int renderMode; 1619 | private boolean requestRender; 1620 | private boolean renderComplete; 1621 | private ArrayList eventQueue = new ArrayList<>(); 1622 | private boolean sizeChanged = true; 1623 | 1624 | // End of member variables protected by the glThreadManager monitor. 1625 | 1626 | private EglHelper eglHelper; 1627 | 1628 | /** 1629 | * Set once at thread construction time, nulled out when the parent view is garbage 1630 | * called. This weak reference allows the GLTextureView to be garbage collected while 1631 | * the GLThread is still alive. 1632 | */ 1633 | private WeakReference glTextureViewWeakRef; 1634 | } 1635 | 1636 | static class LogWriter extends Writer { 1637 | 1638 | @Override public void close() { 1639 | flushBuilder(); 1640 | } 1641 | 1642 | @Override public void flush() { 1643 | flushBuilder(); 1644 | } 1645 | 1646 | @Override public void write(char[] buf, int offset, int count) { 1647 | for (int i = 0; i < count; i++) { 1648 | char c = buf[offset + i]; 1649 | if (c == '\n') { 1650 | flushBuilder(); 1651 | } else { 1652 | builder.append(c); 1653 | } 1654 | } 1655 | } 1656 | 1657 | private void flushBuilder() { 1658 | if (builder.length() > 0) { 1659 | Log.v("GLTextureView", builder.toString()); 1660 | builder.delete(0, builder.length()); 1661 | } 1662 | } 1663 | 1664 | private StringBuilder builder = new StringBuilder(); 1665 | } 1666 | 1667 | private void checkRenderThreadState() { 1668 | if (glThread != null) { 1669 | throw new IllegalStateException("setRenderer has already been called for this instance."); 1670 | } 1671 | } 1672 | 1673 | private static class GLThreadManager { 1674 | private static String TAG = "GLThreadManager"; 1675 | 1676 | public synchronized void threadExiting(GLThread thread) { 1677 | if (LOG_THREADS) { 1678 | Log.i("GLThread", "exiting tid=" + thread.getId()); 1679 | } 1680 | thread.exited = true; 1681 | if (eglOwner == thread) { 1682 | eglOwner = null; 1683 | } 1684 | notifyAll(); 1685 | } 1686 | 1687 | /* 1688 | * Tries once to acquire the right to use an EGL 1689 | * context. Does not block. Requires that we are already 1690 | * in the glThreadManager monitor when this is called. 1691 | * 1692 | * @return true if the right to use an EGL context was acquired. 1693 | */ 1694 | public boolean tryAcquireEglContextLocked(GLThread thread) { 1695 | if (eglOwner == thread || eglOwner == null) { 1696 | eglOwner = thread; 1697 | notifyAll(); 1698 | return true; 1699 | } 1700 | checkGLESVersion(); 1701 | if (multipleGLESContextsAllowed) { 1702 | return true; 1703 | } 1704 | // Notify the owning thread that it should release the context. 1705 | // TODO: implement a fairness policy. Currently 1706 | // if the owning thread is drawing continuously it will just 1707 | // reacquire the EGL context. 1708 | if (eglOwner != null) { 1709 | eglOwner.requestReleaseEglContextLocked(); 1710 | } 1711 | return false; 1712 | } 1713 | 1714 | /* 1715 | * Releases the EGL context. Requires that we are already in the 1716 | * glThreadManager monitor when this is called. 1717 | */ 1718 | public void releaseEglContextLocked(GLThread thread) { 1719 | if (eglOwner == thread) { 1720 | eglOwner = null; 1721 | } 1722 | notifyAll(); 1723 | } 1724 | 1725 | public synchronized boolean shouldReleaseEGLContextWhenPausing() { 1726 | // Release the EGL context when pausing even if 1727 | // the hardware supports multiple EGL contexts. 1728 | // Otherwise the device could run out of EGL contexts. 1729 | return limitedGLESContexts; 1730 | } 1731 | 1732 | public synchronized boolean shouldTerminateEGLWhenPausing() { 1733 | checkGLESVersion(); 1734 | return !multipleGLESContextsAllowed; 1735 | } 1736 | 1737 | public synchronized void checkGLDriver(GL10 gl) { 1738 | if (!glesDriverCheckComplete) { 1739 | checkGLESVersion(); 1740 | String renderer = gl.glGetString(GL10.GL_RENDERER); 1741 | if (glesVersion < kGLES_20) { 1742 | multipleGLESContextsAllowed = !renderer.startsWith(kMSM7K_RENDERER_PREFIX); 1743 | notifyAll(); 1744 | } 1745 | limitedGLESContexts = !multipleGLESContextsAllowed; 1746 | if (LOG_SURFACE) { 1747 | Log.w(TAG, "checkGLDriver renderer = \"" + renderer + "\" multipleContextsAllowed = " 1748 | + multipleGLESContextsAllowed + " limitedGLESContexts = " + limitedGLESContexts); 1749 | } 1750 | glesDriverCheckComplete = true; 1751 | } 1752 | } 1753 | 1754 | private void checkGLESVersion() { 1755 | if (!glesVersionCheckComplete) { 1756 | glesVersionCheckComplete = true; 1757 | } 1758 | } 1759 | 1760 | /** 1761 | * This check was required for some pre-Android-3.0 hardware. Android 3.0 provides 1762 | * support for hardware-accelerated views, therefore multiple EGL contexts are 1763 | * supported on all Android 3.0+ EGL drivers. 1764 | */ 1765 | private boolean glesVersionCheckComplete; 1766 | private int glesVersion; 1767 | private boolean glesDriverCheckComplete; 1768 | private boolean multipleGLESContextsAllowed; 1769 | private boolean limitedGLESContexts; 1770 | private static final int kGLES_20 = 0x20000; 1771 | private static final String kMSM7K_RENDERER_PREFIX = "Q3Dimension MSM7500 "; 1772 | private GLThread eglOwner; 1773 | } 1774 | 1775 | private static final GLThreadManager glThreadManager = new GLThreadManager(); 1776 | 1777 | private final WeakReference mThisWeakRef = new WeakReference<>(this); 1778 | private GLThread glThread; 1779 | private Renderer renderer; 1780 | private boolean detached; 1781 | private EGLConfigChooser eglConfigChooser; 1782 | private EGLContextFactory eglContextFactory; 1783 | private EGLWindowSurfaceFactory eglWindowSurfaceFactory; 1784 | private GLWrapper glWrapper; 1785 | private int debugFlags; 1786 | private int eglContextClientVersion; 1787 | private boolean preserveEGLContextOnPause; 1788 | private List surfaceTextureListeners = new ArrayList<>(); 1789 | } -------------------------------------------------------------------------------- /sample/src/main/java/jp/wasabeef/roundedtextureview/MainActivity.java: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.roundedtextureview; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | 6 | public class MainActivity extends AppCompatActivity { 7 | 8 | private static final String TAG = MainActivity.class.getSimpleName(); 9 | 10 | @Override protected void onCreate(Bundle savedInstanceState) { 11 | super.onCreate(savedInstanceState); 12 | setContentView(R.layout.activity_main); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /sample/src/main/java/jp/wasabeef/roundedtextureview/MultiSampleEGLConfigChooser.java: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.roundedtextureview; 2 | 3 | import android.util.Log; 4 | import javax.microedition.khronos.egl.EGL10; 5 | import javax.microedition.khronos.egl.EGLConfig; 6 | import javax.microedition.khronos.egl.EGLDisplay; 7 | 8 | /** 9 | * EGL configuration Chooser slightly adapted from: 10 | * 11 | * https://code.google.com/p/gdc2011-android-opengl/source/browse/trunk/src/com/example/gdc11/MultisampleConfigChooser.java 12 | */ 13 | public class MultiSampleEGLConfigChooser implements GLTextureView.EGLConfigChooser { 14 | static private final String TAG = MultiSampleEGLConfigChooser.class.getSimpleName(); 15 | 16 | private int[] value; 17 | 18 | private boolean usesCoverageAa = false; 19 | 20 | // pixel format 21 | private int r = 8; 22 | private int g = 8; 23 | private int b = 8; 24 | private int a = 8; 25 | 26 | private int depth = 16; 27 | private int stencil = 4; 28 | private int multisample = 4; 29 | private int renderableType = 4; 30 | 31 | public MultiSampleEGLConfigChooser() { 32 | } 33 | 34 | public MultiSampleEGLConfigChooser(int r, int g, int b, int a, int depth, int stencil, 35 | int multisample, int renderableType) { 36 | this.r = r; 37 | this.g = g; 38 | this.b = b; 39 | this.a = a; 40 | 41 | this.depth = depth; 42 | this.stencil = stencil; 43 | this.multisample = multisample; 44 | this.renderableType = renderableType; 45 | } 46 | 47 | @Override public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { 48 | // try to find a normal configuration first. 49 | int[] configSpec = { 50 | EGL10.EGL_RED_SIZE, r, EGL10.EGL_GREEN_SIZE, g, EGL10.EGL_BLUE_SIZE, b, 51 | EGL10.EGL_ALPHA_SIZE, a, EGL10.EGL_DEPTH_SIZE, depth, EGL10.EGL_STENCIL_SIZE, stencil, 52 | EGL10.EGL_RENDERABLE_TYPE, renderableType, EGL10.EGL_SAMPLE_BUFFERS, 1, EGL10.EGL_SAMPLES, 53 | multisample, EGL10.EGL_NONE 54 | }; 55 | value = new int[1]; 56 | if (!egl.eglChooseConfig(display, configSpec, null, 0, value)) { 57 | throw new IllegalArgumentException("eglChooseConfig failed"); 58 | } 59 | int numConfigs = value[0]; 60 | 61 | // No normal multisampling config was found. Try to create a 62 | // converage multisampling configuration, for the nVidia Tegra2. 63 | // See the EGL_NV_coverage_sample documentation. 64 | if (numConfigs <= 0 && multisample > 1) { 65 | final int EGL_COVERAGE_BUFFERS_NV = 0x30E0; 66 | final int EGL_COVERAGE_SAMPLES_NV = 0x30E1; 67 | 68 | configSpec = new int[] { 69 | EGL10.EGL_RED_SIZE, r, EGL10.EGL_GREEN_SIZE, g, EGL10.EGL_BLUE_SIZE, b, 70 | EGL10.EGL_ALPHA_SIZE, a, EGL10.EGL_DEPTH_SIZE, depth, EGL10.EGL_STENCIL_SIZE, stencil, 71 | EGL10.EGL_RENDERABLE_TYPE, renderableType, EGL_COVERAGE_BUFFERS_NV, 1, 72 | EGL_COVERAGE_SAMPLES_NV, multisample, EGL10.EGL_NONE 73 | }; 74 | 75 | if (!egl.eglChooseConfig(display, configSpec, null, 0, value)) { 76 | throw new IllegalArgumentException("2nd eglChooseConfig failed"); 77 | } 78 | numConfigs = value[0]; 79 | 80 | // Give up, try without multisampling. 81 | if (numConfigs <= 0) { 82 | configSpec = new int[] { 83 | EGL10.EGL_RED_SIZE, r, EGL10.EGL_GREEN_SIZE, g, EGL10.EGL_BLUE_SIZE, b, 84 | EGL10.EGL_ALPHA_SIZE, a, EGL10.EGL_DEPTH_SIZE, depth, EGL10.EGL_STENCIL_SIZE, stencil, 85 | EGL10.EGL_RENDERABLE_TYPE, renderableType, EGL10.EGL_NONE 86 | }; 87 | 88 | if (!egl.eglChooseConfig(display, configSpec, null, 0, value)) { 89 | throw new IllegalArgumentException("3rd eglChooseConfig failed"); 90 | } 91 | numConfigs = value[0]; 92 | 93 | if (numConfigs <= 0) { 94 | throw new IllegalArgumentException("No configs match configSpec"); 95 | } 96 | } else { 97 | usesCoverageAa = true; 98 | Log.i(TAG, "usesCoverageAa"); 99 | } 100 | } 101 | 102 | // Get all matching configurations. 103 | EGLConfig[] configs = new EGLConfig[numConfigs]; 104 | if (!egl.eglChooseConfig(display, configSpec, configs, numConfigs, value)) { 105 | throw new IllegalArgumentException("data eglChooseConfig failed"); 106 | } 107 | 108 | // CAUTION! eglChooseConfigs returns configs with higher bit depth 109 | // first: Even though we asked for rgb565 configurations, rgb888 110 | // configurations are considered to be "better" and returned first. 111 | // You need to explicitly filter the data returned by eglChooseConfig! 112 | int index = -1; 113 | for (int i = 0; i < configs.length; ++i) { 114 | if (findConfigAttrib(egl, display, configs[i], EGL10.EGL_RED_SIZE, 0) == r) { 115 | index = i; 116 | break; 117 | } 118 | } 119 | if (index == -1) { 120 | Log.i(TAG, "Did not find sane config, using first"); 121 | } 122 | EGLConfig config = configs.length > 0 ? configs[index] : null; 123 | if (config == null) { 124 | throw new IllegalArgumentException("No config chosen"); 125 | } 126 | return config; 127 | } 128 | 129 | private int findConfigAttrib(EGL10 egl, EGLDisplay display, EGLConfig config, int attribute, 130 | int defaultValue) { 131 | if (egl.eglGetConfigAttrib(display, config, attribute, value)) { 132 | return value[0]; 133 | } 134 | return defaultValue; 135 | } 136 | 137 | public boolean usesCoverageAa() { 138 | return usesCoverageAa; 139 | } 140 | } -------------------------------------------------------------------------------- /sample/src/main/java/jp/wasabeef/roundedtextureview/RoundedTextureView.java: -------------------------------------------------------------------------------- 1 | package jp.wasabeef.roundedtextureview; 2 | 3 | import android.content.Context; 4 | import android.graphics.Point; 5 | import android.graphics.RectF; 6 | import android.graphics.SurfaceTexture; 7 | import android.opengl.GLES20; 8 | import android.opengl.Matrix; 9 | import android.support.annotation.NonNull; 10 | import android.util.AttributeSet; 11 | import android.util.Log; 12 | import java.nio.ByteBuffer; 13 | import java.nio.ByteOrder; 14 | import java.nio.FloatBuffer; 15 | import java.nio.ShortBuffer; 16 | import javax.microedition.khronos.egl.EGLConfig; 17 | import javax.microedition.khronos.opengles.GL10; 18 | 19 | /** 20 | * To set adjust the rounded corners use {@link #setCornerRadius(float, float, float, float)}. 21 | * 22 | * Created by Daichi Furiya on 2015/10/09. 23 | */ 24 | public class RoundedTextureView extends GLTextureView { 25 | 26 | public interface SurfaceProvider { 27 | void onSurfaceCreated(SurfaceTexture surface); 28 | } 29 | 30 | private GLRenderer glRenderer; 31 | 32 | public RoundedTextureView(Context context) { 33 | this(context, null); 34 | } 35 | 36 | public RoundedTextureView(Context context, AttributeSet attrs) { 37 | super(context, attrs); 38 | setEGLContextClientVersion(2); 39 | 40 | MultiSampleEGLConfigChooser chooser = new MultiSampleEGLConfigChooser(); 41 | setEGLConfigChooser(chooser); 42 | glRenderer = new GLRenderer(this); 43 | glRenderer.setUsesCoverageAa(chooser.usesCoverageAa()); 44 | setRenderer(glRenderer); 45 | setRenderMode(RENDERMODE_WHEN_DIRTY); 46 | setOpaque(false); 47 | } 48 | 49 | public void setCornerRadius(float radius) { 50 | setCornerRadius(radius, radius, radius, radius); 51 | } 52 | 53 | public void setCornerRadius(float topLeft, float topRight, float bottomRight, float bottomLeft) { 54 | glRenderer.setCornerRadius(topLeft, topRight, bottomRight, bottomLeft); 55 | } 56 | 57 | public void setSurfaceProvider(SurfaceProvider provider) { 58 | glRenderer.setSurfaceProvider(provider); 59 | } 60 | 61 | private static class GLRenderer 62 | implements Renderer, SurfaceTexture.OnFrameAvailableListener { 63 | 64 | private static String TAG = GLRenderer.class.getSimpleName(); 65 | 66 | private static final int FLOAT_SIZE_BYTES = 4; 67 | private static final int SHORT_SIZE_BYTES = 2; 68 | private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES; 69 | private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0; 70 | private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3; 71 | 72 | private static final String vertexShader = "" + 73 | "uniform mat4 uMVPMatrix;\n" + 74 | "uniform mat4 uSTMatrix;\n" + 75 | "attribute vec4 aPosition;\n" + 76 | "attribute vec4 aTextureCoord;\n" + 77 | "varying vec2 vTextureCoord;\n" + 78 | "void main() {\n" + 79 | " gl_Position = uMVPMatrix * aPosition;\n" + 80 | " vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" + 81 | "}\n"; 82 | 83 | private static final String fragmentShader = "" + 84 | "#extension GL_OES_EGL_image_external : require\n" + 85 | "precision mediump float;\n" + 86 | "varying vec2 vTextureCoord;\n" + 87 | "uniform samplerExternalOES sTexture;\n" + 88 | "void main() {\n" + 89 | " gl_FragColor = texture2D(sTexture, vTextureCoord);\n" + 90 | "}\n"; 91 | 92 | private float[] mvpMatrix = new float[16]; 93 | private float[] stMatrix = new float[16]; 94 | 95 | private int program; 96 | private int textureID; 97 | private int mvpMatrixHandle; 98 | private int ustMatrixHandle; 99 | private int aPositionHandle; 100 | private int aTextureHandle; 101 | 102 | private static final int GL_TEXTURE_EXTERNAL_OES = 0x00008d65; 103 | 104 | private static final int GL_COVERAGE_BUFFER_BIT_NV = 0x8000; 105 | 106 | private final GLTextureView glTextureView; 107 | private SurfaceTexture surfaceTexture; 108 | private boolean updateSurface = false; 109 | 110 | private float[] triangleVerticesData; 111 | private short[] triangleIndicesData; 112 | private FloatBuffer triangleVertices; 113 | private ShortBuffer triangleIndices; 114 | private RectF roundRadius = new RectF(); 115 | private GLRoundedGeometry roundedGeometry; 116 | private final Point viewPortSize = new Point(); 117 | private final RectF viewPortGLBounds; 118 | private boolean usesCoverageAa = false; 119 | private SurfaceProvider surfaceProvider; 120 | 121 | public GLRenderer(@NonNull GLTextureView view) { 122 | this(view, new GLRoundedGeometry(), new RectF(-1, 1, 1, -1)); 123 | } 124 | 125 | public GLRenderer(@NonNull GLTextureView view, @NonNull GLRoundedGeometry roundedGeometry, 126 | @NonNull RectF viewPortGLBounds) { 127 | glTextureView = view; 128 | this.roundedGeometry = roundedGeometry; 129 | this.viewPortGLBounds = viewPortGLBounds; 130 | viewPortSize.set(1, 1); 131 | 132 | Matrix.setIdentityM(stMatrix, 0); 133 | } 134 | 135 | public void setUsesCoverageAa(boolean usesCoverageAa) { 136 | this.usesCoverageAa = usesCoverageAa; 137 | } 138 | 139 | public void setCornerRadius(float topLeft, float topRight, float bottomRight, 140 | float bottomLeft) { 141 | roundRadius.left = topLeft; 142 | roundRadius.top = topRight; 143 | roundRadius.right = bottomRight; 144 | roundRadius.bottom = bottomLeft; 145 | if (viewPortSize.x > 1) { 146 | updateVertexData(); 147 | } 148 | } 149 | 150 | private void updateVertexData() { 151 | final GLRoundedGeometry.GeometryArrays arrays = 152 | roundedGeometry.generateVertexData(roundRadius, viewPortGLBounds, viewPortSize); 153 | triangleVerticesData = arrays.triangleVertices; 154 | triangleIndicesData = arrays.triangleIndices; 155 | if (triangleVertices != null) { 156 | triangleVertices.clear(); 157 | } else { 158 | triangleVertices = ByteBuffer.allocateDirect(triangleVerticesData.length * FLOAT_SIZE_BYTES) 159 | .order(ByteOrder.nativeOrder()) 160 | .asFloatBuffer(); 161 | } 162 | if (triangleIndices != null) { 163 | triangleIndices.clear(); 164 | } else { 165 | triangleIndices = ByteBuffer.allocateDirect(triangleIndicesData.length * SHORT_SIZE_BYTES) 166 | .order(ByteOrder.nativeOrder()) 167 | .asShortBuffer(); 168 | } 169 | triangleVertices.put(triangleVerticesData).position(0); 170 | triangleIndices.put(triangleIndicesData).position(0); 171 | } 172 | 173 | public void setSurfaceProvider(SurfaceProvider provider) { 174 | surfaceProvider = provider; 175 | } 176 | 177 | @Override public void onSurfaceChanged(GL10 glUnused, int width, int height) { 178 | GLES20.glViewport(0, 0, width, height); 179 | viewPortSize.set(width, height); 180 | updateVertexData(); 181 | } 182 | 183 | @Override public void onSurfaceCreated(GL10 glUnused, EGLConfig config) { 184 | program = createProgram(vertexShader, fragmentShader); 185 | if (program == 0) { 186 | return; 187 | } 188 | aPositionHandle = GLES20.glGetAttribLocation(program, "aPosition"); 189 | checkGlError("glGetAttribLocation aPosition"); 190 | if (aPositionHandle == -1) { 191 | throw new RuntimeException("Could not get attrib location for aPosition"); 192 | } 193 | aTextureHandle = GLES20.glGetAttribLocation(program, "aTextureCoord"); 194 | checkGlError("glGetAttribLocation aTextureCoord"); 195 | if (aTextureHandle == -1) { 196 | throw new RuntimeException("Could not get attrib location for aTextureCoord"); 197 | } 198 | 199 | mvpMatrixHandle = GLES20.glGetUniformLocation(program, "uMVPMatrix"); 200 | checkGlError("glGetUniformLocation uMVPMatrix"); 201 | if (mvpMatrixHandle == -1) { 202 | throw new RuntimeException("Could not get attrib location for uMVPMatrix"); 203 | } 204 | 205 | ustMatrixHandle = GLES20.glGetUniformLocation(program, "uSTMatrix"); 206 | checkGlError("glGetUniformLocation uSTMatrix"); 207 | if (ustMatrixHandle == -1) { 208 | throw new RuntimeException("Could not get attrib location for uSTMatrix"); 209 | } 210 | 211 | int[] textures = new int[1]; 212 | GLES20.glGenTextures(1, textures, 0); 213 | 214 | textureID = textures[0]; 215 | GLES20.glBindTexture(GL_TEXTURE_EXTERNAL_OES, textureID); 216 | checkGlError("glBindTexture textureID"); 217 | 218 | GLES20.glTexParameterf(GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, 219 | GLES20.GL_LINEAR); 220 | GLES20.glTexParameterf(GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, 221 | GLES20.GL_LINEAR); 222 | 223 | surfaceTexture = new SurfaceTexture(textureID); 224 | surfaceTexture.setOnFrameAvailableListener(this); 225 | if (surfaceProvider != null) { 226 | surfaceProvider.onSurfaceCreated(surfaceTexture); 227 | } 228 | 229 | synchronized (this) { 230 | updateSurface = false; 231 | } 232 | } 233 | 234 | @Override public void onDrawFrame(GL10 glUnused) { 235 | synchronized (this) { 236 | if (updateSurface) { 237 | surfaceTexture.updateTexImage(); 238 | surfaceTexture.getTransformMatrix(stMatrix); 239 | updateSurface = false; 240 | } 241 | } 242 | 243 | GLES20.glClearColor(.0f, .0f, .0f, .0f); 244 | int clearMask = GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT; 245 | if (usesCoverageAa) { 246 | // Tegra weirdness 247 | clearMask |= GL_COVERAGE_BUFFER_BIT_NV; 248 | } 249 | GLES20.glClear(clearMask); 250 | 251 | GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, 252 | GLES20.GL_CLAMP_TO_EDGE); 253 | GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, 254 | GLES20.GL_CLAMP_TO_EDGE); 255 | 256 | GLES20.glUseProgram(program); 257 | checkGlError("glUseProgram"); 258 | 259 | GLES20.glActiveTexture(GLES20.GL_TEXTURE0); 260 | GLES20.glBindTexture(GL_TEXTURE_EXTERNAL_OES, textureID); 261 | 262 | triangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET); 263 | GLES20.glVertexAttribPointer(aPositionHandle, 3, GLES20.GL_FLOAT, false, 264 | TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices); 265 | checkGlError("glVertexAttribPointer aPosition"); 266 | GLES20.glEnableVertexAttribArray(aPositionHandle); 267 | checkGlError("glEnableVertexAttribArray aPositionHandle"); 268 | 269 | triangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET); 270 | GLES20.glVertexAttribPointer(aTextureHandle, 3, GLES20.GL_FLOAT, false, 271 | TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices); 272 | checkGlError("glVertexAttribPointer aTextureHandle"); 273 | GLES20.glEnableVertexAttribArray(aTextureHandle); 274 | checkGlError("glEnableVertexAttribArray aTextureHandle"); 275 | 276 | Matrix.setIdentityM(mvpMatrix, 0); 277 | Matrix.scaleM(mvpMatrix, 0, 1f, 1f, 1f); 278 | GLES20.glUniformMatrix4fv(mvpMatrixHandle, 1, false, mvpMatrix, 0); 279 | GLES20.glUniformMatrix4fv(ustMatrixHandle, 1, false, stMatrix, 0); 280 | 281 | GLES20.glDrawElements(GLES20.GL_TRIANGLES, triangleIndicesData.length, GL10.GL_UNSIGNED_SHORT, 282 | triangleIndices); 283 | 284 | checkGlError("glDrawElements"); 285 | GLES20.glFinish(); 286 | } 287 | 288 | @Override synchronized public void onFrameAvailable(SurfaceTexture surfaceTexture) { 289 | updateSurface = true; 290 | glTextureView.requestRender(); 291 | } 292 | 293 | private static int loadShader(int shaderType, String source) { 294 | int shader = GLES20.glCreateShader(shaderType); 295 | if (shader != 0) { 296 | GLES20.glShaderSource(shader, source); 297 | GLES20.glCompileShader(shader); 298 | int[] compiled = new int[1]; 299 | GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0); 300 | if (compiled[0] == 0) { 301 | Log.e(TAG, "Could not compile shader " + shaderType + ":"); 302 | Log.e(TAG, GLES20.glGetShaderInfoLog(shader)); 303 | GLES20.glDeleteShader(shader); 304 | shader = 0; 305 | } 306 | } 307 | return shader; 308 | } 309 | 310 | private static int createProgram(String vertexSource, String fragmentSource) { 311 | int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource); 312 | if (vertexShader == 0) { 313 | return 0; 314 | } 315 | int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource); 316 | if (pixelShader == 0) { 317 | return 0; 318 | } 319 | 320 | int program = GLES20.glCreateProgram(); 321 | if (program != 0) { 322 | GLES20.glAttachShader(program, vertexShader); 323 | checkGlError("glAttachShader"); 324 | GLES20.glAttachShader(program, pixelShader); 325 | checkGlError("glAttachShader"); 326 | GLES20.glLinkProgram(program); 327 | int[] linkStatus = new int[1]; 328 | GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); 329 | if (linkStatus[0] != GLES20.GL_TRUE) { 330 | Log.e(TAG, "Could not link program: "); 331 | Log.e(TAG, GLES20.glGetProgramInfoLog(program)); 332 | GLES20.glDeleteProgram(program); 333 | program = 0; 334 | } 335 | } 336 | return program; 337 | } 338 | 339 | private static void checkGlError(String op) { 340 | int error; 341 | while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) { 342 | Log.e(TAG, op + ": glError " + error); 343 | throw new RuntimeException(op + ": glError " + error); 344 | } 345 | } 346 | } 347 | } -------------------------------------------------------------------------------- /sample/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasabeef/android-RoundedTextureView/d234fe99ec208600efd569e6613a9ea9d98e3b15/sample/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasabeef/android-RoundedTextureView/d234fe99ec208600efd569e6613a9ea9d98e3b15/sample/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasabeef/android-RoundedTextureView/d234fe99ec208600efd569e6613a9ea9d98e3b15/sample/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasabeef/android-RoundedTextureView/d234fe99ec208600efd569e6613a9ea9d98e3b15/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasabeef/android-RoundedTextureView/d234fe99ec208600efd569e6613a9ea9d98e3b15/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /sample/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /sample/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /sample/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | RoundedTextureView 3 | 4 | -------------------------------------------------------------------------------- /sample/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':sample' 2 | --------------------------------------------------------------------------------