*/}}
Browse Source

Android things (gles does not support current brush shader)

YimingWu 5 months ago
parent
commit
5fa4543c86

+ 1 - 0
.gitignore

@@ -2,3 +2,4 @@ build/*
 .vscode/
 screenshots/
 BuildResources/
+android/build-android

+ 15 - 1
CMakeLists.txt

@@ -1,6 +1,16 @@
 cmake_minimum_required(VERSION 3.1)
 project(OurPaint)
 
+if(${ANDROID})
+    include(android/apk_build.cmake)
+    return()
+endif()
+
+if(${LAGUI_USE_GLES})
+    add_definitions(-DLA_USE_GLES)
+endif()
+
+
 IF(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
   SET(CMAKE_INSTALL_PREFIX $ENV{HOME}/OurPaint CACHE PATH "Where to install Our Paint" FORCE)
 ENDIF(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
@@ -13,6 +23,10 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR})
 set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} "-no-pie")
 set(CMAKE_C_FLAGS ${CMAKE_C_FLAGS} "-no-pie")
 
+if(${LAGUI_USE_GLES})
+    add_definitions(-DLA_USE_GLES)
+endif()
+
 find_package(lagui REQUIRED)
 find_package(PNG REQUIRED)
 find_package(LCMS2 REQUIRED)
@@ -48,7 +62,7 @@ include_directories(
     ${LAGUI_INCLUDE_DIRS_ALL}
 )
 
-file(GLOB_RECURSE OurPaintFiles 
+file(GLOB_RECURSE OurPaintFiles
     ourpaint.c ouroperations.c ournodes.c ourtranslations.c ourshader.cpp ourstrings.cpp
     BuildResources/data_splash.c
     BuildResources/data_splash_highdpi.c

+ 189 - 0
android/apk_build.cmake

@@ -0,0 +1,189 @@
+include(ExternalProject)
+
+set(ANDROID_SDK_ROOT $ENV{ANDROID_SDK_ROOT} CACHE PATH "Path to Android SDK")
+
+set(ANDROID_PLATFORM_TOOLS_DIR "${ANDROID_SDK_ROOT}/platform-tools")
+
+file(GLOB ANDROID_BUILD_TOOLS_VERSIONS "${ANDROID_SDK_ROOT}/build-tools/*")
+list(POP_BACK ANDROID_BUILD_TOOLS_VERSIONS ANDROID_BUILD_TOOLS_LATEST)
+set(ANDROID_BUILD_TOOLS_DIR "${ANDROID_BUILD_TOOLS_LATEST}" CACHE PATH "Path to Android build tools (i.e. where to find aapt2, etc)")
+
+file(GLOB ANDROID_NDK_VERSIONS "${ANDROID_SDK_ROOT}/ndk/*")
+list(POP_BACK ANDROID_NDK_VERSIONS ANDROID_NDK_LATEST)
+set(ANDROID_NDK "${ANDROID_NDK_LATEST}" CACHE PATH "Path to Android NDK")
+
+file(GLOB ANDROID_JAR_VERSIONS "${ANDROID_SDK_ROOT}/platforms/*")
+list(POP_BACK ANDROID_JAR_VERSIONS ANDROID_JAR_LATEST)
+set(ANDROID_JAR "${ANDROID_JAR_LATEST}/android.jar" CACHE PATH "Path to Android JAR")
+
+set(LCMS_SRC_DIR "${LCMS_SRC_DIR}" CACHE PATH "Path to LCMS source")
+set(FREETYPE_SRC_DIR "${FREETYPE_SRC_DIR}" CACHE PATH "Path to Freetype source")
+set(PNG_SRC_DIR "${PNG_SRC_DIR_SRC_DIR}" CACHE PATH "Path to PNG source")
+set(LAGUI_SRC_DIR "${LAGUI_SRC_DIR}" CACHE PATH "Path to LaGUI source")
+set(EXECUTABLE_NAME "${EXECUTABLE_NAME}" CACHE STRING "Executable name")
+set(EXECUTABLE_SRC_DIR "${EXECUTABLE_SRC_DIR}" CACHE PATH "Executable source")
+set(ANDROID_NATIVE_API_LEVEL 24 CACHE STRING "Api level")
+
+find_program(ADB       NAMES adb       REQUIRED PATHS ${ANDROID_PLATFORM_TOOLS_DIR})
+find_program(AAPT2     NAMES aapt2     REQUIRED PATHS ${ANDROID_BUILD_TOOLS_DIR})
+find_program(APKSIGNER NAMES apksigner REQUIRED PATHS ${ANDROID_BUILD_TOOLS_DIR})
+find_program(ZIPALIGN  NAMES zipalign  REQUIRED PATHS ${ANDROID_BUILD_TOOLS_DIR})
+
+set(APK_CONTENTS_ROOT "${CMAKE_CURRENT_BINARY_DIR}/apk")
+
+macro(setup_variant VARIANT)
+
+  ExternalProject_Add(freetype-${VARIANT}
+    SOURCE_DIR ${FREETYPE_SRC_DIR}
+    #EXCLUDE_FROM_ALL TRUE
+    CMAKE_ARGS
+    -DANDROID_ABI=${VARIANT}
+    -DANDROID_NDK=${ANDROID_NDK}
+    -DANDROID_STL=c++_static
+    -DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR}/install/${VARIANT}
+    #-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=${APK_CONTENTS_ROOT}/lib/${VARIANT}
+    #-DCMAKE_ARCHIVE_OUTPUT_DIRECTORY=${APK_CONTENTS_ROOT}/lib/${VARIANT}
+    -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK}/build/cmake/android.toolchain.cmake
+    BUILD_ALWAYS True
+  )
+
+  ExternalProject_Add(png-${VARIANT}
+    SOURCE_DIR ${PNG_SRC_DIR}
+    #EXCLUDE_FROM_ALL TRUE
+    CMAKE_ARGS
+    -DANDROID_ABI=${VARIANT}
+    -DANDROID_NDK=${ANDROID_NDK}
+    -DANDROID_STL=c++_static
+    -DPNG_SHARED=OFF
+    -DHAVE_LD_VERSION_SCRIPT=OFF
+    -DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR}/install/${VARIANT}
+    #-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=${APK_CONTENTS_ROOT}/lib/${VARIANT}
+    #-DCMAKE_ARCHIVE_OUTPUT_DIRECTORY=${APK_CONTENTS_ROOT}/lib/${VARIANT}
+    -DANDROID_NATIVE_API_LEVEL=${ANDROID_NATIVE_API_LEVEL}
+    -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK}/build/cmake/android.toolchain.cmake
+    BUILD_ALWAYS True
+  )
+
+  ExternalProject_Add(lcms2-${VARIANT}
+    SOURCE_DIR ${LCMS_SRC_DIR}
+    #EXCLUDE_FROM_ALL TRUE
+    CMAKE_ARGS
+    -DANDROID_ABI=${VARIANT}
+    -DANDROID_NDK=${ANDROID_NDK}
+    -DANDROID_STL=c++_static
+    -DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR}/install/${VARIANT}
+    #-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=${APK_CONTENTS_ROOT}/lib/${VARIANT}
+    #-DCMAKE_ARCHIVE_OUTPUT_DIRECTORY=${APK_CONTENTS_ROOT}/lib/${VARIANT}
+    -DANDROID_NATIVE_API_LEVEL=${ANDROID_NATIVE_API_LEVEL}
+    -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK}/build/cmake/android.toolchain.cmake
+    BUILD_ALWAYS True
+  )
+
+  ExternalProject_Add(lagui-${VARIANT}
+    SOURCE_DIR ${LAGUI_SRC_DIR}
+    #EXCLUDE_FROM_ALL TRUE
+    CMAKE_ARGS
+    -DANDROID_ABI=${VARIANT}
+    -DANDROID_NDK=${ANDROID_NDK}
+    -DANDROID_STL=c++_static
+    -DLAGUI_IGNORE_PACKAGES=True
+    -DLAGUI_USE_GLES=True
+    -DLAGUI_USE_PNG=True
+    -DLAGUI_ANDROID=True
+    -DLAGUI_USE_LUAJIT=False
+    -DFREETYPE_SRC_DIR=${FREETYPE_SRC_DIR}
+    -DPNG_SRC_DIR=${PNG_SRC_DIR}
+    -DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR}/install/${VARIANT}
+    #-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=${APK_CONTENTS_ROOT}/lib/${VARIANT}
+    #-DCMAKE_ARCHIVE_OUTPUT_DIRECTORY=${APK_CONTENTS_ROOT}/lib/${VARIANT}
+    -DANDROID_NATIVE_API_LEVEL=${ANDROID_NATIVE_API_LEVEL}
+    -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK}/build/cmake/android.toolchain.cmake
+    BUILD_ALWAYS True
+  )
+
+  ExternalProject_Add(${EXECUTABLE_NAME}-${VARIANT}
+    SOURCE_DIR ${EXECUTABLE_SRC_DIR}
+    #EXCLUDE_FROM_ALL TRUE
+    CMAKE_ARGS
+    -DANDROID_ABI=${VARIANT}
+    -DANDROID_NDK=${ANDROID_NDK}
+    -DANDROID_STL=c++_static
+    -DLAGUI_SRC_DIR=${LAGUI_SRC_DIR}
+    -DLAGUI_USE_GLES=True
+    -DLAGUI_USE_LUAJIT=False
+    -DLAGUI_USE_PNG=True
+    -DLAGUI_ANDROID=True
+    -DFREETYPE_SRC_DIR=${FREETYPE_SRC_DIR}
+    -DLAGUI_SRC_DIR=${LAGUI_SRC_DIR}
+    -DPNG_SRC_DIR=${PNG_SRC_DIR}
+    -DLCMS_SRC_DIR=${LCMS_SRC_DIR}
+    -DANDROID_COMPILE=True
+    -DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR}/install/${VARIANT}
+    -DCMAKE_LIBRARY_OUTPUT_DIRECTORY=${APK_CONTENTS_ROOT}/lib/${VARIANT}
+    -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY=${APK_CONTENTS_ROOT}/lib/${VARIANT}
+    -DANDROID_NATIVE_API_LEVEL=${ANDROID_NATIVE_API_LEVEL}
+    -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK}/build/cmake/android.toolchain.cmake
+    BUILD_ALWAYS True
+    )
+
+endmacro()
+setup_variant(arm64-v8a)
+#setup_variant(armeabi-v7a)
+#setup_variant(x86)
+setup_variant(x86_64)
+
+set(MANIFEST "${CMAKE_CURRENT_SOURCE_DIR}/android/src/AndroidManifest.xml")
+set(RESOURCES "${CMAKE_CURRENT_SOURCE_DIR}/android/res")
+set(KEYSTORE "${CMAKE_CURRENT_SOURCE_DIR}/android/keystore.jks")
+
+set(VALUES_STRING "${CMAKE_CURRENT_BINARY_DIR}/values_strings.arsc.flat")
+
+include(CMakePrintHelpers)
+cmake_print_variables(CMAKE_CURRENT_SOURCE_DIR)
+cmake_print_variables(CMAKE_CURRENT_BINARY_DIR)
+
+
+set(FINAL_APK "${CMAKE_CURRENT_BINARY_DIR}/app.apk")
+set(RESOURCES_APK "${CMAKE_CURRENT_BINARY_DIR}/app.res.apk")
+set(UNALIGNED_APK "${CMAKE_CURRENT_BINARY_DIR}/app.unaligned.apk")
+
+# keytool -genkeypair -keystore keystore.jks -alias androidkey -validity 10000 -keyalg RSA -keysize 2048 -storepass android -keypass android
+
+add_custom_command(
+  OUTPUT ${VALUES_STRING}
+  COMMAND ${AAPT2} compile ${RESOURCES}/values/strings.xml -o ${CMAKE_CURRENT_BINARY_DIR}
+  DEPENDS ${RESOURCES}/values/strings.xml
+  )
+add_custom_command(
+  OUTPUT ${RESOURCES_APK}
+  COMMAND ${AAPT2} link ${VALUES_STRING} -o ${RESOURCES_APK} --manifest ${MANIFEST} -I ${ANDROID_JAR}
+  DEPENDS ${MANIFEST} ${VALUES_STRING}
+  )
+add_custom_command(
+  OUTPUT ${UNALIGNED_APK}
+  COMMAND ${CMAKE_COMMAND} -E tar x ${RESOURCES_APK}
+  COMMAND ${CMAKE_COMMAND} -E tar c ${UNALIGNED_APK} --format=zip .
+  WORKING_DIRECTORY ${APK_CONTENTS_ROOT}
+  DEPENDS
+    ${RESOURCES_APK}
+    #${EXECUTABLE_NAME}-armeabi-v7a
+    ${EXECUTABLE_NAME}-arm64-v8a
+    #${EXECUTABLE_NAME}-x86
+    ${EXECUTABLE_NAME}-x86_64
+    freetype-x86_64
+    #freetype-x86
+    freetype-arm64-v8a
+    #freetype-armeabi-v7a
+    png-x86_64
+    png-arm64-v8a
+  )
+add_custom_command(
+  OUTPUT ${FINAL_APK}
+  COMMAND ${ZIPALIGN} -p -f -v 4 ${UNALIGNED_APK} ${FINAL_APK}
+  COMMAND ${APKSIGNER} sign -ks ${KEYSTORE} --ks-key-alias androidkey --ks-pass pass:android --key-pass pass:android ${FINAL_APK}
+  DEPENDS ${UNALIGNED_APK}
+  )
+
+add_custom_target(apk ALL DEPENDS ${FINAL_APK})
+
+install(CODE "execute_process(COMMAND ${ADB} install ${FINAL_APK})")

BIN
android/keystore.jks


BIN
android/res/mipmap-hdpi/ic_launcher.png


BIN
android/res/mipmap-mdpi/ic_launcher.png


BIN
android/res/mipmap-xhdpi/ic_launcher.png


BIN
android/res/mipmap-xxhdpi/ic_launcher.png


+ 4 - 0
android/res/values/strings.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="app_name">Our Paint</string>
+</resources>

+ 34 - 0
android/src/AndroidManifest.xml

@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+  package="com.yiming.ourpaint"
+  android:versionCode="1"
+  android:versionName="1.0">
+
+  <uses-sdk android:minSdkVersion="24"/>
+  <uses-feature android:glEsVersion="0x00030002" android:required="true" />
+  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> 
+  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> 
+
+  <!-- This .apk has no Java code itself, so set hasCode to false. -->
+  <!-- android:icon="@mipmap/ic_launcher" --> 
+  <application
+    android:allowBackup="false"
+    android:label="@string/app_name"
+    android:hasCode="false">
+
+    <!-- Our activity is the built-in NativeActivity framework class. This will take care of integrating with our NDK code. -->
+    <activity android:name="android.app.NativeActivity"
+      android:label="@string/app_name"
+      android:configChanges="orientation|keyboardHidden|screenSize"
+      android:clearTaskOnLaunch="false"
+      android:launchMode="singleTask">
+
+      <!-- Tell NativeActivity the name of our .so -->
+      <meta-data android:name="android.app.lib_name" android:value="ourpaint-android" />
+      <intent-filter>
+        <action android:name="android.intent.action.MAIN" />
+        <category android:name="android.intent.category.LAUNCHER" />
+      </intent-filter>
+    </activity>
+  </application>
+</manifest>

+ 49 - 0
android/src/CMakeLists.txt

@@ -0,0 +1,49 @@
+cmake_minimum_required(VERSION 3.17)
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+project(ourpaint-android)
+
+if(${LAGUI_ANDROID})
+    add_definitions(-DLAGUI_ANDROID)
+endif()
+if(${LAGUI_USE_GLES})
+    add_definitions(-DLA_USE_GLES)
+endif()
+
+INCLUDE_DIRECTORIES(${LCMS_SRC_DIR}/../include)
+INCLUDE_DIRECTORIES(${FREETYPE_SRC_DIR}/include)
+INCLUDE_DIRECTORIES(${LAGUI_SRC_DIR})
+INCLUDE_DIRECTORIES(${LAGUI_SRC_DIR}/nanovg)
+INCLUDE_DIRECTORIES(${PNG_SRC_DIR})
+INCLUDE_DIRECTORIES(${ANDROID_NDK}/sources/android/native_app_glue)
+
+if(${LAGUI_USE_PNG})
+    add_definitions(-DLA_WITH_PNG)
+endif()
+
+LINK_DIRECTORIES(../build-android/install/${ANDROID_ABI}/lib)
+LINK_DIRECTORIES(../build-android/install/${ANDROID_ABI}/lib/lagui)
+
+add_compile_options(-fpermissive
+    -Wno-error=incompatible-pointer-types
+    -Wno-error=incompatible-function-pointer-types
+    -Wno-error=int-conversion
+    -w
+)
+
+SET(OPS ../../)
+
+add_library(${PROJECT_NAME} SHARED
+
+${OPS}ourpaint.c ${OPS}ouroperations.c ${OPS}ournodes.c ${OPS}ourtranslations.c ${OPS}ourshader.cpp ${OPS}ourstrings.cpp
+${OPS}BuildResources/data_splash.c
+${OPS}BuildResources/data_splash_highdpi.c
+
+${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+target_link_libraries(${PROJECT_NAME} PUBLIC log lagui GLESv3 EGL android z png freetype lcms2)
+
+#target_link_options(${PROJECT_NAME} BEFORE PUBLIC "-v")
+target_link_options(${PROJECT_NAME} BEFORE PUBLIC "-Wl,--start-group")
+target_link_options(${PROJECT_NAME} PUBLIC "-Wl,--end-group")
+
+install(TARGETS ${PROJECT_NAME})

+ 57 - 27
ouroperations.c

@@ -906,15 +906,15 @@ void our_DuplicateLayerContent(OurLayer* to, OurLayer* from){
             OurTexTile* tt=to->TexTiles[row][col],*ft=from->TexTiles[row][col];
             memcpy(tt,ft,sizeof(OurTexTile));
             tt->CopyBuffer=0;
-            tt->Texture=tnsCreate2DTexture(GL_RGBA16,OUR_TILE_W,OUR_TILE_W,0);
+            tt->Texture=tnsCreate2DTexture(GL_RGBA16UI,OUR_TILE_W,OUR_TILE_W,0);
             int bufsize=sizeof(uint16_t)*OUR_TILE_W*OUR_TILE_W*4;
             tt->FullData=malloc(bufsize);
 
             ft->Data=malloc(bufsize); int width=OUR_TILE_W;
             tnsBindTexture(ft->Texture); glPixelStorei(GL_PACK_ALIGNMENT, 2);
-            glGetTextureSubImage(ft->Texture->GLTexHandle, 0, 0, 0, 0, width, width,1, GL_RGBA, GL_UNSIGNED_SHORT, bufsize, ft->Data);
+            tnsGet2DTextureSubImage(ft->Texture, 0, 0, width, width, GL_RGBA_INTEGER, GL_UNSIGNED_SHORT, bufsize, ft->Data);
             tnsBindTexture(tt->Texture);
-            glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, width, GL_RGBA, GL_UNSIGNED_SHORT, ft->Data);
+            glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, width, GL_RGBA_INTEGER, GL_UNSIGNED_SHORT, ft->Data);
             
             free(ft->Data); ft->Data=0;
         }
@@ -951,8 +951,8 @@ int our_MergeLayer(OurLayer* l){
             int tl,tr,tu,tb; our_LayerEnsureTileDirect(ol,row,col);
             OurTexTile*ot=ol->TexTiles[row][col];
             if((!ot) || (!ot->Texture)) our_LayerEnsureTileDirect(ol,row,col);
-            glBindImageTexture(0, t->Texture->GLTexHandle, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA16);
-            glBindImageTexture(1, ot->Texture->GLTexHandle, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA16);
+            glBindImageTexture(0, t->Texture->GLTexHandle, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA16UI);
+            glBindImageTexture(1, ot->Texture->GLTexHandle, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA16UI);
             glDispatchCompute(32,32,1);
             xmin=TNS_MIN2(xmin,t->l+seam);xmax=TNS_MAX2(xmax,t->r-seam); ymin=TNS_MIN2(ymin,t->b+seam);ymax=TNS_MAX2(ymax,t->u-seam);
         }
@@ -1023,7 +1023,7 @@ void our_TileEnsureUndoBuffer(OurTexTile* t, real xmin,real xmax, real ymin,real
     }
     uint16_t* temp=malloc(bufsize);
     tnsBindTexture(t->Texture);
-    glGetTextureSubImage(t->Texture->GLTexHandle, 0, t->cl, t->cb, 0, cols,rows,1, GL_RGBA, GL_UNSIGNED_SHORT, bufsize, temp);
+    tnsGet2DTextureSubImage(t->Texture, t->cl, t->cb, cols,rows, GL_RGBA_INTEGER, GL_UNSIGNED_SHORT, bufsize, temp);
     for(int row=0;row<rows;row++){
         memcpy(&t->FullData[((+row+t->cb)*OUR_TILE_W+t->cl)*4],&temp[row*cols*4],sizeof(uint16_t)*4*cols);
     }
@@ -1041,7 +1041,7 @@ void our_TileSwapBuffers(OurTexTile* t, uint16_t* data, int IsRedo, int l, int r
     glGetError();
     uint16_t* use_data=temp;
     if(IsRedo){ use_data=data; }
-    glTexSubImage2D(GL_TEXTURE_2D, 0, l, b, cols,rows,GL_RGBA,GL_UNSIGNED_SHORT,use_data);
+    glTexSubImage2D(GL_TEXTURE_2D, 0, l, b, cols,rows,GL_RGBA_INTEGER,GL_UNSIGNED_SHORT,use_data);
     free(temp);
 }
 void ourundo_Tiles(OurUndo* undo){
@@ -1113,11 +1113,13 @@ void our_LayerEnsureTileDirect(OurLayer* ol, int row, int col){
     if(!ol->TexTiles[row][col]) ol->TexTiles[row][col]=memAcquireSimple(sizeof(OurTexTile));
     OurTexTile*t=ol->TexTiles[row][col];
     if(t->Texture) return;
-    t->Texture=tnsCreate2DTexture(GL_RGBA16,OUR_TILE_W,OUR_TILE_W,0);
+    t->Texture=tnsCreate2DTexture(GL_RGBA16UI,OUR_TILE_W,OUR_TILE_W,0);
     int sx=((real)col-OUR_TILE_CTR-0.5)*OUR_TILE_W_USE,sy=((real)row-OUR_TILE_CTR-0.5)*OUR_TILE_W_USE;
     t->l=sx-OUR_TILE_SEAM,t->b=sy-OUR_TILE_SEAM; t->r=t->l+OUR_TILE_W; t->u=t->b+OUR_TILE_W;
     uint16_t initColor[]={0,0,0,0};
-    glClearTexImage(t->Texture->GLTexHandle, 0, GL_RGBA, GL_UNSIGNED_SHORT, 0);
+#ifndef LA_USE_GLES
+    glClearTexImage(t->Texture->GLTexHandle, 0, GL_RGBA_INTEGER, GL_UNSIGNED_SHORT, 0);
+#endif
     t->FullData=calloc(OUR_TILE_W*4,OUR_TILE_W*sizeof(uint16_t));
 }
 void our_LayerEnsureTiles(OurLayer* ol, real xmin,real xmax, real ymin,real ymax, int Aligned, int *tl, int *tr, int* tu, int* tb){
@@ -1135,7 +1137,7 @@ void our_TileTextureToImage(OurTexTile* ot, int SX, int SY, int composite, int B
     int bufsize=sizeof(uint16_t)*OUR_TILE_W_USE*OUR_TILE_W_USE*4;
     ot->Data=malloc(bufsize); int seam=OUR_TILE_SEAM; int width=OUR_TILE_W_USE;
     tnsBindTexture(ot->Texture); glPixelStorei(GL_PACK_ALIGNMENT, 2);
-    glGetTextureSubImage(ot->Texture->GLTexHandle, 0, seam, seam, 0, width, width,1, GL_RGBA, GL_UNSIGNED_SHORT, bufsize, ot->Data);
+    tnsGet2DTextureSubImage(ot->Texture, seam, seam, width, width, GL_RGBA_INTEGER, GL_UNSIGNED_SHORT, bufsize, ot->Data);
     if(composite){
         for(int row=0;row<OUR_TILE_W_USE;row++){
             for(int col=0;col<OUR_TILE_W_USE;col++){
@@ -1165,7 +1167,7 @@ void our_TileImageToTexture(OurTexTile* ot, int SX, int SY){
     if(!our_BufferAnythingVisible(ot->Data, bufsize/sizeof(uint16_t)/4)){ tnsDeleteTexture(ot->Texture); ot->Texture=0; }
     else{
         tnsBindTexture(ot->Texture);
-        glTexSubImage2D(GL_TEXTURE_2D, 0, OUR_TILE_SEAM-pl, OUR_TILE_SEAM-pu, width, height, GL_RGBA, GL_UNSIGNED_SHORT, ot->Data);
+        glTexSubImage2D(GL_TEXTURE_2D, 0, OUR_TILE_SEAM-pl, OUR_TILE_SEAM-pu, width, height, GL_RGBA_INTEGER, GL_UNSIGNED_SHORT, ot->Data);
     }
     free(ot->Data); ot->Data=0;
 }
@@ -1661,14 +1663,14 @@ void our_PaintDoDab(OurDab* d, int tl, int tr, int tu, int tb){
     glUniform1f(Our->uBrushGunkyness,d->Gunkyness);
     glUniform1f(Our->uBrushRecentness,d->Recentness);
     glUniform4fv(Our->uBrushColor,1,d->Color);
-    glDispatchCompute(ceil(d->Size/16), ceil(d->Size/16), 1);
+    glDispatchCompute((GLuint)ceil(d->Size/16), (GLuint)ceil(d->Size/16), 1);
     glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
 }
 void our_PaintDoDabs(OurLayer* l,int tl, int tr, int tu, int tb, int Start, int End){
     for(int row=tb;row<=tu;row++){
         for(int col=tl;col<=tr;col++){
             OurTexTile* ott=l->TexTiles[row][col];
-            glBindImageTexture(0, ott->Texture->GLTexHandle, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA16);
+            glBindImageTexture(0, ott->Texture->GLTexHandle, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA16UI);
             int s[2]; s[0]=l->TexTiles[row][col]->l,s[1]=l->TexTiles[row][col]->b;
             glUniform2iv(Our->uImageOffset,1,s);
             for(int i=Start;i<End;i++){
@@ -1696,9 +1698,14 @@ void our_PaintDoDabsWithSmudgeSegments(OurLayer* l,int tl, int tr, int tu, int t
     glUseProgram(Our->CanvasProgram);
     glUniform1i(Our->uBrushErasing,Our->Erasing);
     glUniform1i(Our->uBrushMix,Our->Erasing?0:Our->BrushMix);
+#ifdef LA_USE_GLES
+    glUniform1i(Our->uBrushRoutineSelectionES,0);
+    glUniform1i(Our->uMixRoutineSelectionES,Our->SpectralMode?1:0);
+#else
     uniforms[Our->uBrushRoutineSelection]=Our->RoutineDoDabs;
     uniforms[Our->uMixRoutineSelection]=Our->SpectralMode?Our->RoutineDoMixSpectral:Our->RoutineDoMixNormal;
     glUniformSubroutinesuiv(GL_COMPUTE_SHADER,2,uniforms);
+#endif
     glUniform1i(Our->uCanvasType,Our->BackgroundType);
     glUniform1i(Our->uCanvasRandom,Our->BackgroundRandom);
     glUniform1f(Our->uCanvasFactor,Our->BackgroundFactor);
@@ -1706,24 +1713,32 @@ void our_PaintDoDabsWithSmudgeSegments(OurLayer* l,int tl, int tr, int tu, int t
     while(oss=lstPopItem(&Segments)){
         if(oss->Resample || Our->CurrentBrush->SmudgeRestart){
             uniforms[Our->uBrushRoutineSelection]=Our->RoutineDoSample;
+#ifdef LA_USE_GLES
+            glUniform1i(Our->uBrushRoutineSelectionES,1);
+#else
             glUniformSubroutinesuiv(GL_COMPUTE_SHADER,2,uniforms);
+#endif
             int x=Our->Dabs[oss->Start].X, y=Our->Dabs[oss->Start].Y; float usize=Our->Dabs[oss->Start].Size;
             float ssize=(usize>15)?(usize+1.5):(usize*1.1); if(ssize<3) ssize=3;
             int colmax=(int)(floor(OUR_TILE_CTR+(float)(x+ssize)/OUR_TILE_W_USE+0.5)); TNS_CLAMP(colmax,0,OUR_TILES_PER_ROW-1);
             int rowmax=(int)(floor(OUR_TILE_CTR+(float)(y+ssize)/OUR_TILE_W_USE+0.5)); TNS_CLAMP(rowmax,0,OUR_TILES_PER_ROW-1);
             int colmin=(int)(floor(OUR_TILE_CTR+(float)(x-ssize)/OUR_TILE_W_USE+0.5)); TNS_CLAMP(colmin,0,OUR_TILES_PER_ROW-1);
             int rowmin=(int)(floor(OUR_TILE_CTR+(float)(y-ssize)/OUR_TILE_W_USE+0.5)); TNS_CLAMP(rowmin,0,OUR_TILES_PER_ROW-1);
-            glBindImageTexture(1, Our->SmudgeTexture->GLTexHandle, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA16);
+            glBindImageTexture(1, Our->SmudgeTexture->GLTexHandle, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA16UI);
             for(int col=colmin;col<=colmax;col++){
                 for(int row=rowmin;row<=rowmax;row++){
-                    glBindImageTexture(0, l->TexTiles[row][col]->Texture->GLTexHandle, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA16);
+                    glBindImageTexture(0, l->TexTiles[row][col]->Texture->GLTexHandle, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA16UI);
                     int sx=l->TexTiles[row][col]->l,sy=l->TexTiles[row][col]->b;
                     our_PaintDoSample(x,y,sx,sy,ssize,(col==colmax)&&(row==rowmax),Our->CurrentBrush->SmudgeRestart);
                 }
             }
             Our->CurrentBrush->SmudgeRestart=0;
             uniforms[Our->uBrushRoutineSelection]=Our->RoutineDoDabs;
+#ifdef LA_USE_GLES
+            glUniform1i(Our->uBrushRoutineSelectionES,0);
+#else
             glUniformSubroutinesuiv(GL_COMPUTE_SHADER,2,uniforms);
+#endif
             glUniform1i(Our->uBrushErasing,Our->Erasing);
         }
 
@@ -1786,7 +1801,7 @@ int our_RenderThumbnail(uint8_t** buf, int* sizeof_buf){
     int bufsize=use_w*use_h*sizeof(uint16_t)*4;
     Our->ImageBuffer=malloc(bufsize);
     tnsBindTexture(off->pColor[0]); glPixelStorei(GL_PACK_ALIGNMENT, 2);
-    glGetTextureSubImage(off->pColor[0]->GLTexHandle, 0, 0, 0, 0, use_w, use_h, 1, GL_RGBA, GL_UNSIGNED_SHORT, bufsize, Our->ImageBuffer);
+    tnsGet2DTextureSubImage(off->pColor[0], 0, 0, use_w, use_h, GL_RGBA_INTEGER, GL_UNSIGNED_SHORT, bufsize, Our->ImageBuffer);
 
     tnsDrawToScreen();
     tnsDelete2DOffscreen(off);
@@ -2262,7 +2277,7 @@ int ourinv_Action(laOperator* a, laEvent* e){
     if(l->Hide || l->Transparency==1 || l->Lock || (l->AsSketch && Our->SketchMode==2)){ ex->HideBrushCircle=0; return LA_FINISHED; }
     Our->LockBackground=1; laNotifyUsers("our.lock_background");
     our_EnsureEraser(e->IsEraser);
-    laHideCursor(); Our->ShowBrushName=0; Our->ShowBrushNumber=0;
+    //laHideCursor(); Our->ShowBrushName=0; Our->ShowBrushNumber=0;
     return LA_RUNNING;
 }
 int ourmod_Paint(laOperator* a, laEvent* e){
@@ -2442,7 +2457,7 @@ int our_TileHasPixels(OurTexTile* ot){
     int bufsize=sizeof(uint16_t)*OUR_TILE_W*OUR_TILE_W*4;
     ot->Data=malloc(bufsize); int width=OUR_TILE_W;
     tnsBindTexture(ot->Texture); glPixelStorei(GL_PACK_ALIGNMENT, 2);
-    glGetTextureSubImage(ot->Texture->GLTexHandle, 0, 0, 0, 0, width, width,1, GL_RGBA, GL_UNSIGNED_SHORT, bufsize, ot->Data);
+    tnsGet2DTextureSubImage(ot->Texture, 0, 0, width, width, GL_RGBA_INTEGER, GL_UNSIGNED_SHORT, bufsize, ot->Data);
     
     int has=0;
     int total_elems = width*width;
@@ -2991,7 +3006,7 @@ void ourRegisterEverything(){
 
     laCreateOperatorType("OUR_show_splash","Show Splash","Show splash screen",0,0,0,ourinv_ShowSplash,0,0,0);
     laCreateOperatorType("OUR_new_layer","New Layer","Create a new layer",0,0,0,ourinv_NewLayer,0,'+',0);
-    laCreateOperatorType("OUR_duplicate_layer","Duplicate Layer","Duplicate a layer",0,0,0,ourinv_DuplicateLayer,0,'⎘',0);
+    laCreateOperatorType("OUR_duplicate_layer","Duplicate Layer","Duplicate a layer",0,0,0,ourinv_DuplicateLayer,0,U'⎘',0);
     laCreateOperatorType("OUR_remove_layer","Remove Layer","Remove this layer",0,0,0,ourinv_RemoveLayer,0,U'🗴',0);
     laCreateOperatorType("OUR_move_layer","Move Layer","Remove this layer",0,0,0,ourinv_MoveLayer,0,0,0);
     laCreateOperatorType("OUR_merge_layer","Merge Layer","Merge this layer with the layer below it",ourchk_MergeLayer,0,0,ourinv_MergeLayer,0,0,0);
@@ -3415,15 +3430,21 @@ int ourInit(){
 
     char error[1024]; int status;
 
-    Our->SmudgeTexture=tnsCreate2DTexture(GL_RGBA16,256,1,0);
+    Our->SmudgeTexture=tnsCreate2DTexture(GL_RGBA16UI,256,1,0);
 
     Our->CanvasShader = glCreateShader(GL_COMPUTE_SHADER);
     const GLchar* source1 = OUR_CANVAS_SHADER;
     char* UseContent=tnsEnsureShaderCommoms(source1,0,0);
-    glShaderSource(Our->CanvasShader, 1, &UseContent, NULL); glCompileShader(Our->CanvasShader);
+#ifdef LA_USE_GLES
+    const GLchar* versionstr=OUR_SHADER_VERSION_320ES;
+#else
+    const GLchar* versionstr=OUR_SHADER_VERSION_430;
+#endif
+    const GLchar* sources1[]={versionstr, UseContent};
+    glShaderSource(Our->CanvasShader, 2, sources1, NULL); glCompileShader(Our->CanvasShader);
     glGetShaderiv(Our->CanvasShader, GL_COMPILE_STATUS, &status);
     if (status == GL_FALSE){
-        glGetShaderInfoLog(Our->CanvasShader, sizeof(error), 0, error); printf("Canvas shader error:\n%s", error); glDeleteShader(Our->CanvasShader); return 0;
+        glGetShaderInfoLog(Our->CanvasShader, sizeof(error), 0, error); logPrintNew("Canvas shader error:\n%s", error); glDeleteShader(Our->CanvasShader); return 0;
     }
     if(UseContent){ free(UseContent); }
 
@@ -3431,22 +3452,23 @@ int ourInit(){
     glAttachShader(Our->CanvasProgram, Our->CanvasShader); glLinkProgram(Our->CanvasProgram);
     glGetProgramiv(Our->CanvasProgram, GL_LINK_STATUS, &status);
     if (status == GL_FALSE){
-        glGetProgramInfoLog(Our->CanvasProgram, sizeof(error), 0, error); printf("Canvas program Linking error:\n%s", error); return 0;
+        glGetProgramInfoLog(Our->CanvasProgram, sizeof(error), 0, error); logPrintNew("Canvas program Linking error:\n%s", error); return 0;
     }
 
     Our->CompositionShader = glCreateShader(GL_COMPUTE_SHADER);
     const GLchar* source2 = OUR_COMPOSITION_SHADER;
-    glShaderSource(Our->CompositionShader, 1, &source2, NULL); glCompileShader(Our->CompositionShader);
+    const GLchar* sources2[]={versionstr, source2};
+    glShaderSource(Our->CompositionShader, 2, sources2, NULL); glCompileShader(Our->CompositionShader);
     glGetShaderiv(Our->CompositionShader, GL_COMPILE_STATUS, &status);
     if (status == GL_FALSE){
-        glGetShaderInfoLog(Our->CompositionShader, sizeof(error), 0, error); printf("Composition shader error:\n%s", error); glDeleteShader(Our->CompositionShader); return 0;
+        glGetShaderInfoLog(Our->CompositionShader, sizeof(error), 0, error); logPrintNew("Composition shader error:\n%s", error); glDeleteShader(Our->CompositionShader); return 0;
     }
 
     Our->CompositionProgram = glCreateProgram();
     glAttachShader(Our->CompositionProgram, Our->CompositionShader); glLinkProgram(Our->CompositionProgram);
     glGetProgramiv(Our->CompositionProgram, GL_LINK_STATUS, &status);
     if (status == GL_FALSE){
-        glGetProgramInfoLog(Our->CompositionProgram, sizeof(error), 0, error); printf("Composition program Linking error:\n%s", error); return 0;
+        glGetProgramInfoLog(Our->CompositionProgram, sizeof(error), 0, error); logPrintNew("Composition program Linking error:\n%s", error); return 0;
     }
 
     Our->uCanvasType=glGetUniformLocation(Our->CanvasProgram,"uCanvasType");
@@ -3468,13 +3490,21 @@ int ourInit(){
     Our->uBrushErasing=glGetUniformLocation(Our->CanvasProgram,"uBrushErasing");
     Our->uBrushMix=glGetUniformLocation(Our->CanvasProgram,"uBrushMix");
 
+#ifdef LA_USE_GLES
+    Our->uBrushRoutineSelectionES=glGetUniformLocation(Our->CanvasProgram, "uBrushRoutineSelectionES");
+#else
     Our->uBrushRoutineSelection=glGetSubroutineUniformLocation(Our->CanvasProgram, GL_COMPUTE_SHADER, "uBrushRoutineSelection");
     Our->RoutineDoDabs=glGetSubroutineIndex(Our->CanvasProgram, GL_COMPUTE_SHADER, "DoDabs");
     Our->RoutineDoSample=glGetSubroutineIndex(Our->CanvasProgram, GL_COMPUTE_SHADER, "DoSample");
-    
+#endif
+
+#ifdef LA_USE_GLES
+    Our->uMixRoutineSelectionES=glGetUniformLocation(Our->CanvasProgram, "uMixRoutineSelectionES");
+#else
     Our->uMixRoutineSelection=glGetSubroutineUniformLocation(Our->CanvasProgram, GL_COMPUTE_SHADER, "uMixRoutineSelection");
     Our->RoutineDoMixNormal=glGetSubroutineIndex(Our->CanvasProgram, GL_COMPUTE_SHADER, "DoMixNormal");
     Our->RoutineDoMixSpectral=glGetSubroutineIndex(Our->CanvasProgram, GL_COMPUTE_SHADER, "DoMixSpectral");
+#endif
 
     Our->uBlendMode=glGetUniformLocation(Our->CompositionProgram,"uBlendMode");
     Our->uAlphaTop=glGetUniformLocation(Our->CompositionProgram,"uAlphaTop");

+ 4 - 0
ourpaint.h

@@ -33,6 +33,8 @@ extern unsigned char DATA_SPLASH_HIGHDPI[];
 #ifdef __cplusplus
 extern "C" {
 #endif
+extern const char OUR_SHADER_VERSION_430[];
+extern const char OUR_SHADER_VERSION_320ES[];
 extern const char OUR_CANVAS_SHADER[];
 extern const char OUR_COMPOSITION_SHADER[];
 extern const char OUR_MIME[];
@@ -429,7 +431,9 @@ STRUCTURE(OurPaint){
     GLint uBrushForce;
     GLint uBrushGunkyness;
     GLint uBrushRoutineSelection;
+    GLint uBrushRoutineSelectionES;
     GLint uMixRoutineSelection;
+    GLint uMixRoutineSelectionES;
     GLint uBrushErasing;
     GLint uBrushMix;
     GLint RoutineDoDabs;

+ 125 - 57
ourshader.cpp

@@ -18,10 +18,16 @@
 
 #include "ourpaint.h"
 
-const char OUR_CANVAS_SHADER[]=R"(#version 430
+const char OUR_SHADER_VERSION_430[]="#version 430";
+const char OUR_SHADER_VERSION_320ES[]="#version 320 es\n#define OUR_GLES";
+
+const char OUR_CANVAS_SHADER[]=R"(
+precision highp uimage2D;
+precision highp float;
+precision highp int;
 layout(local_size_x = 32, local_size_y = 32, local_size_z = 1) in;
-layout(rgba16, binding = 0) uniform image2D img;
-layout(rgba16, binding = 1) coherent uniform image2D smudge_buckets;
+layout(rgba16ui, binding = 0) uniform uimage2D img;
+layout(rgba16ui, binding = 1) coherent uniform uimage2D smudge_buckets;
 uniform int uCanvasType;
 uniform int uCanvasRandom;
 uniform float uCanvasFactor;
@@ -41,6 +47,18 @@ uniform vec4 uBrushColor;
 uniform vec4 uBackgroundColor;
 uniform int uBrushErasing;
 uniform int uBrushMix;
+
+#ifdef OUR_GLES
+uniform int uBrushRoutineSelectionES;
+uniform int uMixRoutineSelectionES;
+#endif
+
+#define OurImageLoad(img, p) \
+    (vec4(imageLoad(img,p))/65535.)
+
+#define OurImageStore(img, p, color) \
+    imageStore(img,p,uvec4(color*65535.))
+
 const vec4 p1_22=vec4(1.0/2.2,1.0/2.2,1.0/2.2,1.0/2.2);
 const vec4 p22=vec4(2.2,2.2,2.2,2.2);
 const float WGM_EPSILON=0.001f;
@@ -70,15 +88,15 @@ void rgb_to_spectral (vec3 rgb, out float spectral_[10]) {
     float r = rgb.r * offset + WGM_EPSILON;
     float g = rgb.g * offset + WGM_EPSILON;
     float b = rgb.b * offset + WGM_EPSILON;
-    float spec_r[10] = float[10](0,0,0,0,0,0,0,0,0,0); for (int i=0; i < 10; i++) {spec_r[i] = spectral_r_small[i] * r;}
-    float spec_g[10] = float[10](0,0,0,0,0,0,0,0,0,0); for (int i=0; i < 10; i++) {spec_g[i] = spectral_g_small[i] * g;}
-    float spec_b[10] = float[10](0,0,0,0,0,0,0,0,0,0); for (int i=0; i < 10; i++) {spec_b[i] = spectral_b_small[i] * b;}
+    float spec_r[10] = float[10](0.,0.,0.,0.,0.,0.,0.,0.,0.,0.); for (int i=0; i < 10; i++) {spec_r[i] = spectral_r_small[i] * r;}
+    float spec_g[10] = float[10](0.,0.,0.,0.,0.,0.,0.,0.,0.,0.); for (int i=0; i < 10; i++) {spec_g[i] = spectral_g_small[i] * g;}
+    float spec_b[10] = float[10](0.,0.,0.,0.,0.,0.,0.,0.,0.,0.); for (int i=0; i < 10; i++) {spec_b[i] = spectral_b_small[i] * b;}
     for (int i=0; i<10; i++) {spectral_[i] = spec_r[i] + spec_g[i] + spec_b[i];}
 }
 vec3 spectral_to_rgb (float spectral[10]) {
     float offset = 1.0 - WGM_EPSILON;
     // We need this tmp. array to allow auto vectorization. <-- How about on GPU?
-    float tmp[3] = float[3](0,0,0);
+    float tmp[3] = float[3](0.,0.,0.);
     for (int i=0; i<10; i++) {
         tmp[0] += T_MATRIX_SMALL[i] * spectral[i];
         tmp[1] += T_MATRIX_SMALL[10+i] * spectral[i];
@@ -147,34 +165,53 @@ float SampleCanvas(vec2 U, vec2 dir,float rfac, float force, float gunky){
     float scrape=dot(normalize(vz),vec3(-normalize(dir).xy,0))*mix(0.3,1.,useforce);
     float top=h-(1.-pow(useforce,1.5)*2.); float tophard=smoothstep(0.4,0.6,top);
     float fac=(gunky>=0.)?mix(mix(1.,top,gunky),tophard,gunky):mix(1.,1.-h,-gunky*0.8);
-    fac=max(fac,scrape*clamp(gunky,0,1));
+    fac=max(fac,scrape*clamp(gunky,0.,1.));
     fac=clamp(fac,0.,1.);
     fac*=rfac;
     return mix(rfac,fac,uCanvasFactor);
 }
 
+#ifndef OUR_GLES
 subroutine vec4 MixRoutines(vec4 a, vec4 b, float fac_a);
-subroutine(MixRoutines) vec4 DoMixNormal(vec4 a, vec4 b, float fac_a){
-    return mix(a,b,1-fac_a);
+#endif
+
+#ifndef OUR_GLES
+subroutine(MixRoutines)
+#endif
+vec4 DoMixNormal(vec4 a, vec4 b, float fac_a){
+    return mix(a,b,1.0f-fac_a);
 }
-subroutine(MixRoutines) vec4 DoMixSpectral(vec4 a, vec4 b, float fac_a){
+
+#ifndef OUR_GLES
+subroutine(MixRoutines)
+#endif
+vec4 DoMixSpectral(vec4 a, vec4 b, float fac_a){
     vec4 result = vec4(0,0,0,0);
-    result.a=mix(a.a,b.a,1-fac_a);
-    float spec_a[10] = {0,0,0,0,0,0,0,0,0,0}; rgb_to_spectral(a.rgb, spec_a);
-    float spec_b[10] = {0,0,0,0,0,0,0,0,0,0}; rgb_to_spectral(b.rgb, spec_b);
-    float spectralmix[10] = {0,0,0,0,0,0,0,0,0,0};
-    for (int i=0; i < 10; i++) { spectralmix[i] = pow(spec_a[i], fac_a) * pow(spec_b[i], 1-fac_a); }
+    result.a=mix(a.a,b.a,1.0f-fac_a);
+    float spec_a[10] = float[10](0.,0.,0.,0.,0.,0.,0.,0.,0.,0.); rgb_to_spectral(a.rgb, spec_a);
+    float spec_b[10] = float[10](0.,0.,0.,0.,0.,0.,0.,0.,0.,0.); rgb_to_spectral(b.rgb, spec_b);
+    float spectralmix[10] = float[10](0.,0.,0.,0.,0.,0.,0.,0.,0.,0.);
+    for (int i=0; i < 10; i++) { spectralmix[i] = pow(spec_a[i], fac_a) * pow(spec_b[i], 1.0f-fac_a); }
     result.rgb=spectral_to_rgb(spectralmix);
     return result;
 }
+
+#ifdef OUR_GLES
+vec4 uMixRoutineSelection(vec4 a, vec4 b, float fac_a){
+    if(uMixRoutineSelectionES==0){ return DoMixNormal(a,b,fac_a); }
+    else{ return DoMixSpectral(a,b,fac_a); }
+}
+#else
 subroutine uniform MixRoutines uMixRoutineSelection;
+#endif
+
 vec4 spectral_mix(vec4 a, vec4 b, float fac_a){
     return uMixRoutineSelection(a,b,fac_a);
 }
 vec4 spectral_mix_unpre(vec4 colora, vec4 colorb, float fac){
-    vec4 ca=(colora.a==0)?colora:vec4(colora.rgb/colora.a,colora.a);
-    vec4 cb=(colorb.a==0)?colorb:vec4(colorb.rgb/colorb.a,colorb.a);
-    float af=colora.a*(1-fac);
+    vec4 ca=(colora.a==0.0f)?colora:vec4(colora.rgb/colora.a,colora.a);
+    vec4 cb=(colorb.a==0.0f)?colorb:vec4(colorb.rgb/colorb.a,colorb.a);
+    float af=colora.a*(1.0f-fac);
     float aa=af/(af+fac*colorb.a+0.000001);
     vec4 result=spectral_mix(ca,cb,aa);
     result.a=mix(colora.a,colorb.a,fac);
@@ -191,21 +228,21 @@ float brightness(vec4 color) {
     return color.r*0.2126+color.b*0.7152+color.g*0.0722;
 }
 vec4 mix_over(vec4 colora, vec4 colorb){
-    vec4 a=(colora.a==0)?colora:vec4(colora.rgb/colora.a,colora.a);
-    vec4 b=(colorb.a==0)?colorb:vec4(colorb.rgb/colorb.a,colorb.a);
-    vec4 m=vec4(0,0,0,0); float aa=colora.a/(colora.a+(1-colora.a)*colorb.a+0.0001);
+    vec4 a=(colora.a==0.0f)?colora:vec4(colora.rgb/colora.a,colora.a);
+    vec4 b=(colorb.a==0.0f)?colorb:vec4(colorb.rgb/colorb.a,colorb.a);
+    vec4 m=vec4(0,0,0,0); float aa=colora.a/(colora.a+(1.0f-colora.a)*colorb.a+0.0001);
     m=spectral_mix(a,b,aa);
-    m.a=colora.a+colorb.a*(1-colora.a);
+    m.a=colora.a+colorb.a*(1.0f-colora.a);
     m=vec4(m.rgb*m.a,m.a);
     return m;
 }
 int dab(float d, vec2 fpx, vec4 color, float size, float hardness, float smudge, vec4 smudge_color, vec4 last_color, out vec4 final){
     vec4 cc=color;
-    float fac=1-pow(d/size,1+1/(1-hardness+1e-4));
+    float fac=1.0f-pow(d/size,1.0f+1.0f/(1.0f-hardness+1e-4));
     float canvas=SampleCanvas(fpx,uBrushDirection,fac,uBrushForce,uBrushGunkyness);
-    cc.a=color.a*canvas*(1-smudge); cc.rgb=cc.rgb*cc.a;
+    cc.a=color.a*canvas*(1.0f-smudge); cc.rgb=cc.rgb*cc.a;
     float erasing=float(uBrushErasing);
-    cc=cc*(1-erasing);
+    cc=cc*(1.0f-erasing);
 
     // this looks better than the one commented out below
     vec4 c2=spectral_mix_unpre(last_color,smudge_color,smudge*fac*color.a*canvas);
@@ -213,20 +250,20 @@ int dab(float d, vec2 fpx, vec4 color, float size, float hardness, float smudge,
     //vec4 c2=mix_over(cc,last_color);
     //c2=spectral_mix_unpre(c2,smudge_color,smudge*fac*color.a*canvas);
 
-    c2=spectral_mix_unpre(c2,c2*(1-fac*color.a),erasing*canvas);
+    c2=spectral_mix_unpre(c2,c2*(1.0f-fac*color.a),erasing*canvas);
     final=c2;
     return 1;
 }
 
 #ifndef saturate
-#define saturate(v) clamp(v, 0, 1)
+#define saturate(v) clamp(v, 0., 1.)
 #endif
 const float HCV_EPSILON = 1e-10;
 const float HCY_EPSILON = 1e-10;
 vec3 hue_to_rgb(float hue){
-    float R = abs(hue * 6 - 3) - 1;
-    float G = 2 - abs(hue * 6 - 2);
-    float B = 2 - abs(hue * 6 - 4);
+    float R = abs(hue * 6. - 3.) - 1.;
+    float G = 2. - abs(hue * 6. - 2.);
+    float B = 2. - abs(hue * 6. - 4.);
     return saturate(vec3(R,G,B));
 }
 vec3 hcy_to_rgb(vec3 hcy){
@@ -234,7 +271,7 @@ vec3 hcy_to_rgb(vec3 hcy){
     vec3 RGB = hue_to_rgb(hcy.x);
     float Z = dot(RGB, HCYwts);
     if (hcy.z < Z) { hcy.y *= hcy.z / Z; }
-    else if (Z < 1) { hcy.y *= (1 - hcy.z) / (1 - Z); }
+    else if (Z < 1.) { hcy.y *= (1. - hcy.z) / (1. - Z); }
     return (RGB - Z) * hcy.y + hcy.z;
 }
 vec3 rgb_to_hcv(vec3 rgb){
@@ -242,7 +279,7 @@ vec3 rgb_to_hcv(vec3 rgb){
     vec4 P = (rgb.g < rgb.b) ? vec4(rgb.bg, -1.0, 2.0/3.0) : vec4(rgb.gb, 0.0, -1.0/3.0);
     vec4 Q = (rgb.r < P.x) ? vec4(P.xyw, rgb.r) : vec4(rgb.r, P.yzx);
     float C = Q.x - min(Q.w, Q.y);
-    float H = abs((Q.w - Q.y) / (6 * C + HCV_EPSILON) + Q.z);
+    float H = abs((Q.w - Q.y) / (6. * C + HCV_EPSILON) + Q.z);
     return vec3(H, C, Q.x);
 }
 vec3 rgb_to_hcy(vec3 rgb){
@@ -252,67 +289,98 @@ vec3 rgb_to_hcy(vec3 rgb){
     float Y = dot(rgb, HCYwts);
     float Z = dot(hue_to_rgb(HCV.x), HCYwts);
     if (Y < Z) { HCV.y *= Z / (HCY_EPSILON + Y); }
-    else { HCV.y *= (1 - Z) / (HCY_EPSILON + 1 - Y); }
+    else { HCV.y *= (1. - Z) / (HCY_EPSILON + 1. - Y); }
     return vec3(HCV.x, HCV.y, Y);
 }
 
-
+#ifndef OUR_GLES
 subroutine void BrushRoutines();
-subroutine(BrushRoutines) void DoDabs(){
+#endif
+
+#ifndef OUR_GLES
+subroutine(BrushRoutines)
+#endif
+void DoDabs(){
     ivec2 px = ivec2(gl_GlobalInvocationID.xy)+uBrushCorner;
     if(px.x<0||px.y<0||px.x>1024||px.y>1024) return;
     vec2 fpx=vec2(px),origfpx=fpx;
     fpx=uBrushCenter+rotate(fpx-uBrushCenter,uBrushAngle);
-    fpx.x=uBrushCenter.x+(fpx.x-uBrushCenter.x)*(1+uBrushSlender);
+    fpx.x=uBrushCenter.x+(fpx.x-uBrushCenter.x)*(1.+uBrushSlender);
     float dd=distance(fpx,uBrushCenter); if(dd>uBrushSize) return;
-    vec4 dabc=imageLoad(img, px);
-    vec4 smudgec=pow(spectral_mix_unpre(pow(imageLoad(smudge_buckets,ivec2(1,0)),p1_22),pow(imageLoad(smudge_buckets,ivec2(0,0)),p1_22),uBrushRecentness),p22);
+    vec4 dabc=OurImageLoad(img, px);
+    vec4 smudgec=pow(spectral_mix_unpre(pow(OurImageLoad(smudge_buckets,ivec2(1,0)),p1_22),pow(OurImageLoad(smudge_buckets,ivec2(0,0)),p1_22),uBrushRecentness),p22);
     vec4 final_color;
     dab(dd,origfpx,uBrushColor,uBrushSize,uBrushHardness,uBrushSmudge,smudgec,dabc,final_color);
-    if(final_color.a>0){
+    if(final_color.a>0.){
         if(uBrushMix==0){ dabc=final_color; }
         else if(uBrushMix==1){ dabc.rgb=final_color.rgb/final_color.a*dabc.a;}
         else if(uBrushMix==2){ vec3 xyz=rgb_to_hcy(dabc.rgb); xyz.xy=rgb_to_hcy(final_color.rgb).xy; dabc.rgb=hcy_to_rgb(xyz); }
         else if(uBrushMix==3){ dabc.rgb=dabc.rgb+final_color.rgb*0.01;dabc.a=dabc.a*0.99+final_color.a*0.01; }
-        imageStore(img, px, dabc);
+        OurImageStore(img, px, dabc);
     }
 }
-subroutine(BrushRoutines) void DoSample(){
+
+#ifndef OUR_GLES
+subroutine(BrushRoutines)
+#endif
+void DoSample(){
     ivec2 p=ivec2(gl_GlobalInvocationID.xy);
     int DoSample=1; vec4 color;
     if(p.y==0){
         vec2 sp=round(vec2(sin(float(p.x)),cos(float(p.x)))*uBrushSize);
         ivec2 px=ivec2(sp)+uBrushCorner; if(px.x<0||px.y<0||px.x>=1024||px.y>=1024){ DoSample=0; }
         if(DoSample!=0){
-            ivec2 b=uBrushCorner; if(b.x>=0&&b.y>=0&&b.x<1024&&b.y<1024){ imageStore(smudge_buckets,ivec2(128+32,0),imageLoad(img, b)); }
-            color=imageLoad(img, px);
-            imageStore(smudge_buckets,ivec2(p.x+128,0),color);
+            ivec2 b=uBrushCorner; if(b.x>=0&&b.y>=0&&b.x<1024&&b.y<1024){ OurImageStore(smudge_buckets,ivec2(128+32,0),OurImageLoad(img, b)); }
+            color=OurImageLoad(img, px);
+            OurImageStore(smudge_buckets,ivec2(p.x+128,0),color);
         }
     }else{DoSample=0;}
     memoryBarrier();barrier(); if(DoSample==0) return;
     if(uBrushErasing==0 || p.x!=0) return;
-    color=vec4(0,0,0,0); for(int i=0;i<32;i++){ color=color+imageLoad(smudge_buckets, ivec2(i+128,0)); }
-    color=spectral_mix_unpre(color/32,imageLoad(smudge_buckets, ivec2(128+32,0)),0.6*(1-uBrushColor.a)); vec4 oldcolor=imageLoad(smudge_buckets, ivec2(0,0));
-    imageStore(smudge_buckets,ivec2(1,0),uBrushErasing==2?color:oldcolor);
-    imageStore(smudge_buckets,ivec2(0,0),color);
+    color=vec4(0.,0.,0.,0.); for(int i=0;i<32;i++){ color=color+OurImageLoad(smudge_buckets, ivec2(i+128,0)); }
+    color=spectral_mix_unpre(color/32.,OurImageLoad(smudge_buckets, ivec2(128+32,0)),0.6*(1.0f-uBrushColor.a)); vec4 oldcolor=OurImageLoad(smudge_buckets, ivec2(0,0));
+    OurImageStore(smudge_buckets,ivec2(1,0),uBrushErasing==2?color:oldcolor);
+    OurImageStore(smudge_buckets,ivec2(0,0),color);
+}
+
+#ifdef OUR_GLES
+void uBrushRoutineSelection(){
+    if(uBrushRoutineSelectionES==0){ DoDabs(); }
+    else{ DoSample(); }
 }
+#else
 subroutine uniform BrushRoutines uBrushRoutineSelection;
+#endif
+
 void main() {
     uBrushRoutineSelection();
 }
 )";
 
-const char OUR_COMPOSITION_SHADER[]=R"(#version 430
+const char OUR_COMPOSITION_SHADER[]=R"(
+precision highp uimage2D;
+precision highp float;
+precision highp int;
 layout(local_size_x = 32, local_size_y = 32, local_size_z = 1) in;
-layout(rgba16, binding = 0) uniform image2D top;
-layout(rgba16, binding = 1) uniform image2D bottom;
+layout(rgba16ui, binding = 0) uniform uimage2D top;
+layout(rgba16ui, binding = 1) uniform uimage2D bottom;
 uniform int uBlendMode;
 uniform float uAlphaTop;
 uniform float uAlphaBottom;
+
+//#define OurImageLoad imageLoad
+//#define OurImageStore imageStore
+
+#define OurImageLoad(img, p) \
+    (vec4(imageLoad(img,p))/65535.)
+
+#define OurImageStore(img, p, color) \
+    imageStore(img,p,uvec4(color*65535.))
+
 vec4 mix_over(vec4 colora, vec4 colorb){
     colora=colora*uAlphaTop/uAlphaBottom;
-    vec4 c; c.a=colora.a+colorb.a*(1-colora.a);
-    c.rgb=(colora.rgb+colorb.rgb*(1-colora.a));
+    vec4 c; c.a=colora.a+colorb.a*(1.0f-colora.a);
+    c.rgb=(colora.rgb+colorb.rgb*(1.0f-colora.a));
     return c;
 }
 vec4 add_over(vec4 colora, vec4 colorb){
@@ -321,9 +389,9 @@ vec4 add_over(vec4 colora, vec4 colorb){
 }
 void main() {
     ivec2 px=ivec2(gl_GlobalInvocationID.xy);
-    vec4 c1=imageLoad(top,px); vec4 c2=imageLoad(bottom,px);
+    vec4 c1=OurImageLoad(top,px); vec4 c2=OurImageLoad(bottom,px);
     vec4 c=(uBlendMode==0)?mix_over(c1,c2):add_over(c1,c2);
-    imageStore(bottom,px,c);
-    imageStore(top,px,vec4(0,0,0,0));
+    OurImageStore(bottom,px,c);
+    OurImageStore(top,px,vec4(1.));
 }
 )";