Android Open Source - abstract-art Shader Factory






From Project

Back to project page abstract-art.

License

The source code is released under:

GNU General Public License

If you think the Android project abstract-art listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

package net.georgewhiteside.android.abstractart;
//from   w  w  w.  j a  v  a  2  s  . c  om
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

import android.content.Context;
import android.content.SharedPreferences;
import android.opengl.GLES20;
import android.preference.PreferenceManager;
import android.util.Log;

//import com.badlogic.gdx.backends.android.AndroidGL20;

// TODO: cache programs in a hashmap or something

/**
 * Constructs the smallest shader possible for the given <code>BattleBackground</code>. This is a terrible mess, but it's
 * a necessary sacrifice to get the best performance out of the mobile platform. (There are quiet whisperings about the
 * mobile shader compilers sucking.)
 * @author George
 *
 */
public class ShaderFactory
{
  private Context context;
  private static final String TAG = "shader";
  
  private SharedPreferences sharedPreferences;
  
  private boolean knobMonolithic = false;  // this option is kept for development reasons
  
  private static final String[] bgPrefix = {"bg3_", "bg4_"};
  
  private String spriteVertexShader =
    "uniform mat4 uMVPMatrix;\n" +
    "attribute vec4 a_position;\n" +
    "attribute vec2 a_texCoord;\n" +
    "varying vec2 v_texCoord;\n" +
    "void main() {\n" +
    "    gl_Position = uMVPMatrix * a_position;\n" +
    "    v_texCoord = a_texCoord;\n" +
    "}\n";
  
  private String spriteFragmentShader =
    "precision mediump float;\n" +
    "varying vec2 v_texCoord;\n" +
    "uniform sampler2D s_texture;\n" +
    "\n" +
    "void main()\n" +
    "{\n" +
    "    vec4 color = texture2D(s_texture, v_texCoord);\n" +
    "    gl_FragColor = color;\n" +
    "}\n";

  private String vertexShader =
      "uniform mat4 uMVPMatrix;\n" +
      "attribute vec4 a_position;\n" +
      "attribute vec2 a_texCoord;\n" +
      "varying vec2 v_texCoord;\n" +
      "void main() {\n" +
      "    gl_Position = uMVPMatrix * a_position;\n" +
      "    v_texCoord = a_texCoord;\n" +
      "}\n";
  
  private String fragmentHeader =
    "precision highp float;\n" +

    "varying vec2 v_texCoord;\n" +
    "uniform sampler2D bg3_texture;\n" +
    "uniform sampler2D bg4_texture;\n" +
    "uniform sampler2D s_palette;\n" +
  
    "uniform vec2 resolution;\n" +
  
    "uniform int bg3_dist_type;\n" +
    "uniform vec3 bg3_dist;\n" +
    "uniform vec4 bg3_palette;\n" +
    "uniform vec2 bg3_scroll;\n" +
    "uniform float bg3_compression;\n" +
    "uniform float bg3_rotation;\n" +
  
    "uniform int bg4_dist_type;\n" +
    "uniform vec3 bg4_dist;\n" +
    "uniform vec4 bg4_palette;\n" +
    "uniform vec2 bg4_scroll;\n" +
    "uniform float bg4_compression;\n" +
    "uniform float bg4_rotation;\n" +
  
    "#define AMPL   0\n" +
    "#define FREQ   1\n" +
    "#define SPEED  2\n" +
  
    "#define BEGIN1 0\n" +
    "#define END1   1\n" +
    "#define BEGIN2 2\n" +
    "#define END2   3\n";
  
  public ShaderFactory(Context context)
  {
    this.context = context;
    sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
  }
  
  /**
   * WARNING: THIS IS MOSTLY HERE AS A REMINDER.
   * 
   * <p>Inconsistencies between mobile chipsets make this difficult-to-impossible
   * to realize, depending on how exactly you try to do it. Saving the compiled
   * shader binaries isn't universally supported, so that's out. Simply caching the
   * program reference handles *might* work, but I don't know enough about how long
   * programs are kept in RAM, or how many are, or if more than one is kept, and knowing
   * exactly when a program IS cleared (from basic testing on my phone it seems to be
   * when the GL context is destroyed, but that's only a tiny piece of the larger
   * puzzle), and whether these behaviors vary across chipsets, and so on. Way too
   * many unknowns here.
   * 
   * <p>Returns a value guaranteed to uniquely identify the shader's functions. This
   * should be used to cache linked shader program IDs, preventing unnecessary
   * compiling and linking on every background change for a given GL instance.
   * 
   * @return a collision-free hash value identifying the shader functions
   */
  public int getShaderSignature(BattleBackground bbg)
  {
    /*
     * The value is determined by checking which effects are used and setting
     * corresponding bit fields or subfields in an integer. The exact method doesn't
     * matter, so long as every possible combination of shader settings generates
     * a unique value. It doesn't even need to generate the same value for a given
     * configuration between application updates.
     */
    return 0;
  }
  
  public int getShader(BattleBackground bbg, float letterBoxSize)
  {
    String fragmentShader = "";
    boolean enablePaletteEffects = sharedPreferences.getBoolean("enablePaletteEffects", false); // SharedPreference
    
    if(knobMonolithic)
    {
      fragmentShader = readTextFile(R.raw.distortion_frag);
    }
    else
    {
      // TODO: build this shader with a StringBuilder instead
    
      fragmentShader += fragmentHeader;
      
      fragmentShader +=
        "void main()\n" +
        "{\n" +
        "    float y = v_texCoord.y * 256.0;\n" + 
        "    if(y < " + letterBoxSize + " || y > 224.0 - " + letterBoxSize + ") { gl_FragColor.rgba = vec4(0.0, 0.0, 0.0, 1.0); } else {";
      
      // iterate over both layers and construct the smallest shader possible
      
      for(int i = 0; i < 2; i++)
      {
        Layer layer = i == 0 ? bbg.bg3 : bbg.bg4;
        
        // we always want the bottom layer, but skip the top layer if it's null
        if(layer == bbg.bg3 || layer == bbg.bg4 && layer.getIndex() != 0)
        {
          String id = bgPrefix[i];
          fragmentShader += "vec2 " + id + "offset = vec2(0.0);\n";
          
          // distortion
          
          // TODO: probe battle background layer to determine if multiple distortions are used, and which ones; if more than one, support them
          
          int numberOfEffects = layer.distortion.getNumberOfEffects();
          //numberOfEffects = 0;
          
          if(numberOfEffects != 0)
          {
            fragmentShader += "float " + id + "distortion_offset = (" + id + "dist[AMPL] * sin(" + id + "dist[FREQ] * y + " + id + "dist[SPEED]));\n";
            
            if(numberOfEffects == 1)
            {
              switch(layer.distortion.getType())
              {
                default:
                  break;
                  
                case 1:
                  fragmentShader += id + "offset.x = " + id + "distortion_offset;\n";
                  break;
                  
                case 2:
                  fragmentShader += id + "offset.x = floor(mod(y, 2.0)) == 0.0 ? " + id + "distortion_offset : -" + id + "distortion_offset;\n";
                  break;
                  
                case 3:
                  fragmentShader += id + "offset.y = mod(" + id + "distortion_offset, resolution.y);\n";
                  break;
                  
                case 4:
                  fragmentShader +=  id + "offset.x = floor(mod(y, 2.0)) == 0.0 ? " + id + "distortion_offset : -" + id + "distortion_offset;\n" +
                            id + "offset.x += (y * (" + id + "compression / resolution.y));\n";
                  break;
              }
            }
            else // 2 or more effects are used
            {
              fragmentShader +=  "if(" + id + "dist_type == 1) {\n" +
                          id + "offset.x = " + id + "distortion_offset;\n" +
                  
                        "} else if(" + id + "dist_type == 2) {\n" +
                          id + "offset.x = floor(mod(y, 2.0)) == 0.0 ? " + id + "distortion_offset : -" + id + "distortion_offset;\n" +
                        
                        "} else if(" + id + "dist_type == 3) {\n" +
                          id + "offset.y = mod(" + id + "distortion_offset, resolution.y);\n" +
                        "}\n" +
                        
                        "if(" + id + "dist_type == 4) {\n" +
                          id + "offset.x = floor(mod(y, 2.0)) == 0.0 ? " + id + "distortion_offset : -" + id + "distortion_offset;\n" +
                          id + "offset.x += (y * (" + id + "compression / resolution.y));\n" +
                        "}\n";
            }
          }

          
          // vertical compression
          
          if(layer.distortion.getType() != 4 && layer.distortion.getCompression() != 0 && layer.distortion.getCompressionDelta() != 0)
          {
            fragmentShader += id + "offset.y += (y * (" + id + "compression / resolution.y));\n";
          }
          
          // layer scrolling
          
          if(  layer.translation.getHorizontalAcceleration() != 0 || layer.translation.getHorizontalVelocity() != 0 ||
            layer.translation.getVerticalAcceleration() != 0 || layer.translation.getVerticalVelocity() != 0 )
          {
            fragmentShader += id + "offset += bg3_scroll;\n";
          }
          
          // divide offset down to correct range
          
          fragmentShader += id + "offset /= resolution;\n";
          
          if(enablePaletteEffects == true) {
            
            // get palette index
            
            fragmentShader += "float " + id + "index = texture2D(" + id + "texture, " + id + "offset + v_texCoord).r;\n";
            fragmentShader += id + "index *= 256.0;\n";
            
            // make sure index is proper (probably not necesary, but I'm paranoid around all this float math)
            
            //fragmentShader += id + "index = floor(" + id + "index + 0.5);\n";
            
            // add palette cycling code if required
          
            switch(layer.getPaletteCycleType())
            {
              default:
                // no palette cycling
                break;
                
              case 1:
                // rotate palette subrange left
                fragmentShader +=
                  "if(" + id + "index >= " + id + "palette[BEGIN1] - 0.5 && " + id + "index <= " + id + "palette[END1] + 0.5)\n" +
                  "{\n" +
                  "    float range = " + id + "palette[END1] - " + id + "palette[BEGIN1];\n" +
                  "    " + id + "index = " + id + "index - " + id + "rotation;\n" +
                  "    if(" + id + "index < " + id + "palette[BEGIN1]) {\n" +
                  "        " + id + "index = " + id + "palette[END1] + 1.0 - abs(" + id + "palette[BEGIN1] - " + id + "index);\n" +
                  "    }\n" +
                  "}\n";
                break;
              
              case 2:
                // rotate two palette subranges left
                fragmentShader +=
                  "if(" + id + "index >= " + id + "palette[BEGIN1] - 0.5 && " + id + "index <= " + id + "palette[END1] + 0.5)\n" +
                  "{\n" +
                  "    float range = " + id + "palette[END1] - " + id + "palette[BEGIN1];\n" +
                  "    " + id + "index = " + id + "index - " + id + "rotation;\n" +
                  "    if(" + id + "index < " + id + "palette[BEGIN1]) {\n" +
                  "        " + id + "index = " + id + "palette[END1] + 1.0 - abs(" + id + "palette[BEGIN1] - " + id + "index);\n" +
                  "    }\n" +
                  "}\n" +
                  "else if(" + id + "index >= " + id + "palette[BEGIN2] - 0.5 && " + id + "index <= " + id + "palette[END2] + 0.5)\n" +
                  "{\n" +
                  "    float range = " + id + "palette[END2] - " + id + "palette[BEGIN2];\n" +
                  "    " + id + "index = " + id + "index - " + id + "rotation;\n" +
                  "    if(" + id + "index < " + id + "palette[BEGIN2]) {\n" +
                  "        " + id + "index = " + id + "palette[END2] + 1.0 - abs(" + id + "palette[BEGIN2] - " + id + "index);\n" +
                  "    }\n" +
                  "}\n";
                break;
              
              case 3:
                // "mirror rotate" palette subrange left (indices cycle like a triangle waveform)
                fragmentShader +=
                  "if(" + id + "index >= " + id + "palette[BEGIN1] - 0.5 && " + id + "index <= " + id + "palette[END1] + 0.5)\n" +
                  "{\n" +
                  "    float range = " + id + "palette[END1] - " + id + "palette[BEGIN1];\n" +
                  "    " + id + "index = " + id + "index + " + id + "rotation - " + id + "palette[BEGIN1];\n" +
                  "    range = floor(range + 0.5);\n" +
                  "    " + id + "index = floor(" + id + "index + 0.5);\n" +
                  "    if(" + id + "index > range * 2.0 + 1.0) {\n" +
                  "        " + id + "index = " + id + "palette[BEGIN1] + (" + id + "index - ((range * 2.0) + 2.0));\n" +
                  "    }\n" +
                  "    else if(" + id + "index > range) {\n" +
                  "        " + id + "index = " + id + "palette[END1] - (" + id + "index - (range + 1.0));\n" +
                  "    }\n" +
                  "    else {\n" +
                  "        " + id + "index += " + id + "palette[BEGIN1];\n" +
                  "    }\n" +
                  "}\n";
                break;
            }
          
            // divide color index down into texture lookup range
            
            fragmentShader += id + "index /= 16.0;\n";
            
            // actual palette color lookup
            
            float paletteRow = layer == bbg.bg3 ? 0.0f : 1.0f;
            fragmentShader += "vec4 " + id + "color = texture2D(s_palette, vec2(" + id + "index, " + paletteRow + " / 16.0));\n";
          } else {
            fragmentShader += "vec4 " + id + "color = texture2D(" + id + "texture, " + id + "offset + v_texCoord);\n";
          }
          
        }
      }
      
      // output blending
      
      if(bbg.bg4.getIndex() != 0)
      {
        // both layers are active; perform an alpha blend with BG4 at 50% opacity
        fragmentShader += 
          "bg4_color.a *= 0.5;\n" +
          "gl_FragColor.rgb = bg4_color.rgb * bg4_color.a + bg3_color.rgb * (1.0 - bg4_color.a);\n" +
          "gl_FragColor.a = 1.0;\n";
      }
      else
      {
        fragmentShader +=
          "gl_FragColor.rgb = bg3_color.rgb;\n" +
          "gl_FragColor.a = 1.0;\n";
      }
    
      // ...aaand the final curly brace:
      
      fragmentShader += "}}\n";
    }
    
    //Log.d("shader", vertexShader);
    //Log.d("shader", fragmentShader);
    
    int result = createProgram(vertexShader, fragmentShader);
    if(result == 0) { throw new RuntimeException("[...] shader compilation failed"); }
    return result;
  }
  
  public int getSpriteShader()
  {
    int result = createProgram(spriteVertexShader, spriteFragmentShader);
    if(result == 0) { throw new RuntimeException("[...] shader compilation failed"); }
    return result;
  }
  
  private int createProgram(String vertexSource, String fragmentSource)
  {
    // courtesy of android.developer.com
    int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
    if(vertexShader == 0)
    {
      return 0;
    }

    int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
    if(pixelShader == 0)
    {
      return 0;
    }

    int program = GLES20.glCreateProgram();
    if(program != 0)
    {
      GLES20.glAttachShader(program, vertexShader);
      checkGlError("glAttachShader");
      GLES20.glAttachShader(program, pixelShader);
      checkGlError("glAttachShader");
      GLES20.glLinkProgram(program);
      int[] linkStatus = new int[1];
      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;
      }
    }
    //Log.i(TAG, "Program link status: " + GLES20.glGetProgramInfoLog(program));
    //Log.i(TAG, "shader program handle: " + program);
    return program;
  }
  
  private int loadShader(int shaderType, String source)
  {
    
//  int loadShader(int type, String code) {
//    int shader = GLES20.glCreateShader(type);
//    GLES20.glShaderSource(shader, code);
//    GLES20.glCompileShader(shader);
//    return shader;
//  }
    
    int shader = GLES20.glCreateShader(shaderType);
    if(shader != 0)
    {
      GLES20.glShaderSource(shader, source);
      GLES20.glCompileShader(shader);
      int[] compiled = new int[1];
      GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
      if(compiled[0] == 0 && shaderType != GLES20.GL_VERTEX_SHADER)
      {
        Log.e(TAG, "Could not compile shader " + shaderType + ":");

        /*
         * Apparently, glGetShaderInfoLog is actually broken, which is... great. The next line does nothing.
         * see http://stackoverflow.com/questions/4588800/glgetshaderinfolog-returns-empty-string-android
         */

        Log.e(TAG, GLES20.glGetShaderInfoLog(shader));
        
        GLES20.glDeleteShader(shader);
        shader = 0;
      }
    } else {
      Log.e(TAG, "glCreateShader() failed; no opengl context");
    }
    
    /*String type = "";
    switch(shaderType)
    {
    case GLES20.GL_FRAGMENT_SHADER:
      type = "Fragment";
      break;
    case GLES20.GL_VERTEX_SHADER:
      type = "Vertex";
      break;
    }
    Log.i(TAG, type + " shader compile status: " + GLES20.glGetShaderInfoLog(shader));*/
    return shader;
  }
  
  private void checkGlError(String op)
  {
    /* from developer.android.com */
    int error;
    while((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR)
    {
      Log.e(TAG, op + ": glError " + error);
      throw new RuntimeException(op + ": glError " + error);
    }
  }
  
  private String readTextFile(final int resourceId)
  {
    /* method lifted from learnopengles.com */
    final InputStream inputStream = context.getResources().openRawResource(resourceId);
    final InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
    final BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
 
    String nextLine;
    final StringBuilder body = new StringBuilder();
     
    try
    {
      while ((nextLine = bufferedReader.readLine()) != null)
      {
        body.append(nextLine);
        body.append('\n');
      }
    }
    catch (IOException e)
    {
      e.printStackTrace();
      return null;
    }
    
    return body.toString();
  }
  
}




Java Source Code List

net.georgewhiteside.android.abstractart.AbstractArt.java
net.georgewhiteside.android.abstractart.BattleBackground.java
net.georgewhiteside.android.abstractart.BattleGroup.java
net.georgewhiteside.android.abstractart.Cache.java
net.georgewhiteside.android.abstractart.Distortion.java
net.georgewhiteside.android.abstractart.Enemy.java
net.georgewhiteside.android.abstractart.FPSCounter.java
net.georgewhiteside.android.abstractart.GLOffscreenSurface.java
net.georgewhiteside.android.abstractart.ImageLoader.java
net.georgewhiteside.android.abstractart.Layer.java
net.georgewhiteside.android.abstractart.Renderer.java
net.georgewhiteside.android.abstractart.RomUtil.java
net.georgewhiteside.android.abstractart.ServiceDialog.java
net.georgewhiteside.android.abstractart.Settings.java
net.georgewhiteside.android.abstractart.ShaderFactory.java
net.georgewhiteside.android.abstractart.Translation.java
net.georgewhiteside.android.abstractart.UniformGridView.java
net.georgewhiteside.android.abstractart.Wallpaper.java
net.georgewhiteside.android.abstractart.settings.BackgroundSelector.java
net.georgewhiteside.android.abstractart.settings.ClearCachePreference.java
net.georgewhiteside.android.abstractart.settings.CreateImageCachePreference.java
net.georgewhiteside.android.abstractart.settings.FrameRatePreference.java
net.georgewhiteside.android.abstractart.settings.ThumbnailAdapter.java
net.georgewhiteside.utility.Dimension.java
net.starmen.pkhack.HackModule.java
org.jf.GLWallpaper.GLWallpaperService.java
sheetrock.panda.changelog.ChangeLog.java