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 @@ + + + + + + +