Allow fading of the entire card

Give cards an overall timestamp, separate from the texture
timestamp. This allows cards to fade in before their
texture has been set.

Bug: 3281327
Change-Id: I9405636ebcbfc2c27d777d435f3abf24265dfbe9
diff --git a/carousel/java/com/android/ex/carousel/CarouselController.java b/carousel/java/com/android/ex/carousel/CarouselController.java
index 762fa58..4b774aa 100644
--- a/carousel/java/com/android/ex/carousel/CarouselController.java
+++ b/carousel/java/com/android/ex/carousel/CarouselController.java
@@ -85,6 +85,7 @@
     private CarouselCallback mCarouselCallback;
     private float mRezInCardCount = 0.0f;
     private long mFadeInDuration = 250L;
+    private long mCardCreationFadeDuration = 0L;
     private Bitmap mDetailLoadingBitmap = Bitmap.createBitmap(
             new int[] {0}, 0, 1, 1, 1, Bitmap.Config.ARGB_4444);
     private int mDragModel = CarouselRS.DRAG_MODEL_SCREEN_DELTA;
@@ -136,6 +137,7 @@
         setLookAt(mEye, mAt, mUp);
         setRezInCardCount(mRezInCardCount);
         setFadeInDuration(mFadeInDuration);
+        setCardCreationFadeDuration(mCardCreationFadeDuration);
         setDetailLoadingBitmap(mDetailLoadingBitmap);
         setStoreConfigs(mStoreConfigs);
     }
@@ -679,7 +681,7 @@
      * until all of the cards have faded in.  Note: using large values will extend the
      * animation until all cards have faded in.
      *
-     * @param t
+     * @param t The time, in milliseconds
      */
     public void setFadeInDuration(long t) {
         mFadeInDuration = t;
@@ -689,6 +691,19 @@
     }
 
     /**
+     * This sets the duration (in ms) that a card takes to fade in when it is initially created,
+     * such as when it is added or when the application starts. The timer starts at the moment
+     * when the card is first created. Replacing a card's contents does not affect the timer.
+     * @param t The time, in milliseconds
+     */
+    public void setCardCreationFadeDuration(long t) {
+        mCardCreationFadeDuration = t;
+        if (mRenderScript != null) {
+            mRenderScript.setCardCreationFadeDuration(t);
+        }
+    }
+
+    /**
      * Tells the carousel that a touch event has started at the designated location.
      * @param x The number of pixels from the left edge that the event occurred
      * @param y The number of pixels from the top edge that the event occurred
diff --git a/carousel/java/com/android/ex/carousel/CarouselRS.java b/carousel/java/com/android/ex/carousel/CarouselRS.java
index fca1791..a9dfc89 100644
--- a/carousel/java/com/android/ex/carousel/CarouselRS.java
+++ b/carousel/java/com/android/ex/carousel/CarouselRS.java
@@ -71,7 +71,9 @@
     private ScriptField_FragmentShaderConstants_s mFSConst;
     private ScriptField_ProgramStore_s mProgramStoresCard;
     private ProgramFragment mSingleTextureFragmentProgram;
+    private ProgramFragment mSingleTextureBlendingFragmentProgram;
     private ProgramFragment mMultiTextureFragmentProgram;
+    private ProgramFragment mMultiTextureBlendingFragmentProgram;
     private ProgramVertex mVertexProgram;
     private ProgramRaster mRasterProgram;
     private Allocation[] mAllocationPool;
@@ -92,6 +94,14 @@
             "gl_FragColor = col; " +
             "}");
 
+    private static final String mSingleTextureBlendingShader = new String(
+            "varying vec2 varTex0;" +
+            "void main() {" +
+            "vec2 t0 = varTex0.xy;" +
+            "vec4 col = texture2D(UNI_Tex0, t0);" +
+            "gl_FragColor = col * UNI_overallAlpha; " +
+            "}");
+
     private static final String mMultiTextureShader = new String(
             "varying vec2 varTex0;" +
             "void main() {" +
@@ -100,6 +110,16 @@
             "vec4 col2 = texture2D(UNI_Tex1, t0);" +
             "gl_FragColor = mix(col, col2, UNI_fadeAmount);}");
 
+    private static final String mMultiTextureBlendingShader = new String(
+            "varying vec2 varTex0;" +
+            "void main() {" +
+            "vec2 t0 = varTex0.xy;" +
+            "vec4 col = texture2D(UNI_Tex0, t0);" +
+            "vec4 col2 = texture2D(UNI_Tex1, t0);" +
+            "gl_FragColor = mix(col, col2, UNI_fadeAmount) * UNI_overallAlpha;" +
+            "}"
+    );
+
     public static interface CarouselCallback {
         /**
          * Called when a card is selected
@@ -355,10 +375,25 @@
         mSingleTextureFragmentProgram.bindSampler(Sampler.CLAMP_LINEAR(mRS), 0);
 
         //
-        // Multi texture program
+        // Single texture program, plus blending
         //
         mFSConst = new ScriptField_FragmentShaderConstants_s(mRS, 1);
         mScript.bind_shaderConstants(mFSConst);
+        ProgramFragment.ShaderBuilder pfbSingleBlend = new ProgramFragment.ShaderBuilder(mRS);
+        // Specify the resource that contains the shader string
+        pfbSingleBlend.setShader(mSingleTextureBlendingShader);
+        // Tell the builder how many textures we have
+        pfbSingleBlend.setTextureCount(1);
+        // Define the constant input layout
+        pfbSingleBlend.addConstant(mFSConst.getAllocation().getType());
+        mSingleTextureBlendingFragmentProgram = pfbSingleBlend.create();
+        // Bind the source of constant data
+        mSingleTextureBlendingFragmentProgram.bindConstants(mFSConst.getAllocation(), 0);
+        mSingleTextureBlendingFragmentProgram.bindSampler(Sampler.CLAMP_LINEAR(mRS), 0);
+
+        //
+        // Multi texture program
+        //
         ProgramFragment.ShaderBuilder pfbMulti = new ProgramFragment.ShaderBuilder(mRS);
         // Specify the resource that contains the shader string
         pfbMulti.setShader(mMultiTextureShader);
@@ -372,9 +407,27 @@
         mMultiTextureFragmentProgram.bindSampler(Sampler.CLAMP_LINEAR(mRS), 0);
         mMultiTextureFragmentProgram.bindSampler(Sampler.CLAMP_LINEAR(mRS), 1);
 
+        //
+        // Multi texture program, plus blending
+        //
+        ProgramFragment.ShaderBuilder pfbMultiBlend = new ProgramFragment.ShaderBuilder(mRS);
+        // Specify the resource that contains the shader string
+        pfbMultiBlend.setShader(mMultiTextureBlendingShader);
+        // Tell the builder how many textures we have
+        pfbMultiBlend.setTextureCount(2);
+        // Define the constant input layout
+        pfbMultiBlend.addConstant(mFSConst.getAllocation().getType());
+        mMultiTextureBlendingFragmentProgram = pfbMultiBlend.create();
+        // Bind the source of constant data
+        mMultiTextureBlendingFragmentProgram.bindConstants(mFSConst.getAllocation(), 0);
+        mMultiTextureBlendingFragmentProgram.bindSampler(Sampler.CLAMP_LINEAR(mRS), 0);
+        mMultiTextureBlendingFragmentProgram.bindSampler(Sampler.CLAMP_LINEAR(mRS), 1);
+
         mScript.set_linearClamp(Sampler.CLAMP_LINEAR(mRS));
         mScript.set_singleTextureFragmentProgram(mSingleTextureFragmentProgram);
+        mScript.set_singleTextureBlendingFragmentProgram(mSingleTextureBlendingFragmentProgram);
         mScript.set_multiTextureFragmentProgram(mMultiTextureFragmentProgram);
+        mScript.set_multiTextureBlendingFragmentProgram(mMultiTextureBlendingFragmentProgram);
     }
 
     private void initProgramStore() {
@@ -801,6 +854,10 @@
         mScript.set_fadeInDuration((int)t); // TODO: Remove cast when RS supports exporting longs
     }
 
+    public void setCardCreationFadeDuration(long t) {
+        mScript.set_cardCreationFadeDuration((int)t);
+    }
+
     private Element elementForBitmap(Bitmap bitmap, Bitmap.Config defaultConfig) {
         Bitmap.Config config = bitmap.getConfig();
         if (config == null) {
diff --git a/carousel/java/com/android/ex/carousel/carousel.rs b/carousel/java/com/android/ex/carousel/carousel.rs
index 4de05d9..5c9ae5b 100644
--- a/carousel/java/com/android/ex/carousel/carousel.rs
+++ b/carousel/java/com/android/ex/carousel/carousel.rs
@@ -34,8 +34,9 @@
     int geometryState; // whether or not geometry is loaded
     int cardVisible; // not bool because of packing bug?
     int detailVisible; // not bool because of packing bug?
-    int64_t textureTimeStamp; // time when this texture was last updated, in seconds
-    int64_t detailTextureTimeStamp; // time when this texture was last updated, in seconds
+    int64_t textureTimeStamp; // time when this texture was last updated, in ms
+    int64_t detailTextureTimeStamp; // time when this texture was last updated, in ms
+    int64_t geometryTimeStamp; // time when the card itself was last updated, in ms
 } Card_t;
 
 typedef struct Ray_s {
@@ -70,6 +71,7 @@
 
 typedef struct FragmentShaderConstants_s {
     float fadeAmount;
+    float overallAlpha;
 } FragmentShaderConstants;
 
 // Request states. Used for loading 3D object properties from the Java client.
@@ -170,6 +172,7 @@
 float frictionCoeff; // how much to slow down the carousel over time
 float dragFactor; // a scale factor for how sensitive the carousel is to user dragging
 int fadeInDuration; // amount of time (in ms) for smoothly switching out textures
+int cardCreationFadeDuration; // amount of time (in ms) to fade while initially showing a card
 float rezInCardCount; // this controls how rapidly distant card textures will be rez-ed in
 float detailFadeRate; // rate at which details fade as they move into the distance
 float4 backgroundColor;
@@ -183,7 +186,9 @@
 rs_program_store programStoreBackground;
 rs_program_store programStoreDetail;
 rs_program_fragment singleTextureFragmentProgram;
+rs_program_fragment singleTextureBlendingFragmentProgram;
 rs_program_fragment multiTextureFragmentProgram;
+rs_program_fragment multiTextureBlendingFragmentProgram;
 rs_program_vertex vertexProgram;
 rs_program_raster rasterProgram;
 rs_allocation defaultTexture; // shown when no other texture is assigned
@@ -321,6 +326,7 @@
     card->detailVisible = false;
     card->textureTimeStamp = 0;
     card->detailTextureTimeStamp = 0;
+    card->geometryTimeStamp = rsUptimeMillis();
 }
 
 void createCards(int start, int total)
@@ -343,10 +349,10 @@
 }
 
 // Computes an alpha value for a card using elapsed time and constant fadeInDuration
-static float getAnimatedAlpha(int64_t startTime, int64_t currentTime)
+static float getAnimatedAlpha(int64_t startTime, int64_t currentTime, int64_t duration)
 {
     double timeElapsed = (double) (currentTime - startTime); // in ms
-    double alpha = (double) timeElapsed / fadeInDuration;
+    double alpha = duration > 0 ? (double) timeElapsed / duration : 1.0;
     return min(1.0f, (float) alpha);
 }
 
@@ -514,6 +520,7 @@
         cards[n].geometryState = STATE_LOADED;
     else
         cards[n].geometryState = STATE_INVALID;
+    cards[n].geometryTimeStamp = rsUptimeMillis();
 }
 
 void setProgramStoresCard(int n, rs_program_store programStore)
@@ -640,8 +647,11 @@
     for (int i = cardCount-1; i >= 0; i--) {
         if (cards[i].cardVisible) {
             // If this card was recently loaded, this will be < 1.0f until the animation completes
-            float animatedAlpha = getAnimatedAlpha(cards[i].textureTimeStamp, currentTime);
-            if (animatedAlpha < 1.0f) {
+            float animatedAlpha = getAnimatedAlpha(cards[i].textureTimeStamp, currentTime,
+                fadeInDuration);
+            float overallAlpha = getAnimatedAlpha(cards[i].geometryTimeStamp, currentTime,
+                cardCreationFadeDuration);
+            if (animatedAlpha < 1.0f || overallAlpha < 1.0f) {
                 stillAnimating = true;
             }
 
@@ -656,6 +666,7 @@
 
             // Set alpha for blending between the textures
             shaderConstants->fadeAmount = min(1.0f, animatedAlpha * positionAlpha);
+            shaderConstants->overallAlpha = overallAlpha;
             rsAllocationMarkDirty(rsGetAllocation(shaderConstants));
 
             // Bind the appropriate shader network.  If there's no alpha blend, then
@@ -664,15 +675,29 @@
             const bool loaded = (state == STATE_LOADED) || (state == STATE_STALE) ||
                 (state == STATE_UPDATING);
             if (shaderConstants->fadeAmount == 1.0f || shaderConstants->fadeAmount < 0.01f) {
-                rsgBindProgramFragment(singleTextureFragmentProgram);
-                rsgBindTexture(singleTextureFragmentProgram, 0,
-                        (loaded && shaderConstants->fadeAmount == 1.0f) ?
-                        cards[i].texture : loadingTexture);
+                if (overallAlpha < 1.0) {
+                    rsgBindProgramFragment(singleTextureBlendingFragmentProgram);
+                    rsgBindTexture(singleTextureBlendingFragmentProgram, 0,
+                            (loaded && shaderConstants->fadeAmount == 1.0f) ?
+                            cards[i].texture : loadingTexture);
+                } else {
+                    rsgBindProgramFragment(singleTextureFragmentProgram);
+                    rsgBindTexture(singleTextureFragmentProgram, 0,
+                            (loaded && shaderConstants->fadeAmount == 1.0f) ?
+                            cards[i].texture : loadingTexture);
+                }
             } else {
-                rsgBindProgramFragment(multiTextureFragmentProgram);
-                rsgBindTexture(multiTextureFragmentProgram, 0, loadingTexture);
-                rsgBindTexture(multiTextureFragmentProgram, 1, loaded ?
-                        cards[i].texture : loadingTexture);
+                if (overallAlpha < 1.0) {
+                    rsgBindProgramFragment(multiTextureBlendingFragmentProgram);
+                    rsgBindTexture(multiTextureBlendingFragmentProgram, 0, loadingTexture);
+                    rsgBindTexture(multiTextureBlendingFragmentProgram, 1, loaded ?
+                            cards[i].texture : loadingTexture);
+                } else {
+                    rsgBindProgramFragment(multiTextureFragmentProgram);
+                    rsgBindTexture(multiTextureFragmentProgram, 0, loadingTexture);
+                    rsgBindTexture(multiTextureFragmentProgram, 1, loaded ?
+                            cards[i].texture : loadingTexture);
+                }
             }
 
             // Draw geometry
@@ -815,7 +840,8 @@
 
                 // Compute alpha for gradually fading in details. Applied to both line and
                 // detail texture. TODO: use a separate background texture for line.
-                float animatedAlpha = getAnimatedAlpha(cards[i].detailTextureTimeStamp, currentTime);
+                float animatedAlpha = getAnimatedAlpha(cards[i].detailTextureTimeStamp,
+                    currentTime, fadeInDuration);
                 if (animatedAlpha < 1.0f) {
                     stillAnimating = true;
                 }