ShaderEditor icon indicating copy to clipboard operation
ShaderEditor copied to clipboard

Suggestion — New uniform: persistent runtime timer

Open joaormatos opened this issue 7 years ago • 3 comments

USE CASE: Jane wrote a new shader that changes visibly with time, but in a slow and smooth manner based on one of the existing timer uniforms. She then decided to set her shader as a home/lockscreen wallpaper. The shader worked fine, but when she opened an app and later returned to the home screen, there was a discontinuity in the time uniform, between the time she left and then returned to the home screen.

This might've been easy to overlook, if not for one detail: the system cached an image of the last frame of the wallpaper as it left the home screen and displayed momentarily (a split second) before starting the shader again.

This caused the screen to visibly flicker from the cached image to a freshly rendered image, causing discomfort due to the jarring change.

Proposed way to fix it: a persistent timer uniform that stops when the shader isn't running.

Things to consider:

  • A system-dependent offset may need to be applied to a persisted value upon loading, to compensate system idiosyncrasies.
  • Precision issues if the uniform is to be a floating point number (in cases when it runs for too long).
  • Some people may want a version that oscillates between two numbers; others might prefer one that grows up to a maximum and then resets back to a starting value.
  • Different contexts should have different timers; hacking around in the shader code editor shouldn't change the state of the timer used for wallpapers.

Alternatively/additionally: persist the last frame of the last shader that ran as a wallpaper and provide it as an uniform so it can be used in a fading animation.

joaormatos avatar Sep 10 '16 05:09 joaormatos

Hm, interesting, on what device/Android version you're experiencing this behavior? Maybe it's just me but somehow I can't reproduce this. Can it be the launcher (not the system) that is caching that last frame?

Anyway, in theory it's not too difficult to offer additional time uniforms (there is more than just time, more on that later on) that operate on relative time. For time, that would quickly raise precision issues indeed (especially on devices supporting mediump only; not too seldom, look at https://github.com/markusfisch/ShaderEditor/issues/10).

To cope with those precision problems, Version 2.4.3 (still in beta) introduced new uniforms: second, subsecond and fTime. second is an int and holds the integer number of seconds since start. subsecond contains the fractional part. fTime is a float within a cycle of #define FTIME_PERIOD seconds from -1.0 to +1.0.

Now, I think the idea of fTime may be best suited to have a new pTime uniform that simply stores (and restores) the position within FTIME_PERIOD. That would avoid precision issues because of too large numbers. Would that be a fit?

You can try the lastest beta here if you like: https://play.google.com/apps/testing/de.markusfisch.android.shadereditor

markusfisch avatar Sep 10 '16 10:09 markusfisch

The device is a Oneplus One running CyanogenMod 13.0 (Android 6.0.1). I'm using the default launcher, Trebuchet (com.cyanogenmod.trebuchet).

In my system, the shader also appears as a background in the Overview Screen, which is where this issue is the most noticeable.

Here's a screencast using a "simple" shader: https://www.youtube.com/watch?v=XRaKcB6qU8o Here's a screencast using a "complex" shader: https://www.youtube.com/watch?v=oUUJ-OjgpTA (Code for the shaders below.)

Note that the issue is only noticeable in "Power save" mode for the simple shader, but it's almost always noticeable for the complex shader, . It seems to be caused by a delay in loading the shader, which seems to depend on both the system and the shader currently set as wallpaper.


I think your idea for pTime is adequate.

The only problem I can think of is that it might be hard to get multiple cycles with different periods out of a single pTime/fTime. Additionally, since there's no way compute some things only once per frame, any additional arithmetic will have to be repeated for every fragment, which might impact performance.

(I've been trying to think of ways to avoid wastefully repeating computations per fragment, but the only general solution I can think of is having a higher level scripting language (JS/Lua/Guile...) to compute custom user-defined uniforms on the CPU, but that doesn't sound like something that'd be fast to load or simple to implement.)


Here's the code for the "simple" shader used in the screencast:

#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif

uniform vec2 resolution;
uniform float time;

void main( void )
{
    float c = min(time,1.0);
    vec2 uv = gl_FragCoord.xy/resolution.xy;

    gl_FragColor = vec4(1.-c, c, 0.0, 1.0 );
}

Here's the code for the "complex" shader used in the screencast:

#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif

uniform float time;
uniform vec2 resolution;
uniform float startRandom;

uniform sampler2D th12c05b;
uniform sampler2D th12c6a;

vec2 rotate2( vec2 v, float angle){
  float s = sin(angle);
  float c = cos(angle);
  return vec2(v.x*c-v.y*s,v.x*s+v.y*c);
}

vec2 c2p(vec2 c){
  vec2(atan(c.y, c.x), length(c));
}

vec2 p2c(vec2 p){
  vec2(p.y*cos(p.x), p.y*sin(p.x));
}

void main( void )
{
  float mx = 1024.;

  float t = time + startRandom;
  vec2 p=c2p((gl_FragCoord.xy-vec2(-resolution.x/7., resolution.y/2.))/512.);
  vec3 color = mix(
                   texture2D(th12c6a,
                             vec2((1.+cos(p.y-t/4.))/2.,
                                  (1.+cos(p.x/2.+1.57+cos(t/4.-p.y)/4.))/2.)).rgb,
                   texture2D(th12c05b,
                             (rotate2(
                                      (vec2(gl_FragCoord.x,gl_FragCoord.y)/2.
                                       + vec2(50.,40.)*(t+.5*pow(cos(t/40.)+sin(t/46.),2.)))/mx,
                                      0.))).rgb,
                   abs(.8+cos(t/3.)*0.1)-0.3)*2.;
  float fade = 1.;
  if(t < fade)
    gl_FragColor = vec4(
                        mix(color,
                            vec3(.3, 0.1, .4)*.6,
                            1.-t/fade), 1.);
  else
    gl_FragColor = vec4( color, 1.0 );
}

The two sampler2D textures used in the "complex" shader are 256x256 and 512x512.

joaormatos avatar Sep 14 '16 14:09 joaormatos

PS: I haven't tried using the beta build yet. I assume it's signed with a different key than the F-Droid build, so I'd need to do a little backup/restore dance to keep my current app data. I'll do it later if there's something you'd like me to test specifically.

joaormatos avatar Sep 14 '16 14:09 joaormatos