diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..382ec00
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,67 @@
+# Mac OS
+.DS_store
+
+# Built application files
+*.apk
+*.ap_
+
+# Gradle files
+.gradle/
+./gradlew.bat
+./gradlew
+build/
+
+# Files for the ART/Dalvik VM
+*.dex
+
+# Java class files
+*.class
+
+# Generated files
+bin/
+gen/
+out/
+
+# Local configuration file (sdk path, etc)
+local.properties
+
+# Proguard folder generated by Eclipse
+proguard/
+
+# Log Files
+*.log
+
+# Android Studio Navigation editor temp files
+.navigation/
+
+# Android Studio captures folder
+captures/
+
+# IntelliJ
+*.iml
+.idea/
+.idea/workspace.xml
+.idea/tasks.xml
+.idea/gradle.xml
+.idea/assetWizardSettings.xml
+.idea/dictionaries
+.idea/libraries
+.idea/caches
+.idea/misc.xml
+.idea/modules.xml
+.idea/navEditor.xml
+.idea/markdown*
+projectFilesBackup/
+
+# Keystore files
+# Uncomment the following line if you do not want to check your keystore files in.
+#*.jks
+
+# External native build folder generated in Android Studio 2.2 and later
+.externalNativeBuild
+
+# Google Services (e.g. APIs or Firebase)
+google-services.json
+
+# Release folder
+app/release/
\ No newline at end of file
diff --git a/.project b/.project
new file mode 100644
index 0000000..9abe3c8
--- /dev/null
+++ b/.project
@@ -0,0 +1,17 @@
+
+
+ android-openGL-canvas
+ Project android-openGL-canvas created by Buildship.
+
+
+
+
+ org.eclipse.buildship.core.gradleprojectbuilder
+
+
+
+
+
+ org.eclipse.buildship.core.gradleprojectnature
+
+
diff --git a/.settings/org.eclipse.buildship.core.prefs b/.settings/org.eclipse.buildship.core.prefs
new file mode 100644
index 0000000..e889521
--- /dev/null
+++ b/.settings/org.eclipse.buildship.core.prefs
@@ -0,0 +1,2 @@
+connection.project.dir=
+eclipse.preferences.version=1
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..a4deed0
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..240a1ca
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,44 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+buildscript {
+ ext.kotlin_version = '1.3.72'
+
+ repositories {
+ maven{url"https://maven.google.com"}
+ jcenter()
+ google()
+ }
+
+ dependencies {
+ classpath 'com.android.tools.build:gradle:3.6.3'
+ classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ maven{url"https://maven.google.com"}
+ jcenter()
+ google()
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..9e6fce1
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,19 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+android.enableJetifier=true
+android.useAndroidX=true
+org.gradle.jvmargs=-Xmx1536m
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..13372ae
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..e7f471d
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Mon Apr 06 09:31:14 ICT 2020
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..9d82f78
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..8a0b282
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/opengl/.classpath b/opengl/.classpath
new file mode 100644
index 0000000..eb19361
--- /dev/null
+++ b/opengl/.classpath
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/opengl/.gitignore b/opengl/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/opengl/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/opengl/.project b/opengl/.project
new file mode 100644
index 0000000..98d27e9
--- /dev/null
+++ b/opengl/.project
@@ -0,0 +1,23 @@
+
+
+ canvasgl
+ Project canvasgl created by Buildship.
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.buildship.core.gradleprojectbuilder
+
+
+
+
+
+ org.eclipse.jdt.core.javanature
+ org.eclipse.buildship.core.gradleprojectnature
+
+
diff --git a/opengl/.settings/org.eclipse.buildship.core.prefs b/opengl/.settings/org.eclipse.buildship.core.prefs
new file mode 100644
index 0000000..b1886ad
--- /dev/null
+++ b/opengl/.settings/org.eclipse.buildship.core.prefs
@@ -0,0 +1,2 @@
+connection.project.dir=..
+eclipse.preferences.version=1
diff --git a/opengl/build.gradle b/opengl/build.gradle
new file mode 100644
index 0000000..973fb35
--- /dev/null
+++ b/opengl/build.gradle
@@ -0,0 +1,58 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android-extensions'
+apply plugin: 'kotlin-android'
+apply plugin: 'com.github.dcendents.android-maven'
+def VERSION_NAME="1"
+
+android {
+ compileSdkVersion 29
+ buildToolsVersion "28.0.3"
+ defaultConfig {
+ minSdkVersion 21
+ targetSdkVersion 29
+ versionCode 1
+ versionName VERSION_NAME
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ lintOptions {
+ checkReleaseBuilds false
+ // Or, if you prefer, you can continue to check for errors in release builds,
+ // but continue the build even when errors are found:
+ abortOnError false
+ }
+
+}
+
+dependencies {
+ implementation fileTree(include: ['*.jar'], dir: 'libs')
+ testImplementation 'junit:junit:4.12'
+ implementation 'androidx.appcompat:appcompat:1.1.0'
+ implementation 'androidx.annotation:annotation:1.1.0'
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+}
+
+repositories {
+ mavenCentral()
+}
\ No newline at end of file
diff --git a/opengl/proguard-rules.pro b/opengl/proguard-rules.pro
new file mode 100644
index 0000000..7b53b10
--- /dev/null
+++ b/opengl/proguard-rules.pro
@@ -0,0 +1,85 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in D:\develop-win\android\dev-tools-win\android-studio-windows\sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+
+-dontpreverify
+-repackageclasses ''
+-allowaccessmodification
+-optimizations !code/simplification/arithmetic
+-keepparameternames
+-renamesourcefileattribute SourceFile
+-keepattributes Exceptions,InnerClasses,Signature,Deprecated, SourceFile,LineNumberTable,*Annotation*,EnclosingMethod
+
+-keep public class * {
+ public protected *;
+}
+
+-keepclassmembernames class * {
+ java.lang.Class class$(java.lang.String);
+ java.lang.Class class$(java.lang.String, boolean);
+}
+
+-keepclasseswithmembernames,includedescriptorclasses class * {
+ native ;
+}
+
+-keepclassmembers,allowoptimization enum * {
+ public static **[] values(); public static ** valueOf(java.lang.String);
+}
+
+-keepclassmembers class * implements java.io.Serializable {
+ static final long serialVersionUID;
+ private static final java.io.ObjectStreamField[] serialPersistentFields;
+ private void writeObject(java.io.ObjectOutputStream);
+ private void readObject(java.io.ObjectInputStream);
+ java.lang.Object writeReplace();
+ java.lang.Object readResolve();
+}
+
+-keep public class * extends android.view.View {
+ public (android.content.Context);
+ public (android.content.Context, android.util.AttributeSet);
+ public (android.content.Context, android.util.AttributeSet, int);
+ public void set*(...);
+}
+
+
+-keepclasseswithmembers class * {
+ public (android.content.Context, android.util.AttributeSet);
+}
+
+-keepclasseswithmembers class * {
+ public (android.content.Context, android.util.AttributeSet, int);
+}
+
+-keepclassmembers class * extends android.content.Context {
+ public void *(android.view.View);
+ public void *(android.view.MenuItem);
+}
+
+-keepclassmembers class * implements android.os.Parcelable {
+ static ** CREATOR;
+}
+
+-keepclassmembers class **.R$* {
+ public static ;
+}
+
+-keepclassmembers class * {
+ @android.webkit.JavascriptInterface ;
+}
diff --git a/opengl/src/main/AndroidManifest.xml b/opengl/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..6ab3801
--- /dev/null
+++ b/opengl/src/main/AndroidManifest.xml
@@ -0,0 +1,19 @@
+
+
+
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/BitmapUtils.kt b/opengl/src/main/java/jp/eita/canvasgl/BitmapUtils.kt
new file mode 100644
index 0000000..78b602f
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/BitmapUtils.kt
@@ -0,0 +1,160 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl
+
+import android.annotation.TargetApi
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.Matrix
+import android.graphics.PorterDuff
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.VectorDrawable
+import android.os.Build
+import androidx.annotation.DrawableRes
+import androidx.annotation.IntRange
+import androidx.core.content.ContextCompat
+
+object BitmapUtils {
+
+ /**
+ * This function will convert drawable ID to bitmap. It will detect if [drawableId] is [BitmapDrawable] or [VectorDrawable].
+ * @param context
+ * @param drawableId
+ *
+ * @return [Bitmap]
+ */
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ fun convertToBitmapFrom(context: Context, @DrawableRes drawableId: Int): Bitmap? {
+ return when (val drawable = ContextCompat.getDrawable(context, drawableId)) {
+ is BitmapDrawable -> return drawable.bitmap
+ is VectorDrawable -> getBitmapFrom(drawable)
+ else -> throw IllegalArgumentException("Unsupported drawable type")
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ private fun getBitmapFrom(vectorDrawable: VectorDrawable): Bitmap? {
+ val bitmap = Bitmap.createBitmap(vectorDrawable.intrinsicWidth,
+ vectorDrawable.intrinsicHeight, Bitmap.Config.ARGB_8888)
+ val canvas = Canvas(bitmap)
+ vectorDrawable.setBounds(0, 0, canvas.width, canvas.height)
+ vectorDrawable.draw(canvas)
+
+ return bitmap
+ }
+
+ /**
+ * @param bitmap The source bitmap.
+ * @param opacity a value between 0 (completely transparent) and 255 (completely
+ * opaque).
+ * @return The opacity-adjusted bitmap. If the source bitmap is mutable it will be
+ * adjusted and returned, otherwise a new bitmap is created.
+ */
+ fun adjustOpacity(bitmap: Bitmap, @IntRange(from = 0, to = 255) opacity: Int): Bitmap? {
+ val mutableBitmap = if (bitmap.isMutable) bitmap else bitmap.copy(Bitmap.Config.ARGB_8888, true)
+ val canvas = Canvas(mutableBitmap)
+ val colour = opacity and 0xFF shl 24
+ canvas.drawColor(colour, PorterDuff.Mode.DST_IN)
+
+ return mutableBitmap
+ }
+
+ /**
+ * @param bitmap The source bitmap.
+ * @param value to make transparent, > 0 will make bold or < 0 will make fuzzy.
+ */
+ fun makeTransparent(src: Bitmap, @IntRange(from = 1, to = 255) value: Int): Bitmap {
+ val mutableBitmap: Bitmap = src.copy(Bitmap.Config.ARGB_8888, true)
+ val canvas = Canvas(mutableBitmap)
+ val colour = value and 0xFF shl 24
+ canvas.drawColor(colour, PorterDuff.Mode.DST_IN)
+
+ return mutableBitmap
+ }
+
+ /**
+ * @param bitmap the Bitmap to be scaled
+ * @param threshold the maxium dimension (either width or height) of the scaled bitmap
+ * @param isNecessaryToKeepOrig is it necessary to keep the original bitmap? If not recycle the original bitmap to prevent memory leak.
+ */
+ fun getScaledDownBitmap(bitmap: Bitmap, threshold: Int, isNecessaryToKeepOrig: Boolean): Bitmap {
+ val width = bitmap.width
+ val height = bitmap.height
+ var newWidth = width
+ var newHeight = height
+ if (width > height && width > threshold) {
+ newWidth = threshold
+ newHeight = (height * newWidth.toFloat() / width).toInt()
+ }
+ if (width in (height + 1)..threshold) {
+ //the bitmap is already smaller than our required dimension, no need to resize it
+ return bitmap
+ }
+ if (width < height && height > threshold) {
+ newHeight = threshold
+ newWidth = (width * newHeight.toFloat() / height).toInt()
+ }
+ if (height in (width + 1)..threshold) {
+ //the bitmap is already smaller than our required dimension, no need to resize it
+ return bitmap
+ }
+ if (width == height && width > threshold) {
+ newWidth = threshold
+ newHeight = newWidth
+ }
+ return if (width == height && width <= threshold) {
+ //the bitmap is already smaller than our required dimension, no need to resize it
+ bitmap
+ } else {
+ getResizedBitmap(bitmap, newWidth, newHeight, isNecessaryToKeepOrig)
+ }
+ }
+
+ private fun getResizedBitmap(bm: Bitmap, newWidth: Int, newHeight: Int, isNecessaryToKeepOrig: Boolean): Bitmap {
+ val width = bm.width
+ val height = bm.height
+ val scaleWidth = newWidth.toFloat() / width
+ val scaleHeight = newHeight.toFloat() / height
+ // CREATE A MATRIX FOR THE MANIPULATION
+ val matrix = Matrix()
+ // RESIZE THE BIT MAP
+ matrix.postScale(scaleWidth, scaleHeight)
+
+ // "RECREATE" THE NEW BITMAP
+ val resizedBitmap = Bitmap.createBitmap(bm, 0, 0, width, height, matrix, false)
+ if (!isNecessaryToKeepOrig) {
+ bm.recycle()
+ }
+
+ return resizedBitmap
+ }
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/CanvasGL.kt b/opengl/src/main/java/jp/eita/canvasgl/CanvasGL.kt
new file mode 100644
index 0000000..e9f57bd
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/CanvasGL.kt
@@ -0,0 +1,360 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl
+
+import android.graphics.*
+import android.opengl.GLES20
+import androidx.annotation.IntRange
+import jp.eita.canvasgl.glcanvas.*
+import jp.eita.canvasgl.glcanvas.GLCanvas.ICustomMVPMatrix
+import jp.eita.canvasgl.matrix.IBitmapMatrix
+import jp.eita.canvasgl.shapeFilter.BasicDrawShapeFilter
+import jp.eita.canvasgl.shapeFilter.DrawCircleFilter
+import jp.eita.canvasgl.shapeFilter.DrawShapeFilter
+import jp.eita.canvasgl.textureFilter.BasicTextureFilter
+import jp.eita.canvasgl.textureFilter.FilterGroup
+import jp.eita.canvasgl.textureFilter.TextureFilter
+import java.util.*
+
+/**
+ * All the depth of textures are the same. So the texture drawn after will cover the texture drawn before.
+ */
+class CanvasGL constructor(override val glCanvas: GLCanvas = GLES20Canvas()) : ICanvasGL {
+
+ private val bitmapTextureMap: MutableMap = WeakHashMap()
+
+ private val defaultTextureFilter: BasicTextureFilter
+
+ private val canvasBackgroundColor: FloatArray
+
+ private val surfaceTextureMatrix = FloatArray(16)
+
+ override var width = 0
+ private set
+
+ override var height = 0
+ private set
+
+ private val defaultDrawShapeFilter: BasicDrawShapeFilter
+
+ private val drawCircleFilter = DrawCircleFilter()
+
+ private var currentTextureFilter: TextureFilter? = null
+
+ init {
+ glCanvas.setOnPreDrawShapeListener(object : GLCanvas.OnPreDrawShapeListener {
+ override fun onPreDraw(program: Int, drawShapeFilter: DrawShapeFilter?) {
+ drawShapeFilter?.onPreDraw(program, this@CanvasGL)
+ }
+
+ })
+ glCanvas.setOnPreDrawTextureListener(object : GLCanvas.OnPreDrawTextureListener {
+ override fun onPreDraw(textureProgram: Int, texture: BasicTexture?, textureFilter: TextureFilter?) {
+ textureFilter!!.onPreDraw(textureProgram, texture!!, this@CanvasGL)
+ }
+
+ })
+ defaultTextureFilter = BasicTextureFilter()
+ defaultDrawShapeFilter = BasicDrawShapeFilter()
+ canvasBackgroundColor = FloatArray(4)
+ }
+
+ override fun bindBitmapToTexture(whichTexture: Int, bitmap: Bitmap): BitmapTexture? {
+ GLES20.glActiveTexture(whichTexture)
+ GLES20Canvas.checkError()
+ val texture = getTexture(bitmap, null) as BitmapTexture?
+ texture!!.onBind(glCanvas)
+ GLES20.glBindTexture(texture.target, texture.id)
+ GLES20Canvas.checkError()
+
+ return texture
+ }
+
+ override fun bindRawTexture(whichTexture: Int, texture: RawTexture) {
+ GLES20.glActiveTexture(whichTexture)
+ GLES20Canvas.checkError()
+ if (!texture.isLoaded) {
+ texture.prepare(glCanvas)
+ }
+ GLES20.glBindTexture(texture.target, texture.id)
+ GLES20Canvas.checkError()
+ }
+
+ override fun beginRenderTarget(texture: RawTexture?) {
+ glCanvas.beginRenderTarget(texture)
+ }
+
+ override fun endRenderTarget() {
+ glCanvas.endRenderTarget()
+ }
+
+ override fun drawSurfaceTexture(texture: BasicTexture?, surfaceTexture: SurfaceTexture?, left: Int, top: Int, right: Int, bottom: Int) {
+ drawSurfaceTexture(texture, surfaceTexture, left, top, right, bottom, defaultTextureFilter)
+ }
+
+ override fun drawSurfaceTexture(texture: BasicTexture?, surfaceTexture: SurfaceTexture?, left: Int, top: Int, right: Int, bottom: Int, textureFilter: TextureFilter?) {
+ drawSurfaceTexture(texture, surfaceTexture, left, top, right, bottom, null, textureFilter)
+ }
+
+ override fun drawSurfaceTexture(texture: BasicTexture?, surfaceTexture: SurfaceTexture?, matrix: IBitmapMatrix?) {
+ drawSurfaceTexture(texture!!, surfaceTexture, matrix, defaultTextureFilter)
+ }
+
+ override fun drawSurfaceTexture(texture: BasicTexture, surfaceTexture: SurfaceTexture?, matrix: IBitmapMatrix?, textureFilter: TextureFilter) {
+ drawSurfaceTexture(texture, surfaceTexture, 0, 0, texture.width, texture.height, matrix, textureFilter)
+ }
+
+ override fun drawSurfaceTexture(texture: BasicTexture?, surfaceTexture: SurfaceTexture?, left: Int, top: Int, right: Int, bottom: Int, matrix: IBitmapMatrix?, textureFilter: TextureFilter?) {
+ currentTextureFilter = textureFilter
+ var filteredTexture = texture
+ if (textureFilter is FilterGroup) {
+ filteredTexture = getFilterGroupTexture(texture, surfaceTexture, textureFilter)
+ }
+ val customMVPMatrix = if (matrix == null) null else object : ICustomMVPMatrix {
+ override fun getMVPMatrix(viewportW: Int, viewportH: Int, x: Float, y: Float, drawW: Float, drawH: Float): FloatArray? {
+ return matrix.obtainResultMatrix(viewportW, viewportH, x, y, drawW, drawH)!!
+ }
+ }
+ if (surfaceTexture == null) {
+ glCanvas.drawTexture(filteredTexture, left, top, right - left, bottom - top, textureFilter, customMVPMatrix)
+ } else {
+ surfaceTexture.getTransformMatrix(surfaceTextureMatrix)
+ glCanvas.drawTexture(filteredTexture, surfaceTextureMatrix, left, top, right - left, bottom - top, textureFilter, customMVPMatrix)
+ }
+ }
+
+ private fun getFilterGroupTexture(texture: BasicTexture?, surfaceTexture: SurfaceTexture?, basicTextureFilter: FilterGroup): BasicTexture? {
+ var textureEdited = texture
+ textureEdited = basicTextureFilter.draw(textureEdited!!, glCanvas, object : FilterGroup.OnDrawListener {
+ override fun onDraw(drawTexture: BasicTexture?, textureFilter: TextureFilter?, isFirst: Boolean) {
+ if (isFirst) {
+ surfaceTexture!!.getTransformMatrix(surfaceTextureMatrix)
+ glCanvas.drawTexture(drawTexture, surfaceTextureMatrix, 0, 0, drawTexture!!.width, drawTexture.height, textureFilter, null)
+ } else {
+ glCanvas.drawTexture(drawTexture, 0, 0, drawTexture!!.width, drawTexture.height, textureFilter, null)
+ }
+ }
+
+ })
+
+ return textureEdited
+ }
+
+ override fun drawBitmap(bitmap: Bitmap, matrix: IBitmapMatrix) {
+ drawBitmap(bitmap, matrix, defaultTextureFilter)
+ }
+
+ override fun drawBitmap(bitmap: Bitmap, matrix: IBitmapMatrix, textureFilter: TextureFilter) {
+ val basicTexture = getTexture(bitmap, textureFilter)
+ save()
+ glCanvas.drawTexture(basicTexture, 0, 0, bitmap.width, bitmap.height, textureFilter, object : ICustomMVPMatrix {
+ override fun getMVPMatrix(viewportW: Int, viewportH: Int, x: Float, y: Float, drawW: Float, drawH: Float): FloatArray? {
+ return matrix.obtainResultMatrix(viewportW, viewportH, x, y, drawW, drawH)
+ }
+ })
+ restore()
+ }
+
+ override fun drawBitmap(bitmap: Bitmap, src: Rect?, dst: RectF?) {
+ drawBitmap(bitmap, RectF(src), dst, defaultTextureFilter)
+ }
+
+ override fun drawBitmap(bitmap: Bitmap, left: Int, top: Int) {
+ drawBitmap(bitmap, left, top, defaultTextureFilter)
+ }
+
+ override fun drawBitmap(bitmap: Bitmap, left: Int, top: Int, textureFilter: TextureFilter) {
+ val basicTexture = getTexture(bitmap, textureFilter)
+ glCanvas.drawTexture(basicTexture, left, top, bitmap.width, bitmap.height, textureFilter, null)
+ }
+
+ override fun drawBitmap(bitmap: Bitmap, src: Rect?, dst: Rect?) {
+ drawBitmap(bitmap, src, RectF(dst))
+ }
+
+ override fun drawBitmap(bitmap: Bitmap, src: RectF?, dst: RectF?, textureFilter: TextureFilter) {
+ if (dst == null) {
+ throw NullPointerException()
+ }
+ val basicTexture = getTexture(bitmap, textureFilter)
+ glCanvas.drawTexture(basicTexture, src, dst, textureFilter, null)
+ }
+
+ override fun drawBitmap(bitmap: Bitmap, left: Int, top: Int, width: Int, height: Int) {
+ drawBitmap(bitmap, left, top, width, height, defaultTextureFilter)
+ }
+
+ override fun drawBitmap(bitmap: Bitmap, left: Int, top: Int, width: Int, height: Int, textureFilter: TextureFilter) {
+ val basicTexture = getTexture(bitmap, textureFilter)
+ glCanvas.drawTexture(basicTexture, left, top, width, height, textureFilter, null)
+ }
+
+ private fun getTexture(bitmap: Bitmap, textureFilter: TextureFilter?): BasicTexture? {
+ currentTextureFilter = textureFilter
+ throwIfCannotDraw(bitmap)
+ var resultTexture = getTextureFromMap(bitmap)
+ if (textureFilter is FilterGroup) {
+ resultTexture = textureFilter.draw(resultTexture!!, glCanvas, object : FilterGroup.OnDrawListener {
+ override fun onDraw(drawTexture: BasicTexture?, textureFilter: TextureFilter?, isFirst: Boolean) {
+ glCanvas.drawTexture(drawTexture, 0, 0, drawTexture!!.width, drawTexture.height, textureFilter, null)
+ }
+ })
+ }
+
+ return resultTexture
+ }
+
+ /***
+ * Use this to the bitmap to texture. Called when your bitmap content pixels changed
+ * @param bitmap the bitmap whose content pixels changed
+ */
+ override fun invalidateTextureContent(bitmap: Bitmap) {
+ val resultTexture = getTextureFromMap(bitmap)
+ if (resultTexture is UploadedTexture) {
+ resultTexture.invalidateContent()
+ }
+ }
+
+ private fun getTextureFromMap(bitmap: Bitmap): BasicTexture? {
+ val resultTexture: BasicTexture?
+ if (bitmapTextureMap.containsKey(bitmap)) {
+ resultTexture = bitmapTextureMap[bitmap]
+ } else {
+ resultTexture = BitmapTexture(bitmap)
+ bitmapTextureMap[bitmap] = resultTexture
+ }
+
+ return resultTexture
+ }
+
+ override fun drawCircle(x: Float, y: Float, radius: Float, paint: GLPaint) {
+ if (paint.style == Paint.Style.FILL) {
+ drawCircleFilter.lineWidth = 0.5f
+ } else {
+ drawCircleFilter.lineWidth = paint.lineWidth / (2 * radius)
+ }
+ glCanvas.drawCircle(x - radius, y - radius, radius, paint, drawCircleFilter)
+ }
+
+ override fun drawLine(starttX: Float, startY: Float, stopX: Float, stopY: Float, paint: GLPaint?) {
+ glCanvas.drawLine(starttX, startY, stopX, stopY, paint, defaultDrawShapeFilter)
+ }
+
+ override fun drawRect(rect: RectF, paint: GLPaint) {
+ drawRect(rect.left, rect.top, rect.right, rect.bottom, paint)
+ }
+
+ override fun drawRect(r: Rect, paint: GLPaint) {
+ drawRect(r.left.toFloat(), r.top.toFloat(), r.right.toFloat(), r.bottom.toFloat(), paint)
+ }
+
+ override fun drawRect(left: Float, top: Float, right: Float, bottom: Float, paint: GLPaint) {
+ if (paint.style == Paint.Style.STROKE) {
+ glCanvas.drawRect(left, top, right - left, bottom - top, paint, defaultDrawShapeFilter)
+ } else {
+ glCanvas.fillRect(left, top, right - left, bottom - top, paint.color, defaultDrawShapeFilter)
+ }
+ }
+
+ override fun save() {
+ glCanvas.save()
+ }
+
+ override fun save(saveFlags: Int) {
+ glCanvas.save(saveFlags)
+ }
+
+ override fun restore() {
+ glCanvas.restore()
+ }
+
+ override fun rotate(degrees: Float) {
+ glCanvas.rotate(degrees, 0f, 0f, 1f)
+ }
+
+ override fun rotate(degrees: Float, px: Float, py: Float) {
+ glCanvas.translate(px, py)
+ rotate(degrees)
+ glCanvas.translate(-px, -py)
+ }
+
+ override fun scale(sx: Float, sy: Float) {
+ glCanvas.scale(sx, sy, 1f)
+ }
+
+ override fun scale(sx: Float, sy: Float, px: Float, py: Float) {
+ glCanvas.translate(px, py)
+ scale(sx, sy)
+ glCanvas.translate(-px, -py)
+ }
+
+ override fun translate(dx: Float, dy: Float) {
+ glCanvas.translate(dx, dy)
+ }
+
+ override fun clearBuffer() {
+ glCanvas.clearBuffer()
+ }
+
+ override fun clearBuffer(color: Int) {
+ canvasBackgroundColor[1] = Color.red(color).toFloat() / 255
+ canvasBackgroundColor[2] = Color.green(color).toFloat() / 255
+ canvasBackgroundColor[3] = Color.blue(color).toFloat() / 255
+ canvasBackgroundColor[0] = Color.alpha(color).toFloat() / 255
+ glCanvas.clearBuffer(canvasBackgroundColor)
+ }
+
+ override fun setSize(width: Int, height: Int) {
+ this.width = width
+ this.height = height
+ glCanvas.setSize(width, height)
+ }
+
+// override fun setSize(scaleRatioSize: Float) {
+//// this.width = (width * scaleRatioSize).toInt()
+//// this.height = (height * scaleRatioSize).toInt()
+//// glCanvas.setSize(this.width, this.height)
+// }
+
+ override fun resume() {}
+
+ override fun pause() {
+ if (currentTextureFilter != null) {
+ currentTextureFilter!!.destroy()
+ }
+ }
+
+ override fun setAlpha(@IntRange(from = 0, to = 255) alpha: Int) {
+ glCanvas.alpha = alpha / 255.toFloat()
+ }
+
+ @Throws(Throwable::class)
+ protected fun finalize() {
+ for (bitmapTexture in bitmapTextureMap.values) {
+ bitmapTexture.recycle()
+ }
+ }
+
+ private fun throwIfCannotDraw(bitmap: Bitmap) {
+ if (bitmap.isRecycled) {
+ throw RuntimeException("Canvas: trying to use a recycled bitmap $bitmap")
+ }
+ if (!bitmap.isPremultiplied && bitmap.config == Bitmap.Config.ARGB_8888 &&
+ bitmap.hasAlpha()) {
+ throw RuntimeException("Canvas: trying to use a non-premultiplied bitmap $bitmap")
+ }
+ }
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/ICanvasGL.kt b/opengl/src/main/java/jp/eita/canvasgl/ICanvasGL.kt
new file mode 100644
index 0000000..7371691
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/ICanvasGL.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl
+
+import android.graphics.Bitmap
+import android.graphics.Rect
+import android.graphics.RectF
+import android.graphics.SurfaceTexture
+import androidx.annotation.IntRange
+import jp.eita.canvasgl.glcanvas.*
+import jp.eita.canvasgl.matrix.IBitmapMatrix
+import jp.eita.canvasgl.textureFilter.TextureFilter
+
+interface ICanvasGL {
+
+ fun bindBitmapToTexture(whichTexture: Int, bitmap: Bitmap): BitmapTexture?
+
+ fun bindRawTexture(whichTexture: Int, texture: RawTexture)
+
+ fun beginRenderTarget(texture: RawTexture?)
+
+ fun endRenderTarget()
+
+ val glCanvas: GLCanvas
+
+ fun drawSurfaceTexture(texture: BasicTexture?, surfaceTexture: SurfaceTexture?, left: Int, top: Int, right: Int, bottom: Int)
+
+ fun drawSurfaceTexture(texture: BasicTexture?, surfaceTexture: SurfaceTexture?, left: Int, top: Int, right: Int, bottom: Int, textureFilter: TextureFilter?)
+
+ fun drawSurfaceTexture(texture: BasicTexture?, surfaceTexture: SurfaceTexture?, matrix: IBitmapMatrix?)
+
+ fun drawSurfaceTexture(texture: BasicTexture, surfaceTexture: SurfaceTexture?, matrix: IBitmapMatrix?, textureFilter: TextureFilter)
+
+ fun drawSurfaceTexture(texture: BasicTexture?, surfaceTexture: SurfaceTexture?, left: Int, top: Int, right: Int, bottom: Int, matrix: IBitmapMatrix?, textureFilter: TextureFilter?)
+
+ fun drawBitmap(bitmap: Bitmap, matrix: IBitmapMatrix)
+
+ fun drawBitmap(bitmap: Bitmap, matrix: IBitmapMatrix, textureFilter: TextureFilter)
+
+ fun drawBitmap(bitmap: Bitmap, src: Rect?, dst: RectF?)
+
+ fun drawBitmap(bitmap: Bitmap, left: Int, top: Int)
+
+ fun drawBitmap(bitmap: Bitmap, left: Int, top: Int, textureFilter: TextureFilter)
+
+ fun drawBitmap(bitmap: Bitmap, src: Rect?, dst: Rect?)
+
+ fun drawBitmap(bitmap: Bitmap, src: RectF?, dst: RectF?, textureFilter: TextureFilter)
+
+ fun drawBitmap(bitmap: Bitmap, left: Int, top: Int, width: Int, height: Int)
+
+ fun drawBitmap(bitmap: Bitmap, left: Int, top: Int, width: Int, height: Int, textureFilter: TextureFilter)
+
+ fun invalidateTextureContent(bitmap: Bitmap)
+
+ fun drawCircle(x: Float, y: Float, radius: Float, paint: GLPaint)
+
+ fun drawLine(starttX: Float, startY: Float, stopX: Float, stopY: Float, paint: GLPaint?)
+
+ fun drawRect(rect: RectF, paint: GLPaint)
+
+ fun drawRect(r: Rect, paint: GLPaint)
+
+ fun drawRect(left: Float, top: Float, right: Float, bottom: Float, paint: GLPaint)
+
+ fun save()
+
+ fun save(saveFlags: Int)
+
+ fun restore()
+
+ fun rotate(degrees: Float)
+
+ fun rotate(degrees: Float, px: Float, py: Float)
+
+ fun scale(sx: Float, sy: Float)
+
+ fun scale(sx: Float, sy: Float, px: Float, py: Float)
+
+ fun translate(dx: Float, dy: Float)
+
+ fun clearBuffer()
+
+ fun clearBuffer(color: Int)
+
+ fun setSize(width: Int, height: Int)
+
+ val width: Int
+
+ val height: Int
+
+ fun resume()
+
+ fun pause()
+
+ /**
+ * If used in a texture view, make sure the setOpaque(false) is called.
+ *
+ * @param alpha alpha value
+ */
+ fun setAlpha(@IntRange(from = 0, to = 255) alpha: Int)
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/OpenGLUtil.kt b/opengl/src/main/java/jp/eita/canvasgl/OpenGLUtil.kt
new file mode 100644
index 0000000..92ec9c6
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/OpenGLUtil.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl
+
+import android.graphics.Bitmap
+import android.graphics.Point
+import android.opengl.GLES11
+import android.opengl.GLES20
+import android.opengl.GLException
+import android.view.View
+import java.nio.IntBuffer
+import javax.microedition.khronos.opengles.GL10
+
+/**
+ * Some tools for OpenGL
+ */
+object OpenGLUtil {
+
+ fun setUniformMatrix4f(location: Int, matrix: FloatArray?) {
+ GLES20.glUniformMatrix4fv(location, 1, false, matrix, 0)
+ }
+
+ @JvmStatic
+ fun setFloat(location: Int, floatValue: Float) {
+ GLES20.glUniform1f(location, floatValue)
+ }
+
+ @JvmStatic
+ @Throws(OutOfMemoryError::class)
+ fun createBitmapFromGLSurface(x: Int, y: Int, w: Int, h: Int, glHeight: Int): Bitmap? {
+ val bitmapBuffer = IntArray(w * h)
+ val bitmapSource = IntArray(w * h)
+ val intBuffer = IntBuffer.wrap(bitmapBuffer)
+ intBuffer.position(0)
+ try {
+ GLES11.glReadPixels(x, glHeight - h - y, w, h, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, intBuffer)
+ var offset1: Int
+ var offset2: Int
+ for (i in 0 until h) {
+ offset1 = i * w
+ offset2 = (h - i - 1) * w
+ for (j in 0 until w) {
+ val texturePixel = bitmapBuffer[offset1 + j]
+ val blue = texturePixel shr 16 and 0xff
+ val red = texturePixel shl 16 and 0x00ff0000
+ val pixel = texturePixel and -0xff0100 or red or blue
+ bitmapSource[offset2 + j] = pixel
+ }
+ }
+ } catch (e: GLException) {
+ return null
+ }
+ return Bitmap.createBitmap(bitmapSource, w, h, Bitmap.Config.ARGB_8888)
+ }
+
+ @JvmStatic
+ fun getPointOfView(view: View): Point {
+ val location = IntArray(2)
+ view.getLocationInWindow(location)
+ return Point(location[0], location[1])
+ }
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/glcanvas/BasicTexture.kt b/opengl/src/main/java/jp/eita/canvasgl/glcanvas/BasicTexture.kt
new file mode 100644
index 0000000..63e58e5
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/glcanvas/BasicTexture.kt
@@ -0,0 +1,194 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.glcanvas
+
+import android.util.Log
+import jp.eita.canvasgl.textureFilter.BasicTextureFilter
+import java.util.*
+
+// BasicTexture is a Texture corresponds to a real GL secondBitmap.
+// The state of a BasicTexture indicates whether its data is loaded to GL memory.
+// If a BasicTexture is loaded into GL memory, it has a GL secondBitmap id.
+abstract class BasicTexture protected constructor(canvas: GLCanvas? = null, id: Int = 0, state: Int = STATE_UNLOADED) : Texture {
+
+ var id = -1
+ protected set
+
+ protected var state: Int
+
+ override var width = UNSPECIFIED
+ protected set
+
+ override var height = UNSPECIFIED
+ protected set
+
+ // Returns the width rounded to the next power of 2.
+ var textureWidth = 0
+ protected set
+
+ // Returns the height rounded to the next power of 2.
+ var textureHeight = 0
+ protected set
+
+ protected var canvasRef: GLCanvas? = null
+
+ private var hasBorder1 = false
+
+ var isRecycled = false
+ protected set
+
+ init {
+ setAssociatedCanvas(canvas)
+ this.id = id
+ this.state = state
+ synchronized(ALL_TEXTURES) { ALL_TEXTURES.put(this, null) }
+ }
+
+ protected fun setAssociatedCanvas(canvas: GLCanvas?) {
+ canvasRef = canvas
+ }
+
+ /**
+ * Sets the content size of this secondBitmap. In OpenGL, the actual secondBitmap
+ * size must be of power of 2, the size of the content may be smaller.
+ */
+ fun setSize(width: Int, height: Int) {
+ this.width = width
+ this.height = height
+ textureWidth = if (width > 0) GLCanvasUtils.nextPowerOf2(width) else 0
+ textureHeight = if (height > 0) GLCanvasUtils.nextPowerOf2(height) else 0
+ if (textureWidth > MAX_TEXTURE_SIZE || textureHeight > MAX_TEXTURE_SIZE) {
+ Log.w(TAG, String.format("secondBitmap is too large: %d x %d",
+ textureWidth, textureHeight), Exception())
+ }
+ }
+
+ open val isFlippedVertically: Boolean
+ get() = false
+
+ // Returns true if the secondBitmap has one pixel transparent border around the
+ // actual content. This is used to avoid jigged edges.
+ //
+ // The jigged edges appear because we use GL_CLAMP_TO_EDGE for secondBitmap wrap
+ // mode (GL_CLAMP is not available in OpenGL ES), so a pixel partially
+ // covered by the secondBitmap will use the color of the edge texel. If we add
+ // the transparent border, the color of the edge texel will be mixed with
+ // appropriate amount of transparent.
+ //
+ // Currently our background is black, so we can draw the thumbnails without
+ // enabling blending.
+ fun hasBorder(): Boolean {
+ return hasBorder1
+ }
+
+ protected fun setBorder(hasBorder: Boolean) {
+ hasBorder1 = hasBorder
+ }
+
+ override fun draw(canvas: GLCanvas?, x: Int, y: Int) {
+ canvas!!.drawTexture(this, x, y, width, height, BasicTextureFilter(), null)
+ }
+
+ override fun draw(canvas: GLCanvas?, x: Int, y: Int, w: Int, h: Int) {
+ canvas!!.drawTexture(this, x, y, w, h, BasicTextureFilter(), null)
+ }
+
+ // onBind is called before GLCanvas binds this secondBitmap.
+ // It should make sure the data is uploaded to GL memory.
+ abstract fun onBind(canvas: GLCanvas): Boolean
+
+ // Returns the GL secondBitmap target for this secondBitmap (e.g. GL_TEXTURE_2D).
+ abstract val target: Int
+
+ val isLoaded: Boolean
+ get() = state == STATE_LOADED
+
+ // recycle() is called when the secondBitmap will never be used again,
+ // so it can free all resources.
+ open fun recycle() {
+ isRecycled = true
+ freeResource()
+ }
+
+ // yield() is called when the secondBitmap will not be used temporarily,
+ // so it can free some resources.
+ // The default implementation unloads the secondBitmap from GL memory, so
+ // the subclass should make sure it can reload the secondBitmap to GL memory
+ // later, or it will have to override this method.
+ open fun yield() {
+ freeResource()
+ }
+
+ private fun freeResource() {
+ val canvas = canvasRef
+ if (canvas != null && id != -1) {
+ canvas.unloadTexture(this)
+ id = -1 // Don't free it again.
+ }
+ state = STATE_UNLOADED
+ setAssociatedCanvas(null)
+ }
+
+ protected fun finalize() {
+ IN_FINALIZER.set(BasicTexture::class)
+ recycle()
+ IN_FINALIZER.set(null)
+ }
+
+ companion object {
+
+ private val TAG = this::class.java.name
+
+ const val UNSPECIFIED = -1
+
+ const val STATE_UNLOADED = 0
+
+ const val STATE_LOADED = 1
+
+ const val STATE_ERROR = -1
+
+ // Log a warning if a secondBitmap is larger along a dimension
+ private const val MAX_TEXTURE_SIZE = 4096
+
+ private val ALL_TEXTURES = WeakHashMap()
+
+ private val IN_FINALIZER = ThreadLocal()
+
+ // This is for deciding if we can call Bitmap's recycle().
+ // We cannot call Bitmap's recycle() in finalizer because at that point
+ // the finalizer of Bitmap may already be called so recycle() will crash.
+ fun inFinalizer(): Boolean {
+ return IN_FINALIZER.get() != null
+ }
+
+ fun yieldAllTextures() {
+ synchronized(ALL_TEXTURES) {
+ for (t in ALL_TEXTURES.keys) {
+ t.yield()
+ }
+ }
+ }
+
+ fun invalidateAllTextures() {
+ synchronized(ALL_TEXTURES) {
+ for (t in ALL_TEXTURES.keys) {
+ t.state = STATE_UNLOADED
+ t.setAssociatedCanvas(null)
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/glcanvas/BitmapTexture.kt b/opengl/src/main/java/jp/eita/canvasgl/glcanvas/BitmapTexture.kt
new file mode 100644
index 0000000..16c7df3
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/glcanvas/BitmapTexture.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.glcanvas
+
+import android.graphics.Bitmap
+
+// BitmapTexture is a secondBitmap whose content is specified by a fixed Bitmap.
+//
+// The secondBitmap does not own the Bitmap. The user should make sure the Bitmap
+// is valid during the secondBitmap's lifetime. When the secondBitmap is recycled, it
+// does not free the Bitmap.
+class BitmapTexture constructor(var bitmap: Bitmap, hasBorder: Boolean = false) : UploadedTexture(hasBorder) {
+
+ override fun onFreeBitmap(bitmap: Bitmap?) {
+ // Do nothing.
+ }
+
+ override fun onGetBitmap(): Bitmap? {
+ return bitmap
+ }
+
+ override val isOpaque: Boolean
+ get() = false
+
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/glcanvas/GLCanvas.kt b/opengl/src/main/java/jp/eita/canvasgl/glcanvas/GLCanvas.kt
new file mode 100644
index 0000000..e9601fc
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/glcanvas/GLCanvas.kt
@@ -0,0 +1,241 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.glcanvas
+
+import android.graphics.Bitmap
+import android.graphics.Rect
+import android.graphics.RectF
+import jp.eita.canvasgl.shapeFilter.DrawShapeFilter
+import jp.eita.canvasgl.textureFilter.TextureFilter
+import java.nio.ByteBuffer
+import java.nio.FloatBuffer
+
+//
+// GLCanvas gives a convenient interface to draw using OpenGL.
+//
+// When a rectangle is specified in this interface, it means the region
+// [x, x+width) * [y, y+height)
+//
+interface GLCanvas {
+
+ val gLId: GLId?
+
+ // Tells GLCanvas the size of the underlying GL surface. This should be
+ // called before first drawing and when the size of GL surface is changed.
+ // This is called by GLRoot and should not be called by the clients
+ // who only want to draw on the GLCanvas. Both width and height must be
+ // nonnegative.
+ fun setSize(width: Int, height: Int)
+
+ // Clear the drawing buffers.
+ fun clearBuffer()
+ fun clearBuffer(argb: FloatArray?)
+
+ // Sets and gets the current alpha, alpha must be in [0, 1].
+ var alpha: Float
+
+ // (current alpha) = (current alpha) * alpha
+ fun multiplyAlpha(alpha: Float)
+
+ // Change the current transform matrix.
+ fun translate(x: Float, y: Float, z: Float)
+
+ fun translate(x: Float, y: Float)
+
+ fun scale(sx: Float, sy: Float, sz: Float)
+
+ fun rotate(angle: Float, x: Float, y: Float, z: Float)
+
+ fun multiplyMatrix(matrix: FloatArray?, offset: Int)
+
+ // Pushes the configuration state (matrix, and alpha) onto
+ // a private stack.
+ fun save()
+
+ // Same as save(), but only save those specified in saveFlags.
+ fun save(saveFlags: Int)
+
+ // Pops from the top of the stack as current configuration state (matrix,
+ // alpha, and clip). This call balances a previous call to save(), and is
+ // used to remove all modifications to the configuration state since the
+ // last save call.
+ fun restore()
+
+ fun drawCircle(x: Float, y: Float, radius: Float, paint: GLPaint?, drawShapeFilter: DrawShapeFilter?)
+
+ // Draws a line using the specified paint from (x1, y1) to (x2, y2).
+ // (Both end points are included).
+ fun drawLine(x1: Float, y1: Float, x2: Float, y2: Float, paint: GLPaint?, drawShapeFilter: DrawShapeFilter?)
+
+ // Draws a rectangle using the specified paint from (x1, y1) to (x2, y2).
+ // (Both end points are included).
+ fun drawRect(x1: Float, y1: Float, x2: Float, y2: Float, paint: GLPaint?, drawShapeFilter: DrawShapeFilter?)
+
+ // Fills the specified rectangle with the specified color.
+ fun fillRect(x: Float, y: Float, width: Float, height: Float, color: Int, drawShapeFilter: DrawShapeFilter?)
+
+ // Draws a secondBitmap to the specified rectangle.
+ fun drawTexture(
+ texture: BasicTexture?, x: Int, y: Int, width: Int, height: Int, textureFilter: TextureFilter?, customMVPMatrix: ICustomMVPMatrix?)
+
+ fun drawMesh(tex: BasicTexture?, x: Int, y: Int, xyBuffer: Int,
+ uvBuffer: Int, indexBuffer: Int, indexCount: Int, mode: Int)
+
+ // Draws the source rectangle part of the secondBitmap to the target rectangle.
+ fun drawTexture(texture: BasicTexture?, source: RectF?, target: RectF?, textureFilter: TextureFilter?, customMVPMatrix: ICustomMVPMatrix?)
+
+ // Draw a secondBitmap with a specified secondBitmap transform.
+ fun drawTexture(texture: BasicTexture?, textureTransform: FloatArray?,
+ x: Int, y: Int, w: Int, h: Int, textureFilter: TextureFilter?, customMVPMatrix: ICustomMVPMatrix?)
+
+ // Draw two textures to the specified rectangle. The actual secondBitmap used is
+ // from * (1 - ratio) + to * ratio
+ // The two textures must have the same size.
+ fun drawMixed(from: BasicTexture?, toColor: Int,
+ ratio: Float, x: Int, y: Int, w: Int, h: Int, drawShapeFilter: DrawShapeFilter?)
+
+ // Draw a region of a secondBitmap and a specified color to the specified
+ // rectangle. The actual color used is from * (1 - ratio) + to * ratio.
+ // The region of the secondBitmap is defined by parameter "src". The target
+ // rectangle is specified by parameter "target".
+ fun drawMixed(from: BasicTexture?, toColor: Int,
+ ratio: Float, src: RectF?, target: RectF?, drawShapeFilter: DrawShapeFilter?)
+
+ // Unloads the specified secondBitmap from the canvas. The resource allocated
+ // to draw the secondBitmap will be released. The specified secondBitmap will return
+ // to the unloaded state. This function should be called only from
+ // BasicTexture or its descendant
+ fun unloadTexture(texture: BasicTexture?): Boolean
+
+ // Delete the specified buffer object, similar to unloadTexture.
+ fun deleteBuffer(bufferId: Int)
+
+ // Delete the textures and buffers in GL side. This function should only be
+ // called in the GL thread.
+ fun deleteRecycledResources()
+
+ // Dump statistics information and clear the counters. For debug only.
+ fun dumpStatisticsAndClear()
+
+ fun beginRenderTarget(texture: RawTexture?)
+
+ fun endRenderTarget()
+
+ /**
+ * Sets secondBitmap parameters to use GL_CLAMP_TO_EDGE for both
+ * GL_TEXTURE_WRAP_S and GL_TEXTURE_WRAP_T. Sets secondBitmap parameters to be
+ * GL_LINEAR for GL_TEXTURE_MIN_FILTER and GL_TEXTURE_MAG_FILTER.
+ * bindTexture() must be called prior to this.
+ *
+ * @param texture The secondBitmap to set parameters on.
+ */
+ fun setTextureParameters(texture: BasicTexture?)
+
+ /**
+ * Initializes the secondBitmap to a size by calling texImage2D on it.
+ *
+ * @param texture The secondBitmap to initialize the size.
+ * @param format The secondBitmap format (e.g. GL_RGBA)
+ * @param type The secondBitmap type (e.g. GL_UNSIGNED_BYTE)
+ */
+ fun initializeTextureSize(texture: BasicTexture?, format: Int, type: Int)
+
+ /**
+ * Initializes the secondBitmap to a size by calling texImage2D on it.
+ *
+ * @param texture The secondBitmap to initialize the size.
+ * @param bitmap The bitmap to initialize the bitmap with.
+ */
+ fun initializeTexture(texture: BasicTexture?, bitmap: Bitmap?)
+
+ /**
+ * Calls glTexSubImage2D to upload a bitmap to the secondBitmap.
+ *
+ * @param texture The target secondBitmap to write to.
+ * @param xOffset Specifies a texel offset in the x direction within the
+ * secondBitmap array.
+ * @param yOffset Specifies a texel offset in the y direction within the
+ * secondBitmap array.
+ * @param format The secondBitmap format (e.g. GL_RGBA)
+ * @param type The secondBitmap type (e.g. GL_UNSIGNED_BYTE)
+ */
+ fun texSubImage2D(texture: BasicTexture?, xOffset: Int, yOffset: Int,
+ bitmap: Bitmap?,
+ format: Int, type: Int)
+
+ /**
+ * Generates buffers and uploads the buffer data.
+ *
+ * @param buffer The buffer to upload
+ * @return The buffer ID that was generated.
+ */
+ fun uploadBuffer(buffer: FloatBuffer?): Int
+
+ /**
+ * Generates buffers and uploads the element array buffer data.
+ *
+ * @param buffer The buffer to upload
+ * @return The buffer ID that was generated.
+ */
+ fun uploadBuffer(buffer: ByteBuffer?): Int
+
+ /**
+ * After LightCycle makes GL calls, this method is called to restore the GL
+ * configuration to the one expected by GLCanvas.
+ */
+ fun recoverFromLightCycle()
+
+ /**
+ * Gets the bounds given by x, y, width, and height as well as the internal
+ * matrix state. There is no special handling for non-90-degree rotations.
+ * It only considers the lower-left and upper-right corners as the bounds.
+ *
+ * @param bounds The output bounds to write to.
+ * @param x The left side of the input rectangle.
+ * @param y The bottom of the input rectangle.
+ * @param width The width of the input rectangle.
+ * @param height The height of the input rectangle.
+ */
+ fun getBounds(bounds: Rect?, x: Int, y: Int, width: Int, height: Int)
+
+ fun setOnPreDrawTextureListener(l: OnPreDrawTextureListener?)
+
+ fun setOnPreDrawShapeListener(l: OnPreDrawShapeListener?)
+
+ interface OnPreDrawTextureListener {
+
+ fun onPreDraw(textureProgram: Int, texture: BasicTexture?, textureFilter: TextureFilter?)
+ }
+
+ interface OnPreDrawShapeListener {
+
+ fun onPreDraw(program: Int, drawShapeFilter: DrawShapeFilter?)
+ }
+
+ interface ICustomMVPMatrix {
+
+ fun getMVPMatrix(viewportW: Int, viewportH: Int, x: Float, y: Float, drawW: Float, drawH: Float): FloatArray?
+ }
+
+ companion object {
+
+ const val SAVE_FLAG_ALL = -0x1
+
+ const val SAVE_FLAG_ALPHA = 0x01
+
+ const val SAVE_FLAG_MATRIX = 0x02
+ }
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/glcanvas/GLCanvasUtils.kt b/opengl/src/main/java/jp/eita/canvasgl/glcanvas/GLCanvasUtils.kt
new file mode 100644
index 0000000..8974ac1
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/glcanvas/GLCanvasUtils.kt
@@ -0,0 +1,343 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.glcanvas
+
+import android.content.Context
+import android.content.pm.PackageInfo
+import android.content.pm.PackageManager
+import android.database.Cursor
+import android.os.Build
+import android.os.ParcelFileDescriptor
+import android.text.TextUtils
+import android.util.Log
+import java.io.Closeable
+import java.io.IOException
+import java.io.InterruptedIOException
+import kotlin.math.min
+
+object GLCanvasUtils {
+
+ private val TAG = this::class.simpleName
+
+ private const val DEBUG_TAG = "GalleryDebug"
+
+ private const val POLY64REV = -0x6a536cd653b4364bL
+
+ private const val INITIALCRC = -0x1L
+
+ private val IS_DEBUG_BUILD = Build.TYPE == "eng" || Build.TYPE == "userdebug"
+
+ private const val MASK_STRING = "********************************"
+
+ private val CRC_TABLE = LongArray(256)
+
+ // Throws AssertionError if the input is false.
+ fun assertTrue(cond: Boolean) {
+ if (!cond) {
+ throw AssertionError()
+ }
+ }
+
+ // Throws AssertionError with the message. We had a method having the form
+ // assertTrue(boolean cond, String message, Object ... args);
+ // However a call to that method will cause memory allocation even if the
+ // condition is false (due to autoboxing generated by "Object ... args"),
+ // so we don't use that anymore.
+ fun fail(message: String?, vararg args: Any?) {
+ throw AssertionError(
+ if (args.isEmpty()) message else String.format(message!!, *args))
+ }
+
+ // Throws NullPointerException if the input is null.
+ fun checkNotNull(`object`: T?): T {
+ if (`object` == null) throw NullPointerException()
+ return `object`
+ }
+
+ // Returns true if two input Object are both null or equal
+ // to each other.
+ fun equals(a: Any?, b: Any?): Boolean {
+ return a == b
+ }
+
+ // Returns the next power of two.
+ // Returns the input if it is already power of 2.
+ // Throws IllegalArgumentException if the input is <= 0 or
+ // the answer overflows.
+ fun nextPowerOf2(n: Int): Int {
+ var result = n
+ require(!(result <= 0 || result > 1 shl 30)) { "n is invalid: $result" }
+ result -= 1
+ result = result or (result shr 16)
+ result = result or (result shr 8)
+ result = result or (result shr 4)
+ result = result or (result shr 2)
+ result = result or (result shr 1)
+
+ return result + 1
+ }
+
+ // Returns the previous power of two.
+ // Returns the input if it is already power of 2.
+ // Throws IllegalArgumentException if the input is <= 0
+ fun prevPowerOf2(n: Int): Int {
+ require(n > 0)
+ return Integer.highestOneBit(n)
+ }
+
+ // Returns the input value x clamped to the range [min, max].
+ fun clamp(x: Int, min: Int, max: Int): Int {
+ if (x > max) return max
+ return if (x < min) min else x
+ }
+
+ // Returns the input value x clamped to the range [min, max].
+ fun clamp(x: Float, min: Float, max: Float): Float {
+ if (x > max) return max
+ return if (x < min) min else x
+ }
+
+ // Returns the input value x clamped to the range [min, max].
+ fun clamp(x: Long, min: Long, max: Long): Long {
+ if (x > max) return max
+ return if (x < min) min else x
+ }
+
+ fun isOpaque(color: Int): Boolean {
+ return color ushr 24 == 0xFF
+ }
+
+ fun swap(array: IntArray, i: Int, j: Int) {
+ val temp = array[i]
+ array[i] = array[j]
+ array[j] = temp
+ }
+
+ /**
+ * A function thats returns a 64-bit crc for string
+ *
+ * @param in input string
+ * @return a 64-bit crc value
+ */
+ fun crc64Long(`in`: String?): Long {
+ return if (`in` == null || `in`.isEmpty()) {
+ 0
+ } else crc64Long(getBytes(`in`))
+ }
+
+ fun crc64Long(buffer: ByteArray): Long {
+ var crc = INITIALCRC
+ for (b in buffer) {
+ crc = CRC_TABLE[crc.toInt() xor b.toInt() and 0xff] xor (crc shr 8)
+ }
+ return crc
+ }
+
+ fun getBytes(`in`: String): ByteArray {
+ val result = ByteArray(`in`.length * 2)
+ var output = 0
+ for (ch in `in`.toCharArray()) {
+ result[output++] = (ch.toInt() and 0xFF).toByte()
+ result[output++] = (ch.toInt() shr 8).toByte()
+ }
+ return result
+ }
+
+ fun closeSilently(c: Closeable?) {
+ if (c == null) return
+ try {
+ c.close()
+ } catch (t: IOException) {
+ Log.w(TAG, "close fail ", t)
+ }
+ }
+
+ fun compare(a: Long, b: Long): Int {
+ return a.compareTo(b)
+ }
+
+ fun ceilLog2(value: Float): Int {
+ var i = 0
+ while (i < 31) {
+ if (1 shl i >= value) break
+ i++
+ }
+ return i
+ }
+
+ fun floorLog2(value: Float): Int {
+ var i: Int = 0
+ while (i < 31) {
+ if (1 shl i > value) break
+ i++
+ }
+ return i - 1
+ }
+
+ fun closeSilently(fd: ParcelFileDescriptor?) {
+ try {
+ fd?.close()
+ } catch (t: Throwable) {
+ Log.w(TAG, "fail to close", t)
+ }
+ }
+
+ fun closeSilently(cursor: Cursor?) {
+ try {
+ cursor?.close()
+ } catch (t: Throwable) {
+ Log.w(TAG, "fail to close", t)
+ }
+ }
+
+ fun interpolateAngle(
+ source: Float, target: Float, progress: Float): Float {
+ // interpolate the angle from source to target
+ // We make the difference in the range of [-179, 180], this is the
+ // shortest path to change source to target.
+ var diff = target - source
+ if (diff < 0) diff += 360f
+ if (diff > 180) diff -= 360f
+ val result = source + diff * progress
+ return if (result < 0) result + 360f else result
+ }
+
+ fun interpolateScale(
+ source: Float, target: Float, progress: Float): Float {
+ return source + progress * (target - source)
+ }
+
+ fun ensureNotNull(value: String?): String {
+ return value ?: ""
+ }
+
+ fun parseFloatSafely(content: String?, defaultValue: Float): Float {
+ return if (content == null) defaultValue else try {
+ content.toFloat()
+ } catch (e: NumberFormatException) {
+ defaultValue
+ }
+ }
+
+ fun parseIntSafely(content: String?, defaultValue: Int): Int {
+ return if (content == null) defaultValue else try {
+ content.toInt()
+ } catch (e: NumberFormatException) {
+ defaultValue
+ }
+ }
+
+ fun isNullOrEmpty(exifMake: String?): Boolean {
+ return TextUtils.isEmpty(exifMake)
+ }
+
+ fun waitWithoutInterrupt(`object`: Object) {
+ try {
+ `object`.wait()
+ } catch (e: InterruptedException) {
+ Log.w(TAG, "unexpected interrupt: $`object`")
+ }
+ }
+
+ fun handleInterrruptedException(e: Throwable?): Boolean {
+ // A helper to deal with the interrupt exception
+ // If an interrupt detected, we will setup the bit again.
+ if (e is InterruptedIOException
+ || e is InterruptedException) {
+ Thread.currentThread().interrupt()
+ return true
+ }
+ return false
+ }
+
+ /**
+ * @return String with special XML characters escaped.
+ */
+ fun escapeXml(s: String): String {
+ val sb = StringBuilder()
+ var i = 0
+ val len = s.length
+ while (i < len) {
+ when (val c = s[i]) {
+ '<' -> sb.append("<")
+ '>' -> sb.append(">")
+ '\"' -> sb.append(""")
+ '\'' -> sb.append("'")
+ '&' -> sb.append("&")
+ else -> sb.append(c)
+ }
+ ++i
+ }
+ return sb.toString()
+ }
+
+ fun getUserAgent(context: Context): String {
+ val packageInfo: PackageInfo
+ packageInfo = try {
+ context.packageManager.getPackageInfo(context.packageName, 0)
+ } catch (e: PackageManager.NameNotFoundException) {
+ throw IllegalStateException("getPackageInfo failed")
+ }
+ return String.format("%s/%s; %s/%s/%s/%s; %s/%s/%s",
+ packageInfo.packageName,
+ packageInfo.versionName,
+ Build.BRAND,
+ Build.DEVICE,
+ Build.MODEL,
+ Build.ID,
+ Build.VERSION.SDK_INT,
+ Build.VERSION.RELEASE,
+ Build.VERSION.INCREMENTAL)
+ }
+
+ fun copyOf(source: Array, newSize: Int): Array {
+ var newSizeEdited = newSize
+ val result = arrayOfNulls(newSizeEdited)
+ newSizeEdited = min(source.size, newSizeEdited)
+ System.arraycopy(source, 0, result, 0, newSizeEdited)
+
+ return result
+ }
+
+ // Mask information for debugging only. It returns info.toString()
directly
+ // for debugging build (i.e., 'eng' and 'userdebug') and returns a mask ("****")
+ // in release build to protect the information (e.g. for privacy issue).
+ fun maskDebugInfo(info: Any?): String? {
+ if (info == null) return null
+ val s = info.toString()
+ val length = min(s.length, MASK_STRING.length)
+ return if (IS_DEBUG_BUILD) s else MASK_STRING.substring(0, length)
+ }
+
+ // This method should be ONLY used for debugging.
+ fun debug(message: String?, vararg args: Any?) {
+ Log.v(DEBUG_TAG, String.format(message!!, *args))
+ }
+
+ init {
+ // http://bioinf.cs.ucl.ac.uk/downloads/crc64/crc64.c
+ var part: Long
+
+ for (i in 0..255) {
+ part = i.toLong()
+ for (j in 0..7) {
+ val x = if (part.toInt() and 1 != 0) POLY64REV else 0
+ part = part.shr(1) xor x
+ }
+ CRC_TABLE[i] = part
+ }
+ }
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/glcanvas/GLES20Canvas.kt b/opengl/src/main/java/jp/eita/canvasgl/glcanvas/GLES20Canvas.kt
new file mode 100644
index 0000000..9ff333d
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/glcanvas/GLES20Canvas.kt
@@ -0,0 +1,1005 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.glcanvas
+
+import android.graphics.Bitmap
+import android.graphics.Rect
+import android.graphics.RectF
+import android.opengl.GLES11Ext
+import android.opengl.GLES20
+import android.opengl.GLUtils
+import android.opengl.Matrix
+import android.util.Log
+import jp.eita.canvasgl.glcanvas.GLCanvas.*
+import jp.eita.canvasgl.glcanvas.TextureMatrixTransformer.convertCoordinate
+import jp.eita.canvasgl.glcanvas.TextureMatrixTransformer.copyTextureCoordinates
+import jp.eita.canvasgl.glcanvas.TextureMatrixTransformer.setTextureMatrix
+import jp.eita.canvasgl.shapeFilter.BasicDrawShapeFilter
+import jp.eita.canvasgl.shapeFilter.DrawShapeFilter
+import jp.eita.canvasgl.textureFilter.BasicTextureFilter
+import jp.eita.canvasgl.textureFilter.TextureFilter
+import jp.eita.canvasgl.util.Loggers
+import java.nio.Buffer
+import java.nio.ByteBuffer
+import java.nio.ByteOrder
+import java.nio.FloatBuffer
+import java.util.*
+import kotlin.math.max
+import kotlin.math.min
+import kotlin.math.roundToInt
+
+/**
+ * drawRect, drawLine, drawCircle --> prepareDraw --> [GLES20Canvas.draw]
+ * drawTexture --> setupTextureFilter --> drawTextureRect --> prepareTexture
+ * --> setPosition --> draw --> setMatrix
+ */
+class GLES20Canvas : GLCanvas {
+
+ private val mUnboundTextures = IntArrayCustom()
+
+ private val mDeleteBuffers = IntArrayCustom()
+
+ // Temporary variables used within calculations
+ private val mTempMatrix = FloatArray(32)
+
+ private val mTempColor = FloatArray(4)
+
+ private val mTempSourceRect = RectF()
+
+ private val mTempTargetRect = RectF()
+
+ private val mTempTextureMatrix = FloatArray(MATRIX_SIZE)
+
+ private val mTempIntArray = IntArray(1)
+
+ private var mDrawParameters = arrayOf(
+ AttributeShaderParameter(POSITION_ATTRIBUTE), // INDEX_POSITION
+ UniformShaderParameter(MATRIX_UNIFORM), // INDEX_MATRIX
+ UniformShaderParameter(COLOR_UNIFORM))
+
+ private var mTextureParameters = arrayOf(
+ AttributeShaderParameter(POSITION_ATTRIBUTE), // INDEX_POSITION
+ UniformShaderParameter(MATRIX_UNIFORM), // INDEX_MATRIX
+ UniformShaderParameter(TEXTURE_MATRIX_UNIFORM), // INDEX_TEXTURE_MATRIX
+ UniformShaderParameter(TEXTURE_SAMPLER_UNIFORM), // INDEX_TEXTURE_SAMPLER
+ UniformShaderParameter(ALPHA_UNIFORM))
+
+ private var mOesTextureParameters = arrayOf(
+ AttributeShaderParameter(POSITION_ATTRIBUTE), // INDEX_POSITION
+ UniformShaderParameter(MATRIX_UNIFORM), // INDEX_MATRIX
+ UniformShaderParameter(TEXTURE_MATRIX_UNIFORM), // INDEX_TEXTURE_MATRIX
+ UniformShaderParameter(TEXTURE_SAMPLER_UNIFORM), // INDEX_TEXTURE_SAMPLER
+ UniformShaderParameter(ALPHA_UNIFORM))
+
+ private var mMeshParameters = arrayOf(
+ AttributeShaderParameter(POSITION_ATTRIBUTE), // INDEX_POSITION
+ UniformShaderParameter(MATRIX_UNIFORM), // INDEX_MATRIX
+ AttributeShaderParameter(TEXTURE_COORD_ATTRIBUTE), // INDEX_TEXTURE_COORD
+ UniformShaderParameter(TEXTURE_SAMPLER_UNIFORM), // INDEX_TEXTURE_SAMPLER
+ UniformShaderParameter(ALPHA_UNIFORM))
+
+ private val mDrawShapeFilterMapProgramId: MutableMap = HashMap()
+
+ private val mTextureFilterMapProgramId: MutableMap = HashMap()
+
+ private val mOESTextureFilterMapProgramId: MutableMap = HashMap()
+
+ // Keep track of restore state
+ private var mMatrices = FloatArray(INITIAL_RESTORE_STATE_SIZE * MATRIX_SIZE)
+
+ private var mAlphas = FloatArray(INITIAL_RESTORE_STATE_SIZE)
+
+ private val mSaveFlags = IntArrayCustom()
+
+ private var mCurrentAlphaIndex = 0
+
+ private var mCurrentMatrixIndex = 0
+
+ // Viewport size
+ private var mWidth = 0
+
+ private var mHeight = 0
+
+ // Projection matrix
+ private val mProjectionMatrix = FloatArray(MATRIX_SIZE)
+
+ // Screen size for when we aren't bound to a secondBitmap
+ private var mScreenWidth = 0
+
+ private var mScreenHeight = 0
+
+ // GL programs
+ private var mDrawProgram: Int
+
+ private var mTextureProgram = 0
+
+ private var mOesTextureProgram = 0
+
+ private var mMeshProgram = 0
+
+ // GL buffer containing BOX_COORDINATES
+ private val mBoxCoordinates: Int
+
+ private var mTextureFilter: TextureFilter? = null
+
+ private var mDrawShapeFilter: DrawShapeFilter? = null
+
+ private var onPreDrawTextureListener: OnPreDrawTextureListener? = null
+
+ private var onPreDrawShapeListener: OnPreDrawShapeListener? = null
+
+ // Keep track of statistics for debugging
+ private var mCountDrawMesh = 0
+
+ private var countTextureRect = 0
+
+ private var mCountFillRect = 0
+
+ private var mCountDrawLine = 0
+
+ // Buffer for framebuffer IDs -- we keep track so we can switch the attached
+ // secondBitmap.
+ private val mFrameBuffer = IntArray(1)
+
+ // Bound textures.
+ private val mTargetTextures = ArrayList()
+
+ override val gLId: GLId? = GLES20IdImpl()
+
+ init {
+ Matrix.setIdentityM(mTempTextureMatrix, 0)
+ Matrix.setIdentityM(mMatrices, mCurrentMatrixIndex)
+ mAlphas[mCurrentAlphaIndex] = 1f
+ mTargetTextures.add(null)
+ val boxBuffer = createBuffer(BOX_COORDINATES)
+ mBoxCoordinates = uploadBuffer(boxBuffer)
+ mDrawProgram = assembleProgram(loadShader(GLES20.GL_VERTEX_SHADER, BasicDrawShapeFilter.DRAW_VERTEX_SHADER), loadShader(GLES20.GL_FRAGMENT_SHADER, BasicDrawShapeFilter.DRAW_FRAGMENT_SHADER), mDrawParameters, mTempIntArray)
+ val textureFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, BasicTextureFilter.TEXTURE_FRAGMENT_SHADER)
+ val meshVertexShader = loadShader(GLES20.GL_VERTEX_SHADER, MESH_VERTEX_SHADER)
+ setupMeshProgram(meshVertexShader, textureFragmentShader)
+ GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA)
+ checkError()
+ }
+
+ private fun setupMeshProgram(meshVertexShader: Int, textureFragmentShader: Int) {
+ mMeshProgram = assembleProgram(meshVertexShader, textureFragmentShader, mMeshParameters, mTempIntArray)
+ }
+
+ override fun setSize(width: Int, height: Int) {
+ mWidth = width
+ mHeight = height
+ checkError()
+ Matrix.setIdentityM(mMatrices, mCurrentMatrixIndex)
+ Matrix.orthoM(mProjectionMatrix, 0, 0f, width.toFloat(), 0f, height.toFloat(), -1f, 1f)
+ if (targetTexture == null) {
+ mScreenWidth = width
+ mScreenHeight = height
+ Matrix.translateM(mMatrices, mCurrentMatrixIndex, 0f, height.toFloat(), 0f)
+ Matrix.scaleM(mMatrices, mCurrentMatrixIndex, 1f, -1f, 1f)
+ }
+ }
+
+ override fun clearBuffer() {
+ GLES20.glClearColor(0f, 0f, 0f, 0f)
+ checkError()
+ GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
+ checkError()
+ }
+
+ override fun clearBuffer(argb: FloatArray?) {
+ GLES20.glClearColor(argb!![1], argb[2], argb[3], argb[0])
+ checkError()
+ GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
+ checkError()
+ }
+
+ override var alpha: Float
+ get() = mAlphas[mCurrentAlphaIndex]
+ set(alpha) {
+ mAlphas[mCurrentAlphaIndex] = alpha
+ }
+
+ override fun multiplyAlpha(alpha: Float) {
+ this.alpha = alpha * alpha
+ }
+
+ override fun translate(x: Float, y: Float, z: Float) {
+ Matrix.translateM(mMatrices, mCurrentMatrixIndex, x, y, z)
+ }
+
+ // This is a faster version of translate(x, y, z) because
+ // (1) we knows z = 0, (2) we inline the Matrix.translateM call,
+ // (3) we unroll the loop
+ override fun translate(x: Float, y: Float) {
+ val index = mCurrentMatrixIndex
+ val m = mMatrices
+ m[index + 12] += m[index] * x + m[index + 4] * y
+ m[index + 13] += m[index + 1] * x + m[index + 5] * y
+ m[index + 14] += m[index + 2] * x + m[index + 6] * y
+ m[index + 15] += m[index + 3] * x + m[index + 7] * y
+ }
+
+ override fun scale(sx: Float, sy: Float, sz: Float) {
+ Matrix.scaleM(mMatrices, mCurrentMatrixIndex, sx, sy, sz)
+ }
+
+ override fun rotate(angle: Float, x: Float, y: Float, z: Float) {
+ if (angle == 0.0f) {
+ return
+ }
+
+ val temp = mTempMatrix
+ Matrix.setRotateM(temp, 0, angle, x, y, z)
+ val matrix = mMatrices
+ val index = mCurrentMatrixIndex
+ Matrix.multiplyMM(temp, MATRIX_SIZE, matrix, index, temp, 0)
+ System.arraycopy(temp, MATRIX_SIZE, matrix, index, MATRIX_SIZE)
+ }
+
+ override fun multiplyMatrix(matrix: FloatArray?, offset: Int) {
+ val temp = mTempMatrix
+ val currentMatrix = mMatrices
+ val index = mCurrentMatrixIndex
+ Matrix.multiplyMM(temp, 0, currentMatrix, index, matrix, offset)
+ System.arraycopy(temp, 0, currentMatrix, index, 16)
+ }
+
+ override fun save() {
+ save(GLCanvas.SAVE_FLAG_ALL)
+ }
+
+ override fun save(saveFlags: Int) {
+ val saveAlpha = saveFlags and GLCanvas.SAVE_FLAG_ALPHA == GLCanvas.SAVE_FLAG_ALPHA
+ if (saveAlpha) {
+ val currentAlpha = alpha
+ mCurrentAlphaIndex++
+ if (mAlphas.size <= mCurrentAlphaIndex) {
+ mAlphas = mAlphas.copyOf(mAlphas.size * 2)
+ }
+ mAlphas[mCurrentAlphaIndex] = currentAlpha
+ }
+ val saveMatrix = saveFlags and GLCanvas.SAVE_FLAG_MATRIX == GLCanvas.SAVE_FLAG_MATRIX
+ if (saveMatrix) {
+ val currentIndex = mCurrentMatrixIndex
+ mCurrentMatrixIndex += MATRIX_SIZE
+ if (mMatrices.size <= mCurrentMatrixIndex) {
+ mMatrices = mMatrices.copyOf(mMatrices.size * 2)
+ }
+ System.arraycopy(mMatrices, currentIndex, mMatrices, mCurrentMatrixIndex, MATRIX_SIZE)
+ }
+ mSaveFlags.add(saveFlags)
+ }
+
+ override fun restore() {
+ val restoreFlags = mSaveFlags.removeLast()
+ val restoreAlpha = restoreFlags and GLCanvas.SAVE_FLAG_ALPHA == GLCanvas.SAVE_FLAG_ALPHA
+ if (restoreAlpha) {
+ mCurrentAlphaIndex--
+ }
+ val restoreMatrix = restoreFlags and GLCanvas.SAVE_FLAG_MATRIX == GLCanvas.SAVE_FLAG_MATRIX
+ if (restoreMatrix) {
+ mCurrentMatrixIndex -= MATRIX_SIZE
+ }
+ }
+
+ override fun drawCircle(x: Float, y: Float, radius: Float, paint: GLPaint?, drawShapeFilter: DrawShapeFilter?) {
+ setupDrawShapeFilter(drawShapeFilter)
+ draw(GLES20.GL_TRIANGLE_STRIP, OFFSET_FILL_RECT, COUNT_FILL_VERTEX, x, y, 2 * radius, 2 * radius, paint!!.color, 0f)
+ }
+
+ override fun drawLine(x1: Float, y1: Float, x2: Float, y2: Float, paint: GLPaint?, drawShapeFilter: DrawShapeFilter?) {
+ setupDrawShapeFilter(drawShapeFilter)
+ draw(GLES20.GL_LINE_STRIP, OFFSET_DRAW_LINE, COUNT_LINE_VERTEX, x1, y1, x2 - x1, y2 - y1,
+ paint)
+ mCountDrawLine++
+ }
+
+ override fun drawRect(x: Float, y: Float, width: Float, height: Float, paint: GLPaint?, drawShapeFilter: DrawShapeFilter?) {
+ setupDrawShapeFilter(drawShapeFilter)
+ draw(GLES20.GL_LINE_LOOP, OFFSET_DRAW_RECT, COUNT_RECT_VERTEX, x, y, width, height, paint)
+ mCountDrawLine++
+ }
+
+ private fun draw(type: Int, offset: Int, count: Int, x: Float, y: Float, width: Float, height: Float,
+ paint: GLPaint?) {
+ draw(type, offset, count, x, y, width, height, paint!!.color, paint.lineWidth)
+ }
+
+ private fun draw(type: Int, offset: Int, count: Int, x: Float, y: Float, width: Float, height: Float,
+ color: Int, lineWidth: Float) {
+ prepareDraw(offset, color, lineWidth)
+ if (onPreDrawShapeListener != null) {
+ onPreDrawShapeListener!!.onPreDraw(mDrawProgram, mDrawShapeFilter)
+ }
+ draw(mDrawParameters, type, count, x, y, width, height, null)
+ }
+
+ private fun prepareDraw(offset: Int, color: Int, lineWidth: Float) {
+ GLES20.glUseProgram(mDrawProgram)
+ checkError()
+ if (lineWidth > 0) {
+ GLES20.glLineWidth(lineWidth)
+ checkError()
+ }
+ val colorArray = getColor(color)
+ enableBlending(true)
+ GLES20.glBlendColor(colorArray[0], colorArray[1], colorArray[2], colorArray[3])
+ checkError()
+ GLES20.glUniform4fv(mDrawParameters[INDEX_COLOR].handle, 1, colorArray, 0)
+ setPosition(mDrawParameters, offset)
+ checkError()
+ }
+
+ private fun getColor(color: Int): FloatArray {
+ val alpha = (color ushr 24 and 0xFF) / 255f * alpha
+ val red = (color ushr 16 and 0xFF) / 255f * alpha
+ val green = (color ushr 8 and 0xFF) / 255f * alpha
+ val blue = (color and 0xFF) / 255f * alpha
+ mTempColor[0] = red
+ mTempColor[1] = green
+ mTempColor[2] = blue
+ mTempColor[3] = alpha
+ return mTempColor
+ }
+
+ private fun setPosition(params: Array, offset: Int) {
+ GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mBoxCoordinates)
+ checkError()
+ GLES20.glVertexAttribPointer(params[INDEX_POSITION].handle, COORDS_PER_VERTEX,
+ GLES20.GL_FLOAT, false, VERTEX_STRIDE, offset * VERTEX_STRIDE)
+ checkError()
+ GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0)
+ checkError()
+ }
+
+ private fun draw(params: Array, type: Int, count: Int, x: Float, y: Float, width: Float,
+ height: Float, customMVPMatrix: ICustomMVPMatrix?) {
+ setMatrix(params, x, y, width, height, customMVPMatrix)
+ val positionHandle = params[INDEX_POSITION].handle
+ GLES20.glEnableVertexAttribArray(positionHandle)
+ checkError()
+ GLES20.glDrawArrays(type, 0, count)
+ checkError()
+ GLES20.glDisableVertexAttribArray(positionHandle)
+ checkError()
+ }
+
+ private fun setMatrix(params: Array, x: Float, y: Float, width: Float, height: Float, customMVPMatrix: ICustomMVPMatrix?) {
+ if (customMVPMatrix != null) {
+ GLES20.glUniformMatrix4fv(params[INDEX_MATRIX].handle, 1, false, customMVPMatrix.getMVPMatrix(mScreenWidth, mScreenHeight, x, y, width, height), 0)
+ checkError()
+ return
+ }
+ GLES20.glViewport(0, 0, mScreenWidth, mScreenHeight)
+ Matrix.translateM(mTempMatrix, 0, mMatrices, mCurrentMatrixIndex, x, y, 0f)
+ Matrix.scaleM(mTempMatrix, 0, width, height, 1f)
+ Matrix.multiplyMM(mTempMatrix, MATRIX_SIZE, mProjectionMatrix, 0, mTempMatrix, 0)
+ printMatrix("translate matrix:", mTempMatrix, MATRIX_SIZE)
+ GLES20.glUniformMatrix4fv(params[INDEX_MATRIX].handle, 1, false, mTempMatrix, MATRIX_SIZE)
+ checkError()
+ }
+
+ override fun fillRect(x: Float, y: Float, width: Float, height: Float, color: Int, drawShapeFilter: DrawShapeFilter?) {
+ setupDrawShapeFilter(drawShapeFilter)
+ draw(GLES20.GL_TRIANGLE_STRIP, OFFSET_FILL_RECT, COUNT_FILL_VERTEX, x, y, width, height,
+ color, 0f)
+ mCountFillRect++
+ }
+
+ override fun drawTexture(texture: BasicTexture?, x: Int, y: Int, width: Int, height: Int, textureFilter: TextureFilter?, customMVPMatrix: ICustomMVPMatrix?) {
+ if (width <= 0 || height <= 0) {
+ return
+ }
+ setupTextureFilter(texture!!.target, textureFilter)
+ copyTextureCoordinates(texture, mTempSourceRect)
+ mTempTargetRect[x.toFloat(), y.toFloat(), x + width.toFloat()] = y + height.toFloat()
+ convertCoordinate(mTempSourceRect, texture)
+ changeTargetIfNeeded(mTempSourceRect, mTempTargetRect, texture)
+ drawTextureRect(texture, mTempSourceRect, mTempTargetRect, customMVPMatrix)
+ }
+
+ override fun drawTexture(texture: BasicTexture?, source: RectF?, target: RectF?, textureFilter: TextureFilter?, customMVPMatrix: ICustomMVPMatrix?) {
+ if (target!!.width() <= 0 || target.height() <= 0) {
+ return
+ }
+ setupTextureFilter(texture!!.target, textureFilter)
+ mTempSourceRect.set(source!!)
+ mTempTargetRect.set(target)
+ convertCoordinate(mTempSourceRect, texture)
+ changeTargetIfNeeded(mTempSourceRect, mTempTargetRect, texture)
+ drawTextureRect(texture, mTempSourceRect, mTempTargetRect, customMVPMatrix)
+ }
+
+ override fun drawTexture(texture: BasicTexture?, textureTransform: FloatArray?, x: Int, y: Int, w: Int,
+ h: Int, textureFilter: TextureFilter?, customMVPMatrix: ICustomMVPMatrix?) {
+ if (w <= 0 || h <= 0) {
+ return
+ }
+ setupTextureFilter(texture!!.target, textureFilter)
+ mTempTargetRect[x.toFloat(), y.toFloat(), x + w.toFloat()] = y + h.toFloat()
+ drawTextureRect(texture, textureTransform, mTempTargetRect, customMVPMatrix)
+ }
+
+ private fun drawTextureRect(texture: BasicTexture?, source: RectF, target: RectF, customMVPMatrix: ICustomMVPMatrix?) {
+ setTextureMatrix(source, mTempTextureMatrix)
+ drawTextureRect(texture, mTempTextureMatrix, target, customMVPMatrix)
+ }
+
+ private fun drawTextureRect(texture: BasicTexture?, textureMatrix: FloatArray?, target: RectF, customMVPMatrix: ICustomMVPMatrix?) {
+ val params = prepareTexture(texture)
+ setPosition(params, OFFSET_FILL_RECT)
+ // printMatrix("texture matrix", textureMatrix, 0);
+ GLES20.glUniformMatrix4fv(params[INDEX_TEXTURE_MATRIX].handle, 1, false, textureMatrix, 0)
+ if (onPreDrawTextureListener != null) {
+ onPreDrawTextureListener!!.onPreDraw(if (texture!!.target == GLES20.GL_TEXTURE_2D) mTextureProgram else mOesTextureProgram, texture, mTextureFilter)
+ }
+ checkError()
+ if (texture!!.isFlippedVertically) {
+ save(GLCanvas.SAVE_FLAG_MATRIX)
+ translate(0f, target.centerY())
+ scale(1f, -1f, 1f)
+ translate(0f, -target.centerY())
+ }
+ draw(params, GLES20.GL_TRIANGLE_STRIP, COUNT_FILL_VERTEX, target.left, target.top,
+ target.width(), target.height(), customMVPMatrix)
+ if (texture.isFlippedVertically) {
+ restore()
+ }
+ countTextureRect++
+ }
+
+ private fun prepareTexture(texture: BasicTexture?): Array {
+ val params: Array
+ val program: Int
+ if (texture!!.target == GLES20.GL_TEXTURE_2D) {
+ params = mTextureParameters
+ program = mTextureProgram
+ } else {
+ params = mOesTextureParameters
+ program = mOesTextureProgram
+ }
+ prepareTexture(texture, program, params)
+ return params
+ }
+
+ private fun prepareTexture(texture: BasicTexture?, program: Int, params: Array) {
+ GLES20.glUseProgram(program)
+ checkError()
+ enableBlending(!texture!!.isOpaque || alpha < OPAQUE_ALPHA)
+ GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
+ checkError()
+ texture.onBind(this)
+ GLES20.glBindTexture(texture.target, texture.id)
+ checkError()
+ GLES20.glUniform1i(params[INDEX_TEXTURE_SAMPLER].handle, 0)
+ checkError()
+ GLES20.glUniform1f(params[INDEX_ALPHA].handle, alpha)
+ checkError()
+ }
+
+ override fun drawMesh(texture: BasicTexture?, x: Int, y: Int, xyBuffer: Int, uvBuffer: Int,
+ indexBuffer: Int, indexCount: Int, mode: Int) {
+ prepareTexture(texture, mMeshProgram, mMeshParameters)
+ GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, indexBuffer)
+ checkError()
+ GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, xyBuffer)
+ checkError()
+ val positionHandle = mMeshParameters[INDEX_POSITION].handle
+ GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false,
+ VERTEX_STRIDE, 0)
+ checkError()
+ GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, uvBuffer)
+ checkError()
+ val texCoordHandle = mMeshParameters[INDEX_TEXTURE_COORD].handle
+ GLES20.glVertexAttribPointer(texCoordHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT,
+ false, VERTEX_STRIDE, 0)
+ checkError()
+ GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0)
+ checkError()
+ GLES20.glEnableVertexAttribArray(positionHandle)
+ checkError()
+ GLES20.glEnableVertexAttribArray(texCoordHandle)
+ checkError()
+ setMatrix(mMeshParameters, x.toFloat(), y.toFloat(), 1f, 1f, null)
+ GLES20.glDrawElements(mode, indexCount, GLES20.GL_UNSIGNED_BYTE, 0)
+ checkError()
+ GLES20.glDisableVertexAttribArray(positionHandle)
+ checkError()
+ GLES20.glDisableVertexAttribArray(texCoordHandle)
+ checkError()
+ GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0)
+ checkError()
+ mCountDrawMesh++
+ }
+
+ override fun drawMixed(texture: BasicTexture?, toColor: Int, ratio: Float, x: Int, y: Int, w: Int, h: Int, drawShapeFilter: DrawShapeFilter?) {
+ copyTextureCoordinates(texture!!, mTempSourceRect)
+ mTempTargetRect[x.toFloat(), y.toFloat(), x + w.toFloat()] = y + h.toFloat()
+ drawMixed(texture, toColor, ratio, mTempSourceRect, mTempTargetRect, drawShapeFilter)
+ }
+
+ override fun drawMixed(texture: BasicTexture?, toColor: Int, ratio: Float, source: RectF?, target: RectF?, drawShapeFilter: DrawShapeFilter?) {
+ if (target!!.width() <= 0 || target.height() <= 0) {
+ return
+ }
+ save(GLCanvas.SAVE_FLAG_ALPHA)
+ val currentAlpha = alpha
+ val cappedRatio = min(1f, max(0f, ratio))
+ val textureAlpha = (1f - cappedRatio) * currentAlpha
+ alpha = textureAlpha
+ drawTexture(texture, source, target, BasicTextureFilter(), null)
+ val colorAlpha = cappedRatio * currentAlpha
+ alpha = colorAlpha
+ fillRect(target.left, target.top, target.width(), target.height(), toColor, drawShapeFilter)
+ restore()
+ }
+
+ override fun unloadTexture(texture: BasicTexture?): Boolean {
+ val unload = texture!!.isLoaded
+ if (unload) {
+ synchronized(mUnboundTextures) { mUnboundTextures.add(texture.id) }
+ }
+ return unload
+ }
+
+ override fun deleteBuffer(bufferId: Int) {
+ synchronized(mUnboundTextures) { mDeleteBuffers.add(bufferId) }
+ }
+
+ override fun deleteRecycledResources() {
+ synchronized(mUnboundTextures) {
+ var ids = mUnboundTextures
+ if (mUnboundTextures.size() > 0) {
+ gLId!!.glDeleteTextures(ids.size(), ids.internalArray, 0)
+ ids.clear()
+ }
+ ids = mDeleteBuffers
+ if (ids.size() > 0) {
+ gLId!!.glDeleteBuffers(ids.size(), ids.internalArray, 0)
+ ids.clear()
+ }
+ }
+ }
+
+ override fun dumpStatisticsAndClear() {
+ val line = String.format("MESH:%d, TEX_RECT:%d, FILL_RECT:%d, LINE:%d", mCountDrawMesh,
+ countTextureRect, mCountFillRect, mCountDrawLine)
+ mCountDrawMesh = 0
+ countTextureRect = 0
+ mCountFillRect = 0
+ mCountDrawLine = 0
+ Log.d(TAG, line)
+ }
+
+ override fun endRenderTarget() {
+ val oldTexture = mTargetTextures.removeAt(mTargetTextures.size - 1)
+ val texture = targetTexture
+ setRenderTarget(oldTexture, texture)
+ restore() // restore matrix and alpha
+ }
+
+ override fun beginRenderTarget(texture: RawTexture?) {
+ save() // save matrix and alpha and blending
+ val oldTexture = targetTexture
+ mTargetTextures.add(texture)
+ setRenderTarget(oldTexture, texture)
+ }
+
+ private val targetTexture: RawTexture?
+ get() = mTargetTextures[mTargetTextures.size - 1]
+
+ private fun setRenderTarget(oldTexture: BasicTexture?, texture: RawTexture?) {
+ if (oldTexture == null && texture != null) {
+ if (texture.target == GLES20.GL_TEXTURE_2D) {
+ GLES20.glGenFramebuffers(1, mFrameBuffer, 0)
+ checkError()
+ GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffer[0])
+ checkError()
+ } else {
+ GLES11Ext.glGenFramebuffersOES(1, mFrameBuffer, 0)
+ checkError()
+ GLES11Ext.glBindFramebufferOES(GLES11Ext.GL_FRAMEBUFFER_OES, mFrameBuffer[0])
+ checkError()
+ }
+ } else if (oldTexture != null && texture == null) {
+ if (oldTexture.target == GLES20.GL_TEXTURE_2D) {
+ GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0)
+ checkError()
+ GLES20.glDeleteFramebuffers(1, mFrameBuffer, 0)
+ checkError()
+ } else {
+ GLES11Ext.glBindFramebufferOES(GLES11Ext.GL_FRAMEBUFFER_OES, 0)
+ checkError()
+ GLES11Ext.glDeleteFramebuffersOES(1, mFrameBuffer, 0)
+ checkError()
+ }
+ }
+ if (texture == null) {
+ setSize(mScreenWidth, mScreenHeight)
+ } else {
+ setSize(texture.width, texture.height)
+ if (!texture.isLoaded) {
+ texture.prepare(this)
+ }
+ if (texture.target == GLES20.GL_TEXTURE_2D) {
+ GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
+ texture.target, texture.id, 0)
+ checkError()
+ checkFramebufferStatus()
+ } else {
+ GLES11Ext.glFramebufferTexture2DOES(GLES11Ext.GL_FRAMEBUFFER_OES, GLES11Ext.GL_COLOR_ATTACHMENT0_OES,
+ texture.target, texture.id, 0)
+ checkError()
+ checkFramebufferStatusOes()
+ }
+ }
+ }
+
+ override fun setTextureParameters(texture: BasicTexture?) {
+ val target = texture!!.target
+ GLES20.glBindTexture(target, texture.id)
+ checkError()
+ GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE)
+ GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE)
+ GLES20.glTexParameterf(target, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR.toFloat())
+ GLES20.glTexParameterf(target, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR.toFloat())
+ }
+
+ override fun initializeTextureSize(texture: BasicTexture?, format: Int, type: Int) {
+ val target = texture!!.target
+ GLES20.glBindTexture(target, texture.id)
+ checkError()
+ val width = texture.textureWidth
+ val height = texture.textureHeight
+ GLES20.glTexImage2D(target, 0, format, width, height, 0, format, type, null)
+ }
+
+ override fun initializeTexture(texture: BasicTexture?, bitmap: Bitmap?) {
+ val target = texture!!.target
+ GLES20.glBindTexture(target, texture.id)
+ checkError()
+ GLUtils.texImage2D(target, 0, bitmap, 0)
+ }
+
+ override fun texSubImage2D(texture: BasicTexture?, xOffset: Int, yOffset: Int, bitmap: Bitmap?,
+ format: Int, type: Int) {
+ val target = texture!!.target
+ GLES20.glBindTexture(target, texture.id)
+ checkError()
+ GLUtils.texSubImage2D(target, 0, xOffset, yOffset, bitmap, format, type)
+ }
+
+ override fun uploadBuffer(buffer: FloatBuffer?): Int {
+ return uploadBuffer(buffer, FLOAT_SIZE)
+ }
+
+ override fun uploadBuffer(buffer: ByteBuffer?): Int {
+ return uploadBuffer(buffer, 1)
+ }
+
+ private fun uploadBuffer(buffer: Buffer?, elementSize: Int): Int {
+ gLId!!.glGenBuffers(1, mTempIntArray, 0)
+ checkError()
+ val bufferId = mTempIntArray[0]
+ GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, bufferId)
+ checkError()
+ GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, buffer!!.capacity() * elementSize, buffer,
+ GLES20.GL_STATIC_DRAW)
+ checkError()
+ return bufferId
+ }
+
+ override fun recoverFromLightCycle() {
+// GLES20.glViewport(0, 0, mWidth, mHeight);
+ GLES20.glDisable(GLES20.GL_DEPTH_TEST)
+ GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA)
+ checkError()
+ }
+
+ override fun getBounds(bounds: Rect?, x: Int, y: Int, width: Int, height: Int) {
+ Matrix.translateM(mTempMatrix, 0, mMatrices, mCurrentMatrixIndex, x.toFloat(), y.toFloat(), 0f)
+ Matrix.scaleM(mTempMatrix, 0, width.toFloat(), height.toFloat(), 1f)
+ Matrix.multiplyMV(mTempMatrix, MATRIX_SIZE, mTempMatrix, 0, BOUNDS_COORDINATES, 0)
+ Matrix.multiplyMV(mTempMatrix, MATRIX_SIZE + 4, mTempMatrix, 0, BOUNDS_COORDINATES, 4)
+ bounds!!.left = mTempMatrix[MATRIX_SIZE].roundToInt()
+ bounds.right = mTempMatrix[MATRIX_SIZE + 4].roundToInt()
+ bounds.top = mTempMatrix[MATRIX_SIZE + 1].roundToInt()
+ bounds.bottom = mTempMatrix[MATRIX_SIZE + 5].roundToInt()
+ bounds.sort()
+ }
+
+ private fun setupDrawShapeFilter(drawShapeFilter: DrawShapeFilter?) {
+ if (drawShapeFilter == null) {
+ throw NullPointerException("draw shape filter is null.")
+ }
+ mDrawShapeFilter = drawShapeFilter
+ if (mDrawShapeFilterMapProgramId.containsKey(drawShapeFilter)) {
+ mDrawProgram = mDrawShapeFilterMapProgramId[drawShapeFilter]!!
+ loadHandles(mDrawParameters, mDrawProgram)
+ return
+ }
+ mDrawProgram = loadAndAssemble(mDrawParameters, drawShapeFilter.vertexShader, drawShapeFilter.fragmentShader)
+ mDrawShapeFilterMapProgramId[drawShapeFilter] = mDrawProgram
+ }
+
+ private fun setupTextureFilter(target: Int, textureFilter: TextureFilter?) {
+ if (textureFilter == null) {
+ throw NullPointerException("Texture filter is null.")
+ }
+
+ mTextureFilter = textureFilter
+ if (target == GLES20.GL_TEXTURE_2D) {
+ if (mTextureFilterMapProgramId.containsKey(textureFilter)) {
+ mTextureProgram = mTextureFilterMapProgramId[textureFilter]!!
+ loadHandles(mTextureParameters, mTextureProgram)
+ return
+ }
+ mTextureProgram = loadAndAssemble(mTextureParameters, textureFilter.vertexShader, textureFilter.fragmentShader)
+ mTextureFilterMapProgramId[textureFilter] = mTextureProgram
+ } else {
+ if (mOESTextureFilterMapProgramId.containsKey(textureFilter)) {
+ mOesTextureProgram = mOESTextureFilterMapProgramId[textureFilter]!!
+ loadHandles(mOesTextureParameters, mOesTextureProgram)
+ return
+ }
+ mOesTextureProgram = loadAndAssemble(mOesTextureParameters, textureFilter.vertexShader, textureFilter.oesFragmentProgram)
+ mOESTextureFilterMapProgramId[textureFilter] = mOesTextureProgram
+ }
+ }
+
+ private fun loadAndAssemble(shaderParameters: Array, vertexProgram: String?, fragmentProgram: String?): Int {
+ val vertexShaderHandle = loadShader(GLES20.GL_VERTEX_SHADER, vertexProgram)
+ val fragmentShaderHandle = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentProgram)
+ return assembleProgram(vertexShaderHandle, fragmentShaderHandle, shaderParameters, mTempIntArray)
+ }
+
+ override fun setOnPreDrawTextureListener(l: OnPreDrawTextureListener?) {
+ onPreDrawTextureListener = l
+ }
+
+ override fun setOnPreDrawShapeListener(l: OnPreDrawShapeListener?) {
+ onPreDrawShapeListener = l
+ }
+
+ abstract class ShaderParameter(protected val mName: String) {
+
+ var handle = 0
+
+ abstract fun loadHandle(program: Int)
+ }
+
+ private class UniformShaderParameter(name: String) : ShaderParameter(name) {
+ override fun loadHandle(program: Int) {
+ handle = GLES20.glGetUniformLocation(program, mName)
+ checkError()
+ }
+ }
+
+ private class AttributeShaderParameter(name: String) : ShaderParameter(name) {
+ override fun loadHandle(program: Int) {
+ handle = GLES20.glGetAttribLocation(program, mName)
+ checkError()
+ }
+ }
+
+ companion object {
+ const val POSITION_ATTRIBUTE = "aPosition"
+ const val COLOR_UNIFORM = "uColor"
+ const val MATRIX_UNIFORM = "uMatrix"
+ const val TEXTURE_MATRIX_UNIFORM = "uTextureMatrix"
+ const val TEXTURE_SAMPLER_UNIFORM = "uTextureSampler"
+ const val ALPHA_UNIFORM = "uAlpha"
+ const val TEXTURE_COORD_ATTRIBUTE = "aTextureCoordinate"
+ const val MESH_VERTEX_SHADER = (""
+ + "uniform mat4 " + MATRIX_UNIFORM + ";\n"
+ + "attribute vec2 " + POSITION_ATTRIBUTE + ";\n"
+ + "attribute vec2 " + TEXTURE_COORD_ATTRIBUTE + ";\n"
+ + "varying vec2 vTextureCoord;\n"
+ + "void main() {\n"
+ + " vec4 pos = vec4(" + POSITION_ATTRIBUTE + ", 0.0, 1.0);\n"
+ + " gl_Position = " + MATRIX_UNIFORM + " * pos;\n"
+ + " vTextureCoord = " + TEXTURE_COORD_ATTRIBUTE + ";\n"
+ + "}\n")
+
+ const val INITIAL_RESTORE_STATE_SIZE = 8
+
+ // ************** Constants **********************
+ private val TAG = GLES20Canvas::class.java.simpleName
+
+ private const val FLOAT_SIZE = java.lang.Float.SIZE / java.lang.Byte.SIZE
+
+ private const val OPAQUE_ALPHA = 0.95f
+
+ private const val COORDS_PER_VERTEX = 2
+
+ private const val VERTEX_STRIDE = COORDS_PER_VERTEX * FLOAT_SIZE
+
+ private const val COUNT_FILL_VERTEX = 4
+
+ private const val COUNT_LINE_VERTEX = 2
+
+ private const val COUNT_RECT_VERTEX = 4
+
+ private const val OFFSET_FILL_RECT = 0
+
+ private const val OFFSET_DRAW_LINE = OFFSET_FILL_RECT + COUNT_FILL_VERTEX
+
+ private const val OFFSET_DRAW_RECT = OFFSET_DRAW_LINE + COUNT_LINE_VERTEX
+
+ private val BOX_COORDINATES = floatArrayOf(0f, 0f, 1f, 0f, 0f, 1f, 1f, 1f, 0f, 0f, 1f, 1f, 0f, 0f, 0f, 1f, 1f, 1f, 1f, 0f)
+
+ private val BOUNDS_COORDINATES = floatArrayOf(0f, 0f, 0f, 1f, 1f, 1f, 0f, 1f)
+
+ private const val MATRIX_SIZE = 16
+
+ // Handle indices -- common
+ private const val INDEX_POSITION = 0
+
+ private const val INDEX_MATRIX = 1
+
+ // Handle indices -- draw
+ private const val INDEX_COLOR = 2
+
+ // Handle indices -- secondBitmap
+ private const val INDEX_TEXTURE_MATRIX = 2
+
+ private const val INDEX_TEXTURE_SAMPLER = 3
+
+ private const val INDEX_ALPHA = 4
+
+ // Handle indices -- mesh
+ private const val INDEX_TEXTURE_COORD = 2
+
+ val gLId: GLId = GLES20IdImpl()
+
+ private fun createBuffer(values: FloatArray): FloatBuffer {
+ // First create an nio buffer, then create a VBO from it.
+ val size = values.size * FLOAT_SIZE
+ val buffer = ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder())
+ .asFloatBuffer()
+ buffer.put(values, 0, values.size).position(0)
+
+ return buffer
+ }
+
+ private fun assembleProgram(vertexShader: Int, fragmentShader: Int, params: Array, linkStatus: IntArray): Int {
+ var program = GLES20.glCreateProgram()
+ checkError()
+ if (program == 0) {
+ throw RuntimeException("Cannot create GL program: " + GLES20.glGetError())
+ }
+ GLES20.glAttachShader(program, vertexShader)
+ checkError()
+ GLES20.glAttachShader(program, fragmentShader)
+ checkError()
+ GLES20.glLinkProgram(program)
+ checkError()
+ GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0)
+ if (linkStatus[0] != GLES20.GL_TRUE) {
+ Log.e(TAG, "Could not link program: ")
+ Log.e(TAG, GLES20.glGetProgramInfoLog(program))
+ GLES20.glDeleteProgram(program)
+ program = 0
+ }
+ loadHandles(params, program)
+
+ return program
+ }
+
+ private fun loadHandles(params: Array, program: Int) {
+ for (param in params) {
+ param.loadHandle(program)
+ }
+ }
+
+ private fun loadShader(type: Int, shaderCode: String?): Int {
+ // create a vertex shader type (GLES20.GL_VERTEX_SHADER)
+ // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
+ val shader = GLES20.glCreateShader(type)
+
+ // add the source code to the shader and compile it
+ GLES20.glShaderSource(shader, shaderCode)
+ checkError()
+ GLES20.glCompileShader(shader)
+ checkError()
+
+ return shader
+ }
+
+ private fun enableBlending(enableBlending: Boolean) {
+ if (enableBlending) {
+ GLES20.glEnable(GLES20.GL_BLEND)
+ checkError()
+ } else {
+ GLES20.glDisable(GLES20.GL_BLEND)
+ checkError()
+ }
+ }
+
+ private fun changeTargetIfNeeded(source: RectF, target: RectF, texture: BasicTexture?) {
+ val yBound = texture!!.height.toFloat() / texture.textureHeight
+ val xBound = texture.width.toFloat() / texture.textureWidth
+ if (source.right > xBound) {
+ target.right = target.left + target.width() * (xBound - source.left) / source.width()
+ }
+ if (source.bottom > yBound) {
+ target.bottom = target.top + target.height() * (yBound - source.top) / source.height()
+ }
+ }
+
+ private fun checkFramebufferStatus() {
+ val status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER)
+ if (status != GLES20.GL_FRAMEBUFFER_COMPLETE) {
+ val msg = when (status) {
+ GLES20.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT -> "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT"
+ GLES20.GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS -> "GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS"
+ GLES20.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT -> "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT"
+ GLES20.GL_FRAMEBUFFER_UNSUPPORTED -> "GL_FRAMEBUFFER_UNSUPPORTED"
+ else -> ""
+ }
+
+ throw RuntimeException(msg + ":" + Integer.toHexString(status))
+ }
+ }
+
+ private fun checkFramebufferStatusOes() {
+ val status = GLES11Ext.glCheckFramebufferStatusOES(GLES11Ext.GL_FRAMEBUFFER_OES)
+ if (status != GLES11Ext.GL_FRAMEBUFFER_COMPLETE_OES) {
+ val msg = when (status) {
+ GLES11Ext.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_OES -> "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT"
+ GLES11Ext.GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_OES -> "GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS"
+ GLES11Ext.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_OES -> "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT"
+ GLES11Ext.GL_FRAMEBUFFER_UNSUPPORTED_OES -> "GL_FRAMEBUFFER_UNSUPPORTED"
+ else -> ""
+ }
+
+ throw RuntimeException(msg + ":" + Integer.toHexString(status))
+ }
+ }
+
+ fun checkError() {
+ val error = GLES20.glGetError()
+ if (error != 0) {
+ val t = Throwable()
+ Log.e(TAG, "GL error: $error", t)
+ }
+ }
+
+ fun printMatrix(message: String?, m: FloatArray, offset: Int) {
+ if (!Loggers.DEBUG) {
+ return
+ }
+ val b = StringBuilder(message!!)
+ b.append('\n')
+ val size = 4
+ for (i in 0 until size) {
+ val format = "%.6f"
+ b.append(String.format(format, m[offset + i]))
+ b.append(", ")
+ b.append(String.format(format, m[offset + 4 + i]))
+ b.append(", ")
+ b.append(String.format(format, m[offset + 8 + i]))
+ b.append(", ")
+ b.append(String.format(format, m[offset + 12 + i]))
+ if (i < size - 1) {
+ b.append(", ")
+ }
+ b.append('\n')
+ }
+ Loggers.v(TAG, b.toString())
+ }
+ }
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/glcanvas/GLES20IdImpl.kt b/opengl/src/main/java/jp/eita/canvasgl/glcanvas/GLES20IdImpl.kt
new file mode 100644
index 0000000..5b6e17b
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/glcanvas/GLES20IdImpl.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.glcanvas
+
+import android.opengl.GLES20
+
+class GLES20IdImpl : GLId {
+
+ private val mTempIntArray: IntArray = IntArray(1)
+
+ override fun generateTexture(): Int {
+ GLES20.glGenTextures(1, mTempIntArray, 0)
+ GLES20Canvas.checkError()
+ return mTempIntArray[0]
+ }
+
+ override fun glGenBuffers(n: Int, buffers: IntArray, offset: Int) {
+ GLES20.glGenBuffers(n, buffers, offset)
+ GLES20Canvas.checkError()
+ }
+
+ override fun glDeleteTextures(n: Int, textures: IntArray, offset: Int) {
+ GLES20.glDeleteTextures(n, textures, offset)
+ GLES20Canvas.checkError()
+ }
+
+ override fun glDeleteBuffers(n: Int, buffers: IntArray, offset: Int) {
+ GLES20.glDeleteBuffers(n, buffers, offset)
+ GLES20Canvas.checkError()
+ }
+
+ override fun glDeleteFrameBuffers(n: Int, buffers: IntArray, offset: Int) {
+ GLES20.glDeleteFramebuffers(n, buffers, offset)
+ GLES20Canvas.checkError()
+ }
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/glcanvas/GLId.kt b/opengl/src/main/java/jp/eita/canvasgl/glcanvas/GLId.kt
new file mode 100644
index 0000000..bcff2bf
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/glcanvas/GLId.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.glcanvas
+
+// This mimics corresponding GL functions.
+interface GLId {
+
+ fun generateTexture(): Int
+
+ fun glGenBuffers(n: Int, buffers: IntArray, offset: Int)
+
+ fun glDeleteTextures(n: Int, textures: IntArray, offset: Int)
+
+ fun glDeleteBuffers(n: Int, buffers: IntArray, offset: Int)
+
+ fun glDeleteFrameBuffers(n: Int, buffers: IntArray, offset: Int)
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/glcanvas/GLPaint.kt b/opengl/src/main/java/jp/eita/canvasgl/glcanvas/GLPaint.kt
new file mode 100644
index 0000000..b551e94
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/glcanvas/GLPaint.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.glcanvas
+
+import android.graphics.Color
+import android.graphics.Paint
+
+data class GLPaint(
+
+ var lineWidth: Float = 1f,
+
+ var color: Int = Color.WHITE,
+
+ var style: Paint.Style = Paint.Style.FILL
+)
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/glcanvas/IntArrayCustom.kt b/opengl/src/main/java/jp/eita/canvasgl/glcanvas/IntArrayCustom.kt
new file mode 100644
index 0000000..2bb4935
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/glcanvas/IntArrayCustom.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.glcanvas
+
+class IntArrayCustom {
+
+ var internalArray: IntArray = IntArray(INIT_CAPACITY)
+ private set
+
+ private var mSize = 0
+
+ fun add(value: Int) {
+ if (internalArray.size == mSize) {
+ val temp = IntArray(mSize + mSize)
+ System.arraycopy(internalArray, 0, temp, 0, mSize)
+ internalArray = temp
+ }
+ internalArray[mSize++] = value
+ }
+
+ fun removeLast(): Int {
+ mSize--
+ return internalArray[mSize]
+ }
+
+ fun size(): Int {
+ return mSize
+ }
+
+ // For testing only
+ fun toArray(array: IntArray?): IntArray {
+ var result = array
+ if (result == null || result.size < mSize) {
+ result = IntArray(mSize)
+ }
+ System.arraycopy(internalArray, 0, result, 0, mSize)
+
+ return result
+ }
+
+ fun clear() {
+ mSize = 0
+ if (internalArray.size != INIT_CAPACITY) internalArray = IntArray(INIT_CAPACITY)
+ }
+
+ companion object {
+ private const val INIT_CAPACITY = 8
+ }
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/glcanvas/RawTexture.kt b/opengl/src/main/java/jp/eita/canvasgl/glcanvas/RawTexture.kt
new file mode 100644
index 0000000..633e23c
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/glcanvas/RawTexture.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.glcanvas
+
+import android.opengl.GLES20
+import android.util.Log
+import javax.microedition.khronos.opengles.GL11
+
+class RawTexture constructor(width: Int, height: Int, override val isOpaque: Boolean) : BasicTexture() {
+
+ /**
+ * Call this when surfaceTexture calls updateTexImage
+ */
+ var isNeedInvalidate = false
+
+ /**
+ * @param isFlipped whether vertically flip this texture
+ */
+ override var isFlippedVertically = false
+
+ override val target = GL11.GL_TEXTURE_2D
+
+ init {
+ setSize(width, height)
+ }
+
+ fun prepare(canvas: GLCanvas) {
+ val glId = canvas.gLId
+ id = glId!!.generateTexture()
+ if (target == GLES20.GL_TEXTURE_2D) {
+ canvas.initializeTextureSize(this, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE)
+ }
+ canvas.setTextureParameters(this)
+ state = STATE_LOADED
+ setAssociatedCanvas(canvas)
+ }
+
+ override fun onBind(canvas: GLCanvas): Boolean {
+ if (isLoaded) return true
+ Log.w(TAG, "lost the content due to context change")
+ return false
+ }
+
+ override fun yield() {
+ // we cannot free the secondBitmap because we have no backup.
+ }
+
+ companion object {
+
+ private val TAG: String = this::class.java.name
+ }
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/glcanvas/Texture.kt b/opengl/src/main/java/jp/eita/canvasgl/glcanvas/Texture.kt
new file mode 100644
index 0000000..14db291
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/glcanvas/Texture.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.glcanvas
+
+// Texture is a rectangular image which can be drawn on GLCanvas.
+// The isOpaque() function gives a hint about whether the secondBitmap is opaque,
+// so the drawing can be done faster.
+//
+// This is the current secondBitmap hierarchy:
+//
+// Texture
+// -- ColorTexture
+// -- FadeInTexture
+// -- BasicTexture
+// -- UploadedTexture
+// -- BitmapTexture
+// -- Tile
+// -- ResourceTexture
+// -- NinePatchTexture
+// -- CanvasTexture
+// -- StringTexture
+//
+interface Texture {
+
+ val width: Int
+
+ val height: Int
+
+ fun draw(canvas: GLCanvas?, x: Int, y: Int)
+
+ fun draw(canvas: GLCanvas?, x: Int, y: Int, w: Int, h: Int)
+
+ val isOpaque: Boolean
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/glcanvas/TextureMatrixTransformer.kt b/opengl/src/main/java/jp/eita/canvasgl/glcanvas/TextureMatrixTransformer.kt
new file mode 100644
index 0000000..2ff3eff
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/glcanvas/TextureMatrixTransformer.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.glcanvas
+
+import android.graphics.RectF
+
+object TextureMatrixTransformer {
+
+ // This function changes the source coordinate to the secondBitmap coordinates.
+ // It also clips the source and target coordinates if it is beyond the
+ // bound of the secondBitmap.
+ fun convertCoordinate(source: RectF, texture: BasicTexture) {
+ val width = texture.width
+ val height = texture.height
+ val texWidth = texture.textureWidth
+ val texHeight = texture.textureHeight
+ // Convert to secondBitmap coordinates
+ source.left /= texWidth.toFloat()
+ source.right /= texWidth.toFloat()
+ source.top /= texHeight.toFloat()
+ source.bottom /= texHeight.toFloat()
+
+ // Clip if the rendering range is beyond the bound of the secondBitmap.
+ val xBound = width.toFloat() / texWidth
+ if (source.right > xBound) {
+ source.right = xBound
+ }
+ val yBound = height.toFloat() / texHeight
+ if (source.bottom > yBound) {
+ source.bottom = yBound
+ }
+ }
+
+ fun setTextureMatrix(source: RectF, textureMatrix: FloatArray) {
+ textureMatrix[0] = source.width()
+ textureMatrix[5] = source.height()
+ textureMatrix[12] = source.left
+ textureMatrix[13] = source.top
+ }
+
+ fun copyTextureCoordinates(texture: BasicTexture, outRect: RectF) {
+ var left = 0
+ var top = 0
+ var right = texture.width
+ var bottom = texture.height
+ if (texture.hasBorder()) {
+ left = 1
+ top = 1
+ right -= 1
+ bottom -= 1
+ }
+ outRect[left.toFloat(), top.toFloat(), right.toFloat()] = bottom.toFloat()
+ }
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/glcanvas/UploadedTexture.kt b/opengl/src/main/java/jp/eita/canvasgl/glcanvas/UploadedTexture.kt
new file mode 100644
index 0000000..b6014be
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/glcanvas/UploadedTexture.kt
@@ -0,0 +1,253 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.glcanvas
+
+import android.graphics.Bitmap
+import android.opengl.GLUtils
+import java.util.*
+import javax.microedition.khronos.opengles.GL11
+
+// UploadedTextures use a Bitmap for the content of the secondBitmap.
+//
+// Subclasses should implement onGetBitmap() to provide the Bitmap and
+// implement onFreeBitmap(mBitmap) which will be called when the Bitmap
+// is not needed anymore.
+//
+// isContentValid() is meaningful only when the isLoaded() returns true.
+// It means whether the content needs to be updated.
+//
+// The user of this class should call recycle() when the secondBitmap is not
+// needed anymore.
+//
+// By default an UploadedTexture is opaque (so it can be drawn faster without
+// blending). The user or subclass can override it using setOpaque().
+abstract class UploadedTexture protected constructor(hasBorder: Boolean = false) : BasicTexture(null, 0, STATE_UNLOADED) {
+
+ private var mContentValid = true
+
+ /**
+ * Whether the content on GPU is valid.
+ */
+ val isContentValid: Boolean
+ get() = isLoaded && mContentValid
+
+ // indicate this textures is being uploaded in background
+ var isUploading = false
+ protected set
+
+ private var isThrottled = false
+
+ protected var mBitmap: Bitmap? = null
+
+ private var border = 0
+
+ override val target: Int
+ get() = GL11.GL_TEXTURE_2D
+
+ init {
+ if (hasBorder) {
+ setBorder(true)
+ border = 1
+ }
+ }
+
+ private class BorderKey : Cloneable {
+
+ var vertical = false
+
+ var config: Bitmap.Config? = null
+
+ var length = 0
+
+ override fun hashCode(): Int {
+ val x = config.hashCode() xor length
+
+ return if (vertical) x else -x
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (other !is BorderKey) {
+ return false
+ }
+
+ return vertical == other.vertical && config == other.config && length == other.length
+ }
+
+ public override fun clone(): BorderKey {
+ return try {
+ super.clone() as BorderKey
+ } catch (e: CloneNotSupportedException) {
+ throw AssertionError(e)
+ }
+ }
+ }
+
+ private val bitmap: Bitmap?
+ get() {
+ if (mBitmap == null) {
+ mBitmap = onGetBitmap()
+ val w = mBitmap!!.width + border * 2
+ val h = mBitmap!!.height + border * 2
+ if (width == UNSPECIFIED) {
+ setSize(w, h)
+ }
+ }
+
+ return mBitmap
+ }
+
+ private fun freeBitmap() {
+// Assert.assertTrue(mBitmap != null);
+ onFreeBitmap(mBitmap)
+ mBitmap = null
+ }
+
+ protected abstract fun onGetBitmap(): Bitmap?
+
+ protected abstract fun onFreeBitmap(bitmap: Bitmap?)
+
+ fun invalidateContent() {
+ if (mBitmap != null) freeBitmap()
+ mContentValid = false
+ width = UNSPECIFIED
+ height = UNSPECIFIED
+ }
+
+ /**
+ * Updates the content on GPU's memory.
+ * @param canvas
+ */
+ fun updateContent(canvas: GLCanvas) {
+ if (!isLoaded) {
+ if (isThrottled && ++UPLOADED_COUNT > UPLOAD_LIMIT) {
+ return
+ }
+ uploadToCanvas(canvas)
+ } else if (!mContentValid) {
+ val bitmap = bitmap
+ val format = GLUtils.getInternalFormat(bitmap)
+ val type = GLUtils.getType(bitmap)
+ canvas.texSubImage2D(this, border, border, bitmap, format, type)
+ freeBitmap()
+ mContentValid = true
+ }
+ }
+
+ private fun uploadToCanvas(canvas: GLCanvas) {
+ val bitmap = bitmap
+ if (bitmap != null) {
+ try {
+ val bWidth = bitmap.width
+ val bHeight = bitmap.height
+ width = bWidth + border * 2
+ height = bHeight + border * 2
+ val texWidth = textureWidth
+ val texHeight = textureHeight
+
+ // Upload the bitmap to a new secondBitmap.
+ id = canvas.gLId!!.generateTexture()
+ canvas.setTextureParameters(this)
+ if (bWidth == texWidth && bHeight == texHeight) {
+ canvas.initializeTexture(this, bitmap)
+ } else {
+ val format = GLUtils.getInternalFormat(bitmap)
+ val type = GLUtils.getType(bitmap)
+ val config = bitmap.config
+ canvas.initializeTextureSize(this, format, type)
+ canvas.texSubImage2D(this, border, border, bitmap, format, type)
+ if (border > 0) {
+ // Left border
+ var line = getBorderLine(true, config, texHeight)
+ canvas.texSubImage2D(this, 0, 0, line, format, type)
+
+ // Top border
+ line = getBorderLine(false, config, texWidth)
+ canvas.texSubImage2D(this, 0, 0, line, format, type)
+ }
+
+ // Right border
+ if (border + bWidth < texWidth) {
+ val line = getBorderLine(true, config, texHeight)
+ canvas.texSubImage2D(this, border + bWidth, 0, line, format, type)
+ }
+
+ // Bottom border
+ if (border + bHeight < texHeight) {
+ val line = getBorderLine(false, config, texWidth)
+ canvas.texSubImage2D(this, 0, border + bHeight, line, format, type)
+ }
+ }
+ } finally {
+ freeBitmap()
+ }
+ // Update secondBitmap state.
+ setAssociatedCanvas(canvas)
+ state = STATE_LOADED
+ mContentValid = true
+ } else {
+ state = STATE_ERROR
+ throw RuntimeException("Texture load fail, no bitmap")
+ }
+ }
+
+ override fun onBind(canvas: GLCanvas): Boolean {
+ updateContent(canvas)
+
+ return isContentValid
+ }
+
+ override fun recycle() {
+ super.recycle()
+ if (mBitmap != null) freeBitmap()
+ }
+
+ companion object {
+
+ private const val TAG = "Texture"
+
+ // To prevent keeping allocation the borders, we store those used borders here.
+ // Since the length will be power of two, it won't use too much memory.
+ private val BORDER_LINES = HashMap()
+
+ private val BORDER_KEY = BorderKey()
+
+ private var UPLOADED_COUNT = 0
+
+ private const val UPLOAD_LIMIT = 100
+
+ private fun getBorderLine(
+ vertical: Boolean, config: Bitmap.Config, length: Int): Bitmap? {
+ val key = BORDER_KEY
+ key.vertical = vertical
+ key.config = config
+ key.length = length
+ var bitmap = BORDER_LINES[key]
+ if (bitmap == null) {
+ bitmap = if (vertical) Bitmap.createBitmap(1, length, config) else Bitmap.createBitmap(length, 1, config)
+ BORDER_LINES[key.clone()] = bitmap
+ }
+ return bitmap
+ }
+
+ fun resetUploadLimit() {
+ UPLOADED_COUNT = 0
+ }
+
+ fun uploadLimitReached(): Boolean {
+ return UPLOADED_COUNT > UPLOAD_LIMIT
+ }
+ }
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/glview/GLContinuousView.kt b/opengl/src/main/java/jp/eita/canvasgl/glview/GLContinuousView.kt
new file mode 100644
index 0000000..98db264
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/glview/GLContinuousView.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.glview
+
+import android.content.Context
+import android.util.AttributeSet
+import jp.eita.canvasgl.ICanvasGL
+
+abstract class GLContinuousView : GLView {
+
+ constructor(context: Context?) : super(context)
+
+ constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
+
+ override fun init() {
+ super.init()
+ renderMode = RENDERMODE_CONTINUOUSLY
+ }
+
+ override fun onGLDraw(canvas: ICanvasGL) {
+
+ }
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/glview/GLView.kt b/opengl/src/main/java/jp/eita/canvasgl/glview/GLView.kt
new file mode 100644
index 0000000..83aed97
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/glview/GLView.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.glview
+
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.PixelFormat
+import android.graphics.Rect
+import android.opengl.GLSurfaceView
+import android.util.AttributeSet
+import jp.eita.canvasgl.CanvasGL
+import jp.eita.canvasgl.ICanvasGL
+import jp.eita.canvasgl.OpenGLUtil.createBitmapFromGLSurface
+import javax.microedition.khronos.egl.EGLConfig
+import javax.microedition.khronos.opengles.GL10
+
+abstract class GLView : GLSurfaceView, GLSurfaceView.Renderer {
+
+ protected var canvas: CanvasGL? = null
+
+ protected var gl: GL10? = null
+
+ var onSizeChangeCallback: OnSizeChangeCallback? = null
+
+ constructor(context: Context?) : super(context)
+
+ constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
+
+ init {
+ init()
+ }
+
+ protected open fun init() {
+ setZOrderOnTop(true)
+ setEGLContextClientVersion(2)
+ setEGLConfigChooser(8, 8, 8, 8, 16, 0)
+ preserveEGLContextOnPause = true
+ holder.setFormat(PixelFormat.TRANSLUCENT)
+ setRenderer(this)
+ renderMode = RENDERMODE_WHEN_DIRTY
+ }
+
+ override fun onSurfaceCreated(gl: GL10, config: EGLConfig) {
+ canvas = CanvasGL()
+ }
+
+ override fun onSurfaceChanged(gl: GL10, width: Int, height: Int) {
+ canvas!!.setSize(width, height)
+ }
+
+ override fun onDrawFrame(gl: GL10) {
+ this.gl = gl
+ canvas!!.clearBuffer()
+ onGLDraw(canvas!!)
+ }
+
+ /**
+ * May call twice at first.
+ */
+ protected abstract fun onGLDraw(canvas: ICanvasGL)
+
+ fun restart() {
+ onResume()
+ }
+
+ fun stop() {
+ onPause()
+ canvas?.pause()
+ }
+
+ fun destroy() {}
+
+ override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
+ super.onSizeChanged(w, h, oldw, oldh)
+ onSizeChangeCallback?.onSizeChange(w, h, oldw, oldh)
+ }
+
+ fun getDrawingBitmap(rect: Rect, getDrawingCacheCallback: GetDrawingCacheCallback) {
+ queueEvent(Runnable {
+ if (gl == null) {
+ return@Runnable
+ }
+ onDrawFrame(gl!!)
+ onDrawFrame(gl!!)
+ val bitmapFromGLSurface = createBitmapFromGLSurface(rect.left, rect.top, rect.right, rect.bottom, height)
+ post { getDrawingCacheCallback.onFetch(bitmapFromGLSurface) }
+ })
+ requestRender()
+ }
+
+ interface OnSizeChangeCallback {
+
+ fun onSizeChange(w: Int, h: Int, oldw: Int, oldh: Int)
+ }
+
+ interface GetDrawingCacheCallback {
+
+ fun onFetch(bitmap: Bitmap?)
+ }
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/glview/texture/BaseGLCanvasTextureView.kt b/opengl/src/main/java/jp/eita/canvasgl/glview/texture/BaseGLCanvasTextureView.kt
new file mode 100644
index 0000000..d9df6c2
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/glview/texture/BaseGLCanvasTextureView.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.glview.texture
+
+import android.content.Context
+import android.graphics.Color
+import android.graphics.Rect
+import android.util.AttributeSet
+import androidx.annotation.ColorInt
+import jp.eita.canvasgl.CanvasGL
+import jp.eita.canvasgl.ICanvasGL
+import jp.eita.canvasgl.OpenGLUtil.createBitmapFromGLSurface
+import jp.eita.canvasgl.glview.GLView.GetDrawingCacheCallback
+import jp.eita.canvasgl.util.Loggers
+
+/**
+ * From init to run: onSizeChange --> onSurfaceTextureAvailable --> createGLThread --> createSurface --> onSurfaceCreated --> onSurfaceChanged
+ * From pause to run: onResume --> createSurface --> onSurfaceChanged
+ * From stop to run: onResume --> onSurfaceTextureAvailable --> createGLThread --> createSurface --> onSurfaceCreated --> onSurfaceChanged
+ */
+abstract class BaseGLCanvasTextureView : BaseGLTextureView, GLViewRenderer {
+
+ protected var mCanvas: ICanvasGL? = null
+
+ internal var backgroundColor = Color.TRANSPARENT
+
+ constructor(context: Context?) : super(context)
+
+ constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
+
+ constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
+
+ override fun init() {
+ super.init()
+ setRenderer(this)
+ }
+
+ override fun onSurfaceCreated() {
+ Loggers.d(TAG, "onSurfaceCreated: ")
+ mCanvas = CanvasGL()
+ }
+
+ override fun onSurfaceChanged(width: Int, height: Int) {
+ Loggers.d(TAG, "onSurfaceChanged: ")
+ mCanvas!!.setSize(width, height)
+ }
+
+ override fun onDrawFrame() {
+ mCanvas!!.clearBuffer(backgroundColor)
+ onGLDraw(mCanvas)
+ }
+
+ override fun onPause() {
+ super.onPause()
+ if (mCanvas != null) {
+ mCanvas!!.pause()
+ }
+ }
+
+ abstract override fun onGLDraw(canvas: ICanvasGL?)
+
+ /**
+ * If setOpaque(true) used, this method will not work.
+ */
+ fun setRenderBackgroundColor(@ColorInt color: Int) {
+ backgroundColor = color
+ }
+
+ fun getDrawingBitmap(rect: Rect, getDrawingCacheCallback: GetDrawingCacheCallback) {
+ queueEvent(Runnable {
+ onDrawFrame()
+ onDrawFrame()
+ val bitmapFromGLSurface = createBitmapFromGLSurface(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, height)
+ post { getDrawingCacheCallback.onFetch(bitmapFromGLSurface) }
+ })
+ requestRender()
+ }
+
+ companion object {
+ private const val TAG = "BaseGLCanvasTextureView"
+ }
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/glview/texture/BaseGLTextureView.kt b/opengl/src/main/java/jp/eita/canvasgl/glview/texture/BaseGLTextureView.kt
new file mode 100644
index 0000000..bc1a794
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/glview/texture/BaseGLTextureView.kt
@@ -0,0 +1,257 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.glview.texture
+
+import android.content.Context
+import android.graphics.SurfaceTexture
+import android.util.AttributeSet
+import android.util.Log
+import android.view.TextureView
+import android.view.TextureView.SurfaceTextureListener
+import jp.eita.canvasgl.ICanvasGL
+import jp.eita.canvasgl.glview.texture.gles.EglContextWrapper
+import jp.eita.canvasgl.glview.texture.gles.GLThread
+import jp.eita.canvasgl.glview.texture.gles.GLThread.OnCreateGLContextListener
+import jp.eita.canvasgl.util.Loggers
+import java.util.*
+
+/**
+ * Can be used in ScrollView or ListView.
+ * Can make it not opaque by setOpaque(false).
+ *
+ *
+ * The surface of canvasGL is provided by TextureView.
+ *
+ *
+ * onSurfaceTextureSizeChanged onResume onPause onSurfaceTextureDestroyed onSurfaceTextureUpdated
+ * From init to run: onSizeChanged --> onSurfaceTextureAvailable --> createGLThread --> createSurface
+ * From run to pause: onPause --> destroySurface
+ * From pause to run: onResume --> createSurface
+ * From run to stop: onPause --> destroySurface --> onSurfaceTextureDestroyed --> EGLHelper.finish --> GLThread.exit
+ * From stop to run: onResume --> onSurfaceTextureAvailable --> createGLThread --> createSurface
+ */
+abstract class BaseGLTextureView : TextureView, SurfaceTextureListener {
+
+ protected open var mGLThread: GLThread? = null
+
+ internal var glThreadBuilder: GLThread.Builder? = null
+
+ protected val cacheEvents: MutableList = ArrayList()
+
+ internal var surfaceTextureListener: SurfaceTextureListener? = null
+
+ private var onCreateGLContextListener: OnCreateGLContextListener? = null
+
+ private var surfaceAvailable = false
+
+ private var renderer: GLViewRenderer? = null
+
+ constructor(context: Context?) : super(context)
+
+ constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
+
+ constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
+
+ init {
+ super.setSurfaceTextureListener(this)
+ }
+
+ override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
+ Loggers.d(TAG, "onSizeChanged: ")
+ super.onSizeChanged(w, h, oldw, oldh)
+ mGLThread?.onWindowResize(w, h)
+ }
+
+ open fun onPause() {
+ mGLThread?.onPause()
+ }
+
+ fun onResume() {
+ mGLThread?.onResume()
+ }
+
+ fun queueEvent(r: Runnable) {
+ if (mGLThread == null) {
+ cacheEvents.add(r)
+ return
+ }
+ mGLThread?.queueEvent(r)
+ }
+
+ fun requestRender() {
+ if (mGLThread != null) {
+ mGLThread!!.requestRender()
+ } else {
+ Log.w(TAG, "GLThread is not created when requestRender")
+ }
+ }
+
+ /**
+ * Wait until render command is sent to OpenGL
+ */
+ fun requestRenderAndWait() {
+ if (mGLThread != null) {
+ mGLThread!!.requestRenderAndWait()
+ } else {
+ Log.w(TAG, "GLThread is not created when requestRender")
+ }
+ }
+
+ protected fun surfaceCreated() {
+ mGLThread!!.surfaceCreated()
+ }
+
+ protected open fun surfaceDestroyed() {
+ // Surface will be destroyed when we return
+ if (mGLThread != null) {
+ mGLThread!!.surfaceDestroyed()
+ mGLThread!!.requestExitAndWait()
+ }
+ surfaceAvailable = false
+ mGLThread = null
+ }
+
+ protected fun surfaceChanged(w: Int, h: Int) {
+ mGLThread!!.onWindowResize(w, h)
+ }
+
+ protected fun surfaceRedrawNeeded() {
+ if (mGLThread != null) {
+ mGLThread!!.requestRenderAndWait()
+ }
+ }
+
+ override fun onDetachedFromWindow() {
+ Loggers.d(TAG, "onDetachedFromWindow: ")
+ if (mGLThread != null) {
+ mGLThread!!.requestExitAndWait()
+ }
+ super.onDetachedFromWindow()
+ }
+
+ @Throws(Throwable::class)
+ protected fun finalize() {
+ try {
+ if (mGLThread != null) {
+ // GLThread may still be running if this view was never
+ // attached to a window.
+ mGLThread!!.requestExitAndWait()
+ }
+ } finally {
+
+ }
+ }
+
+ protected open fun init() {
+
+ }
+
+ /**
+ * @return If the context is not created, then EGL10.EGL_NO_CONTEXT will be returned.
+ */
+ val currentEglContext: EglContextWrapper?
+ get() = if (mGLThread == null) null else mGLThread!!.eglContext
+
+ fun setOnCreateGLContextListener(onCreateGLContextListener: OnCreateGLContextListener?) {
+ this.onCreateGLContextListener = onCreateGLContextListener
+ }
+
+ override fun setSurfaceTextureListener(surfaceTextureListener: SurfaceTextureListener) {
+ this.surfaceTextureListener = surfaceTextureListener
+ }
+
+ protected open val renderMode: Int
+ protected get() = GLThread.RENDERMODE_WHEN_DIRTY
+
+ protected abstract fun onGLDraw(canvas: ICanvasGL?)
+ fun setRenderer(renderer: GLViewRenderer?) {
+ this.renderer = renderer
+ }
+
+ override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
+ Loggers.d(TAG, "onSurfaceTextureAvailable: ")
+ surfaceAvailable = true
+ glThreadBuilder = GLThread.Builder()
+ if (mGLThread == null) {
+ glThreadBuilder!!.setRenderMode(renderMode)
+ .setSurface(surface)
+ .setRenderer(renderer)
+ createGLThread()
+ } else {
+ mGLThread!!.setSurface(surface)
+ freshSurface(width, height)
+ }
+ surfaceTextureListener?.onSurfaceTextureAvailable(surface, width, height)
+ }
+
+ protected open fun createGLThread() {
+ Loggers.d(TAG, "createGLThread: ")
+ if (!surfaceAvailable) {
+ return
+ }
+ mGLThread = glThreadBuilder!!.createGLThread()
+ mGLThread!!.setOnCreateGLContextListener(object : OnCreateGLContextListener {
+ override fun onCreate(eglContext: EglContextWrapper?) {
+ post {
+ if (onCreateGLContextListener != null) {
+ onCreateGLContextListener!!.onCreate(eglContext)
+ }
+ }
+ }
+ })
+ mGLThread!!.start()
+ freshSurface(width, height)
+ for (cacheEvent in cacheEvents) {
+ mGLThread!!.queueEvent(cacheEvent)
+ }
+ cacheEvents.clear()
+ }
+
+ /**
+ * surface inited or updated.
+ */
+ private fun freshSurface(width: Int, height: Int) {
+ surfaceCreated()
+ surfaceChanged(width, height)
+ surfaceRedrawNeeded()
+ }
+
+ override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) {
+ Loggers.d(TAG, "onSurfaceTextureSizeChanged: ")
+ surfaceChanged(width, height)
+ surfaceRedrawNeeded()
+ surfaceTextureListener?.onSurfaceTextureSizeChanged(surface, width, height)
+ }
+
+ /**
+ * This will be called when windows detached. Activity onStop will cause window detached.
+ */
+ override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
+ Loggers.d(TAG, "onSurfaceTextureDestroyed: ")
+ surfaceDestroyed()
+ surfaceTextureListener?.onSurfaceTextureSizeChanged(surface, width, height)
+ return true
+ }
+
+ override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {
+ surfaceTextureListener?.onSurfaceTextureUpdated(surface)
+ }
+
+ companion object {
+
+ private const val TAG = "BaseGLTextureView"
+ }
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/glview/texture/GLContinuousTextureView.kt b/opengl/src/main/java/jp/eita/canvasgl/glview/texture/GLContinuousTextureView.kt
new file mode 100644
index 0000000..3379c5a
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/glview/texture/GLContinuousTextureView.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.glview.texture
+
+import android.content.Context
+import android.util.AttributeSet
+import jp.eita.canvasgl.glview.texture.gles.GLThread
+
+/**
+ * [jp.eita.canvasgl.glview.GLContinuousView] is better.
+ */
+abstract class GLContinuousTextureView : GLTextureView {
+
+ constructor(context: Context?) : super(context)
+
+ constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
+
+ constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
+
+ override val renderMode: Int
+ get() = GLThread.RENDERMODE_CONTINUOUSLY
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/glview/texture/GLMultiTexConsumerView.kt b/opengl/src/main/java/jp/eita/canvasgl/glview/texture/GLMultiTexConsumerView.kt
new file mode 100644
index 0000000..c382b8e
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/glview/texture/GLMultiTexConsumerView.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.glview.texture
+
+import android.content.Context
+import android.util.AttributeSet
+import jp.eita.canvasgl.ICanvasGL
+import jp.eita.canvasgl.glview.texture.gles.EglContextWrapper
+import java.util.*
+
+/**
+ * This class is used to accept eglContext and textures from outside. Then it can use them to draw.
+ * The [.setSharedEglContext] must be called as the precondition to consume outside texture.
+ */
+abstract class GLMultiTexConsumerView : BaseGLCanvasTextureView {
+
+ protected var consumedTextures: MutableList = ArrayList()
+
+ protected var mSharedEglContext: EglContextWrapper? = null
+
+ constructor(context: Context?) : super(context)
+
+ constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
+
+ constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
+
+ /**
+ * @param sharedEglContext The openGL context from other or [EglContextWrapper.EGL_NO_CONTEXT_WRAPPER]
+ */
+ fun setSharedEglContext(sharedEglContext: EglContextWrapper?) {
+ mSharedEglContext = sharedEglContext
+ glThreadBuilder!!.setSharedEglContext(sharedEglContext!!)
+ createGLThread()
+ }
+
+ override fun createGLThread() {
+ if (mSharedEglContext != null) {
+ super.createGLThread()
+ }
+ }
+
+ /**
+ * This must be called for a GLMultiTexConsumerView.
+ *
+ * @param glTexture texture from outSide.
+ */
+ fun addConsumeGLTexture(glTexture: GLTexture) {
+ consumedTextures.add(glTexture)
+ }
+
+ /**
+ * Will not call until @param surfaceTexture not null
+ */
+ protected abstract fun onGLDraw(canvas: ICanvasGL?, consumedTextures: List?)
+ override fun onGLDraw(canvas: ICanvasGL?) {
+ val iterator = consumedTextures.iterator()
+ while (iterator.hasNext()) {
+ val next = iterator.next()
+ if (next.rawTexture.isRecycled) {
+ iterator.remove()
+ }
+ }
+ onGLDraw(canvas, consumedTextures)
+ }
+
+ override fun surfaceDestroyed() {
+ super.surfaceDestroyed()
+ consumedTextures.clear()
+ }
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/glview/texture/GLMultiTexProducerView.kt b/opengl/src/main/java/jp/eita/canvasgl/glview/texture/GLMultiTexProducerView.kt
new file mode 100644
index 0000000..4caf372
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/glview/texture/GLMultiTexProducerView.kt
@@ -0,0 +1,178 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.glview.texture
+
+import android.content.Context
+import android.opengl.GLES11Ext
+import android.opengl.GLES20
+import android.os.Build
+import android.util.AttributeSet
+import android.util.Log
+import jp.eita.canvasgl.ICanvasGL
+import jp.eita.canvasgl.glview.texture.gles.GLThread
+import jp.eita.canvasgl.util.Loggers
+import java.util.*
+
+/**
+ * Used to generate multiple textures or consume textures from others.
+ * This will not create [GLThread] automatically. You need to call [.setSharedEglContext] to trigger it.
+ * Support providing multiple textures to Camera or Media.
+ * This can also consume textures from other GL zone( Should be in same GL context)
+ * And since this inherits [GLMultiTexConsumerView], the [.setSharedEglContext] must be called
+ */
+abstract class GLMultiTexProducerView : GLMultiTexConsumerView {
+
+ private var producedTextureTarget = GLES11Ext.GL_TEXTURE_EXTERNAL_OES
+
+ private val producedTextureList: MutableList = ArrayList()
+
+ private var surfaceTextureCreatedListener: SurfaceTextureCreatedListener? = null
+
+ constructor(context: Context?) : super(context)
+ constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
+ constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
+
+ override fun onGLDraw(canvas: ICanvasGL?, consumedTextures: List?) {
+ onGLDraw(canvas, producedTextureList, consumedTextures)
+ }
+
+ override val renderMode: Int
+ get() = GLThread.RENDERMODE_WHEN_DIRTY
+
+ internal val initialTexCount: Int = 1
+
+
+ /**
+ * @return The initial produced texture count
+ */
+ protected open fun getInitialTexCount(): Int {
+ return 1
+ }
+
+ /**
+ * If it is used, it must be called before [GLThread.start] called.
+ *
+ * @param producedTextureTarget GLES20.GL_TEXTURE_2D or GLES11Ext.GL_TEXTURE_EXTERNAL_OES
+ */
+ fun setProducedTextureTarget(producedTextureTarget: Int) {
+ this.producedTextureTarget = producedTextureTarget
+ }
+
+ /**
+ * Create a new produced texture and upload it to the canvas.
+ */
+ fun addProducedGLTexture(width: Int, height: Int, opaque: Boolean, target: Int): GLTexture {
+ val glTexture = GLTexture.createRaw(width, height, opaque, target, mCanvas!!)
+ producedTextureList.add(glTexture)
+ return glTexture
+ }
+
+ override fun onSurfaceChanged(width: Int, height: Int) {
+ super.onSurfaceChanged(width, height)
+ Loggers.d(TAG, "onSurfaceChanged: $width, $height")
+ if (producedTextureList.isEmpty()) {
+ for (i in 0 until initialTexCount) {
+ // This must be in this thread because it relies on the GLContext of this thread
+ producedTextureList.add(GLTexture.createRaw(width, height, false, producedTextureTarget, mCanvas!!))
+ }
+ post {
+ if (producedTextureList.isNotEmpty() && surfaceTextureCreatedListener != null) {
+ surfaceTextureCreatedListener!!.onCreated(producedTextureList)
+ }
+ }
+ } else {
+ for (glTexture in producedTextureList) {
+ glTexture.rawTexture.setSize(width, height)
+ }
+ }
+ }
+
+ override fun onDrawFrame() {
+ if (producedTextureTarget != GLES20.GL_TEXTURE_2D) {
+ for (glTexture in producedTextureList) {
+ glTexture.surfaceTexture.updateTexImage()
+ glTexture.rawTexture.isNeedInvalidate = true
+ }
+ }
+ super.onDrawFrame()
+ }
+
+ override fun onPause() {
+ super.onPause()
+ Loggers.d(TAG, "onPause")
+ recycleProduceTexture()
+ if (mGLThread == null) {
+ Log.w(TAG, "!!!!!! You may not call setShareEglContext !!!")
+ }
+ }
+
+ override fun surfaceDestroyed() {
+ super.surfaceDestroyed()
+ recycleProduceTexture()
+ }
+
+ private fun recycleProduceTexture() {
+ for (glTexture in producedTextureList) {
+ if (!glTexture.rawTexture.isRecycled) {
+ glTexture.rawTexture.recycle()
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ if (!glTexture.surfaceTexture.isReleased) {
+ glTexture.surfaceTexture.release()
+ }
+ } else {
+ glTexture.surfaceTexture.release()
+ }
+ }
+ producedTextureList.clear()
+ }
+
+ /**
+ * Set the listener to listen the texture creation.
+ *
+ * @param surfaceTextureCreatedListener The texture listener
+ */
+ fun setSurfaceTextureCreatedListener(surfaceTextureCreatedListener: SurfaceTextureCreatedListener?) {
+ this.surfaceTextureCreatedListener = surfaceTextureCreatedListener
+ }
+
+ /**
+ * If [.setSharedEglContext] is not called, this will not be triggered.
+ * The consumedTextures are obtained from [GLMultiTexConsumerView.addConsumeGLTexture]
+ *
+ * @param canvas the canvas to draw things
+ * @param producedTextures The textures created by itself.
+ * @param consumedTextures May be null. This only available when it gets from other GLMultiTexProducerView
+ */
+ protected abstract fun onGLDraw(canvas: ICanvasGL?, producedTextures: List?, consumedTextures: List?)
+
+ /**
+ * Listen when the produced textures created.
+ */
+ interface SurfaceTextureCreatedListener {
+ /**
+ * You can get the created Textures from this method.
+ * The number of textures is decided by [GLMultiTexProducerView.getInitialTexCount]
+ *
+ * @param producedTextureList The created Textures
+ */
+ fun onCreated(producedTextureList: List)
+ }
+
+ companion object {
+ private const val TAG = "GLMultiTexProducerView"
+ }
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/glview/texture/GLSurfaceTextureProducerView.kt b/opengl/src/main/java/jp/eita/canvasgl/glview/texture/GLSurfaceTextureProducerView.kt
new file mode 100644
index 0000000..5d8a465
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/glview/texture/GLSurfaceTextureProducerView.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.glview.texture
+
+import android.content.Context
+import android.graphics.SurfaceTexture
+import android.util.AttributeSet
+import jp.eita.canvasgl.ICanvasGL
+import jp.eita.canvasgl.glcanvas.BasicTexture
+import jp.eita.canvasgl.glcanvas.RawTexture
+import jp.eita.canvasgl.glview.texture.gles.EglContextWrapper
+
+/**
+ * This will generate a texture which is in the eglContext of the CanvasGL. And the texture can be used outside.
+ * The [.setSharedEglContext] will be called automatically when [.onSurfaceTextureAvailable]
+ * For example, the generated texture can be used in camera preview texture or [GLMultiTexConsumerView].
+ *
+ * From pause to run: onResume --> createSurface --> onSurfaceChanged
+ */
+abstract class GLSurfaceTextureProducerView : GLMultiTexProducerView {
+
+ constructor(context: Context?) : super(context)
+
+ constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
+
+ constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
+
+ override fun getInitialTexCount(): Int {
+ return 1
+ }
+
+ override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
+ super.onSurfaceTextureAvailable(surface, width, height)
+ if (mSharedEglContext == null) {
+ setSharedEglContext(EglContextWrapper.EGL_NO_CONTEXT_WRAPPER)
+ }
+ }
+
+ fun setOnSurfaceTextureSet(onSurfaceTextureSet: OnSurfaceTextureSet) {
+ setSurfaceTextureCreatedListener(object : SurfaceTextureCreatedListener {
+ override fun onCreated(producedTextureList: List) {
+ val glTexture = producedTextureList[0]
+ onSurfaceTextureSet.onSet(glTexture.surfaceTexture, glTexture.rawTexture)
+ }
+
+ })
+ }
+
+ override fun onGLDraw(canvas: ICanvasGL?, producedTextures: List?, consumedTextures: List?) {
+ val glTexture = producedTextures!![0]
+ if (consumedTextures!!.isNotEmpty()) {
+ val consumeTexture = consumedTextures[0]
+ onGLDraw(canvas, glTexture.surfaceTexture, glTexture.rawTexture, consumeTexture.surfaceTexture, consumeTexture.rawTexture)
+ onGLDraw(canvas, glTexture, consumeTexture)
+ } else {
+ onGLDraw(canvas, glTexture.surfaceTexture, glTexture.rawTexture, null, null)
+ onGLDraw(canvas, glTexture, null)
+ }
+ }
+
+ @Deprecated("")
+ protected fun onGLDraw(canvas: ICanvasGL?, producedSurfaceTexture: SurfaceTexture?, producedRawTexture: RawTexture?, outsideSurfaceTexture: SurfaceTexture?, outsideTexture: BasicTexture?) {
+ }
+
+ protected fun onGLDraw(canvas: ICanvasGL?, producedGLTexture: GLTexture?, outsideGLTexture: GLTexture?) {}
+ interface OnSurfaceTextureSet {
+ fun onSet(surfaceTexture: SurfaceTexture?, surfaceTextureRelatedTexture: RawTexture?)
+ }
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/glview/texture/GLTexture.kt b/opengl/src/main/java/jp/eita/canvasgl/glview/texture/GLTexture.kt
new file mode 100644
index 0000000..b731386
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/glview/texture/GLTexture.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.glview.texture
+
+import android.graphics.SurfaceTexture
+import jp.eita.canvasgl.ICanvasGL
+import jp.eita.canvasgl.glcanvas.RawTexture
+
+class GLTexture(val rawTexture: RawTexture, val surfaceTexture: SurfaceTexture) {
+
+ companion object {
+
+ fun createRaw(width: Int, height: Int, opaque: Boolean, target: Int, canvasGL: ICanvasGL): GLTexture {
+ val rawTexture = RawTexture(width, height, opaque)
+ if (!rawTexture.isLoaded) {
+ rawTexture.prepare(canvasGL.glCanvas)
+ }
+ val surfaceTexture = SurfaceTexture(rawTexture.id)
+
+ return GLTexture(rawTexture, surfaceTexture)
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/glview/texture/GLTextureView.kt b/opengl/src/main/java/jp/eita/canvasgl/glview/texture/GLTextureView.kt
new file mode 100644
index 0000000..a51b476
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/glview/texture/GLTextureView.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.glview.texture
+
+import android.content.Context
+import android.graphics.SurfaceTexture
+import android.util.AttributeSet
+
+abstract class GLTextureView : BaseGLCanvasTextureView {
+
+ constructor(context: Context?) : super(context)
+
+ constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
+
+ constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
+
+ override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
+ super.onSurfaceTextureAvailable(surface, width, height)
+ if (mGLThread == null) {
+ createGLThread()
+ }
+ }
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/glview/texture/GLViewRenderer.kt b/opengl/src/main/java/jp/eita/canvasgl/glview/texture/GLViewRenderer.kt
new file mode 100644
index 0000000..0662b81
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/glview/texture/GLViewRenderer.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.glview.texture
+
+interface GLViewRenderer {
+
+ fun onSurfaceCreated()
+
+ fun onSurfaceChanged(width: Int, height: Int)
+
+ fun onDrawFrame()
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/glview/texture/gles/EGLLogWrapper.kt b/opengl/src/main/java/jp/eita/canvasgl/glview/texture/gles/EGLLogWrapper.kt
new file mode 100644
index 0000000..b525ecc
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/glview/texture/gles/EGLLogWrapper.kt
@@ -0,0 +1,526 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.glview.texture.gles
+
+import android.opengl.GLDebugHelper
+import android.opengl.GLException
+import java.io.IOException
+import java.io.Writer
+import javax.microedition.khronos.egl.*
+
+/**
+ * For Test
+ */
+class EGLLogWrapper(egl: EGL, configFlags: Int, log: Writer?) : EGL11 {
+
+ var mLog: Writer?
+
+ var logArgumentNames: Boolean
+
+ var checkError: Boolean
+
+ private val egl10: EGL10
+
+ private var argCount = 0
+
+ override fun eglChooseConfig(display: EGLDisplay, attrib_list: IntArray,
+ configs: Array, config_size: Int, num_config: IntArray): Boolean {
+ begin("eglChooseConfig")
+ arg("display", display)
+ arg("attrib_list", attrib_list)
+ arg("config_size", config_size)
+ end()
+ val result = egl10.eglChooseConfig(display, attrib_list, configs,
+ config_size, num_config)
+ arg("configs", configs)
+ arg("num_config", num_config)
+ returns(result)
+ checkError()
+ return result
+ }
+
+ override fun eglCopyBuffers(display: EGLDisplay, surface: EGLSurface,
+ native_pixmap: Any): Boolean {
+ begin("eglCopyBuffers")
+ arg("display", display)
+ arg("surface", surface)
+ arg("native_pixmap", native_pixmap)
+ end()
+ val result = egl10.eglCopyBuffers(display, surface, native_pixmap)
+ returns(result)
+ checkError()
+ return result
+ }
+
+ override fun eglCreateContext(display: EGLDisplay, config: EGLConfig,
+ share_context: EGLContext, attrib_list: IntArray): EGLContext {
+ begin("eglCreateContext")
+ arg("display", display)
+ arg("config", config)
+ arg("share_context", share_context)
+ arg("attrib_list", attrib_list)
+ end()
+ val result = egl10.eglCreateContext(display, config,
+ share_context, attrib_list)
+ returns(result)
+ checkError()
+ return result
+ }
+
+ override fun eglCreatePbufferSurface(display: EGLDisplay,
+ config: EGLConfig, attrib_list: IntArray): EGLSurface {
+ begin("eglCreatePbufferSurface")
+ arg("display", display)
+ arg("config", config)
+ arg("attrib_list", attrib_list)
+ end()
+ val result = egl10.eglCreatePbufferSurface(display, config,
+ attrib_list)
+ returns(result)
+ checkError()
+ return result
+ }
+
+ override fun eglCreatePixmapSurface(display: EGLDisplay,
+ config: EGLConfig, native_pixmap: Any, attrib_list: IntArray): EGLSurface {
+ begin("eglCreatePixmapSurface")
+ arg("display", display)
+ arg("config", config)
+ arg("native_pixmap", native_pixmap)
+ arg("attrib_list", attrib_list)
+ end()
+ val result = egl10.eglCreatePixmapSurface(display, config,
+ native_pixmap, attrib_list)
+ returns(result)
+ checkError()
+ return result
+ }
+
+ override fun eglCreateWindowSurface(display: EGLDisplay,
+ config: EGLConfig, native_window: Any, attrib_list: IntArray): EGLSurface {
+ begin("eglCreateWindowSurface")
+ arg("display", display)
+ arg("config", config)
+ arg("native_window", native_window)
+ arg("attrib_list", attrib_list)
+ end()
+ val result = egl10.eglCreateWindowSurface(display, config,
+ native_window, attrib_list)
+ returns(result)
+ checkError()
+ return result
+ }
+
+ override fun eglDestroyContext(display: EGLDisplay, context: EGLContext): Boolean {
+ begin("eglDestroyContext")
+ arg("display", display)
+ arg("context", context)
+ end()
+ val result = egl10.eglDestroyContext(display, context)
+ returns(result)
+ checkError()
+ return result
+ }
+
+ override fun eglDestroySurface(display: EGLDisplay, surface: EGLSurface): Boolean {
+ begin("eglDestroySurface")
+ arg("display", display)
+ arg("surface", surface)
+ end()
+ val result = egl10.eglDestroySurface(display, surface)
+ returns(result)
+ checkError()
+ return result
+ }
+
+ override fun eglGetConfigAttrib(display: EGLDisplay, config: EGLConfig,
+ attribute: Int, value: IntArray): Boolean {
+ begin("eglGetConfigAttrib")
+ arg("display", display)
+ arg("config", config)
+ arg("attribute", attribute)
+ end()
+ val result = egl10.eglGetConfigAttrib(display, config, attribute,
+ value)
+ arg("value", value)
+ returns(result)
+ checkError()
+ return false
+ }
+
+ override fun eglGetConfigs(display: EGLDisplay, configs: Array,
+ config_size: Int, num_config: IntArray): Boolean {
+ begin("eglGetConfigs")
+ arg("display", display)
+ arg("config_size", config_size)
+ end()
+ val result = egl10.eglGetConfigs(display, configs, config_size,
+ num_config)
+ arg("configs", configs)
+ arg("num_config", num_config)
+ returns(result)
+ checkError()
+ return result
+ }
+
+ override fun eglGetCurrentContext(): EGLContext {
+ begin("eglGetCurrentContext")
+ end()
+ val result = egl10.eglGetCurrentContext()
+ returns(result)
+ checkError()
+ return result
+ }
+
+ override fun eglGetCurrentDisplay(): EGLDisplay {
+ begin("eglGetCurrentDisplay")
+ end()
+ val result = egl10.eglGetCurrentDisplay()
+ returns(result)
+ checkError()
+ return result
+ }
+
+ override fun eglGetCurrentSurface(readdraw: Int): EGLSurface {
+ begin("eglGetCurrentSurface")
+ arg("readdraw", readdraw)
+ end()
+ val result = egl10.eglGetCurrentSurface(readdraw)
+ returns(result)
+ checkError()
+ return result
+ }
+
+ override fun eglGetDisplay(native_display: Any): EGLDisplay {
+ begin("eglGetDisplay")
+ arg("native_display", native_display)
+ end()
+ val result = egl10.eglGetDisplay(native_display)
+ returns(result)
+ checkError()
+ return result
+ }
+
+ override fun eglGetError(): Int {
+ begin("eglGetError")
+ end()
+ val result = egl10.eglGetError()
+ returns(getErrorString(result))
+ return result
+ }
+
+ override fun eglInitialize(display: EGLDisplay, major_minor: IntArray): Boolean {
+ begin("eglInitialize")
+ arg("display", display)
+ end()
+ val result = egl10.eglInitialize(display, major_minor)
+ returns(result)
+ arg("major_minor", major_minor)
+ checkError()
+ return result
+ }
+
+ override fun eglMakeCurrent(display: EGLDisplay, draw: EGLSurface,
+ read: EGLSurface, context: EGLContext): Boolean {
+ begin("eglMakeCurrent")
+ arg("display", display)
+ arg("draw", draw)
+ arg("read", read)
+ arg("context", context)
+ end()
+ val result = egl10.eglMakeCurrent(display, draw, read, context)
+ returns(result)
+ checkError()
+ return result
+ }
+
+ override fun eglQueryContext(display: EGLDisplay, context: EGLContext,
+ attribute: Int, value: IntArray): Boolean {
+ begin("eglQueryContext")
+ arg("display", display)
+ arg("context", context)
+ arg("attribute", attribute)
+ end()
+ val result = egl10.eglQueryContext(display, context, attribute,
+ value)
+ returns(value[0])
+ returns(result)
+ checkError()
+ return result
+ }
+
+ override fun eglQueryString(display: EGLDisplay, name: Int): String {
+ begin("eglQueryString")
+ arg("display", display)
+ arg("name", name)
+ end()
+ val result = egl10.eglQueryString(display, name)
+ returns(result)
+ checkError()
+ return result
+ }
+
+ override fun eglQuerySurface(display: EGLDisplay, surface: EGLSurface,
+ attribute: Int, value: IntArray): Boolean {
+ begin("eglQuerySurface")
+ arg("display", display)
+ arg("surface", surface)
+ arg("attribute", attribute)
+ end()
+ val result = egl10.eglQuerySurface(display, surface, attribute,
+ value)
+ returns(value[0])
+ returns(result)
+ checkError()
+ return result
+ }
+
+ override fun eglSwapBuffers(display: EGLDisplay, surface: EGLSurface): Boolean {
+ begin("eglSwapBuffers")
+ arg("display", display)
+ arg("surface", surface)
+ end()
+ val result = egl10.eglSwapBuffers(display, surface)
+ returns(result)
+ checkError()
+ return result
+ }
+
+ override fun eglTerminate(display: EGLDisplay): Boolean {
+ begin("eglTerminate")
+ arg("display", display)
+ end()
+ val result = egl10.eglTerminate(display)
+ returns(result)
+ checkError()
+ return result
+ }
+
+ override fun eglWaitGL(): Boolean {
+ begin("eglWaitGL")
+ end()
+ val result = egl10.eglWaitGL()
+ returns(result)
+ checkError()
+ return result
+ }
+
+ override fun eglWaitNative(engine: Int, bindTarget: Any): Boolean {
+ begin("eglWaitNative")
+ arg("engine", engine)
+ arg("bindTarget", bindTarget)
+ end()
+ val result = egl10.eglWaitNative(engine, bindTarget)
+ returns(result)
+ checkError()
+ return result
+ }
+
+ private fun checkError() {
+ var eglError: Int
+ if (egl10.eglGetError().also { eglError = it } != EGL10.EGL_SUCCESS) {
+ val errorMessage = "eglError: " + getErrorString(eglError)
+ logLine(errorMessage)
+ if (checkError) {
+ throw GLException(eglError, errorMessage)
+ }
+ }
+ }
+
+ private fun logLine(message: String) {
+ log("""
+ $message
+
+ """.trimIndent())
+ }
+
+ private fun log(message: String) {
+ try {
+ mLog!!.write(message)
+ } catch (e: IOException) {
+ // Ignore exception, keep on trying
+ }
+ }
+
+ private fun begin(name: String) {
+ log("$name(")
+ argCount = 0
+ }
+
+ private fun arg(name: String, value: String) {
+ if (argCount++ > 0) {
+ log(", ")
+ }
+ if (logArgumentNames) {
+ log("$name=")
+ }
+ log(value)
+ }
+
+ private fun end() {
+ log(");\n")
+ flush()
+ }
+
+ private fun flush() {
+ try {
+ mLog!!.flush()
+ } catch (e: IOException) {
+ mLog = null
+ }
+ }
+
+ private fun arg(name: String, value: Int) {
+ arg(name, value.toString())
+ }
+
+ private fun arg(name: String, `object`: Any) {
+ arg(name, toString(`object`))
+ }
+
+ private fun arg(name: String, `object`: EGLDisplay) {
+ if (`object` === EGL10.EGL_DEFAULT_DISPLAY) {
+ arg(name, "EGL10.EGL_DEFAULT_DISPLAY")
+ } else if (`object` === EGL10.EGL_NO_DISPLAY) {
+ arg(name, "EGL10.EGL_NO_DISPLAY")
+ } else {
+ arg(name, toString(`object`))
+ }
+ }
+
+ private fun arg(name: String, `object`: EGLContext) {
+ if (`object` === EGL10.EGL_NO_CONTEXT) {
+ arg(name, "EGL10.EGL_NO_CONTEXT")
+ } else {
+ arg(name, toString(`object`))
+ }
+ }
+
+ private fun arg(name: String, `object`: EGLSurface) {
+ if (`object` === EGL10.EGL_NO_SURFACE) {
+ arg(name, "EGL10.EGL_NO_SURFACE")
+ } else {
+ arg(name, toString(`object`))
+ }
+ }
+
+ private fun returns(result: String) {
+ log(" returns $result;\n")
+ flush()
+ }
+
+ private fun returns(result: Int) {
+ returns(result.toString())
+ }
+
+ private fun returns(result: Boolean) {
+ returns(java.lang.Boolean.toString(result))
+ }
+
+ private fun returns(result: Any) {
+ returns(toString(result))
+ }
+
+ private fun toString(obj: Any?): String {
+ return obj?.toString() ?: "null"
+ }
+
+ private fun arg(name: String, arr: IntArray) {
+ if (arr == null) {
+ arg(name, "null")
+ } else {
+ arg(name, toString(arr.size, arr, 0))
+ }
+ }
+
+ private fun arg(name: String, arr: Array?) {
+ if (arr == null) {
+ arg(name, "null")
+ } else {
+ arg(name, toString(arr.size, arr, 0))
+ }
+ }
+
+ private fun toString(n: Int, arr: IntArray, offset: Int): String {
+ val buf = StringBuilder()
+ buf.append("{\n")
+ val arrLen = arr.size
+ for (i in 0 until n) {
+ val index = offset + i
+ buf.append(" [").append(index).append("] = ")
+ if (index < 0 || index >= arrLen) {
+ buf.append("out of bounds")
+ } else {
+ buf.append(arr[index])
+ }
+ buf.append('\n')
+ }
+ buf.append("}")
+ return buf.toString()
+ }
+
+ private fun toString(n: Int, arr: Array, offset: Int): String {
+ val buf = StringBuilder()
+ buf.append("{\n")
+ val arrLen = arr.size
+ for (i in 0 until n) {
+ val index = offset + i
+ buf.append(" [").append(index).append("] = ")
+ if (index < 0 || index >= arrLen) {
+ buf.append("out of bounds")
+ } else {
+ buf.append(arr[index])
+ }
+ buf.append('\n')
+ }
+ buf.append("}")
+ return buf.toString()
+ }
+
+ companion object {
+ private fun getHex(value: Int): String {
+ return "0x" + Integer.toHexString(value)
+ }
+
+ fun getErrorString(error: Int): String {
+ return when (error) {
+ EGL10.EGL_SUCCESS -> "EGL_SUCCESS"
+ EGL10.EGL_NOT_INITIALIZED -> "EGL_NOT_INITIALIZED"
+ EGL10.EGL_BAD_ACCESS -> "EGL_BAD_ACCESS"
+ EGL10.EGL_BAD_ALLOC -> "EGL_BAD_ALLOC"
+ EGL10.EGL_BAD_ATTRIBUTE -> "EGL_BAD_ATTRIBUTE"
+ EGL10.EGL_BAD_CONFIG -> "EGL_BAD_CONFIG"
+ EGL10.EGL_BAD_CONTEXT -> "EGL_BAD_CONTEXT"
+ EGL10.EGL_BAD_CURRENT_SURFACE -> "EGL_BAD_CURRENT_SURFACE"
+ EGL10.EGL_BAD_DISPLAY -> "EGL_BAD_DISPLAY"
+ EGL10.EGL_BAD_MATCH -> "EGL_BAD_MATCH"
+ EGL10.EGL_BAD_NATIVE_PIXMAP -> "EGL_BAD_NATIVE_PIXMAP"
+ EGL10.EGL_BAD_NATIVE_WINDOW -> "EGL_BAD_NATIVE_WINDOW"
+ EGL10.EGL_BAD_PARAMETER -> "EGL_BAD_PARAMETER"
+ EGL10.EGL_BAD_SURFACE -> "EGL_BAD_SURFACE"
+ EGL11.EGL_CONTEXT_LOST -> "EGL_CONTEXT_LOST"
+ else -> getHex(error)
+ }
+ }
+ }
+
+ init {
+ egl10 = egl as EGL10
+ mLog = log
+ logArgumentNames = GLDebugHelper.CONFIG_LOG_ARGUMENT_NAMES and configFlags != 0
+ checkError = GLDebugHelper.CONFIG_CHECK_GL_ERROR and configFlags != 0
+ }
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/glview/texture/gles/EglContextWrapper.kt b/opengl/src/main/java/jp/eita/canvasgl/glview/texture/gles/EglContextWrapper.kt
new file mode 100644
index 0000000..828d4e7
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/glview/texture/gles/EglContextWrapper.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.glview.texture.gles
+
+import android.opengl.EGL14
+import javax.microedition.khronos.egl.EGL10
+import javax.microedition.khronos.egl.EGLContext
+
+open class EglContextWrapper {
+
+ open lateinit var eglContextOld: EGLContext
+
+ open var eglContext: android.opengl.EGLContext? = null
+
+ class EGLNoContextWrapper : EglContextWrapper() {
+
+ internal fun setEglContext(eglContext: android.opengl.EGLContext?) {
+
+ }
+
+ internal fun setEglContextOld(eglContextOld: EGLContext) {
+
+ }
+
+ init {
+ eglContextOld = EGL10.EGL_NO_CONTEXT
+ eglContext = EGL14.EGL_NO_CONTEXT
+ }
+ }
+
+ companion object {
+
+ val EGL_NO_CONTEXT_WRAPPER: EglContextWrapper = EGLNoContextWrapper()
+ }
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/glview/texture/gles/EglHelper.kt b/opengl/src/main/java/jp/eita/canvasgl/glview/texture/gles/EglHelper.kt
new file mode 100644
index 0000000..6b9fc93
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/glview/texture/gles/EglHelper.kt
@@ -0,0 +1,202 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.glview.texture.gles
+
+import android.util.Log
+import jp.eita.canvasgl.util.FileLogger
+import jp.eita.canvasgl.util.Loggers
+import javax.microedition.khronos.egl.*
+
+class EglHelper : IEglHelper {
+
+ private val eglConfigChooser: GLThread.EGLConfigChooser
+
+ private val eglContextFactory: GLThread.EGLContextFactory
+
+ private val eglWindowSurfaceFactory: GLThread.EGLWindowSurfaceFactory
+
+ private var egl: EGL10? = null
+
+ private var eglDisplay: EGLDisplay? = null
+
+ private var eglSurface: EGLSurface? = null
+
+ private var eglConfig: EGLConfig? = null
+
+ private var eglContext1: EGLContext? = null
+
+ constructor(eglConfigChooser: GLThread.EGLConfigChooser, eglContextFactory: GLThread.EGLContextFactory, eglWindowSurfaceFactory: GLThread.EGLWindowSurfaceFactory) {
+ this.eglConfigChooser = eglConfigChooser
+ this.eglContextFactory = eglContextFactory
+ this.eglWindowSurfaceFactory = eglWindowSurfaceFactory
+ }
+
+ /**
+ * Initialize EGL for a given configuration spec.
+ *
+ * @param eglContext
+ */
+ override fun start(eglContext: EglContextWrapper?): EglContextWrapper? {
+ FileLogger.w("EglHelper", "start() tid=" + Thread.currentThread().id)
+ /*
+ * Get an EGL instance
+ */egl = EGLContext.getEGL() as EGL10
+
+ /*
+ * Get to the default display.
+ */eglDisplay = egl!!.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY)
+ if (eglDisplay === EGL10.EGL_NO_DISPLAY) {
+ throw RuntimeException("eglGetDisplay failed")
+ }
+
+ /*
+ * We can now initialize EGL for that display
+ */
+ val version = IntArray(2)
+ if (!egl!!.eglInitialize(eglDisplay, version)) {
+ throw RuntimeException("eglInitialize failed")
+ }
+ eglConfig = eglConfigChooser.chooseConfig(egl!!, eglDisplay)
+
+ /*
+ * Create an EGL context. We want to do this as rarely as we can, because an
+ * EGL context is a somewhat heavy object.
+ */eglContext1 = eglContextFactory.createContext(egl!!, eglDisplay, eglConfig, eglContext!!.eglContextOld)
+ if (eglContext1 == null || eglContext1 === EGL10.EGL_NO_CONTEXT) {
+ eglContext1 = null
+ throwEglException("createContext", egl!!.eglGetError())
+ }
+ FileLogger.w("EglHelper", "createContext " + eglContext1 + " tid=" + Thread.currentThread().id)
+ eglSurface = null
+ val eglContextWrapper = EglContextWrapper()
+ eglContextWrapper.eglContextOld = eglContext1!!
+ return eglContextWrapper
+ }
+
+ /**
+ * Create an egl surface for the current SurfaceHolder surface. If a surface
+ * already exists, destroy it before creating the new surface.
+ *
+ * @return true if the surface was created successfully.
+ */
+ override fun createSurface(surface: Any?): Boolean {
+ Loggers.w("EglHelper", "createSurface() tid=" + Thread.currentThread().id)
+ /*
+ * Check preconditions.
+ */if (egl == null) {
+ throw RuntimeException("egl not initialized")
+ }
+ if (eglDisplay == null) {
+ throw RuntimeException("eglDisplay not initialized")
+ }
+ if (eglConfig == null) {
+ throw RuntimeException("mEglConfig not initialized")
+ }
+
+ /*
+ * The window size has changed, so we need to create a new
+ * surface.
+ */destroySurfaceImp()
+ fun throwEglException(function: String) {
+ throwEglException(function, egl!!.eglGetError())
+ }
+ /*
+ * Create an EGL surface we can render into.
+ */eglSurface = eglWindowSurfaceFactory.createWindowSurface(egl!!,
+ eglDisplay, eglConfig, surface)
+ if (eglSurface == null || eglSurface === EGL10.EGL_NO_SURFACE) {
+ val error = egl!!.eglGetError()
+ if (error == EGL10.EGL_BAD_NATIVE_WINDOW) {
+ Log.e("EglHelper", "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.")
+ }
+ return false
+ }
+
+ /*
+ * Before we can issue GL commands, we need to make sure
+ * the context is current and bound to a surface.
+ */if (!egl!!.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext1)) {
+ /*
+ * Could not make the context current, probably because the underlying
+ * SurfaceView surface has been destroyed.
+ */
+ logEglErrorAsWarning("EGLHelper", "eglMakeCurrent", egl!!.eglGetError())
+ return false
+ }
+ return true
+ }
+
+ /**
+ * Display the current render surface.
+ *
+ * @return the EGL error code from eglSwapBuffers.
+ */
+ override fun swap(): Int {
+ return if (!egl!!.eglSwapBuffers(eglDisplay, eglSurface)) {
+ egl!!.eglGetError()
+ } else EGL10.EGL_SUCCESS
+ }
+
+ override fun destroySurface() {
+ FileLogger.w(TAG, "destroySurface() tid=" + Thread.currentThread().id)
+ destroySurfaceImp()
+ }
+
+ private fun destroySurfaceImp() {
+ if (eglSurface != null && eglSurface !== EGL10.EGL_NO_SURFACE) {
+ egl!!.eglMakeCurrent(eglDisplay, EGL10.EGL_NO_SURFACE,
+ EGL10.EGL_NO_SURFACE,
+ EGL10.EGL_NO_CONTEXT)
+ eglWindowSurfaceFactory.destroySurface(egl!!, eglDisplay, eglSurface)
+ eglSurface = null
+ }
+ }
+
+ override fun finish() {
+ FileLogger.w(TAG, "finish() tid=" + Thread.currentThread().id)
+ if (eglContext1 != null) {
+ eglContextFactory.destroyContext(egl!!, eglDisplay!!, eglContext1!!)
+ eglContext1 = null
+ }
+ if (eglDisplay != null) {
+ egl!!.eglTerminate(eglDisplay)
+ eglDisplay = null
+ }
+ }
+
+ override fun setPresentationTime(nsecs: Long) {}
+
+ companion object {
+
+ private const val TAG = "EglHelper"
+
+ fun throwEglException(function: String, error: Int) {
+ val message = formatEglError(function, error)
+ FileLogger.e(TAG, "throwEglException tid=" + Thread.currentThread().id + " "
+ + message)
+
+ throw RuntimeException(message)
+ }
+
+ fun logEglErrorAsWarning(tag: String?, function: String, error: Int) {
+ Log.w(tag, formatEglError(function, error))
+ }
+
+ fun formatEglError(function: String, error: Int): String {
+ return function + " failed: " + EGLLogWrapper.getErrorString(error)
+ }
+ }
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/glview/texture/gles/EglHelperFactory.kt b/opengl/src/main/java/jp/eita/canvasgl/glview/texture/gles/EglHelperFactory.kt
new file mode 100644
index 0000000..92c1bbe
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/glview/texture/gles/EglHelperFactory.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.glview.texture.gles
+
+object EglHelperFactory {
+
+ fun create(
+ configChooser: GLThread.EGLConfigChooser,
+ eglContextFactory: GLThread.EGLContextFactory,
+ eglWindowSurfaceFactory: GLThread.EGLWindowSurfaceFactory
+ ): IEglHelper {
+ return EglHelper(configChooser, eglContextFactory, eglWindowSurfaceFactory)
+ }
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/glview/texture/gles/GLThread.kt b/opengl/src/main/java/jp/eita/canvasgl/glview/texture/gles/GLThread.kt
new file mode 100644
index 0000000..b1c204c
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/glview/texture/gles/GLThread.kt
@@ -0,0 +1,1110 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.glview.texture.gles
+
+import android.annotation.TargetApi
+import android.opengl.EGL14
+import android.opengl.EGLContext
+import android.opengl.EGLExt
+import android.opengl.EGLSurface
+import android.os.Build
+import android.util.Log
+import android.view.Choreographer
+import android.view.Choreographer.FrameCallback
+import androidx.annotation.RequiresApi
+import jp.eita.canvasgl.glview.texture.GLViewRenderer
+import jp.eita.canvasgl.glview.texture.gles.EglContextWrapper.Companion.EGL_NO_CONTEXT_WRAPPER
+import jp.eita.canvasgl.glview.texture.gles.EglHelper.Companion.logEglErrorAsWarning
+import jp.eita.canvasgl.glview.texture.gles.EglHelper.Companion.throwEglException
+import jp.eita.canvasgl.glview.texture.gles.EglHelperFactory.create
+import jp.eita.canvasgl.util.FileLogger
+import java.util.*
+import javax.microedition.khronos.egl.EGL10
+import javax.microedition.khronos.egl.EGL11
+import javax.microedition.khronos.egl.EGLConfig
+import javax.microedition.khronos.egl.EGLDisplay
+import javax.microedition.khronos.opengles.GL
+
+/**
+ * This is the thread where the gl draw runs in.
+ * Create GL Context --> Create Surface
+ * And then draw with OpenGL and finally eglSwap to update the screen.
+ */
+class GLThread internal constructor(private val mEGLConfigChooser: EGLConfigChooser?, private val mEGLContextFactory: EGLContextFactory
+ , private val mEGLWindowSurfaceFactory: EGLWindowSurfaceFactory, private val mRenderer: GLViewRenderer
+ , private var mRenderMode: Int, private var mSurface: Any?, sharedEglContext: EglContextWrapper?) : Thread() {
+
+ private val sGLThreadManager = GLThreadManager()
+
+ private var onCreateGLContextListener: OnCreateGLContextListener? = null
+
+ // Once the thread is started, all accesses to the following member
+ // variables are protected by the sGLThreadManager monitor
+ private var mShouldExit = false
+
+ private var mExited = false
+
+ private var mRequestPaused = false
+
+ private var mPaused = false
+
+ private var mHasSurface = false
+
+ private var mSurfaceIsBad = false
+
+ private var mWaitingForSurface = false
+
+ private var mHaveEglContext = false
+
+ private var mHaveEglSurface = false
+
+ private var mFinishedCreatingEglSurface = false
+
+ private var mWidth = 0
+
+ private var mHeight = 0
+
+ private var mRequestRender = true
+
+ private var mWantRenderNotification = false
+
+ private var mRenderComplete = false
+
+ private val mEventQueue = ArrayList()
+
+ private var mSizeChanged = true
+
+ private var changeSurface = false
+
+ var eglContext: EglContextWrapper? = EGL_NO_CONTEXT_WRAPPER
+ private set
+ private val mChoreographerRenderWrapper = ChoreographerRenderWrapper(this)
+
+ private var frameTimeNanos: Long = 0
+
+ private var mEglHelper: IEglHelper? = null
+
+ init {
+ eglContext = sharedEglContext
+ }
+
+ fun setSurface(surface: Any) {
+ if (mSurface !== surface) {
+ changeSurface = true
+ }
+ mSurface = surface
+ }
+
+ override fun run() {
+ name = "GLThread $id"
+ FileLogger.i(TAG, "starting tid=$id")
+ try {
+ guardedRun()
+ } catch (e: InterruptedException) {
+ // fall thru and exit normally
+ FileLogger.e(TAG, "", e)
+ } finally {
+ sGLThreadManager.threadExiting(this)
+ }
+ }
+
+ /**
+ * This private method should only be called inside a
+ * synchronized(sGLThreadManager) block.
+ */
+ private fun stopEglSurfaceLocked() {
+ if (mHaveEglSurface) {
+ mHaveEglSurface = false
+ mEglHelper!!.destroySurface()
+ }
+ }
+
+ /**
+ * This private method should only be called inside a
+ * synchronized(sGLThreadManager) block.
+ */
+ private fun stopEglContextLocked() {
+ if (mHaveEglContext) {
+ mEglHelper!!.finish()
+ mHaveEglContext = false
+ sGLThreadManager.releaseEglContextLocked(this)
+ }
+ }
+
+ @Throws(InterruptedException::class)
+ private fun guardedRun() {
+ mEglHelper = create(mEGLConfigChooser!!, mEGLContextFactory, mEGLWindowSurfaceFactory)
+ mHaveEglContext = false
+ mHaveEglSurface = false
+ mWantRenderNotification = false
+ try {
+ var createEglContext = false
+ var createEglSurface = false
+ var createGlInterface = false
+ var lostEglContext = false
+ var sizeChanged = false
+ var wantRenderNotification = false
+ var doRenderNotification = false
+ var askedToReleaseEglContext = false
+ var w = 0
+ var h = 0
+ var event: Runnable? = null
+ while (true) {
+ synchronized(sGLThreadManager) {
+ // Create egl context here
+ while (true) {
+ if (mShouldExit) {
+ return
+ }
+ if (mEventQueue.isNotEmpty() && mHaveEglContext) {
+ event = mEventQueue.removeAt(0)
+ break
+ }
+
+ // Update the pause state.
+ var pausing = false
+ if (mPaused != mRequestPaused) {
+ pausing = mRequestPaused
+ mPaused = mRequestPaused
+ sGLThreadManager.notifyAll()
+ FileLogger.i(TAG, "mPaused is now $mPaused tid=$id")
+ }
+
+ // Have we lost the EGL context?
+ if (lostEglContext) {
+ FileLogger.i(TAG, "lostEglContext")
+ stopEglSurfaceLocked()
+ stopEglContextLocked()
+ lostEglContext = false
+ }
+
+ // When pausing, release the EGL surface:
+ if (pausing && mHaveEglSurface) {
+ FileLogger.i(TAG, "releasing EGL surface because paused tid=$id")
+ stopEglSurfaceLocked()
+ }
+
+ // Have we lost the SurfaceView surface?
+ if (!mHasSurface && !mWaitingForSurface) {
+ FileLogger.i(TAG, "noticed surfaceView surface lost tid=$id")
+ if (mHaveEglSurface) {
+ stopEglSurfaceLocked()
+ }
+ mWaitingForSurface = true
+ mSurfaceIsBad = false
+ sGLThreadManager.notifyAll()
+ }
+
+ // Have we acquired the surface view surface?
+ if (mHasSurface && mWaitingForSurface) {
+ FileLogger.i(TAG, "noticed surfaceView surface acquired tid=$id")
+ mWaitingForSurface = false
+ sGLThreadManager.notifyAll()
+ }
+ if (doRenderNotification) {
+// Log.i(TAG, "sending render notification tid=" + getId());
+ mWantRenderNotification = false
+ doRenderNotification = false
+ mRenderComplete = true
+ sGLThreadManager.notifyAll()
+ }
+
+ // Ready to draw?
+ if (readyToDraw()) {
+
+ // If we don't have an EGL context, try to acquire one.
+ if (!mHaveEglContext) {
+ if (askedToReleaseEglContext) {
+ askedToReleaseEglContext = false
+ } else if (sGLThreadManager.tryAcquireEglContextLocked(this)) {
+ try {
+ eglContext = mEglHelper!!.start(eglContext)
+ if (onCreateGLContextListener != null) {
+ onCreateGLContextListener!!.onCreate(eglContext)
+ }
+ } catch (t: RuntimeException) {
+ sGLThreadManager.releaseEglContextLocked(this)
+ throw t
+ }
+ mHaveEglContext = true
+ createEglContext = true
+ sGLThreadManager.notifyAll()
+ }
+ }
+ if (mHaveEglContext && !mHaveEglSurface) {
+ mHaveEglSurface = true
+ createEglSurface = true
+ createGlInterface = true
+ sizeChanged = true
+ }
+ if (mHaveEglSurface) {
+ if (mSizeChanged) {
+ sizeChanged = true
+ w = mWidth
+ h = mHeight
+ mWantRenderNotification = true
+ FileLogger.i(TAG, "noticing that we want render notification tid=$id")
+
+ // Destroy and recreate the EGL surface.
+ createEglSurface = true
+ mSizeChanged = false
+ }
+ if (changeSurface) {
+ createEglSurface = true
+ changeSurface = false
+ }
+ mRequestRender = false
+ sGLThreadManager.notifyAll()
+ if (mWantRenderNotification) {
+ wantRenderNotification = true
+ }
+ break
+ }
+ }
+
+ // By design, this is the only place in a GLThread thread where we wait().
+ if (LOG_THREADS) {
+ FileLogger.limitLog("", TAG, "waiting tid=" + id
+ + " mHaveEglContext: " + mHaveEglContext
+ + " mHaveEglSurface: " + mHaveEglSurface
+ + " mFinishedCreatingEglSurface: " + mFinishedCreatingEglSurface
+ + " mPaused: " + mPaused
+ + " mHasSurface: " + mHasSurface
+ + " mSurfaceIsBad: " + mSurfaceIsBad
+ + " mWaitingForSurface: " + mWaitingForSurface
+ + " mWidth: " + mWidth
+ + " mHeight: " + mHeight
+ + " mRequestRender: " + mRequestRender
+ + " mRenderMode: " + mRenderMode, 600)
+ }
+ sGLThreadManager.wait()
+ }
+ } // end of synchronized(sGLThreadManager)
+ if (event != null) {
+ event!!.run()
+ event = null
+ continue
+ }
+ if (createEglSurface) {
+ FileLogger.w(TAG, "egl createSurface")
+ if (mEglHelper!!.createSurface(mSurface)) {
+ synchronized(sGLThreadManager) {
+ mFinishedCreatingEglSurface = true
+ sGLThreadManager.notifyAll()
+ }
+ } else {
+ synchronized(sGLThreadManager) {
+ mFinishedCreatingEglSurface = true
+ mSurfaceIsBad = true
+ sGLThreadManager.notifyAll()
+ }
+ continue
+ }
+ createEglSurface = false
+ }
+ if (createGlInterface) {
+ createGlInterface = false
+ }
+
+ // Make sure context and surface are created
+ if (createEglContext) {
+ FileLogger.w("GLThread", "onSurfaceCreated")
+ mRenderer.onSurfaceCreated()
+ createEglContext = false
+ }
+ if (sizeChanged) {
+ FileLogger.w(TAG, "onSurfaceChanged($w, $h)")
+ mRenderer.onSurfaceChanged(w, h)
+ sizeChanged = false
+ }
+ if (mChoreographerRenderWrapper.canSwap()) {
+ if (LOG_RENDERER_DRAW_FRAME) {
+ Log.w(TAG, "onDrawFrame tid=$id")
+ }
+ mRenderer.onDrawFrame()
+ mEglHelper!!.setPresentationTime(frameTimeNanos)
+ val swapError = mEglHelper!!.swap()
+ mChoreographerRenderWrapper.disableSwap()
+ when (swapError) {
+ EGL10.EGL_SUCCESS -> {
+ }
+ EGL11.EGL_CONTEXT_LOST -> {
+ FileLogger.i(TAG, "egl context lost tid=$id")
+ lostEglContext = true
+ }
+ else -> {
+ // Other errors typically mean that the current surface is bad,
+ // probably because the SurfaceView surface has been destroyed,
+ // but we haven't been notified yet.
+ // Log the error to help developers understand why rendering stopped.
+ logEglErrorAsWarning("GLThread", "eglSwapBuffers", swapError)
+ synchronized(sGLThreadManager) {
+ mSurfaceIsBad = true
+ sGLThreadManager.notifyAll()
+ }
+ }
+ }
+ }
+ if (wantRenderNotification) {
+ doRenderNotification = true
+ wantRenderNotification = false
+ }
+ }
+ } finally {
+ /*
+ * clean-up everything...
+ */
+ synchronized(sGLThreadManager) {
+ stopEglSurfaceLocked()
+ stopEglContextLocked()
+ }
+ }
+ }
+
+ @Synchronized
+ override fun start() {
+ super.start()
+ mChoreographerRenderWrapper.start()
+ }
+
+ fun ableToDraw(): Boolean {
+ return mHaveEglContext && mHaveEglSurface && readyToDraw()
+ }
+
+ private fun readyToDraw(): Boolean {
+ return (!mPaused && mHasSurface && !mSurfaceIsBad
+ && mWidth > 0 && mHeight > 0
+ && mRequestRender)
+ }
+
+ fun setOnCreateGLContextListener(onCreateGLContextListener: OnCreateGLContextListener?) {
+ this.onCreateGLContextListener = onCreateGLContextListener
+ }
+
+ var renderMode: Int
+ get() = mRenderMode
+ set(renderMode) {
+ require(renderMode in RENDERMODE_WHEN_DIRTY..RENDERMODE_CONTINUOUSLY) { "renderMode" }
+ synchronized(sGLThreadManager) {
+ mRenderMode = renderMode
+ sGLThreadManager.notifyAll()
+ }
+ }
+
+ @JvmOverloads
+ fun requestRender(frameTimeNanos: Long = 0) {
+ this.frameTimeNanos = frameTimeNanos
+ synchronized(sGLThreadManager) {
+ mRequestRender = true
+ sGLThreadManager.notifyAll()
+ }
+ }
+
+ fun requestRenderAndWait() {
+ synchronized(sGLThreadManager) {
+
+ // If we are already on the GL thread, this means a client callback
+ // has caused reentrancy, for example via updating the SurfaceView parameters.
+ // We will return to the client rendering code, so here we don't need to
+ // do anything.
+ if (currentThread() === this) {
+ return
+ }
+ mWantRenderNotification = true
+ mRequestRender = true
+ mRenderComplete = false
+ sGLThreadManager.notifyAll()
+ while (!mExited && !mPaused && !mRenderComplete && ableToDraw()) {
+ try {
+ sGLThreadManager.wait()
+ } catch (ex: InterruptedException) {
+ currentThread().interrupt()
+ }
+ }
+ }
+ }
+
+ fun surfaceCreated() {
+ synchronized(sGLThreadManager) {
+ FileLogger.i(TAG, "surfaceCreated tid=$id")
+ mHasSurface = true
+ mFinishedCreatingEglSurface = false
+ sGLThreadManager.notifyAll()
+ while (mWaitingForSurface
+ && !mFinishedCreatingEglSurface
+ && !mExited) {
+ try {
+ sGLThreadManager.wait()
+ } catch (e: InterruptedException) {
+ currentThread().interrupt()
+ }
+ }
+ }
+ }
+
+ /**
+ * mHasSurface = false --> mWaitingForSurface = true
+ * -->
+ */
+ fun surfaceDestroyed() {
+ synchronized(sGLThreadManager) {
+ FileLogger.i(TAG, "surfaceDestroyed tid=$id")
+ mHasSurface = false
+ sGLThreadManager.notifyAll()
+ while (!mWaitingForSurface && !mExited) {
+ try {
+ sGLThreadManager.wait()
+ } catch (e: InterruptedException) {
+ currentThread().interrupt()
+ }
+ }
+ }
+ }
+
+ /**
+ * mRequestPaused --> mPaused, pausing
+ * --> pausing && mHaveEglSurface, stopEglSurfaceLocked()
+ * --> pausing && mHaveEglContext, preserve context or not.
+ */
+ fun onPause() {
+ synchronized(sGLThreadManager) {
+ FileLogger.i(TAG, "onPause tid=$id")
+ mRequestPaused = true
+ sGLThreadManager.notifyAll()
+ while (!mExited && !mPaused) {
+ FileLogger.i(TAG, "onPause waiting for mPaused.")
+ try {
+ sGLThreadManager.wait()
+ } catch (ex: InterruptedException) {
+ currentThread().interrupt()
+ }
+ }
+ mChoreographerRenderWrapper.stop()
+ }
+ }
+
+ fun onResume() {
+ synchronized(sGLThreadManager) {
+ FileLogger.i(TAG, "onResume tid=$id")
+ mRequestPaused = false
+ mRequestRender = true
+ mRenderComplete = false
+ sGLThreadManager.notifyAll()
+ while (!mExited && mPaused && !mRenderComplete) {
+ FileLogger.i(TAG, "onResume waiting for !mPaused.")
+ try {
+ sGLThreadManager.wait()
+ } catch (ex: InterruptedException) {
+ currentThread().interrupt()
+ }
+ }
+ mChoreographerRenderWrapper.start()
+ }
+ }
+
+ fun onWindowResize(w: Int, h: Int) {
+ synchronized(sGLThreadManager) {
+ FileLogger.d(TAG, "width:$w height:$h")
+ mWidth = w
+ mHeight = h
+ mSizeChanged = true
+ mRequestRender = true
+ mRenderComplete = false
+
+ // If we are already on the GL thread, this means a client callback
+ // has caused reentrancy, for example via updating the SurfaceView parameters.
+ // We need to process the size change eventually though and update our EGLSurface.
+ // So we set the parameters and return so they can be processed on our
+ // next iteration.
+ if (currentThread() === this) {
+ return
+ }
+ sGLThreadManager.notifyAll()
+
+ // Wait for thread to react to resize and render a frame
+ while (!mExited && !mPaused && !mRenderComplete
+ && ableToDraw()) {
+ FileLogger.i(TAG, "onWindowResize waiting for render complete from tid=$id")
+ try {
+ sGLThreadManager.wait()
+ } catch (ex: InterruptedException) {
+ currentThread().interrupt()
+ }
+ }
+ }
+ }
+
+ fun requestExitAndWait() {
+ // don't call this from GLThread thread or it is a guaranteed
+ // deadlock!
+ synchronized(sGLThreadManager) {
+ mShouldExit = true
+ sGLThreadManager.notifyAll()
+ while (!mExited) {
+ try {
+ sGLThreadManager.wait()
+ } catch (ex: InterruptedException) {
+ currentThread().interrupt()
+ }
+ }
+ }
+ }
+
+ /**
+ * Queue an "event" to be run on the GL rendering thread.
+ *
+ * @param r the runnable to be run on the GL rendering thread.
+ */
+ fun queueEvent(r: Runnable?) {
+ requireNotNull(r) { "r must not be null" }
+ synchronized(sGLThreadManager) {
+ mEventQueue.add(r)
+ sGLThreadManager.notifyAll()
+ }
+ }
+
+ // End of member variables protected by the sGLThreadManager monitor.
+ interface OnCreateGLContextListener {
+ fun onCreate(eglContext: EglContextWrapper?)
+ }
+
+ interface GLWrapper {
+ /**
+ * Wraps a gl interface in another gl interface.
+ *
+ * @param gl a GL interface that is to be wrapped.
+ * @return either the input argument or another GL object that wraps the input argument.
+ */
+ fun wrap(gl: GL?): GL?
+ }
+
+ interface EGLConfigChooser {
+ /**
+ * Choose a configuration from the list. Implementors typically
+ * implement this method by calling
+ * [EGL10.eglChooseConfig] and iterating through the results. Please consult the
+ * EGL specification available from The Khronos Group to learn how to call eglChooseConfig.
+ *
+ * @param egl the EGL10 for the current display.
+ * @param display the current display.
+ * @return the chosen configuration.
+ */
+ fun chooseConfig(egl: EGL10, display: EGLDisplay?): EGLConfig
+ fun chooseConfig(display: android.opengl.EGLDisplay?, recordable: Boolean): android.opengl.EGLConfig?
+ }
+
+ interface EGLContextFactory {
+ fun createContext(egl: EGL10, display: EGLDisplay?, eglConfig: EGLConfig?, eglContext: javax.microedition.khronos.egl.EGLContext?): javax.microedition.khronos.egl.EGLContext?
+ fun destroyContext(egl: EGL10, display: EGLDisplay, context: javax.microedition.khronos.egl.EGLContext)
+ fun createContextAPI17(display: android.opengl.EGLDisplay?, eglConfig: android.opengl.EGLConfig?, eglContext: EGLContext?): EGLContext?
+ fun destroyContext(display: android.opengl.EGLDisplay, context: EGLContext)
+ }
+
+ interface EGLWindowSurfaceFactory {
+ /**
+ * @return null if the surface cannot be constructed.
+ */
+ fun createWindowSurface(egl: EGL10, display: EGLDisplay?, config: EGLConfig?,
+ nativeWindow: Any?): javax.microedition.khronos.egl.EGLSurface?
+
+ fun destroySurface(egl: EGL10, display: EGLDisplay?, surface: javax.microedition.khronos.egl.EGLSurface?)
+ fun createWindowSurface(display: android.opengl.EGLDisplay?, config: android.opengl.EGLConfig?,
+ nativeWindow: Any?): EGLSurface?
+
+ fun destroySurface(display: android.opengl.EGLDisplay?, surface: EGLSurface?)
+ }
+
+ private class GLThreadManager : Object() {
+
+ private var mEglOwner: GLThread? = null
+
+ @Synchronized
+ fun threadExiting(thread: GLThread) {
+ FileLogger.i(TAG, "exiting tid=" + thread.id)
+ thread.mExited = true
+ if (mEglOwner === thread) {
+ mEglOwner = null
+ }
+ notifyAll()
+ }
+
+ /*
+ * Tries once to acquire the right to use an EGL
+ * context. Does not block. Requires that we are already
+ * in the sGLThreadManager monitor when this is called.
+ *
+ * @return true if the right to use an EGL context was acquired.
+ */
+ fun tryAcquireEglContextLocked(thread: GLThread): Boolean {
+ if (mEglOwner === thread || mEglOwner == null) {
+ mEglOwner = thread
+ notifyAll()
+ return true
+ }
+ return true
+ }
+
+ /*
+ * Releases the EGL context. Requires that we are already in the
+ * sGLThreadManager monitor when this is called.
+ */
+ fun releaseEglContextLocked(thread: GLThread) {
+ if (mEglOwner === thread) {
+ mEglOwner = null
+ }
+ notifyAll()
+ }
+ }
+
+ abstract class BaseConfigChooser(configSpec: IntArray, contextClientVersion: Int) : EGLConfigChooser {
+
+ protected var mConfigSpec: IntArray
+
+ private val contextClientVersion: Int
+
+ init {
+ mConfigSpec = filterConfigSpec(configSpec)
+ this.contextClientVersion = contextClientVersion
+ }
+
+ override fun chooseConfig(egl: EGL10, display: EGLDisplay?): EGLConfig {
+ val numConfig = IntArray(1)
+ require(egl.eglChooseConfig(display, mConfigSpec, null, 0,
+ numConfig)) { "eglChooseConfig failed" }
+ val numConfigs = numConfig[0]
+ require(numConfigs > 0) { "No configs match configSpec" }
+ val configs = arrayOfNulls(numConfigs)
+ require(egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs,
+ numConfig)) { "eglChooseConfig#2 failed" }
+ return chooseConfig(egl, display, configs)
+ ?: throw IllegalArgumentException("No config chosen")
+ }
+
+ abstract fun chooseConfig(egl: EGL10, display: EGLDisplay?,
+ configs: Array): EGLConfig?
+
+ private fun filterConfigSpec(configSpec: IntArray): IntArray {
+ if (contextClientVersion != 2 && contextClientVersion != 3) {
+ return configSpec
+ }
+ /* We know none of the subclasses define EGL_RENDERABLE_TYPE.
+ * And we know the configSpec is well formed.
+ */
+ val len = configSpec.size
+ val newConfigSpec = IntArray(len + 2)
+ System.arraycopy(configSpec, 0, newConfigSpec, 0, len - 1)
+ newConfigSpec[len - 1] = EGL10.EGL_RENDERABLE_TYPE
+ if (contextClientVersion == 2) {
+ newConfigSpec[len] = EGL14.EGL_OPENGL_ES2_BIT /* EGL_OPENGL_ES2_BIT */
+ } else {
+ newConfigSpec[len] = EGLExt.EGL_OPENGL_ES3_BIT_KHR /* EGL_OPENGL_ES3_BIT_KHR */
+ }
+ newConfigSpec[len + 1] = EGL10.EGL_NONE
+
+ return newConfigSpec
+ }
+
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+ override fun chooseConfig(display: android.opengl.EGLDisplay?, recordable: Boolean): android.opengl.EGLConfig? {
+ var renderableType = EGL14.EGL_OPENGL_ES2_BIT
+ if (contextClientVersion >= 3) {
+ renderableType = renderableType or EGLExt.EGL_OPENGL_ES3_BIT_KHR
+ }
+
+ // The actual surface is generally RGBA or RGBX, so situationally omitting alpha
+ // doesn't really help. It can also lead to a huge performance hit on glReadPixels()
+ // when reading into a GL_RGBA buffer.
+ val attribList = intArrayOf(
+ EGL14.EGL_RED_SIZE, 8,
+ EGL14.EGL_GREEN_SIZE, 8,
+ EGL14.EGL_BLUE_SIZE, 8,
+ EGL14.EGL_ALPHA_SIZE, 8, //EGL14.EGL_DEPTH_SIZE, 16,
+ //EGL14.EGL_STENCIL_SIZE, 8,
+ EGL14.EGL_RENDERABLE_TYPE, renderableType,
+ EGL14.EGL_NONE, 0, // placeholder for recordable [@-3]
+ EGL14.EGL_NONE
+ )
+ if (recordable) {
+ attribList[attribList.size - 3] = EGL_RECORDABLE_ANDROID
+ attribList[attribList.size - 2] = 1
+ }
+
+ val configs = arrayOfNulls(1)
+ val numConfigs = IntArray(1)
+ if (!EGL14.eglChooseConfig(display, attribList, 0, configs, 0, configs.size,
+ numConfigs, 0)) {
+ Log.w("GLThread", "unable to find RGB8888 / $contextClientVersion EGLConfig")
+ return null
+ }
+
+ return configs[0]
+ }
+
+ companion object {
+
+ private const val EGL_RECORDABLE_ANDROID = 0x3142
+ }
+ }
+
+ /**
+ * Choose a configuration with exactly the specified r,g,b,a sizes,
+ * and at least the specified depth and stencil sizes.
+ */
+ open class ComponentSizeChooser(redSize: Int, greenSize: Int, blueSize: Int,
+ alphaSize: Int, depthSize: Int, stencilSize: Int, contextClientVersion: Int) : BaseConfigChooser(intArrayOf(
+ EGL10.EGL_RED_SIZE, redSize,
+ EGL10.EGL_GREEN_SIZE, greenSize,
+ EGL10.EGL_BLUE_SIZE, blueSize,
+ EGL10.EGL_ALPHA_SIZE, alphaSize,
+ EGL10.EGL_DEPTH_SIZE, depthSize,
+ EGL10.EGL_STENCIL_SIZE, stencilSize,
+ EGL10.EGL_NONE), contextClientVersion) {
+
+ // Subclasses can adjust these values:
+ protected var mRedSize: Int
+
+ protected var mGreenSize: Int
+
+ protected var mBlueSize: Int
+
+ protected var mAlphaSize: Int
+
+ protected var mDepthSize: Int
+
+ protected var mStencilSize: Int
+
+ private val mValue: IntArray
+
+ override fun chooseConfig(egl: EGL10, display: EGLDisplay?,
+ configs: Array): EGLConfig? {
+ for (config in configs) {
+ val d = findConfigAttrib(egl, display, config,
+ EGL10.EGL_DEPTH_SIZE, 0)
+ val s = findConfigAttrib(egl, display, config,
+ EGL10.EGL_STENCIL_SIZE, 0)
+ if (d >= mDepthSize && s >= mStencilSize) {
+ val r = findConfigAttrib(egl, display, config,
+ EGL10.EGL_RED_SIZE, 0)
+ val g = findConfigAttrib(egl, display, config,
+ EGL10.EGL_GREEN_SIZE, 0)
+ val b = findConfigAttrib(egl, display, config,
+ EGL10.EGL_BLUE_SIZE, 0)
+ val a = findConfigAttrib(egl, display, config,
+ EGL10.EGL_ALPHA_SIZE, 0)
+ if (r == mRedSize && g == mGreenSize
+ && b == mBlueSize && a == mAlphaSize) {
+ return config
+ }
+ }
+ }
+ return null
+ }
+
+ private fun findConfigAttrib(egl: EGL10, display: EGLDisplay?,
+ config: EGLConfig?, attribute: Int, defaultValue: Int): Int {
+ return if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) {
+ mValue[0]
+ } else {
+ defaultValue
+ }
+ }
+
+ init {
+ mValue = IntArray(1)
+ mRedSize = redSize
+ mGreenSize = greenSize
+ mBlueSize = blueSize
+ mAlphaSize = alphaSize
+ mDepthSize = depthSize
+ mStencilSize = stencilSize
+ }
+ }
+
+ /**
+ * This class will choose a RGB_888 surface with
+ * or without a depth buffer.
+ */
+ class SimpleEGLConfigChooser : ComponentSizeChooser {
+ constructor(withDepthBuffer: Boolean, contextClientVersion: Int) : super(8, 8, 8, 0, if (withDepthBuffer) 16 else 0, 0, contextClientVersion)
+ constructor(redSize: Int, greenSize: Int, blueSize: Int, alphaSize: Int, depthSize: Int, stencilSize: Int, contextClientVersion: Int) : super(redSize, greenSize, blueSize, alphaSize, depthSize, stencilSize, contextClientVersion)
+
+ companion object {
+ fun createConfigChooser(withDepthBuffer: Boolean, contextClientVersion: Int): SimpleEGLConfigChooser {
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ SimpleEGLConfigChooser(withDepthBuffer, contextClientVersion)
+ } else {
+ SimpleEGLConfigChooser(5, 6, 5, 8, 0, 0, contextClientVersion)
+ }
+ }
+ }
+ }
+
+ class DefaultContextFactory(private val contextClientVersion: Int) : EGLContextFactory {
+
+ private val EGL_CONTEXT_CLIENT_VERSION = 0x3098
+
+ override fun createContext(egl: EGL10, display: EGLDisplay?, eglConfig: EGLConfig?, eglContext: javax.microedition.khronos.egl.EGLContext?): javax.microedition.khronos.egl.EGLContext? {
+ val attribList = intArrayOf(
+ EGL_CONTEXT_CLIENT_VERSION, contextClientVersion,
+ EGL10.EGL_NONE)
+ return egl.eglCreateContext(display, eglConfig, eglContext,
+ if (contextClientVersion != 0) attribList else null)
+ }
+
+ override fun destroyContext(egl: EGL10, display: EGLDisplay,
+ context: javax.microedition.khronos.egl.EGLContext) {
+ if (!egl.eglDestroyContext(display, context)) {
+ FileLogger.e(TAG, "DefaultContextFactory display:$display context: $context")
+ throwEglException("eglDestroyContext", egl.eglGetError())
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+ override fun createContextAPI17(display: android.opengl.EGLDisplay?, eglConfig: android.opengl.EGLConfig?, eglContext: EGLContext?): EGLContext? {
+ val attribList = intArrayOf(
+ EGL14.EGL_CONTEXT_CLIENT_VERSION, contextClientVersion,
+ EGL14.EGL_NONE)
+ return EGL14.eglCreateContext(display, eglConfig, eglContext, attribList, 0)
+ }
+
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+ override fun destroyContext(display: android.opengl.EGLDisplay, context: EGLContext) {
+ if (!EGL14.eglDestroyContext(display, context)) {
+ FileLogger.e(TAG, "DefaultContextFactory display:$display context: $context")
+ throwEglException("eglDestroyContext", EGL14.eglGetError())
+ }
+ }
+
+ }
+
+ class DefaultWindowSurfaceFactory : EGLWindowSurfaceFactory {
+
+ override fun createWindowSurface(egl: EGL10, display: EGLDisplay?,
+ config: EGLConfig?, nativeWindow: Any?): javax.microedition.khronos.egl.EGLSurface? {
+ val surfaceAttribs = intArrayOf(
+ EGL10.EGL_NONE
+ )
+ var result: javax.microedition.khronos.egl.EGLSurface? = null
+ try {
+ result = egl.eglCreateWindowSurface(display, config, nativeWindow, surfaceAttribs)
+ } catch (e: IllegalArgumentException) {
+ // This exception indicates that the surface flinger surface
+ // is not valid. This can happen if the surface flinger surface has
+ // been torn down, but the application has not yet been
+ // notified via SurfaceHolder.Callback.surfaceDestroyed.
+ // In theory the application should be notified first,
+ // but in practice sometimes it is not. See b/4588890
+ Log.e("DefaultWindow", "eglCreateWindowSurface", e)
+ }
+ return result
+ }
+
+ override fun destroySurface(egl: EGL10, display: EGLDisplay?,
+ surface: javax.microedition.khronos.egl.EGLSurface?) {
+ egl.eglDestroySurface(display, surface)
+ }
+
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+ override fun createWindowSurface(display: android.opengl.EGLDisplay?, config: android.opengl.EGLConfig?, nativeWindow: Any?): EGLSurface? {
+ val surfaceAttribs = intArrayOf(
+ EGL14.EGL_NONE
+ )
+ var result: EGLSurface? = null
+ try {
+ result = EGL14.eglCreateWindowSurface(display, config, nativeWindow, surfaceAttribs, 0)
+ } catch (e: IllegalArgumentException) {
+ // This exception indicates that the surface flinger surface
+ // is not valid. This can happen if the surface flinger surface has
+ // been torn down, but the application has not yet been
+ // notified via SurfaceHolder.Callback.surfaceDestroyed.
+ // In theory the application should be notified first,
+ // but in practice sometimes it is not. See b/4588890
+ Log.e("DefaultWindow", "eglCreateWindowSurface", e)
+ }
+ return result
+ }
+
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+ override fun destroySurface(display: android.opengl.EGLDisplay?, surface: EGLSurface?) {
+ EGL14.eglDestroySurface(display, surface)
+ }
+ }
+
+ class Builder {
+
+ private var configChooser: EGLConfigChooser? = null
+
+ private var eglContextFactory: EGLContextFactory? = null
+
+ private var eglWindowSurfaceFactory: EGLWindowSurfaceFactory? = null
+
+ private var renderer: GLViewRenderer? = null
+
+ private var eglContextClientVersion = 2
+
+ private var renderMode = RENDERMODE_WHEN_DIRTY
+
+ private var surface: Any? = null
+
+ private var eglContext = EGL_NO_CONTEXT_WRAPPER
+
+ fun setSurface(surface: Any?): Builder {
+ this.surface = surface
+ return this
+ }
+
+ fun setEGLConfigChooser(needDepth: Boolean): Builder {
+ setEGLConfigChooser(SimpleEGLConfigChooser.createConfigChooser(needDepth, eglContextClientVersion))
+ return this
+ }
+
+ fun setEGLConfigChooser(configChooser: EGLConfigChooser?): Builder {
+ this.configChooser = configChooser
+ return this
+ }
+
+ fun setEGLConfigChooser(redSize: Int, greenSize: Int, blueSize: Int,
+ alphaSize: Int, depthSize: Int, stencilSize: Int): Builder {
+ setEGLConfigChooser(ComponentSizeChooser(redSize, greenSize,
+ blueSize, alphaSize, depthSize, stencilSize, eglContextClientVersion))
+ return this
+ }
+
+ fun setEglContextFactory(eglContextFactory: EGLContextFactory?): Builder {
+ this.eglContextFactory = eglContextFactory
+ return this
+ }
+
+ fun setEglWindowSurfaceFactory(eglWindowSurfaceFactory: EGLWindowSurfaceFactory?): Builder {
+ this.eglWindowSurfaceFactory = eglWindowSurfaceFactory
+ return this
+ }
+
+ fun setRenderer(renderer: GLViewRenderer?): Builder {
+ this.renderer = renderer
+ return this
+ }
+
+ fun setGLWrapper(mGLWrapper: GLWrapper?): Builder {
+ return this
+ }
+
+ fun setEglContextClientVersion(eglContextClientVersion: Int): Builder {
+ this.eglContextClientVersion = eglContextClientVersion
+ return this
+ }
+
+ fun setRenderMode(renderMode: Int): Builder {
+ this.renderMode = renderMode
+ return this
+ }
+
+ fun setSharedEglContext(sharedEglContext: EglContextWrapper): Builder {
+ eglContext = sharedEglContext
+ return this
+ }
+
+ fun createGLThread(): GLThread {
+ if (renderer == null) {
+ throw NullPointerException("renderer has not been set")
+ }
+ if (surface == null && eglWindowSurfaceFactory == null) {
+ throw NullPointerException("surface has not been set")
+ }
+ if (configChooser == null) {
+ configChooser = SimpleEGLConfigChooser.createConfigChooser(true, eglContextClientVersion)
+ }
+ if (eglContextFactory == null) {
+ eglContextFactory = DefaultContextFactory(eglContextClientVersion)
+ }
+ if (eglWindowSurfaceFactory == null) {
+ eglWindowSurfaceFactory = DefaultWindowSurfaceFactory()
+ }
+ return GLThread(configChooser, eglContextFactory!!, eglWindowSurfaceFactory!!, renderer!!, renderMode, surface, eglContext)
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+ class ChoreographerRender @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) constructor(private val glThread: GLThread) : FrameCallback {
+
+ // Only used when render mode is RENDERMODE_CONTINUOUSLY
+ private var canSwap = true
+ override fun doFrame(frameTimeNanos: Long) {
+ if (glThread.mRenderMode == RENDERMODE_CONTINUOUSLY) {
+ canSwap = true
+ glThread.requestRender(frameTimeNanos)
+ Choreographer.getInstance().postFrameCallback(this)
+ }
+ }
+
+ fun start() {
+ Choreographer.getInstance().postFrameCallback(this)
+ }
+
+ fun stop() {
+ Choreographer.getInstance().removeFrameCallback(this)
+ }
+
+ fun isCanSwap(): Boolean {
+ return canSwap || glThread.mRenderMode == RENDERMODE_WHEN_DIRTY
+ }
+
+ fun setCanSwap(canSwap: Boolean) {
+ this.canSwap = canSwap
+ }
+
+ }
+
+ class ChoreographerRenderWrapper(glThread: GLThread) {
+
+ private var choreographerRender: ChoreographerRender? = null
+
+ init {
+ choreographerRender = ChoreographerRender(glThread)
+ }
+
+ fun start() {
+ choreographerRender?.start()
+ }
+
+ fun stop() {
+ choreographerRender?.start()
+ }
+
+ fun canSwap(): Boolean {
+ return if (choreographerRender != null) {
+ choreographerRender!!.isCanSwap()
+ } else true
+ }
+
+ fun disableSwap() {
+ choreographerRender?.setCanSwap(false)
+ }
+ }
+
+ companion object {
+
+ const val LOG_RENDERER_DRAW_FRAME = false
+
+ const val LOG_THREADS = false
+
+ const val RENDERMODE_WHEN_DIRTY = 0
+
+ const val RENDERMODE_CONTINUOUSLY = 1
+
+ private const val TAG = "GLThread"
+ }
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/glview/texture/gles/IEglHelper.kt b/opengl/src/main/java/jp/eita/canvasgl/glview/texture/gles/IEglHelper.kt
new file mode 100644
index 0000000..11ebf74
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/glview/texture/gles/IEglHelper.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.glview.texture.gles
+
+interface IEglHelper {
+
+ fun start(eglContext: EglContextWrapper?): EglContextWrapper?
+
+ fun createSurface(surface: Any?): Boolean
+
+ fun swap(): Int
+
+ fun destroySurface()
+
+ fun finish()
+
+ fun setPresentationTime(nsecs: Long)
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/matrix/BaseBitmapMatrix.kt b/opengl/src/main/java/jp/eita/canvasgl/matrix/BaseBitmapMatrix.kt
new file mode 100644
index 0000000..47a320f
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/matrix/BaseBitmapMatrix.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.matrix
+
+import android.opengl.Matrix
+import java.util.*
+
+abstract class BaseBitmapMatrix : IBitmapMatrix {
+
+ @JvmField
+ protected var transform = FloatArray(7)
+
+ @JvmField
+ protected var tempMultiplyMatrix4 = FloatArray(MATRIX_SIZE)
+
+ @JvmField
+ protected var mViewMatrix = FloatArray(MATRIX_SIZE)
+
+ @JvmField
+ protected var mProjectionMatrix = FloatArray(MATRIX_SIZE)
+
+ @JvmField
+ protected var mModelMatrix = FloatArray(MATRIX_SIZE)
+
+ @JvmField
+ protected var viewProjectionMatrix = FloatArray(MATRIX_SIZE)
+
+ @JvmField
+ protected var mvp = FloatArray(MATRIX_SIZE)
+
+ fun reset() {
+ Matrix.setIdentityM(mViewMatrix, 0)
+ Matrix.setIdentityM(mProjectionMatrix, 0)
+ Matrix.setIdentityM(mModelMatrix, 0)
+ Matrix.setIdentityM(viewProjectionMatrix, 0)
+ Matrix.setIdentityM(mvp, 0)
+ Matrix.setIdentityM(tempMultiplyMatrix4, 0)
+ Arrays.fill(transform, 0f)
+ transform[SCALE_X] = 1f
+ transform[SCALE_Y] = 1f
+ }
+
+ companion object {
+
+ const val TRANSLATE_X = 0
+
+ const val TRANSLATE_Y = 1
+
+ const val SCALE_X = 2
+
+ const val SCALE_Y = 3
+
+ const val ROTATE_X = 4
+
+ const val ROTATE_Y = 5
+
+ const val ROTATE_Z = 6
+
+ const val MATRIX_SIZE = 16
+
+ const val NEAR = 1f
+
+ const val FAR = 10f // The plane is at -10
+
+ const val EYEZ = 5f
+
+ const val Z_RATIO = (FAR + NEAR) / 2 / NEAR // The scale ratio when the picture moved to the middle of the perspective projection.
+ }
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/matrix/IBitmapMatrix.kt b/opengl/src/main/java/jp/eita/canvasgl/matrix/IBitmapMatrix.kt
new file mode 100644
index 0000000..36ce36e
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/matrix/IBitmapMatrix.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.matrix
+
+/**
+ * The output is MVP matrix of OpenGL, which is used to calculate gl_position.
+ * gl_position = MVP * [x,y,z,w]
+ */
+interface IBitmapMatrix {
+
+ fun obtainResultMatrix(viewportW: Int, viewportH: Int, x: Float, y: Float, drawW: Float, drawH: Float): FloatArray?
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/shapeFilter/BasicDrawShapeFilter.kt b/opengl/src/main/java/jp/eita/canvasgl/shapeFilter/BasicDrawShapeFilter.kt
new file mode 100644
index 0000000..98f84f1
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/shapeFilter/BasicDrawShapeFilter.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.shapeFilter
+
+import jp.eita.canvasgl.ICanvasGL
+import jp.eita.canvasgl.glcanvas.GLES20Canvas
+
+open class BasicDrawShapeFilter : DrawShapeFilter {
+
+ override val vertexShader = DRAW_VERTEX_SHADER
+
+ override val fragmentShader = DRAW_FRAGMENT_SHADER
+
+ override fun onPreDraw(program: Int, canvas: ICanvasGL?) {}
+
+ override fun destroy() {}
+
+ companion object {
+
+ const val MATRIX_UNIFORM = GLES20Canvas.MATRIX_UNIFORM
+
+ const val POSITION_ATTRIBUTE = GLES20Canvas.POSITION_ATTRIBUTE
+
+ const val COLOR_UNIFORM = GLES20Canvas.COLOR_UNIFORM
+
+ const val DRAW_FRAGMENT_SHADER = (""
+ + "precision mediump float;\n"
+ + "uniform vec4 " + COLOR_UNIFORM + ";\n"
+ + "void main() {\n"
+ + " gl_FragColor = " + COLOR_UNIFORM + ";\n"
+ + "}\n")
+
+ const val DRAW_VERTEX_SHADER = (""
+ + "uniform mat4 " + MATRIX_UNIFORM + ";\n"
+ + "attribute vec2 " + POSITION_ATTRIBUTE + ";\n"
+ + "void main() {\n"
+ + " vec4 pos = vec4(" + POSITION_ATTRIBUTE + ", 0.0, 1.0);\n"
+ + " gl_Position = " + MATRIX_UNIFORM + " * pos;\n"
+ + "}\n")
+ }
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/shapeFilter/DrawCircleFilter.kt b/opengl/src/main/java/jp/eita/canvasgl/shapeFilter/DrawCircleFilter.kt
new file mode 100644
index 0000000..6c28645
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/shapeFilter/DrawCircleFilter.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.shapeFilter
+
+import android.opengl.GLES20
+import jp.eita.canvasgl.ICanvasGL
+import jp.eita.canvasgl.OpenGLUtil.setFloat
+
+class DrawCircleFilter : BasicDrawShapeFilter() {
+
+ var lineWidth = 0f
+
+ override fun onPreDraw(program: Int, canvas: ICanvasGL?) {
+ super.onPreDraw(program, canvas)
+ val lineWidthLocation = GLES20.glGetUniformLocation(program, UNIFORM_LINE_WIDTH)
+ setFloat(lineWidthLocation, lineWidth)
+ }
+
+ companion object {
+ const val VARYING_DRAW_REGION_COORD = "vDrawRegionCoord"
+
+ const val VERTEXT_SHADER = (""
+ + "uniform mat4 " + MATRIX_UNIFORM + ";\n"
+ + "attribute vec2 " + POSITION_ATTRIBUTE + ";\n"
+ + "varying vec2 " + VARYING_DRAW_REGION_COORD + ";\n"
+ + "void main() {\n"
+ + " vec4 pos = vec4(" + POSITION_ATTRIBUTE + ", 0.0, 1.0);\n"
+ + " gl_Position = " + MATRIX_UNIFORM + " * pos;\n"
+ + " " + VARYING_DRAW_REGION_COORD + " = pos.xy;\n"
+ + "}\n")
+
+ const val UNIFORM_LINE_WIDTH = "lineWidth"
+
+ const val FRAGMENT_SHADER = (""
+ + "precision mediump float;\n"
+ + "varying vec2 " + VARYING_DRAW_REGION_COORD + ";\n"
+ + "uniform vec4 " + COLOR_UNIFORM + ";\n"
+ + "uniform float " + UNIFORM_LINE_WIDTH + ";\n"
+ + "void main() {\n"
+ + " float dx = " + VARYING_DRAW_REGION_COORD + ".x - 0.5;\n"
+ + " float dy = " + VARYING_DRAW_REGION_COORD + ".y - 0.5;\n"
+ + " float powVal = dx*dx + dy*dy; \n"
+ + " float subRadius = 0.5 - " + UNIFORM_LINE_WIDTH + "; \n"
+ + " if(powVal >= subRadius * subRadius && powVal <= 0.5 * 0.5) {\n"
+ + " gl_FragColor = " + COLOR_UNIFORM + ";\n"
+ + " } else {\n"
+ + " gl_FragColor = vec4(0, 0, 0, 0);\n"
+ + " }\n"
+ + " \n"
+ + "}\n")
+ }
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/shapeFilter/DrawShapeFilter.kt b/opengl/src/main/java/jp/eita/canvasgl/shapeFilter/DrawShapeFilter.kt
new file mode 100644
index 0000000..7fc9197
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/shapeFilter/DrawShapeFilter.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.shapeFilter
+
+import jp.eita.canvasgl.ICanvasGL
+
+interface DrawShapeFilter {
+
+ val vertexShader: String?
+
+ val fragmentShader: String?
+
+ fun onPreDraw(program: Int, canvas: ICanvasGL?)
+
+ fun destroy()
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/textureFilter/BasicTextureFilter.kt b/opengl/src/main/java/jp/eita/canvasgl/textureFilter/BasicTextureFilter.kt
new file mode 100644
index 0000000..9ab08e0
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/textureFilter/BasicTextureFilter.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.textureFilter
+
+import jp.eita.canvasgl.ICanvasGL
+import jp.eita.canvasgl.glcanvas.BasicTexture
+import jp.eita.canvasgl.glcanvas.GLES20Canvas
+
+open class BasicTextureFilter : TextureFilter {
+
+ override val vertexShader: String
+ get() = TEXTURE_VERTEX_SHADER
+
+ override val fragmentShader: String
+ get() = TEXTURE_FRAGMENT_SHADER
+
+ override val oesFragmentProgram: String
+ get() {
+ return """
+ #extension GL_OES_EGL_image_external : require
+ ${fragmentShader.replace(SAMPLER_2D, SAMPLER_EXTERNAL_OES)}
+ """.trimIndent()
+ }
+
+ override fun onPreDraw(program: Int, texture: BasicTexture, canvas: ICanvasGL) {}
+
+ override fun destroy() {}
+
+ companion object {
+
+ const val MATRIX_UNIFORM = GLES20Canvas.MATRIX_UNIFORM
+
+ const val TEXTURE_MATRIX_UNIFORM = GLES20Canvas.TEXTURE_MATRIX_UNIFORM
+
+ const val POSITION_ATTRIBUTE = GLES20Canvas.POSITION_ATTRIBUTE
+
+ const val VARYING_TEXTURE_COORD = "vTextureCoord"
+
+ const val TEXTURE_VERTEX_SHADER = (""
+ + "uniform mat4 " + MATRIX_UNIFORM + ";\n"
+ + "uniform mat4 " + TEXTURE_MATRIX_UNIFORM + ";\n"
+ + "attribute vec2 " + POSITION_ATTRIBUTE + ";\n"
+ + "varying vec2 " + VARYING_TEXTURE_COORD + ";\n"
+ + "void main() {\n"
+ + " vec4 pos = vec4(" + POSITION_ATTRIBUTE + ", 0.0, 1.0);\n"
+ + " gl_Position = " + MATRIX_UNIFORM + " * pos;\n"
+ + " " + VARYING_TEXTURE_COORD + " = (" + TEXTURE_MATRIX_UNIFORM + " * pos).xy;\n"
+ + "}\n")
+
+ const val ALPHA_UNIFORM = GLES20Canvas.ALPHA_UNIFORM
+
+ const val TEXTURE_SAMPLER_UNIFORM = GLES20Canvas.TEXTURE_SAMPLER_UNIFORM
+
+ const val SAMPLER_2D = "sampler2D"
+
+ const val TEXTURE_FRAGMENT_SHADER = (""
+ + "precision mediump float;\n"
+ + "varying vec2 " + VARYING_TEXTURE_COORD + ";\n"
+ + "uniform float " + ALPHA_UNIFORM + ";\n"
+ + "uniform " + SAMPLER_2D + " " + TEXTURE_SAMPLER_UNIFORM + ";\n"
+ + "void main() {\n"
+ + " gl_FragColor = texture2D(" + TEXTURE_SAMPLER_UNIFORM + ", " + VARYING_TEXTURE_COORD + ");\n"
+ + " gl_FragColor *= " + ALPHA_UNIFORM + ";\n"
+ + "}\n")
+
+ const val SAMPLER_EXTERNAL_OES = "samplerExternalOES"
+ }
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/textureFilter/ContrastFilter.kt b/opengl/src/main/java/jp/eita/canvasgl/textureFilter/ContrastFilter.kt
new file mode 100644
index 0000000..2c5e2c9
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/textureFilter/ContrastFilter.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.textureFilter
+
+import android.opengl.GLES20
+import androidx.annotation.FloatRange
+import jp.eita.canvasgl.ICanvasGL
+import jp.eita.canvasgl.OpenGLUtil.setFloat
+import jp.eita.canvasgl.glcanvas.BasicTexture
+
+/**
+ * Changes the contrast of the image.
+ *
+ * contrast value ranges from 0.0 to 4.0, with 1.0 as the normal level
+ */
+class ContrastFilter(@param:FloatRange(from = 0.0, to = 4.0) private var contrast: Float) : BasicTextureFilter(), OneValueFilter {
+
+ override val fragmentShader: String
+ get() = CONTRAST_FRAGMENT_SHADER
+
+ override fun onPreDraw(program: Int, texture: BasicTexture, canvas: ICanvasGL) {
+ super.onPreDraw(program, texture, canvas)
+ val contrastLocation = GLES20.glGetUniformLocation(program, UNIFORM_CONTRAST)
+ setFloat(contrastLocation, contrast)
+ }
+
+ override fun setValue(@FloatRange(from = 0.0, to = 4.0) contrast: Float) {
+ this.contrast = contrast
+ }
+
+ companion object {
+
+ const val UNIFORM_CONTRAST = "contrast"
+
+ const val CONTRAST_FRAGMENT_SHADER = (""
+ + "precision mediump float;\n"
+ + "varying vec2 " + VARYING_TEXTURE_COORD + ";\n"
+ + "uniform float " + ALPHA_UNIFORM + ";\n"
+ + "uniform float " + UNIFORM_CONTRAST + ";\n"
+ + "uniform sampler2D " + TEXTURE_SAMPLER_UNIFORM + ";\n"
+ + "void main() {\n"
+ + " vec4 textureColor = texture2D(" + TEXTURE_SAMPLER_UNIFORM + ", " + VARYING_TEXTURE_COORD + ");\n"
+ + " gl_FragColor = vec4(((textureColor.rgb - vec3(0.5)) * " + UNIFORM_CONTRAST + "+ vec3(0.5)), textureColor.w);\n"
+ + " gl_FragColor *= " + ALPHA_UNIFORM + ";\n"
+ + "}\n")
+ }
+
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/textureFilter/CropFilter.kt b/opengl/src/main/java/jp/eita/canvasgl/textureFilter/CropFilter.kt
new file mode 100644
index 0000000..a047535
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/textureFilter/CropFilter.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.textureFilter
+
+import android.opengl.GLES20
+import jp.eita.canvasgl.ICanvasGL
+import jp.eita.canvasgl.OpenGLUtil.setFloat
+import jp.eita.canvasgl.glcanvas.BasicTexture
+
+class CropFilter(var left: Float, var top: Float, var right: Float, var bottom: Float) : BasicTextureFilter() {
+
+ override val fragmentShader: String
+ get() = CROP_FRAGMENT_SHADER
+
+ override fun onPreDraw(program: Int, texture: BasicTexture, canvas: ICanvasGL) {
+ super.onPreDraw(program, texture, canvas)
+ setFloat(GLES20.glGetUniformLocation(program, UNIFORM_LEFT), left)
+ setFloat(GLES20.glGetUniformLocation(program, UNIFORM_TOP), top)
+ setFloat(GLES20.glGetUniformLocation(program, UNIFORM_RIGHT), right)
+ setFloat(GLES20.glGetUniformLocation(program, UNIFORM_BOTTOM), bottom)
+ }
+
+ companion object {
+
+ private const val UNIFORM_LEFT = "left"
+
+ private const val UNIFORM_TOP = "top"
+
+ private const val UNIFORM_RIGHT = "right"
+
+ private const val UNIFORM_BOTTOM = "bottom"
+
+ private const val CROP_FRAGMENT_SHADER = (""
+ + "precision mediump float;\n"
+ + "varying vec2 " + VARYING_TEXTURE_COORD + ";\n"
+ + "uniform float " + ALPHA_UNIFORM + ";\n"
+ + "uniform " + SAMPLER_2D + " " + TEXTURE_SAMPLER_UNIFORM + ";\n"
+ + " uniform highp float " + UNIFORM_LEFT + ";\n"
+ + " uniform highp float " + UNIFORM_TOP + ";\n"
+ + " uniform highp float " + UNIFORM_RIGHT + ";\n"
+ + " uniform highp float " + UNIFORM_BOTTOM + ";\n"
+ + "void main() {\n"
+ + "if( " + VARYING_TEXTURE_COORD + ".x > " + UNIFORM_LEFT + " && " + VARYING_TEXTURE_COORD + ".x < " + UNIFORM_RIGHT +
+ " && " + VARYING_TEXTURE_COORD + ".y > " + UNIFORM_TOP + " && " + VARYING_TEXTURE_COORD + ".y < " + UNIFORM_BOTTOM + ") {"
+ + " gl_FragColor = texture2D(" + TEXTURE_SAMPLER_UNIFORM + ", " + VARYING_TEXTURE_COORD + ");\n"
+ + "} else {"
+ + " gl_FragColor = " + "vec4(0, 0, 0, 0)" + ";\n"
+ + "}"
+ + "}\n")
+
+ fun calc(wantCoord: Int, size: Int): Float {
+ return wantCoord.toFloat() / size
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/textureFilter/DarkenBlendFilter.kt b/opengl/src/main/java/jp/eita/canvasgl/textureFilter/DarkenBlendFilter.kt
new file mode 100644
index 0000000..9ce68b9
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/textureFilter/DarkenBlendFilter.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.textureFilter
+
+import android.graphics.Bitmap
+
+class DarkenBlendFilter : TwoTextureFilter {
+ override val fragmentShader: String
+ get() = DARKEN_BLEND_FRAGMENT_SHADER
+
+ constructor(bitmap: Bitmap) : super(bitmap)
+
+ constructor() : super()
+
+ companion object {
+ const val DARKEN_BLEND_FRAGMENT_SHADER = "precision mediump float; \n" +
+ "varying vec2 " + VARYING_TEXTURE_COORD + ";\n" +
+ " varying vec2 " + VARYING_TEXTURE_COORD2 + ";\n" +
+ "\n" +
+ " uniform sampler2D " + TEXTURE_SAMPLER_UNIFORM + ";\n" +
+ " uniform sampler2D " + UNIFORM_TEXTURE_SAMPLER2 + ";\n" +
+ " \n" +
+ " void main() {\n" +
+ " " +
+ " lowp vec4 base = texture2D(" + TEXTURE_SAMPLER_UNIFORM + ", " + VARYING_TEXTURE_COORD + ");\n" +
+ " lowp vec4 overlayer = texture2D(" + UNIFORM_TEXTURE_SAMPLER2 + ", " + VARYING_TEXTURE_COORD2 + ");\n" +
+ " \n" +
+ " gl_FragColor = vec4(min(overlayer.rgb * base.a, base.rgb * overlayer.a) + overlayer.rgb * (1.0 - base.a) + base.rgb * (1.0 - overlayer.a), 1.0);\n" +
+ " }"
+ }
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/textureFilter/FilterGroup.kt b/opengl/src/main/java/jp/eita/canvasgl/textureFilter/FilterGroup.kt
new file mode 100644
index 0000000..bd19860
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/textureFilter/FilterGroup.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.textureFilter
+
+import jp.eita.canvasgl.glcanvas.BasicTexture
+import jp.eita.canvasgl.glcanvas.GLCanvas
+import jp.eita.canvasgl.glcanvas.RawTexture
+import jp.eita.canvasgl.util.Loggers
+import java.util.*
+
+open class FilterGroup(private var filters: List?) : BasicTextureFilter() {
+
+ private val rawTextureList: MutableList = ArrayList()
+
+ protected var mergedFilters: MutableList? = null
+
+ private var outputTexture: BasicTexture? = null
+
+ private var initialTexture: BasicTexture? = null
+
+ init {
+ updateMergedFilters()
+ }
+
+ private fun createTextures(initialTexture: BasicTexture) {
+ recycleTextures()
+ for (i in mergedFilters!!.indices) {
+ rawTextureList.add(RawTexture(initialTexture.width, initialTexture.height, false))
+ }
+ }
+
+ private fun recycleTextures() {
+ for (rawTexture in rawTextureList) {
+ rawTexture.recycle()
+ }
+ rawTextureList.clear()
+ }
+
+ fun draw(initialTexture: BasicTexture, glCanvas: GLCanvas, onDrawListener: OnDrawListener): BasicTexture? {
+ if (initialTexture is RawTexture) {
+ if (!initialTexture.isNeedInvalidate) {
+ return outputTexture
+ }
+ } else if (this.initialTexture === initialTexture && outputTexture != null) {
+ return outputTexture
+ }
+ if (rawTextureList.size != mergedFilters!!.size || this.initialTexture !== initialTexture) {
+ createTextures(initialTexture)
+ }
+ this.initialTexture = initialTexture
+ var drawTexture: BasicTexture? = initialTexture
+ var i = 0
+ val size = rawTextureList.size
+ while (i < size) {
+ val rawTexture = rawTextureList[i]
+ val textureFilter = mergedFilters!![i]
+ glCanvas.beginRenderTarget(rawTexture)
+ onDrawListener.onDraw(drawTexture, textureFilter, i == 0)
+ glCanvas.endRenderTarget()
+ drawTexture = rawTexture
+ i++
+ }
+ outputTexture = drawTexture
+ return drawTexture
+ }
+
+ override fun destroy() {
+ super.destroy()
+ Loggers.d(TAG, "destroy")
+ recycleTextures()
+ }
+
+ fun updateMergedFilters() {
+ if (filters == null) {
+ return
+ }
+ if (mergedFilters == null) {
+ mergedFilters = ArrayList()
+ } else {
+ mergedFilters!!.clear()
+ }
+ var filters: List?
+ for (filter in this.filters!!) {
+ if (filter is FilterGroup) {
+ filter.updateMergedFilters()
+ filters = filter.mergedFilters
+ if (filters == null || filters.isEmpty()) continue
+ mergedFilters!!.addAll(filters)
+ continue
+ }
+ mergedFilters!!.add(filter)
+ }
+ }
+
+ interface OnDrawListener {
+
+ fun onDraw(drawTexture: BasicTexture?, textureFilter: TextureFilter?, isFirst: Boolean)
+ }
+
+ companion object {
+ private const val TAG = "FilterGroup"
+ }
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/textureFilter/HueFilter.kt b/opengl/src/main/java/jp/eita/canvasgl/textureFilter/HueFilter.kt
new file mode 100644
index 0000000..ad8e223
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/textureFilter/HueFilter.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.textureFilter
+
+import android.opengl.GLES20
+import androidx.annotation.FloatRange
+import jp.eita.canvasgl.ICanvasGL
+import jp.eita.canvasgl.OpenGLUtil.setFloat
+import jp.eita.canvasgl.glcanvas.BasicTexture
+
+class HueFilter(@FloatRange(from = 0.0, to = 360.0) hue: Float) : BasicTextureFilter(), OneValueFilter {
+
+ private var mHueLocation = 0
+
+ private var hueAdjust: Float
+
+ override val fragmentShader: String
+ get() = HUE_FRAGMENT_SHADER
+
+ init {
+ hueAdjust = hue % 360.0f * Math.PI.toFloat() / 180.0f
+ }
+
+ override fun onPreDraw(program: Int, texture: BasicTexture, canvas: ICanvasGL) {
+ super.onPreDraw(program, texture, canvas)
+ mHueLocation = GLES20.glGetUniformLocation(program, UNIFORM_HUE)
+ setFloat(mHueLocation, hueAdjust)
+ }
+
+ override fun setValue(@FloatRange(from = 0.0, to = 360.0) hue: Float) {
+ hueAdjust = hue % 360.0f * Math.PI.toFloat() / 180.0f
+ }
+
+ companion object {
+
+ const val UNIFORM_HUE = "hueAdjust"
+
+ const val HUE_FRAGMENT_SHADER = "" +
+ "precision highp float;\n" +
+ "varying vec2 " + VARYING_TEXTURE_COORD + ";\n" +
+ " uniform float " + ALPHA_UNIFORM + ";\n" +
+ "\n" +
+ "uniform sampler2D " + TEXTURE_SAMPLER_UNIFORM + ";\n" +
+ "uniform mediump float " + UNIFORM_HUE + ";\n" +
+ "const highp vec4 kRGBToYPrime = vec4 (0.299, 0.587, 0.114, 0.0);\n" +
+ "const highp vec4 kRGBToI = vec4 (0.595716, -0.274453, -0.321263, 0.0);\n" +
+ "const highp vec4 kRGBToQ = vec4 (0.211456, -0.522591, 0.31135, 0.0);\n" +
+ "\n" +
+ "const highp vec4 kYIQToR = vec4 (1.0, 0.9563, 0.6210, 0.0);\n" +
+ "const highp vec4 kYIQToG = vec4 (1.0, -0.2721, -0.6474, 0.0);\n" +
+ "const highp vec4 kYIQToB = vec4 (1.0, -1.1070, 1.7046, 0.0);\n" +
+ "\n" +
+ "void main (){\n" +
+ "" +
+ " // Sample the input pixel\n" +
+ " highp vec4 color = texture2D(" + TEXTURE_SAMPLER_UNIFORM + ", " + VARYING_TEXTURE_COORD + ");\n" +
+ "\n" +
+ " // Convert to YIQ\n" +
+ " highp float YPrime = dot (color, kRGBToYPrime);\n" +
+ " highp float I = dot (color, kRGBToI);\n" +
+ " highp float Q = dot (color, kRGBToQ);\n" +
+ "\n" +
+ " // Calculate the hue and chroma\n" +
+ " highp float hue = atan (Q, I);\n" +
+ " highp float chroma = sqrt (I * I + Q * Q);\n" +
+ "\n" +
+ " // Make the user's adjustments\n" +
+ " hue += (-" + UNIFORM_HUE + "); //why negative rotation?\n" +
+ "\n" +
+ " // Convert back to YIQ\n" +
+ " Q = chroma * sin (hue);\n" +
+ " I = chroma * cos (hue);\n" +
+ "\n" +
+ " // Convert back to RGB\n" +
+ " highp vec4 yIQ = vec4 (YPrime, I, Q, 0.0);\n" +
+ " color.r = dot (yIQ, kYIQToR);\n" +
+ " color.g = dot (yIQ, kYIQToG);\n" +
+ " color.b = dot (yIQ, kYIQToB);\n" +
+ "\n" +
+ " // Save the result\n" +
+ " gl_FragColor = color;\n" +
+ " gl_FragColor *= " + ALPHA_UNIFORM + ";\n" +
+ "}\n"
+ }
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/textureFilter/OneValueFilter.kt b/opengl/src/main/java/jp/eita/canvasgl/textureFilter/OneValueFilter.kt
new file mode 100644
index 0000000..9b153f6
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/textureFilter/OneValueFilter.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.textureFilter
+
+interface OneValueFilter {
+
+ fun setValue(value: Float)
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/textureFilter/PixelationFilter.kt b/opengl/src/main/java/jp/eita/canvasgl/textureFilter/PixelationFilter.kt
new file mode 100644
index 0000000..5dfd1ef
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/textureFilter/PixelationFilter.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.textureFilter
+
+import android.opengl.GLES20
+import androidx.annotation.FloatRange
+import jp.eita.canvasgl.ICanvasGL
+import jp.eita.canvasgl.OpenGLUtil.setFloat
+import jp.eita.canvasgl.glcanvas.BasicTexture
+
+class PixelationFilter(@param:FloatRange(from = 1.0, to = 100.0) private var mPixel: Float) : BasicTextureFilter(), OneValueFilter {
+
+ private var mImageWidthFactorLocation = 0
+
+ private var mImageHeightFactorLocation = 0
+
+ private var mPixelLocation = 0
+
+ override val fragmentShader: String
+ get() = PIXELATION_FRAGMENT_SHADER
+
+ override fun onPreDraw(program: Int, texture: BasicTexture, canvas: ICanvasGL) {
+ super.onPreDraw(program, texture, canvas)
+ mImageWidthFactorLocation = GLES20.glGetUniformLocation(program, UNIFORM_IMAGE_WIDTH_FACTOR)
+ mImageHeightFactorLocation = GLES20.glGetUniformLocation(program, UNIFORM_IMAGE_HEIGHT_FACTOR)
+ mPixelLocation = GLES20.glGetUniformLocation(program, UNIFORM_PIXEL)
+ setFloat(mImageWidthFactorLocation, 1.0f / texture.width)
+ setFloat(mImageHeightFactorLocation, 1.0f / texture.height)
+ setFloat(mPixelLocation, mPixel)
+ }
+
+ override fun setValue(@FloatRange(from = 1.0, to = 100.0) value: Float) {
+ mPixel = value
+ }
+
+ companion object {
+
+ const val UNIFORM_IMAGE_WIDTH_FACTOR = "imageWidthFactor"
+
+ const val UNIFORM_IMAGE_HEIGHT_FACTOR = "imageHeightFactor"
+
+ const val UNIFORM_PIXEL = "pixel"
+
+ const val PIXELATION_FRAGMENT_SHADER = "" +
+ "precision highp float;\n" +
+ " varying vec2 " + VARYING_TEXTURE_COORD + ";\n" +
+ "uniform float " + UNIFORM_IMAGE_WIDTH_FACTOR + ";\n" +
+ "uniform float " + UNIFORM_IMAGE_HEIGHT_FACTOR + ";\n" +
+ " uniform float " + ALPHA_UNIFORM + ";\n" +
+ "uniform sampler2D " + TEXTURE_SAMPLER_UNIFORM + ";\n" +
+ "uniform float " + UNIFORM_PIXEL + ";\n" +
+ "void main() {\n" +
+ "" +
+ " vec2 uv = " + VARYING_TEXTURE_COORD + ".xy;\n" +
+ " float dx = " + UNIFORM_PIXEL + " * " + UNIFORM_IMAGE_WIDTH_FACTOR + ";\n" +
+ " float dy = " + UNIFORM_PIXEL + " * " + UNIFORM_IMAGE_HEIGHT_FACTOR + ";\n" +
+ " vec2 coord = vec2(dx * floor(uv.x / dx), dy * floor(uv.y / dy));\n" +
+ " vec4 tc = texture2D(" + TEXTURE_SAMPLER_UNIFORM + ", coord);\n" +
+ " gl_FragColor = vec4(tc);\n" +
+ " gl_FragColor *= " + ALPHA_UNIFORM + ";\n" +
+ "}"
+ }
+
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/textureFilter/SaturationFilter.kt b/opengl/src/main/java/jp/eita/canvasgl/textureFilter/SaturationFilter.kt
new file mode 100644
index 0000000..58ee44a
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/textureFilter/SaturationFilter.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.textureFilter
+
+import android.opengl.GLES20
+import androidx.annotation.FloatRange
+import jp.eita.canvasgl.ICanvasGL
+import jp.eita.canvasgl.OpenGLUtil.setFloat
+import jp.eita.canvasgl.glcanvas.BasicTexture
+
+/**
+ * saturation: The degree of saturation or desaturation to apply to the image (0.0 - 2.0, with 1.0 as the default)
+ */
+class SaturationFilter(@param:FloatRange(from = 0.0, to = 2.0) private var mSaturation: Float) : BasicTextureFilter(), OneValueFilter {
+
+ private var mSaturationLocation = 0
+
+ override val fragmentShader: String
+ get() = SATURATION_FRAGMENT_SHADER
+
+ override fun onPreDraw(program: Int, texture: BasicTexture, canvas: ICanvasGL) {
+ super.onPreDraw(program, texture, canvas)
+ mSaturationLocation = GLES20.glGetUniformLocation(program, UNIFORM_SATURATION)
+ setFloat(mSaturationLocation, mSaturation)
+ }
+
+ override fun setValue(@FloatRange(from = 0.0, to = 2.0) value: Float) {
+ mSaturation = value
+ }
+
+ companion object {
+
+ const val UNIFORM_SATURATION = "saturation"
+
+ const val SATURATION_FRAGMENT_SHADER = "" +
+ "precision mediump float;\n" +
+ " varying vec2 " + VARYING_TEXTURE_COORD + ";\n" +
+ " \n" +
+ " uniform sampler2D " + TEXTURE_SAMPLER_UNIFORM + ";\n" +
+ " uniform float " + ALPHA_UNIFORM + ";\n" +
+ " uniform float " + UNIFORM_SATURATION + ";\n" +
+ " \n" +
+ " // Values from \"Graphics Shaders: Theory and Practice\" by Bailey and Cunningham\n" +
+ " const vec3 luminanceWeighting = vec3(0.2125, 0.7154, 0.0721);\n" +
+ " \n" +
+ " void main() {\n" +
+ " " +
+ " vec4 textureColor = texture2D(" + TEXTURE_SAMPLER_UNIFORM + ", " + VARYING_TEXTURE_COORD + ");\n" +
+ " float luminance = dot(textureColor.rgb, luminanceWeighting);\n" +
+ " vec3 greyScaleColor = vec3(luminance);\n" +
+ " \n" +
+ " gl_FragColor = vec4(mix(greyScaleColor, textureColor.rgb, " + UNIFORM_SATURATION + "), textureColor.w);\n" +
+ " gl_FragColor *= " + ALPHA_UNIFORM + ";\n" +
+ " }"
+ }
+
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/textureFilter/TextureFilter.kt b/opengl/src/main/java/jp/eita/canvasgl/textureFilter/TextureFilter.kt
new file mode 100644
index 0000000..fa6b6fb
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/textureFilter/TextureFilter.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.textureFilter
+
+import jp.eita.canvasgl.ICanvasGL
+import jp.eita.canvasgl.glcanvas.BasicTexture
+
+interface TextureFilter {
+
+ val vertexShader: String?
+
+ val fragmentShader: String?
+
+ val oesFragmentProgram: String?
+
+ fun onPreDraw(program: Int, texture: BasicTexture, canvas: ICanvasGL)
+
+ fun destroy()
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/textureFilter/TwoTextureFilter.kt b/opengl/src/main/java/jp/eita/canvasgl/textureFilter/TwoTextureFilter.kt
new file mode 100644
index 0000000..d868f9b
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/textureFilter/TwoTextureFilter.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.textureFilter
+
+import android.graphics.Bitmap
+import android.graphics.RectF
+import android.opengl.GLES20
+import android.opengl.Matrix
+import jp.eita.canvasgl.ICanvasGL
+import jp.eita.canvasgl.glcanvas.BasicTexture
+import jp.eita.canvasgl.glcanvas.GLES20Canvas.Companion.checkError
+import jp.eita.canvasgl.glcanvas.GLES20Canvas.Companion.printMatrix
+import jp.eita.canvasgl.glcanvas.TextureMatrixTransformer.convertCoordinate
+import jp.eita.canvasgl.glcanvas.TextureMatrixTransformer.copyTextureCoordinates
+import jp.eita.canvasgl.glcanvas.TextureMatrixTransformer.setTextureMatrix
+import jp.eita.canvasgl.glview.texture.GLTexture
+import java.util.*
+
+/**
+ * A filter that supports mix two textures.
+ * This accept the second texture as a RawTexture or a Bitmap
+ */
+abstract class TwoTextureFilter : BasicTextureFilter {
+
+ protected val mTempTextureMatrix = FloatArray(16)
+
+ protected var secondBitmap: Bitmap? = null
+
+ /**
+ * Set the second texture. If secondBitmap exists, this will be ignored.
+ *
+ * @param secondRawTexture The second texture. If secondBitmap exists, this will be ignored
+ */
+ protected var secondRawTexture: GLTexture? = null
+
+ private val mTempSrcRectF = RectF()
+
+ override val vertexShader: String
+ get() = VERTEX_SHADER
+
+ /**
+ * Set the second bitmap. If this exists, the secondRawTexture will be ignored
+ *
+ * @param secondBitmap The second bitmap that will be mixed
+ */
+ constructor(secondBitmap: Bitmap) {
+ this.secondBitmap = secondBitmap
+ }
+
+ constructor()
+
+ fun setBitmap(secondBitmap: Bitmap) {
+ this.secondBitmap = secondBitmap
+ }
+
+ private fun resetMatrix() {
+ Arrays.fill(mTempTextureMatrix, 0f)
+ }
+
+ override fun onPreDraw(program: Int, texture: BasicTexture, canvas: ICanvasGL) {
+ super.onPreDraw(program, texture, canvas)
+ if (useSecondBitmap()) {
+ handleSecondBitmapTexture(program, canvas)
+ return
+ }
+ if (secondRawTexture != null) {
+ handleSecondRawTexture(program, canvas)
+ }
+ }
+
+ private fun handleSecondBitmapTexture(program: Int, canvas: ICanvasGL) {
+ val secondTexture: BasicTexture? = canvas.bindBitmapToTexture(GLES20.GL_TEXTURE3, secondBitmap!!)
+ resetMatrix()
+ Matrix.setIdentityM(mTempTextureMatrix, 0)
+ copyTextureCoordinates(secondTexture!!, mTempSrcRectF)
+ convertCoordinate(mTempSrcRectF, secondTexture)
+ setTextureMatrix(mTempSrcRectF, mTempTextureMatrix)
+ printMatrix("two tex matrix", mTempTextureMatrix, 0)
+ val textureMatrixPosition = GLES20.glGetUniformLocation(program, TEXTURE_MATRIX_UNIFORM2)
+ GLES20.glUniformMatrix4fv(textureMatrixPosition, 1, false, mTempTextureMatrix, 0)
+ val sampler2 = GLES20.glGetUniformLocation(program, UNIFORM_TEXTURE_SAMPLER2)
+ checkError()
+ GLES20.glUniform1i(sampler2, 3) // match GL_TEXTURE3
+ checkError()
+ }
+
+ private fun handleSecondRawTexture(program: Int, canvas: ICanvasGL) {
+ val rawTexture = secondRawTexture!!.rawTexture
+ canvas.bindRawTexture(GLES20.GL_TEXTURE3, rawTexture)
+ resetMatrix()
+ secondRawTexture!!.surfaceTexture.getTransformMatrix(mTempTextureMatrix)
+ val textureMatrixPosition = GLES20.glGetUniformLocation(program, TEXTURE_MATRIX_UNIFORM2)
+ GLES20.glUniformMatrix4fv(textureMatrixPosition, 1, false, mTempTextureMatrix, 0)
+ val sampler2 = GLES20.glGetUniformLocation(program, UNIFORM_TEXTURE_SAMPLER2)
+ checkError()
+ GLES20.glUniform1i(sampler2, 3)
+ checkError()
+ }
+
+ override val oesFragmentProgram: String
+ get() {
+ return if (useSecondBitmap()) {
+ """
+ #extension GL_OES_EGL_image_external : require
+ ${fragmentShader.replaceFirst(SAMPLER_2D.toRegex(), SAMPLER_EXTERNAL_OES)}
+ """.trimIndent()
+ } else """
+ #extension GL_OES_EGL_image_external : require
+ ${fragmentShader.replace(SAMPLER_2D.toRegex(), SAMPLER_EXTERNAL_OES)}
+ """.trimIndent()
+ }
+
+ private fun useSecondBitmap(): Boolean {
+ return secondBitmap != null
+ }
+
+ companion object {
+
+ const val VARYING_TEXTURE_COORD2 = "vTextureCoord2"
+
+ const val UNIFORM_TEXTURE_SAMPLER2 = "uTextureSampler2"
+
+ private const val TEXTURE_MATRIX_UNIFORM2 = "uTextureMatrix2"
+
+ private const val VERTEX_SHADER = " \n" +
+ "attribute vec2 " + POSITION_ATTRIBUTE + ";\n" +
+ "varying vec2 " + VARYING_TEXTURE_COORD + ";\n" +
+ "varying vec2 " + VARYING_TEXTURE_COORD2 + ";\n" +
+ "uniform mat4 " + MATRIX_UNIFORM + ";\n" +
+ "uniform mat4 " + TEXTURE_MATRIX_UNIFORM + ";\n" +
+ "uniform mat4 " + TEXTURE_MATRIX_UNIFORM2 + ";\n" +
+ " \n" +
+ "void main() {\n" +
+ " vec4 pos = vec4(" + POSITION_ATTRIBUTE + ", 0.0, 1.0);\n" +
+ " gl_Position = " + MATRIX_UNIFORM + " * pos;\n" +
+ " " + VARYING_TEXTURE_COORD + " = (" + TEXTURE_MATRIX_UNIFORM + " * pos).xy;\n" +
+ " " + VARYING_TEXTURE_COORD2 + " = (" + TEXTURE_MATRIX_UNIFORM2 + " * pos).xy;\n" +
+ "}"
+ }
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/util/FileLogger.kt b/opengl/src/main/java/jp/eita/canvasgl/util/FileLogger.kt
new file mode 100644
index 0000000..99abcb6
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/util/FileLogger.kt
@@ -0,0 +1,427 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.util
+
+import android.os.Process
+import android.util.Log
+import jp.eita.canvasgl.util.FileUtil.createFile
+import jp.eita.canvasgl.util.FileUtil.delete
+import jp.eita.canvasgl.util.FileUtil.writeToFile
+import java.io.File
+import java.io.FileFilter
+import java.security.InvalidParameterException
+import java.text.SimpleDateFormat
+import java.util.*
+import java.util.concurrent.Executors
+
+object FileLogger {
+
+ private val LOG_DATE_TIME_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
+
+ private val logExecutor = Executors.newSingleThreadExecutor()
+
+ var isLogEnable = false
+
+ var logLevel = LogLevel.VERBOSE
+
+ private var logFileManager: LogFileManager? = null
+
+ private val limitLogMap: MutableMap = HashMap()
+
+ fun init(dirPath: String) {
+ isLogEnable = true
+ val file = File(dirPath)
+ if (!file.exists() || !file.isDirectory) {
+ throw InvalidParameterException()
+ }
+ logFileManager = LogFileManager(dirPath)
+ }
+
+ /**
+ * @param id the id for this log. Must be unique
+ * @param cntTimesAfterLogOnce example: 1000 log once, then after 1000 call of this will log again
+ */
+ fun limitLog(id: String, tag: String, message: String?, cntTimesAfterLogOnce: Int) {
+ if (!limitLogMap.containsKey(id)) {
+ limitLogMap[id] = 0
+ } else {
+ val currentCnt = limitLogMap[id]
+ if (currentCnt!! < cntTimesAfterLogOnce) {
+ limitLogMap[id] = currentCnt + 1
+ return
+ } else {
+ limitLogMap[id] = 0
+ }
+ }
+ d(tag, message)
+ }
+
+ /**
+ * log for debug
+ *
+ * @param message log message
+ * @param tag tag
+ * @see Log.d
+ */
+ fun d(tag: String, message: String) {
+ if (isLogEnable) {
+ Log.d(tag, message)
+ writeToFileIfNeeded(tag, message, LogLevel.DEBUG)
+ }
+ }
+
+ /**
+ * log for debug
+ *
+ * @param message log message
+ * @param throwable throwable
+ * @param tag tag
+ * @see Log.d
+ */
+ fun d(tag: String, message: String, throwable: Throwable?) {
+ if (isLogEnable) {
+ Log.d(tag, message, throwable)
+ writeToFileIfNeeded(tag, """
+ $message
+ ${Log.getStackTraceString(throwable)}
+ """.trimIndent(), LogLevel.DEBUG)
+ }
+ }
+
+ /**
+ * log for debug
+ *
+ * @param tag tag
+ * @param format message format, such as "%d ..."
+ * @param params message content params
+ * @see Log.d
+ */
+ fun d(tag: String, format: String?, vararg params: Any?) {
+ if (isLogEnable) {
+ val msg = String.format(format!!, *params)
+ Log.d(tag, msg)
+ writeToFileIfNeeded(tag, msg, LogLevel.DEBUG)
+ }
+ }
+
+ /**
+ * log for warning
+ *
+ * @param message log message
+ * @param tag tag
+ * @see Log.w
+ */
+ fun w(tag: String, message: String) {
+ if (isLogEnable) {
+ Log.w(tag, message)
+ writeToFileIfNeeded(tag, message, LogLevel.WARN)
+ }
+ }
+
+ /**
+ * log for warning
+ *
+ * @param tag tag
+ * @param throwable throwable
+ * @see Log.w
+ */
+ fun w(tag: String, throwable: Throwable?) {
+ if (isLogEnable) {
+ Log.w(tag, throwable)
+ writeToFileIfNeeded(tag, Log.getStackTraceString(throwable), LogLevel.WARN)
+ }
+ }
+
+ /**
+ * log for warning
+ *
+ * @param message log message
+ * @param throwable throwable
+ * @param tag tag
+ * @see Log.w
+ */
+ fun w(tag: String, message: String, throwable: Throwable?) {
+ if (isLogEnable) {
+ Log.w(tag, message, throwable)
+ writeToFileIfNeeded(tag, """
+ $message
+ ${Log.getStackTraceString(throwable)}
+ """.trimIndent(), LogLevel.WARN)
+ }
+ }
+
+ /**
+ * log for warning
+ *
+ * @param tag tag
+ * @param format message format, such as "%d ..."
+ * @param params message content params
+ * @see Log.w
+ */
+ fun w(tag: String, format: String?, vararg params: Any?) {
+ if (isLogEnable) {
+ val msg = String.format(format!!, *params)
+ Log.w(tag, msg)
+ writeToFileIfNeeded(tag, msg, LogLevel.WARN)
+ }
+ }
+
+ /**
+ * log for error
+ *
+ * @param message message
+ * @param tag tag
+ * @see Log.i
+ */
+ fun e(tag: String, message: String) {
+ if (isLogEnable) {
+ Log.e(tag, message)
+ writeToFileIfNeeded(tag, message, LogLevel.ERROR)
+ }
+ }
+
+ /**
+ * log for error
+ *
+ * @param message log message
+ * @param throwable throwable
+ * @param tag tag
+ * @see Log.i
+ */
+ fun e(tag: String, message: String, throwable: Throwable?) {
+ if (isLogEnable) {
+ Log.e(tag, message, throwable)
+ writeToFileIfNeeded(tag, """
+ $message
+ ${Log.getStackTraceString(throwable)}
+ """.trimIndent(), LogLevel.ERROR)
+ }
+ }
+
+ /**
+ * log for error
+ *
+ * @param tag tag
+ * @param format message format, such as "%d ..."
+ * @param params message content params
+ * @see Log.e
+ */
+ fun e(tag: String, format: String?, vararg params: Any?) {
+ if (isLogEnable) {
+ val msg = String.format(format!!, *params)
+ Log.e(tag, msg)
+ writeToFileIfNeeded(tag, msg, LogLevel.ERROR)
+ }
+ }
+
+ /**
+ * log for information
+ *
+ * @param message message
+ * @param tag tag
+ * @see Log.i
+ */
+ fun i(tag: String, message: String) {
+ if (isLogEnable) {
+ Log.i(tag, message)
+ writeToFileIfNeeded(tag, message, LogLevel.INFO)
+ }
+ }
+
+ /**
+ * log for information
+ *
+ * @param message log message
+ * @param throwable throwable
+ * @param tag tag
+ * @see Log.i
+ */
+ fun i(tag: String, message: String, throwable: Throwable?) {
+ if (isLogEnable) {
+ Log.i(tag, message, throwable)
+ writeToFileIfNeeded(tag, """
+ $message
+ ${Log.getStackTraceString(throwable)}
+ """.trimIndent(), LogLevel.INFO)
+ }
+ }
+
+ /**
+ * log for information
+ *
+ * @param tag tag
+ * @param format message format, such as "%d ..."
+ * @param params message content params
+ * @see Log.i
+ */
+ fun i(tag: String, format: String?, vararg params: Any?) {
+ if (isLogEnable) {
+ val msg = String.format(format!!, *params)
+ Log.i(tag, msg)
+ writeToFileIfNeeded(tag, msg, LogLevel.INFO)
+ }
+ }
+
+ /**
+ * log for verbos
+ *
+ * @param message log message
+ * @param tag tag
+ * @see Log.v
+ */
+ fun v(tag: String, message: String) {
+ if (isLogEnable) {
+ Log.v(tag, message)
+ writeToFileIfNeeded(tag, message, LogLevel.VERBOSE)
+ }
+ }
+
+ /**
+ * log for verbose
+ *
+ * @param message log message
+ * @param throwable throwable
+ * @param tag tag
+ * @see Log.v
+ */
+ fun v(tag: String, message: String, throwable: Throwable?) {
+ if (isLogEnable) {
+ Log.v(tag, message, throwable)
+ writeToFileIfNeeded(tag, """
+ $message
+ ${Log.getStackTraceString(throwable)}
+ """.trimIndent(), LogLevel.VERBOSE)
+ }
+ }
+
+ /**
+ * log for verbose
+ *
+ * @param tag tag
+ * @param format message format, such as "%d ..."
+ * @param params message content params
+ * @see Log.v
+ */
+ fun v(tag: String, format: String, vararg params: Any?) {
+ if (isLogEnable) {
+ val msg = String.format(format, *params)
+ Log.v(tag, msg)
+ writeToFileIfNeeded(tag, msg, LogLevel.VERBOSE)
+ }
+ }
+
+ private fun writeToFileIfNeeded(tag: String, msg: String?, logLevel: LogLevel) {
+ val strBuilder = StringBuilder()
+ val stackTrace = Throwable().stackTrace
+ val methodStackCnt = 2
+ strBuilder
+ .append(" ")
+ .append(" tid=").append(Thread.currentThread().id)
+ .append(" ")
+ .append(stackTrace[methodStackCnt].fileName)
+ .append("[").append(stackTrace[methodStackCnt].lineNumber)
+ .append("] ").append("; ")
+ .append(stackTrace[methodStackCnt].methodName)
+ .append(": ")
+ if (logLevel.value < this.logLevel.value || logFileManager == null) {
+ return
+ }
+ logExecutor.execute { appendLog(strBuilder.toString() + tag, msg) }
+ }
+
+ private fun appendLog(tag: String, msg: String?) {
+ val logMsg = formatLog(tag, msg)
+ flushLogToFile(logMsg)
+ }
+
+ private fun flushLogToFile(logMsg: String) {
+ logFileManager!!.writeLogToFile(logMsg)
+ }
+
+ private fun formatLog(tag: String, msg: String?): String {
+ return String.format(Locale.US, "%s pid=%d %s; %s\n", LOG_DATE_TIME_FORMAT.format(Date()), Process.myPid(), tag, msg)
+ }
+
+ enum class LogLevel(val value: Int) {
+ VERBOSE(Log.VERBOSE), DEBUG(Log.DEBUG), INFO(Log.INFO), WARN(Log.WARN), ERROR(Log.ERROR), ASSERT(Log.ASSERT);
+
+ }
+
+ class LogFileManager internal constructor(private val mLogFileDir: String) {
+
+ private var currentLogFile: File? = null
+
+ private val fileFilter = FileFilter { file ->
+ val tmp = file.name.toLowerCase(Locale.getDefault())
+ tmp.startsWith("log") && tmp.endsWith(".txt")
+ }
+
+ private val newLogFile: File?
+ get() {
+ val dir = File(mLogFileDir)
+ val files = dir.listFiles(fileFilter)
+ if (files == null || files.isEmpty()) {
+ return createNewLogFileIfNeed()
+ }
+ val sortedFiles = sortFiles(files)
+ if (files.size > LOG_FILES_MAX_NUM) {
+ delete(sortedFiles[0])
+ }
+
+ return createNewLogFileIfNeed()
+ }
+
+ fun writeLogToFile(logMessage: String?) {
+ if (currentLogFile == null || currentLogFile!!.length() >= LOG_FILE_MAX_SIZE) {
+ currentLogFile = newLogFile
+ }
+ writeToFile(logMessage!!, currentLogFile!!.path)
+ }
+
+ private fun createNewLogFileIfNeed(): File? {
+ return createFile(mLogFileDir + File.separator + PREFIX + LOG_FILE_DATE_FORMAT.format(Date()) + ".txt")
+ }
+
+ private fun sortFiles(files: Array): List {
+ val fileList = listOf(*files)
+ Collections.sort(fileList, FileComparator())
+
+ return fileList
+ }
+
+ private inner class FileComparator : Comparator {
+
+ override fun compare(file1: File, file2: File): Int {
+ return if (file1.lastModified() < file2.lastModified()) {
+ -1
+ } else {
+ 1
+ }
+ }
+ }
+
+ companion object {
+
+ const val PREFIX = "Log"
+
+ private const val LOG_FILES_MAX_NUM = 5
+
+ private const val LOG_FILE_MAX_SIZE = 1000 * 1000 * 20
+
+ private val LOG_FILE_DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd", Locale.US)
+ }
+ }
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/util/FileUtil.kt b/opengl/src/main/java/jp/eita/canvasgl/util/FileUtil.kt
new file mode 100644
index 0000000..19009e0
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/util/FileUtil.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.util
+
+import android.text.TextUtils
+import java.io.File
+import java.io.FileWriter
+
+object FileUtil {
+
+ @JvmStatic
+ @Synchronized
+ fun createFile(path: String): File? {
+ if (TextUtils.isEmpty(path)) {
+ return null
+ }
+ val file = File(path)
+ if (file.isFile) {
+ return file
+ }
+ val parentFile = file.parentFile
+ if (parentFile != null && (parentFile.isDirectory || parentFile.mkdirs())) {
+ try {
+ if (file.createNewFile()) {
+ return file
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+
+ return null
+ }
+
+ @JvmStatic
+ @Synchronized
+ fun delete(path: File?): Boolean {
+ if (null == path) {
+ return true
+ }
+ if (path.isDirectory) {
+ val files = path.listFiles()
+ if (null != files) {
+ for (file in files) {
+ if (!delete(file)) {
+ return false
+ }
+ }
+ }
+ }
+
+ return !path.exists() || path.delete()
+ }
+
+ @JvmStatic
+ fun writeToFile(content: String, filePath: String) {
+ var fileWriter: FileWriter? = null
+ try {
+ fileWriter = FileWriter(filePath, true)
+ fileWriter.write(content)
+ fileWriter.flush()
+ } catch (t: Throwable) {
+ t.printStackTrace()
+ } finally {
+ if (fileWriter != null) {
+ try {
+ fileWriter.close()
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/opengl/src/main/java/jp/eita/canvasgl/util/Loggers.kt b/opengl/src/main/java/jp/eita/canvasgl/util/Loggers.kt
new file mode 100644
index 0000000..cb6b249
--- /dev/null
+++ b/opengl/src/main/java/jp/eita/canvasgl/util/Loggers.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.canvasgl.util
+
+import android.util.Log
+
+object Loggers {
+ var DEBUG = true
+
+ fun d(tag: String?, msg: String) {
+ if (DEBUG) {
+ Log.d(tag, msg)
+ }
+ }
+
+ fun v(tag: String?, msg: String) {
+ if (DEBUG) {
+ Log.v(tag, msg)
+ }
+ }
+
+ fun i(tag: String?, msg: String) {
+ if (DEBUG) {
+ Log.i(tag, msg)
+ }
+ }
+
+ fun w(tag: String?, msg: String) {
+ if (DEBUG) {
+ Log.w(tag, msg)
+ }
+ }
+
+ fun e(tag: String?, msg: String) {
+ if (DEBUG) {
+ Log.e(tag, msg)
+ }
+ }
+}
\ No newline at end of file
diff --git a/openglsample/.classpath b/openglsample/.classpath
new file mode 100644
index 0000000..eb19361
--- /dev/null
+++ b/openglsample/.classpath
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/openglsample/.gitignore b/openglsample/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/openglsample/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/openglsample/.project b/openglsample/.project
new file mode 100644
index 0000000..c9567df
--- /dev/null
+++ b/openglsample/.project
@@ -0,0 +1,23 @@
+
+
+ canvasglsample
+ Project canvasglsample created by Buildship.
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.buildship.core.gradleprojectbuilder
+
+
+
+
+
+ org.eclipse.jdt.core.javanature
+ org.eclipse.buildship.core.gradleprojectnature
+
+
diff --git a/openglsample/.settings/org.eclipse.buildship.core.prefs b/openglsample/.settings/org.eclipse.buildship.core.prefs
new file mode 100644
index 0000000..b1886ad
--- /dev/null
+++ b/openglsample/.settings/org.eclipse.buildship.core.prefs
@@ -0,0 +1,2 @@
+connection.project.dir=..
+eclipse.preferences.version=1
diff --git a/openglsample/build.gradle b/openglsample/build.gradle
new file mode 100644
index 0000000..385a5d4
--- /dev/null
+++ b/openglsample/build.gradle
@@ -0,0 +1,71 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android-extensions'
+apply plugin: 'kotlin-android'
+
+android {
+ compileSdkVersion 29
+ buildToolsVersion '28.0.3'
+
+ defaultConfig {
+ applicationId "jp.eita.example"
+ minSdkVersion 21
+ targetSdkVersion 29
+ versionCode 1
+ versionName "1"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+
+ }
+
+ buildTypes {
+
+ release {
+
+ minifyEnabled true
+ }
+ }
+
+ lintOptions {
+ checkReleaseBuilds false
+ abortOnError false
+ }
+
+}
+
+repositories {
+ maven {
+ url "https://jitpack.io"
+ }
+
+ mavenCentral()
+}
+
+dependencies {
+ implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+ implementation fileTree(include: ['*.jar'], dir: 'libs')
+ androidTestImplementation('androidx.test.espresso:espresso-core:3.2.0')
+ implementation 'androidx.appcompat:appcompat:1.1.0'
+ implementation 'androidx.recyclerview:recyclerview:1.1.0'
+ implementation 'jp.co.cyberagent.android.gpuimage:gpuimage-library:1.4.1'
+ implementation 'com.github.tajchert:nammu:1.2.0'
+
+ testImplementation 'junit:junit:4.12'
+ implementation project(':opengl')
+ implementation "androidx.core:core-ktx:1.2.0"
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+}
diff --git a/openglsample/proguard-rules.pro b/openglsample/proguard-rules.pro
new file mode 100644
index 0000000..7f86d05
--- /dev/null
+++ b/openglsample/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in D:\develop-win\android\dev-tools-win\android-studio-windows\sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/openglsample/src/main/AndroidManifest.xml b/openglsample/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..a5da97d
--- /dev/null
+++ b/openglsample/src/main/AndroidManifest.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/openglsample/src/main/assets/test_video.mp4 b/openglsample/src/main/assets/test_video.mp4
new file mode 100644
index 0000000..2041a0b
Binary files /dev/null and b/openglsample/src/main/assets/test_video.mp4 differ
diff --git a/openglsample/src/main/assets/test_video_2.mp4 b/openglsample/src/main/assets/test_video_2.mp4
new file mode 100644
index 0000000..0693ce4
Binary files /dev/null and b/openglsample/src/main/assets/test_video_2.mp4 differ
diff --git a/openglsample/src/main/java/jp/eita/example/bubble/OpenGLActivity.kt b/openglsample/src/main/java/jp/eita/example/bubble/OpenGLActivity.kt
new file mode 100644
index 0000000..40ed1c7
--- /dev/null
+++ b/openglsample/src/main/java/jp/eita/example/bubble/OpenGLActivity.kt
@@ -0,0 +1,203 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.example.bubble
+
+import android.graphics.Bitmap
+import android.graphics.PointF
+import android.os.Bundle
+import android.os.Handler
+import android.text.Editable
+import android.text.TextWatcher
+import androidx.appcompat.app.AppCompatActivity
+import jp.eita.canvasgl.BitmapUtils
+import jp.eita.canvasgl.OpenGLUtil
+import jp.eita.canvasgl.textureFilter.*
+import jp.eita.example.R
+import jp.eita.example.structure.ScaleRatioList
+import jp.eita.example.bubble.model.Bubble
+import jp.eita.example.structure.AlphaList
+import kotlinx.android.synthetic.main.activity_opengl.*
+import java.util.*
+import kotlin.collections.ArrayList
+
+class OpenGLActivity : AppCompatActivity() {
+
+ private val upFilterList: MutableList = ArrayList()
+
+ private lateinit var bitmap: Bitmap
+
+ private val scaleRatioList: ScaleRatioList = ScaleRatioList(0)
+
+ private val alphaList: AlphaList = AlphaList(0)
+
+ private val runnable = Runnable {
+ loopChangingPropertiesBubbles()
+ }
+
+ init {
+ Handler().postDelayed(runnable, 150)
+ }
+
+ private fun loopChangingPropertiesBubbles() {
+ changingPropertiesBubbles()
+ Handler().postDelayed(runnable, 150)
+ }
+
+ private fun changingPropertiesBubbles() {
+ for (i in 0 until anim_gl_view.bubbles.size) {
+ anim_gl_view.bubbles[i].scaleSizeRatio = scaleRatioList.listDetails[i].scaleRatioValue
+ anim_gl_view.bubbles[i].alpha = alphaList.listDetails[i].scaleRatioValue
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_opengl)
+ title = getString(R.string.bubble_title)
+ setUpBitmap()
+ setUpEditTextScaleRatio()
+ initFilterList(upFilterList)
+ setUpButtonLike()
+ }
+
+ private fun setUpBitmap() {
+ bitmap = BitmapUtils.convertToBitmapFrom(applicationContext, R.drawable.ic_fast_food)!!
+// BitmapUtils.adjustOpacity(bitmap = bitmap, opacity = 120)
+// bitmap = BitmapUtils.getScaledDownBitmap(bitmap, 64, false)
+// anim_gl_view.alpha = 0.6f
+ }
+
+ private fun setUpEditTextScaleRatio() {
+ editTextScaleRatio.addTextChangedListener(object : TextWatcher {
+
+ override fun afterTextChanged(s: Editable?) {
+ try {
+ val value: Double = if (s == null || s.toString() == "") {
+ return
+ } else if (s.toString() == "0.") {
+ 0.1
+ } else {
+ s.toString().toDouble()
+ }
+ anim_gl_view.onPause()
+// anim_gl_view.updateScaleRatioForAllBubbles(value.toFloat())
+ anim_gl_view.onResume()
+ } catch (ex: Exception) {
+
+ }
+ }
+
+ override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
+
+ }
+
+ override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
+
+ }
+
+ })
+ }
+
+ private fun initFilterList(filterList: MutableList) {
+ filterList.add(BasicTextureFilter())
+ filterList.add(ContrastFilter(1.6f))
+ filterList.add(SaturationFilter(1.6f))
+ filterList.add(PixelationFilter(12F))
+ filterList.add(HueFilter(100F))
+ }
+
+ private fun setUpButtonLike() {
+ buttonLike.setOnClickListener {
+ anim_gl_view.onPause()
+ anim_gl_view.bubbles.add(createBubble(upFilterList))
+ anim_gl_view.onResume()
+ }
+ }
+
+ private fun createBubble(filterList: List): Bubble {
+ val random = Random()
+ val textureFilter = filterList[random.nextInt(filterList.size)]
+ val vy = -(MIN_VY + random.nextInt(MAX_VY)) * VY_MULTIPLIER
+ var vx = (MIN_VX + random.nextInt(MAX_VX)) * VX_MULTIPLIER
+ vx = if (random.nextBoolean()) vx else -vx
+ val vRotate = 0.05f
+
+ val point = OpenGLUtil.getPointOfView(buttonLike)
+ val x = point.x.toFloat()
+ val y = point.y.toFloat()
+
+ val scaleRatio = if (editTextScaleRatio.text.toString().isEmpty()) {
+ 0.1f
+ } else {
+ editTextScaleRatio.text.toString().toFloat()
+ }
+ val alpha = 255
+ scaleRatioList.add(scaleRatio)
+ alphaList.add(alpha)
+ val bubble = Bubble(
+ PointF(x, y - 700),
+ vx,
+ vy,
+ vRotate,
+ bitmap,
+ textureFilter,
+ scaleSizeRatio = scaleRatio,
+ alpha = alpha
+ )
+
+ return bubble
+ }
+
+ override fun onResume() {
+ super.onResume()
+ anim_gl_view.onResume()
+ }
+
+ override fun onPause() {
+ super.onPause()
+ anim_gl_view.onPause()
+ }
+
+ companion object {
+
+ const val VY_MULTIPLIER = 0.01f // px/ms
+
+ const val VX_MULTIPLIER = 0.01f
+
+ const val MIN_VY = 10
+
+ const val MAX_VY = 30
+
+ const val MIN_VX = 10
+
+ const val MAX_VX = 30
+ }
+}
\ No newline at end of file
diff --git a/openglsample/src/main/java/jp/eita/example/bubble/OpenGLView.kt b/openglsample/src/main/java/jp/eita/example/bubble/OpenGLView.kt
new file mode 100644
index 0000000..af4217c
--- /dev/null
+++ b/openglsample/src/main/java/jp/eita/example/bubble/OpenGLView.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.example.bubble
+
+import android.content.Context
+import android.util.AttributeSet
+import jp.eita.canvasgl.ICanvasGL
+import jp.eita.canvasgl.glview.GLContinuousView
+import jp.eita.example.bubble.model.Bubble
+import jp.eita.example.bubble.model.MovableCollisionObject
+import jp.eita.example.bubble.model.Wall
+import jp.eita.example.bubble.model.Wall.WallX
+import jp.eita.example.bubble.model.Wall.WallY
+import java.util.*
+
+class OpenGLView : GLContinuousView {
+
+ val bubbles: MutableList = ArrayList()
+
+ private val wallTop: Wall = WallY(0F)
+
+ private val wallLeft: Wall = WallX(0F)
+
+ private var wallBottom: Wall? = null
+
+ private var wallRight: Wall? = null
+
+ constructor(context: Context?) : super(context)
+
+ constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
+
+ override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
+ super.onSizeChanged(w, h, oldw, oldh)
+ wallBottom = WallY(h.toFloat())
+ wallRight = WallX(w.toFloat())
+ }
+
+ override fun onGLDraw(canvas: ICanvasGL) {
+ val iterator: MutableIterator = bubbles.iterator()
+ while (iterator.hasNext()) {
+ val bubble = iterator.next()
+ bubble.glDraw(canvas)
+ if (wallTop.isTouch(bubble.point, bubble.collisionRadius) || wallBottom!!.isTouch(bubble.point, bubble.collisionRadius)) {
+ bubble.onCollision(MovableCollisionObject.CollisionListener.DIRECTION_VERTICAL)
+ } else if (wallLeft.isTouch(bubble.point, bubble.collisionRadius) || wallRight!!.isTouch(bubble.point, bubble.collisionRadius)) {
+ bubble.onCollision(MovableCollisionObject.CollisionListener.DIRECTION_HORIZONTAL)
+ }
+
+ bubble.updatePosition(INTERNAL_TIME_MS)
+ }
+ }
+
+ companion object {
+
+ private const val INTERNAL_TIME_MS = 16
+ }
+}
\ No newline at end of file
diff --git a/openglsample/src/main/java/jp/eita/example/bubble/model/Bubble.kt b/openglsample/src/main/java/jp/eita/example/bubble/model/Bubble.kt
new file mode 100644
index 0000000..3f0feec
--- /dev/null
+++ b/openglsample/src/main/java/jp/eita/example/bubble/model/Bubble.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.example.bubble.model
+
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.graphics.PointF
+import androidx.annotation.FloatRange
+import jp.eita.canvasgl.ICanvasGL
+import jp.eita.canvasgl.textureFilter.BasicTextureFilter
+import jp.eita.canvasgl.textureFilter.TextureFilter
+
+class Bubble : MovableCollisionObject {
+
+ private val bitmap: Bitmap
+
+ private var textureFilter: TextureFilter? = null
+
+ private val paint: Paint
+
+ constructor(
+ point: PointF,
+ vx: Float,
+ vy: Float,
+ vRotate: Float,
+ bitmap: Bitmap,
+ textureFilter: TextureFilter? = null,
+ @FloatRange(from = 0.1, to = 2.0) scaleSizeRatio: Float = DEFAULT_SCALE_VALUE,
+ alpha: Int = DEFAULT_ALPHA_VALUE
+ ) : super(point, vx, vy, vRotate, bitmap.width / 2f) {
+ this.bitmap = bitmap
+ this.paint = Paint()
+ if (textureFilter == null) {
+ this.textureFilter = BasicTextureFilter()
+ } else {
+ this.textureFilter = textureFilter
+ }
+ this.alpha = alpha
+ this.scaleSizeRatio = scaleSizeRatio
+ }
+
+ fun glDraw(canvas: ICanvasGL) {
+ canvas.save()
+ val left = (point.x - bitmap.width / 2f).toInt()
+ val top = (point.y - bitmap.height / 2f).toInt()
+ canvas.rotate(rotateDegree, point.x, point.y)
+ canvas.scale(scaleSizeRatio, scaleSizeRatio, scaleSizeRatio, scaleSizeRatio)
+ canvas.setAlpha(alpha)
+ canvas.drawBitmap(bitmap, left, top, textureFilter!!)
+ canvas.restore()
+ }
+
+ fun normalDraw(canvas: Canvas) {
+ canvas.save()
+ val left = (point.x - bitmap.width / 2.toFloat()).toInt()
+ val top = (point.y - bitmap.height / 2.toFloat()).toInt()
+ canvas.rotate(rotateDegree, point.x, point.y)
+ canvas.drawBitmap(bitmap, left.toFloat(), top.toFloat(), paint)
+ canvas.restore()
+ }
+
+ override fun onCollision(direction: Int) {
+ super.onCollision(direction = direction)
+ if (direction == CollisionListener.DIRECTION_HORIZONTAL) {
+ vx = -vx
+ } else if (direction == CollisionListener.DIRECTION_VERTICAL) {
+ vy = -vy
+ }
+ }
+}
\ No newline at end of file
diff --git a/openglsample/src/main/java/jp/eita/example/bubble/model/MovableCollisionObject.kt b/openglsample/src/main/java/jp/eita/example/bubble/model/MovableCollisionObject.kt
new file mode 100644
index 0000000..0970d0a
--- /dev/null
+++ b/openglsample/src/main/java/jp/eita/example/bubble/model/MovableCollisionObject.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.example.bubble.model
+
+import android.graphics.PointF
+
+open class MovableCollisionObject : MovableObject {
+
+ private var onCollisionListener: CollisionListener? = null
+
+ constructor(
+ point: PointF,
+ vx: Float,
+ vy: Float,
+ vRotate: Float,
+ collisionRadius: Float
+ ) : super(point, vx, vy, vRotate, collisionRadius)
+
+ open fun onCollision(direction: Int) {
+ onCollisionListener?.onCollision(direction = direction)
+ }
+
+ interface CollisionListener {
+
+ fun onCollision(direction: Int)
+
+ companion object {
+
+ const val DIRECTION_HORIZONTAL = 0
+
+ const val DIRECTION_VERTICAL = 1
+ }
+ }
+}
\ No newline at end of file
diff --git a/openglsample/src/main/java/jp/eita/example/bubble/model/MovableObject.kt b/openglsample/src/main/java/jp/eita/example/bubble/model/MovableObject.kt
new file mode 100644
index 0000000..e756e1d
--- /dev/null
+++ b/openglsample/src/main/java/jp/eita/example/bubble/model/MovableObject.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.example.bubble.model
+
+import android.graphics.PointF
+import androidx.annotation.FloatRange
+import androidx.annotation.IntRange
+
+open class MovableObject {
+
+ var point: PointF
+
+ var vx: Float
+
+ var vy: Float
+
+ var vRotate: Float
+
+ var collisionRadius: Float
+
+ var rotateDegree = 0f
+
+ var scaleSizeRatio: Float = DEFAULT_SCALE_VALUE
+ set(@FloatRange(from = 0.1, to = 2.0) value) {
+ field = value
+ }
+
+ var alpha: Int = DEFAULT_ALPHA_VALUE
+ set(@IntRange(from = 0, to = 255) value) {
+ field = value
+ }
+
+ constructor(
+ point: PointF,
+ vx: Float,
+ vy: Float,
+ vRotate: Float,
+ collisionRadius: Float
+ ) {
+ this.point = point
+ this.vx = vx
+ this.vy = vy
+ this.vRotate = vRotate
+ this.collisionRadius = collisionRadius
+ }
+
+ fun reset(point: PointF, vx: Float, vy: Float, vRotate: Float, collisionRadius: Float, scaleRatio: Float) {
+ this.point = point
+ this.vx = vx
+ this.vy = vy
+ this.vRotate = vRotate
+ this.collisionRadius = collisionRadius
+ this.scaleSizeRatio = scaleRatio
+ this.alpha = DEFAULT_ALPHA_VALUE
+ }
+
+ open fun updatePosition(timeMs: Int) {
+ point.x += vx * timeMs
+ point.y += vy * timeMs
+ rotateDegree += vRotate * timeMs
+ }
+
+ override fun toString(): String {
+ return "${this::class.simpleName}{" +
+ "point=" + point +
+ ", vx=" + vx +
+ ", vy=" + vy +
+ ", collisionRadius=" + collisionRadius +
+ ", vRotate=" + vRotate +
+ ", rotateDegree= $rotateDegree" +
+ ", alpha= $alpha" +
+ ", scaleSizeRatio= $scaleSizeRatio" +
+ '}'
+ }
+
+ companion object {
+
+ const val DEFAULT_SCALE_VALUE = 1.0f
+
+ const val DEFAULT_ALPHA_VALUE = 255
+ }
+}
\ No newline at end of file
diff --git a/openglsample/src/main/java/jp/eita/example/bubble/model/Wall.kt b/openglsample/src/main/java/jp/eita/example/bubble/model/Wall.kt
new file mode 100644
index 0000000..841a242
--- /dev/null
+++ b/openglsample/src/main/java/jp/eita/example/bubble/model/Wall.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.example.bubble.model
+
+import android.graphics.PointF
+import kotlin.math.abs
+
+abstract class Wall(protected var value: Float) {
+
+ abstract fun isTouch(point: PointF, objRadius: Float): Boolean
+
+ class WallX(value: Float) : Wall(value) {
+ override fun isTouch(point: PointF, objRadius: Float): Boolean {
+ return abs(point.x - value) <= objRadius
+ }
+ }
+
+ class WallY(value: Float) : Wall(value) {
+ override fun isTouch(point: PointF, objRadius: Float): Boolean {
+ return abs(point.y - value) <= objRadius
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/openglsample/src/main/java/jp/eita/example/main/MainActivity.kt b/openglsample/src/main/java/jp/eita/example/main/MainActivity.kt
new file mode 100644
index 0000000..9e024e2
--- /dev/null
+++ b/openglsample/src/main/java/jp/eita/example/main/MainActivity.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.example.main
+
+import android.content.Intent
+import android.os.Bundle
+import android.view.View
+import androidx.appcompat.app.AppCompatActivity
+import androidx.recyclerview.widget.LinearLayoutManager
+import jp.eita.example.main.OpenGLName.OPENGL
+import jp.eita.example.R
+import jp.eita.example.bubble.OpenGLActivity
+import kotlinx.android.synthetic.main.activity_main.*
+
+class MainActivity : AppCompatActivity() {
+
+ private lateinit var openGLAdapter: OpenGLAdapter
+
+ private val onClickListener: View.OnClickListener = View.OnClickListener {
+ val intent: Intent
+ when (it.tag) {
+ OPENGL -> {
+ intent = Intent(this, OpenGLActivity::class.java)
+ }
+ else -> {
+ return@OnClickListener
+ }
+ }
+ startActivity(intent)
+ }
+
+ private val listParameters: List = listOf(
+ OpenGLParameters(OPENGL)
+ )
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_main)
+ setupRecyclerView()
+ }
+
+ private fun setupRecyclerView() {
+ openGLAdapter = OpenGLAdapter(listParameters, onClickListener)
+ recycler_view.adapter = openGLAdapter
+ recycler_view.layoutManager = LinearLayoutManager(this)
+ }
+}
\ No newline at end of file
diff --git a/openglsample/src/main/java/jp/eita/example/main/OpenGLAdapter.kt b/openglsample/src/main/java/jp/eita/example/main/OpenGLAdapter.kt
new file mode 100644
index 0000000..23d7693
--- /dev/null
+++ b/openglsample/src/main/java/jp/eita/example/main/OpenGLAdapter.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.example.main
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import jp.eita.example.R
+
+class OpenGLAdapter(
+ private val listParameters: List,
+ private val onClickListener: View.OnClickListener
+) : RecyclerView.Adapter() {
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): OpenGLViewHolder {
+ val view = LayoutInflater.from(parent.context).inflate(R.layout.research_item_view, parent, false)
+ view.setOnClickListener(onClickListener)
+
+ return OpenGLViewHolder(view)
+ }
+
+ override fun getItemCount(): Int {
+ return listParameters.size
+ }
+
+ override fun onBindViewHolder(holder: OpenGLViewHolder, position: Int) {
+ holder.openGLParameters = listParameters[position]
+ }
+}
\ No newline at end of file
diff --git a/openglsample/src/main/java/jp/eita/example/main/OpenGLName.kt b/openglsample/src/main/java/jp/eita/example/main/OpenGLName.kt
new file mode 100644
index 0000000..c5d6992
--- /dev/null
+++ b/openglsample/src/main/java/jp/eita/example/main/OpenGLName.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.example.main
+
+object OpenGLName {
+
+ const val OPENGL: String = "Bubble"
+}
\ No newline at end of file
diff --git a/openglsample/src/main/java/jp/eita/example/main/OpenGLParameters.kt b/openglsample/src/main/java/jp/eita/example/main/OpenGLParameters.kt
new file mode 100644
index 0000000..4fd565c
--- /dev/null
+++ b/openglsample/src/main/java/jp/eita/example/main/OpenGLParameters.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.example.main
+
+data class OpenGLParameters(
+ var name: String = "NO_NAME"
+)
\ No newline at end of file
diff --git a/openglsample/src/main/java/jp/eita/example/main/OpenGLViewHolder.kt b/openglsample/src/main/java/jp/eita/example/main/OpenGLViewHolder.kt
new file mode 100644
index 0000000..551b31f
--- /dev/null
+++ b/openglsample/src/main/java/jp/eita/example/main/OpenGLViewHolder.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.example.main
+
+import android.view.View
+import android.widget.Button
+import androidx.recyclerview.widget.RecyclerView
+import jp.eita.example.R
+
+
+class OpenGLViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+
+ private val buttonResearch: Button = itemView.findViewById(R.id.button_research_view)
+
+ var openGLParameters: OpenGLParameters = OpenGLParameters()
+ set(value) {
+ field = value
+ buttonResearch.text = value.name
+ itemView.tag = value.name
+ }
+}
\ No newline at end of file
diff --git a/openglsample/src/main/java/jp/eita/example/structure/AlphaList.kt b/openglsample/src/main/java/jp/eita/example/structure/AlphaList.kt
new file mode 100644
index 0000000..52cfb11
--- /dev/null
+++ b/openglsample/src/main/java/jp/eita/example/structure/AlphaList.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.example.structure
+
+class AlphaList(initialCapacity: Int) : ArrayList(initialCapacity) {
+
+ val listDetails: MutableList = ArrayList()
+
+ override fun add(element: Int): Boolean {
+ listDetails.add(Detail(element))
+ return super.add(element)
+ }
+
+ class Detail(value: Int) {
+
+ private var rangeAlpha = intArrayOf(
+ (value * 0.2).toInt(),
+ (value * 0.4).toInt(),
+ (value * 0.6).toInt(),
+ (value * 0.8).toInt(),
+ value,
+ (value * 0.8).toInt(),
+ (value * 0.6).toInt(),
+ (value * 0.4).toInt(),
+ (value * 0.2).toInt()
+ )
+
+ private var crawlerAlpha: Int = 0
+
+ val scaleRatioValue: Int
+ get() {
+ when {
+ crawlerAlpha >= rangeAlpha.size - 1 -> {
+ crawlerAlpha = 0
+ }
+ else -> {
+ crawlerAlpha += 1
+ }
+ }
+
+ return rangeAlpha[crawlerAlpha]
+ }
+ }
+}
\ No newline at end of file
diff --git a/openglsample/src/main/java/jp/eita/example/structure/ScaleRatioList.kt b/openglsample/src/main/java/jp/eita/example/structure/ScaleRatioList.kt
new file mode 100644
index 0000000..d700185
--- /dev/null
+++ b/openglsample/src/main/java/jp/eita/example/structure/ScaleRatioList.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jp.eita.example.structure
+
+class ScaleRatioList(initialCapacity: Int) : ArrayList(initialCapacity) {
+
+ val listDetails: MutableList = ArrayList()
+
+ override fun add(element: Float): Boolean {
+ listDetails.add(Detail(element))
+ return super.add(element)
+ }
+
+ class Detail(value: Float) {
+
+ private var rangeScaleRatio: FloatArray = floatArrayOf(
+ value * 1.0f,
+ value * 0.8f,
+ value * 0.6f,
+ value * 0.4f,
+ value * 0.6f,
+ value * 0.8f,
+ value * 1.0f
+ )
+
+ private var crawlerRangeScaleRatio: Int = 0
+
+ val scaleRatioValue: Float
+ get() {
+ when {
+ crawlerRangeScaleRatio >= rangeScaleRatio.size - 1 -> {
+ crawlerRangeScaleRatio = 0
+ }
+ else -> {
+ crawlerRangeScaleRatio += 1
+ }
+ }
+
+ return rangeScaleRatio[crawlerRangeScaleRatio]
+ }
+ }
+}
\ No newline at end of file
diff --git a/openglsample/src/main/res/drawable/ic_fast_food.xml b/openglsample/src/main/res/drawable/ic_fast_food.xml
new file mode 100644
index 0000000..2056c2d
--- /dev/null
+++ b/openglsample/src/main/res/drawable/ic_fast_food.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/openglsample/src/main/res/drawable/ic_launcher_background.xml b/openglsample/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..ca3826a
--- /dev/null
+++ b/openglsample/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/openglsample/src/main/res/drawable/ic_launcher_foreground.xml b/openglsample/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000..10377c7
--- /dev/null
+++ b/openglsample/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/openglsample/src/main/res/drawable/ic_sushi.xml b/openglsample/src/main/res/drawable/ic_sushi.xml
new file mode 100644
index 0000000..be50359
--- /dev/null
+++ b/openglsample/src/main/res/drawable/ic_sushi.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/openglsample/src/main/res/layout/activity_main.xml b/openglsample/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..b372198
--- /dev/null
+++ b/openglsample/src/main/res/layout/activity_main.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/openglsample/src/main/res/layout/activity_opengl.xml b/openglsample/src/main/res/layout/activity_opengl.xml
new file mode 100644
index 0000000..24ebaa5
--- /dev/null
+++ b/openglsample/src/main/res/layout/activity_opengl.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/openglsample/src/main/res/layout/research_item_view.xml b/openglsample/src/main/res/layout/research_item_view.xml
new file mode 100644
index 0000000..46abda2
--- /dev/null
+++ b/openglsample/src/main/res/layout/research_item_view.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/openglsample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/openglsample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..bbd3e02
--- /dev/null
+++ b/openglsample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/openglsample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/openglsample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..bbd3e02
--- /dev/null
+++ b/openglsample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/openglsample/src/main/res/mipmap-hdpi/ic_launcher.png b/openglsample/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..a38707a
Binary files /dev/null and b/openglsample/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/openglsample/src/main/res/mipmap-hdpi/ic_launcher_round.png b/openglsample/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..a38707a
Binary files /dev/null and b/openglsample/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/openglsample/src/main/res/mipmap-mdpi/ic_launcher.png b/openglsample/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..0266b13
Binary files /dev/null and b/openglsample/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/openglsample/src/main/res/mipmap-mdpi/ic_launcher_round.png b/openglsample/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..0266b13
Binary files /dev/null and b/openglsample/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/openglsample/src/main/res/mipmap-xhdpi/ic_launcher.png b/openglsample/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..06a8789
Binary files /dev/null and b/openglsample/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/openglsample/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/openglsample/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..06a8789
Binary files /dev/null and b/openglsample/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/openglsample/src/main/res/mipmap-xxhdpi/ic_launcher.png b/openglsample/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..5ac8d06
Binary files /dev/null and b/openglsample/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/openglsample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/openglsample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..5ac8d06
Binary files /dev/null and b/openglsample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/openglsample/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/openglsample/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..c8faa58
Binary files /dev/null and b/openglsample/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/openglsample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/openglsample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..c8faa58
Binary files /dev/null and b/openglsample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/openglsample/src/main/res/values/colors.xml b/openglsample/src/main/res/values/colors.xml
new file mode 100644
index 0000000..cb09b5e
--- /dev/null
+++ b/openglsample/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #3F51B5
+ #303F9F
+ #FF4081
+
\ No newline at end of file
diff --git a/openglsample/src/main/res/values/dimens.xml b/openglsample/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..e00c2dd
--- /dev/null
+++ b/openglsample/src/main/res/values/dimens.xml
@@ -0,0 +1,5 @@
+
+
+ 16dp
+ 16dp
+
\ No newline at end of file
diff --git a/openglsample/src/main/res/values/strings.xml b/openglsample/src/main/res/values/strings.xml
new file mode 100644
index 0000000..74ca2cc
--- /dev/null
+++ b/openglsample/src/main/res/values/strings.xml
@@ -0,0 +1,4 @@
+
+ Android OpenGL
+ Bubble
+
\ No newline at end of file
diff --git a/openglsample/src/main/res/values/styles.xml b/openglsample/src/main/res/values/styles.xml
new file mode 100644
index 0000000..173741b
--- /dev/null
+++ b/openglsample/src/main/res/values/styles.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..82daab4
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,16 @@
+/*
+ * Copyright [2020 - Present] [Lê Trần Ngọc Thành - 瑛太 (eita)] [kineita (Github)]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+include ':opengl', ':openglsample'
\ No newline at end of file