[shaders] Make updating uniforms controls more efficient.

- Add needsRAF() and onRAF() to the UniformControl interface.
- Implement all those methods on all the uniform controls.
- Move fps module to infra-sk.
- Add uniform-fps-sk control which displays the frames per second.
- Switch shader-app-sk to use needsRAF() and onRAF() to avoid
  calling _render() on every requestAnimationFrame.

Bug: skia:11272
Change-Id: I0d39de38ca1cbaccadbae6e2df6b888e5ce03404
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/373656
Commit-Queue: Joe Gregorio <jcgregorio@google.com>
Reviewed-by: Kevin Lubick <kjlubick@google.com>
diff --git a/shaders/modules/fps/fps.ts b/infra-sk/modules/fps/fps.ts
similarity index 100%
rename from shaders/modules/fps/fps.ts
rename to infra-sk/modules/fps/fps.ts
diff --git a/shaders/modules/fps/fps_test.ts b/infra-sk/modules/fps/fps_test.ts
similarity index 100%
rename from shaders/modules/fps/fps_test.ts
rename to infra-sk/modules/fps/fps_test.ts
diff --git a/shaders/modules/fps/index.ts b/infra-sk/modules/fps/index.ts
similarity index 100%
rename from shaders/modules/fps/index.ts
rename to infra-sk/modules/fps/index.ts
diff --git a/infra-sk/modules/uniform-color-sk/uniform-color-sk.ts b/infra-sk/modules/uniform-color-sk/uniform-color-sk.ts
index 15d2a15..9e173f4 100644
--- a/infra-sk/modules/uniform-color-sk/uniform-color-sk.ts
+++ b/infra-sk/modules/uniform-color-sk/uniform-color-sk.ts
@@ -104,6 +104,14 @@
     }
   }
 
+  onRAF(): void {
+    // noop.
+  }
+
+  needsRAF(): boolean {
+    return false;
+  }
+
   private hasAlphaChannel(): boolean {
     return this._uniform.columns === 4;
   }
diff --git a/infra-sk/modules/uniform-color-sk/uniform-color-sk_test.ts b/infra-sk/modules/uniform-color-sk/uniform-color-sk_test.ts
index a9da2af..32c5cd6 100644
--- a/infra-sk/modules/uniform-color-sk/uniform-color-sk_test.ts
+++ b/infra-sk/modules/uniform-color-sk/uniform-color-sk_test.ts
@@ -116,5 +116,9 @@
         };
       });
     });
+
+    it('does not need raf updates', () => {
+      assert.isFalse(element.needsRAF());
+    });
   });
 });
diff --git a/infra-sk/modules/uniform-dimensions-sk/uniform-dimensions-sk.ts b/infra-sk/modules/uniform-dimensions-sk/uniform-dimensions-sk.ts
index d969c61..1f4cc3f 100644
--- a/infra-sk/modules/uniform-dimensions-sk/uniform-dimensions-sk.ts
+++ b/infra-sk/modules/uniform-dimensions-sk/uniform-dimensions-sk.ts
@@ -61,6 +61,7 @@
     super(UniformDimensionsSk.template);
   }
 
+
   private static template = (ele: UniformDimensionsSk) => html`
     <select @change=${ele.selectionChanged} size="1">
       ${choices.map((choice, index) => html`
@@ -83,6 +84,14 @@
     // This is a noop, we don't restore predefined uniform values.
   }
 
+  onRAF(): void {
+    // noop.
+  }
+
+  needsRAF(): boolean {
+    return false;
+  }
+
   connectedCallback(): void {
     super.connectedCallback();
     this._render();
diff --git a/infra-sk/modules/uniform-dimensions-sk/uniform-dimensions-sk_test.ts b/infra-sk/modules/uniform-dimensions-sk/uniform-dimensions-sk_test.ts
index 9a88f10..b371277 100644
--- a/infra-sk/modules/uniform-dimensions-sk/uniform-dimensions-sk_test.ts
+++ b/infra-sk/modules/uniform-dimensions-sk/uniform-dimensions-sk_test.ts
@@ -56,5 +56,9 @@
         };
       });
     });
+
+    it('does not need raf updates', () => {
+      assert.isFalse(element.needsRAF());
+    });
   });
 });
diff --git a/infra-sk/modules/uniform-fps-sk/index.ts b/infra-sk/modules/uniform-fps-sk/index.ts
new file mode 100644
index 0000000..06d4572
--- /dev/null
+++ b/infra-sk/modules/uniform-fps-sk/index.ts
@@ -0,0 +1,2 @@
+import './uniform-fps-sk';
+import './uniform-fps-sk.scss';
diff --git a/infra-sk/modules/uniform-fps-sk/uniform-fps-sk-demo.html b/infra-sk/modules/uniform-fps-sk/uniform-fps-sk-demo.html
new file mode 100644
index 0000000..42f3534
--- /dev/null
+++ b/infra-sk/modules/uniform-fps-sk/uniform-fps-sk-demo.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+  <title>uniform-fps-sk</title>
+  <meta charset="utf-8" />
+  <meta http-equiv="X-UA-Compatible" content="IE=edge">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+</head>
+
+<body>
+  <h1>uniform-fps-sk</h1>
+  <uniform-fps-sk></uniform-fps-sk>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/infra-sk/modules/uniform-fps-sk/uniform-fps-sk-demo.ts b/infra-sk/modules/uniform-fps-sk/uniform-fps-sk-demo.ts
new file mode 100644
index 0000000..b811cfb
--- /dev/null
+++ b/infra-sk/modules/uniform-fps-sk/uniform-fps-sk-demo.ts
@@ -0,0 +1 @@
+import './index';
diff --git a/infra-sk/modules/uniform-fps-sk/uniform-fps-sk.scss b/infra-sk/modules/uniform-fps-sk/uniform-fps-sk.scss
new file mode 100644
index 0000000..52886e9
--- /dev/null
+++ b/infra-sk/modules/uniform-fps-sk/uniform-fps-sk.scss
@@ -0,0 +1,3 @@
+uniform-fps-sk {
+  display: block;
+}
diff --git a/infra-sk/modules/uniform-fps-sk/uniform-fps-sk.ts b/infra-sk/modules/uniform-fps-sk/uniform-fps-sk.ts
new file mode 100644
index 0000000..da755e1
--- /dev/null
+++ b/infra-sk/modules/uniform-fps-sk/uniform-fps-sk.ts
@@ -0,0 +1,57 @@
+/**
+ * @module modules/uniform-fps-sk
+ * @description <h2><code>uniform-fps-sk</code></h2>
+ *
+ * Displays the frames per second.
+ */
+import { define } from 'elements-sk/define';
+import { html } from 'lit-html';
+import { ElementSk } from '../ElementSk';
+import { FPS } from '../fps/fps';
+import { Uniform, UniformControl } from '../uniform/uniform';
+
+const defaultUniform: Uniform = {
+  name: 'raf',
+  rows: 0,
+  columns: 0,
+  slot: 0,
+};
+
+export class UniformFpsSk extends ElementSk implements UniformControl {
+    uniform: Uniform = defaultUniform;
+
+    private fps: FPS = new FPS();
+
+    constructor() {
+      super(UniformFpsSk.template);
+    }
+
+    // eslint-disable-next-line @typescript-eslint/no-unused-vars
+    private static template = (ele: UniformFpsSk) => html`fps`;
+
+    // eslint-disable-next-line @typescript-eslint/no-unused-vars
+    applyUniformValues(uniforms: number[]): void {
+      // noop as UniformRafSk doesn't supply uniforms.
+    }
+
+    // eslint-disable-next-line @typescript-eslint/no-unused-vars
+    restoreUniformValues(uniforms: number[]): void {
+      // noop as UniformRafSk doesn't supply uniforms.
+    }
+
+    onRAF(): void {
+      this.fps.raf();
+      this.textContent = `${this.fps.fps.toFixed(0)} fps`;
+    }
+
+    needsRAF(): boolean {
+      return true;
+    }
+
+    connectedCallback(): void {
+      super.connectedCallback();
+      this._render();
+    }
+}
+
+define('uniform-fps-sk', UniformFpsSk);
diff --git a/infra-sk/modules/uniform-fps-sk/uniform-fps-sk_test.ts b/infra-sk/modules/uniform-fps-sk/uniform-fps-sk_test.ts
new file mode 100644
index 0000000..cccaf54
--- /dev/null
+++ b/infra-sk/modules/uniform-fps-sk/uniform-fps-sk_test.ts
@@ -0,0 +1,26 @@
+import './index';
+import { assert } from 'chai';
+import { UniformFpsSk } from './uniform-fps-sk';
+
+import { setUpElementUnderTest } from '../test_util';
+
+describe('uniform-fps-sk', () => {
+  const newInstance = setUpElementUnderTest<UniformFpsSk>('uniform-fps-sk');
+
+  let element: UniformFpsSk;
+  beforeEach(() => {
+    element = newInstance();
+  });
+
+  describe('uniform-fps-sk', () => {
+    it('needs raf updates', () => {
+      assert.isTrue(element.needsRAF());
+    });
+
+    it('updates on onRAF()', () => {
+      assert.equal(element.textContent, 'fps');
+      element.onRAF();
+      assert.equal(element.textContent, '0 fps');
+    });
+  });
+});
diff --git a/infra-sk/modules/uniform-generic-sk/uniform-generic-sk.ts b/infra-sk/modules/uniform-generic-sk/uniform-generic-sk.ts
index 0f87340..36ffaee 100644
--- a/infra-sk/modules/uniform-generic-sk/uniform-generic-sk.ts
+++ b/infra-sk/modules/uniform-generic-sk/uniform-generic-sk.ts
@@ -100,6 +100,14 @@
       }
     }
   }
+
+  onRAF(): void {
+    // noop.
+  }
+
+  needsRAF(): boolean {
+    return false;
+  }
 }
 
 define('uniform-generic-sk', UniformGenericSk);
diff --git a/infra-sk/modules/uniform-generic-sk/uniform-generic-sk_test.ts b/infra-sk/modules/uniform-generic-sk/uniform-generic-sk_test.ts
index 2945a04..2f661aa 100644
--- a/infra-sk/modules/uniform-generic-sk/uniform-generic-sk_test.ts
+++ b/infra-sk/modules/uniform-generic-sk/uniform-generic-sk_test.ts
@@ -73,5 +73,9 @@
       element.applyUniformValues(uniforms);
       assert.deepEqual(uniforms, [0, 1, 0.5, 0.3, 1, 0]);
     });
+
+    it('does not need raf updates', () => {
+      assert.isFalse(element.needsRAF());
+    });
   });
 });
diff --git a/infra-sk/modules/uniform-imageresolution-sk/uniform-imageresolution-sk.ts b/infra-sk/modules/uniform-imageresolution-sk/uniform-imageresolution-sk.ts
index 8b65a4c..66b8845 100644
--- a/infra-sk/modules/uniform-imageresolution-sk/uniform-imageresolution-sk.ts
+++ b/infra-sk/modules/uniform-imageresolution-sk/uniform-imageresolution-sk.ts
@@ -32,6 +32,14 @@
     // This is a noop, we don't restore predefined uniform values.
   }
 
+  onRAF(): void {
+    // noop
+  }
+
+  needsRAF(): boolean {
+    return false;
+  }
+
   get uniform(): Uniform {
     return this._uniform;
   }
diff --git a/infra-sk/modules/uniform-imageresolution-sk/uniform-imageresolution-sk_test.ts b/infra-sk/modules/uniform-imageresolution-sk/uniform-imageresolution-sk_test.ts
index 4773d0a..5b45e9b 100644
--- a/infra-sk/modules/uniform-imageresolution-sk/uniform-imageresolution-sk_test.ts
+++ b/infra-sk/modules/uniform-imageresolution-sk/uniform-imageresolution-sk_test.ts
@@ -18,5 +18,9 @@
       element.applyUniformValues(uniforms);
       assert.deepEqual(uniforms, [imageSize, imageSize, 0]);
     });
+
+    it('does not need raf updates', () => {
+      assert.isFalse(element.needsRAF());
+    });
   });
 });
diff --git a/infra-sk/modules/uniform-mouse-sk/uniform-mouse-sk.ts b/infra-sk/modules/uniform-mouse-sk/uniform-mouse-sk.ts
index 6046198..41d028e 100644
--- a/infra-sk/modules/uniform-mouse-sk/uniform-mouse-sk.ts
+++ b/infra-sk/modules/uniform-mouse-sk/uniform-mouse-sk.ts
@@ -44,6 +44,14 @@
     // This is a noop, we don't restore predefined uniform values.
   }
 
+  onRAF(): void {
+    // noop.
+  }
+
+  needsRAF(): boolean {
+    return false;
+  }
+
   get elementToMonitor(): HTMLElement {
     return this._elementToMonitor!;
   }
diff --git a/infra-sk/modules/uniform-mouse-sk/uniform-mouse-sk_test.ts b/infra-sk/modules/uniform-mouse-sk/uniform-mouse-sk_test.ts
index 6baea25..aacd71c 100644
--- a/infra-sk/modules/uniform-mouse-sk/uniform-mouse-sk_test.ts
+++ b/infra-sk/modules/uniform-mouse-sk/uniform-mouse-sk_test.ts
@@ -29,5 +29,9 @@
         };
       });
     });
+
+    it('does not need raf updates', () => {
+      assert.isFalse(element.needsRAF());
+    });
   });
 });
diff --git a/infra-sk/modules/uniform-slider-sk/uniform-slider-sk.ts b/infra-sk/modules/uniform-slider-sk/uniform-slider-sk.ts
index 6a9f407..314a6a3 100644
--- a/infra-sk/modules/uniform-slider-sk/uniform-slider-sk.ts
+++ b/infra-sk/modules/uniform-slider-sk/uniform-slider-sk.ts
@@ -61,6 +61,14 @@
   restoreUniformValues(uniforms: number[]): void {
       this.input!.valueAsNumber = uniforms[this.uniform.slot];
   }
+
+  onRAF(): void {
+    // noop.
+  }
+
+  needsRAF(): boolean {
+    return false;
+  }
 }
 
 define('uniform-slider-sk', UniformSliderSk);
diff --git a/infra-sk/modules/uniform-slider-sk/uniform-slider-sk_test.ts b/infra-sk/modules/uniform-slider-sk/uniform-slider-sk_test.ts
index c91c2d5..40916ad 100644
--- a/infra-sk/modules/uniform-slider-sk/uniform-slider-sk_test.ts
+++ b/infra-sk/modules/uniform-slider-sk/uniform-slider-sk_test.ts
@@ -56,5 +56,9 @@
         };
       });
     });
+
+    it('does not need raf updates', () => {
+      assert.isFalse(element.needsRAF());
+    });
   });
 });
diff --git a/infra-sk/modules/uniform-time-sk/uniform-time-sk.ts b/infra-sk/modules/uniform-time-sk/uniform-time-sk.ts
index d241840..0ee04dc 100644
--- a/infra-sk/modules/uniform-time-sk/uniform-time-sk.ts
+++ b/infra-sk/modules/uniform-time-sk/uniform-time-sk.ts
@@ -48,7 +48,7 @@
     <play-arrow-icon-sk ?hidden=${ele.playing}></play-arrow-icon-sk>
     <pause-icon-sk ?hidden=${!ele.playing}></pause-icon-sk>
   </button>
-  <span>${ele.time.toFixed(3)}</span>
+  <span id=ms>${ele.time.toFixed(3)}</span>
   <span>${ele._uniform.name}</span>
 `;
 
@@ -74,6 +74,14 @@
     // This is a noop, we don't restore predefined uniform values.
   }
 
+  onRAF(): void {
+    this.render();
+  }
+
+  needsRAF(): boolean {
+    return true;
+  }
+
   /** Allows overriding the Date.now function for testing. */
   get dateNow(): DateNow {
     return this._dateNow;
diff --git a/infra-sk/modules/uniform-time-sk/uniform-time-sk_test.ts b/infra-sk/modules/uniform-time-sk/uniform-time-sk_test.ts
index a9ee48b..97fb11b 100644
--- a/infra-sk/modules/uniform-time-sk/uniform-time-sk_test.ts
+++ b/infra-sk/modules/uniform-time-sk/uniform-time-sk_test.ts
@@ -84,5 +84,16 @@
       element.applyUniformValues(uniforms);
       assert.deepEqual(uniforms, [0, 10, 0]);
     });
+
+    it('needs raf updates', () => {
+      assert.isTrue(element.needsRAF());
+    });
+
+    it('updates on a call to onRAF', () => {
+      element.dateNow = () => 0; // ms
+      element.time = 10; // s
+      element.onRAF();
+      assert.equal('10.000', $$('#ms', element)?.textContent);
+    });
   });
 });
diff --git a/infra-sk/modules/uniform/uniform.ts b/infra-sk/modules/uniform/uniform.ts
index e64018d..60ba5eb 100644
--- a/infra-sk/modules/uniform/uniform.ts
+++ b/infra-sk/modules/uniform/uniform.ts
@@ -24,4 +24,10 @@
 
   /** Copies the values from the uniforms array into the control. */
   restoreUniformValues(uniforms: number[]): void;
+
+  /** Function to call on every requestAnimationFrame. Only called if needsRAF() returns true. */
+  onRAF(): void;
+
+  /** Returns true if this controls needs to update on every requestAnimationFrame, such as uniform-time-sk. */
+  needsRAF(): boolean;
 }
diff --git a/shaders/modules/shadernode/index.ts b/shaders/modules/shadernode/index.ts
index f34ca05..c84ac25 100644
--- a/shaders/modules/shadernode/index.ts
+++ b/shaders/modules/shadernode/index.ts
@@ -32,6 +32,13 @@
 export const numPredefinedUniforms = predefinedUniforms.match(/^uniform/gm)!.length - numPredefinedShaderUniforms;
 
 /**
+ * Counts the number of controls that handle pre-defined uniforms.
+ *
+ * Takes into account the uniform-fps-sk which doesn't correspond to a uniform.
+ */
+export const numPredefinedUniformControls = numPredefinedUniforms + 1;
+
+/**
  * The number of lines prefixed to every shader for predefined uniforms. Needed
  * to properly adjust error line numbers.
  */
diff --git a/shaders/modules/shaders-app-sk/shaders-app-sk.ts b/shaders/modules/shaders-app-sk/shaders-app-sk.ts
index 377711e..e99df36 100644
--- a/shaders/modules/shaders-app-sk/shaders-app-sk.ts
+++ b/shaders/modules/shaders-app-sk/shaders-app-sk.ts
@@ -3,13 +3,12 @@
  * @description <h2><code>shaders-app-sk</code></h2>
  *
  */
-import { $ } from 'common-sk/modules/dom';
+import { $, $$ } from 'common-sk/modules/dom';
 import 'codemirror/mode/clike/clike'; // Syntax highlighting for c-like languages.
 import { define } from 'elements-sk/define';
 import { html, TemplateResult } from 'lit-html';
 import { errorMessage } from 'elements-sk/errorMessage';
 import CodeMirror from 'codemirror';
-import { $$ } from 'common-sk/modules/dom';
 import { stateReflector } from 'common-sk/modules/stateReflector';
 import { HintableObject } from 'common-sk/modules/hintable';
 import { isDarkMode } from '../../../infra-sk/modules/theme-chooser-sk/theme-chooser-sk';
@@ -27,6 +26,7 @@
 import '../../../infra-sk/modules/theme-chooser-sk';
 import { SKIA_VERSION } from '../../build/version';
 import { ElementSk } from '../../../infra-sk/modules/ElementSk/ElementSk';
+import '../../../infra-sk/modules/uniform-fps-sk';
 import '../../../infra-sk/modules/uniform-time-sk';
 import '../../../infra-sk/modules/uniform-generic-sk';
 import '../../../infra-sk/modules/uniform-dimensions-sk';
@@ -35,10 +35,9 @@
 import '../../../infra-sk/modules/uniform-color-sk';
 import '../../../infra-sk/modules/uniform-imageresolution-sk';
 import { UniformControl } from '../../../infra-sk/modules/uniform/uniform';
-import { FPS } from '../fps/fps';
 import { DimensionsChangedEventDetail } from '../../../infra-sk/modules/uniform-dimensions-sk/uniform-dimensions-sk';
 import {
-  defaultShader, numPredefinedUniformLines, predefinedUniforms, ShaderNode,
+  defaultShader, numPredefinedUniformControls, numPredefinedUniformLines, predefinedUniforms, ShaderNode,
 } from '../shadernode';
 
 // eslint-disable-next-line @typescript-eslint/no-var-requires
@@ -163,14 +162,16 @@
   // stateReflector update function.
   private stateChanged: stateChangedCallback | null = null;
 
-  private fps: FPS = new FPS();
+  private uniformControlsNeedingRAF: UniformControl[] = [];
 
   constructor() {
     super(ShadersAppSk.template);
   }
 
   private static uniformControls = (ele: ShadersAppSk): TemplateResult[] => {
-    const ret: TemplateResult[] = [];
+    const ret: TemplateResult[] = [
+      html`<uniform-fps-sk></uniform-fps-sk>`, // Always start with the fps control.
+    ];
     const node = ele.shaderNode;
     if (!node) {
       return ret;
@@ -257,10 +258,7 @@
         </div>
       </div>
       <div id=shaderControls>
-        <div id=fps>
-          ${ele.fps.fps.toFixed(0)} fps
-        </div>
-        <div id=uniformControls>
+        <div id=uniformControls @input=${ele.uniformControlsChange} @change=${ele.uniformControlsChange}>
           ${ShadersAppSk.uniformControls(ele)}
         </div>
         <button
@@ -387,6 +385,7 @@
 
       const predefinedUniformValues = new Array(this.shaderNode!.numPredefinedUniformValues).fill(0);
       this.setUniformValuesToControls(predefinedUniformValues.concat(this.shaderNode!.currentUserUniformValues));
+      this.findAllUniformControlsThatNeedRAF();
 
       this.run();
     } catch (error) {
@@ -451,9 +450,17 @@
   }
 
   /** Populate the uniforms values from the controls. */
-  private getUniformValuesFromControls(): number[] {
+  private getUserUniformValuesFromControls(): number[] {
     const uniforms: number[] = new Array(this.shaderNode!.getUniformFloatCount()).fill(0);
-    $('#uniformControls > *').forEach((control) => {
+    $('#uniformControls > *').slice(numPredefinedUniformControls).forEach((control) => {
+      (control as unknown as UniformControl).applyUniformValues(uniforms);
+    });
+    return uniforms.slice(this.shaderNode?.numPredefinedUniformValues || 0);
+  }
+
+  private getPredefinedUniformValuesFromControls(): number[] {
+    const uniforms: number[] = new Array(this.shaderNode!.getUniformFloatCount()).fill(0);
+    $('#uniformControls > *').slice(0, numPredefinedUniformControls).forEach((control) => {
       (control as unknown as UniformControl).applyUniformValues(uniforms);
     });
     return uniforms;
@@ -466,38 +473,33 @@
     });
   }
 
-  private getCurrentUserUniformValues(uniforms: number[]): number[] {
-    if (this.shaderNode) {
-      return uniforms.slice(this.shaderNode.numPredefinedUniformValues);
-    }
-    return [];
+  private findAllUniformControlsThatNeedRAF(): void {
+    this.uniformControlsNeedingRAF = [];
+    $('#uniformControls > *').forEach((control) => {
+      const uniformControl = (control as unknown as UniformControl);
+      if (uniformControl.needsRAF()) {
+        this.uniformControlsNeedingRAF.push(uniformControl);
+      }
+    });
   }
 
-  private getPredefinedUniformValues(uniforms: number[]): number[] {
-    if (this.shaderNode) {
-      return uniforms.slice(0, this.shaderNode.numPredefinedUniformValues);
-    }
-    return [];
+  private uniformControlsChange() {
+    this.shaderNode!.currentUserUniformValues = this.getUserUniformValuesFromControls();
+    this._render();
   }
 
   private drawFrame() {
-    this.fps.raf();
     this.kit!.setCurrentContext(this.canvasKitContext);
-    const uniformsArray = this.getUniformValuesFromControls();
-
-    // TODO(jcgregorio) Change this to be event driven.
-    this.shaderNode!.currentUserUniformValues = this.getCurrentUserUniformValues(uniformsArray);
-
-    const shader = this.shaderNode!.getShader(this.getPredefinedUniformValues(uniformsArray));
+    const shader = this.shaderNode!.getShader(this.getPredefinedUniformValuesFromControls());
     if (!shader) {
       errorMessage('Failed to get shader.', 0);
       return;
     }
 
     // Allow uniform controls to update, such as uniform-timer-sk.
-    // TODO(jcgregorio) This is overkill, allow controls to register for a 'raf'
-    //   event if they need to update frequently.
-    this._render();
+    this.uniformControlsNeedingRAF.forEach((element) => {
+      element.onRAF();
+    });
 
     // Draw the shader.
     this.canvas!.clear(this.kit!.BLACK);