public override void OnRenderImage(Texture source, RenderTexture destination)
        {
            GetDimensions(out var width, out var height);
            var tempRT = RenderTexture.GetTemporary(width, height);

            base.OnRenderImage(source, tempRT);

            rpMat.SetTexture("_ProsTex", tempRT);
            rpMat.SetVector(SP.eyeGaze, EyeGaze.Get(eyeGazeSource, headset));
            rpMat.SetVector(SP.headsetDiameter, headset.GetRetinalDiameter());
            rpMat.SetFloat("_natural_vision_width", this.width);
            rpMat.SetFloat("_natural_vision_saturation", saturation);
            rpMat.SetFloat("_natural_vision_brightness", _brightness);
            UpdateAcuity();
            Graphics.Blit(Prosthesis.Source, destination, rpMat);

            RenderTexture.ReleaseTemporary(tempRT);
        }
        /*
         * Protected methods
         */

        protected void Initialise(string phosShader, string tailShader)
        {
            // load shaders
            phos = new Material(Shader.Find(phosShader));
            tail = new Material(Shader.Find(tailShader));

            if (phos == null || tail == null)
            {
                Debug.LogError($"{name} does not have a material.");
                return;
            }

            // upload electrode/axon textures to the GPU
            phos.SetTexture(SP.electrodeTexture, epiretinalData.GetPhospheneTexture(headset, pattern, layout));
            tail.SetTexture(SP.axonTexture, epiretinalData.GetAxonTexture(headset));

            // create texture for the fading data
            fadeRT = new DoubleBufferedRenderTexture(headset.GetWidth(), headset.GetHeight());
            fadeRT.Initialise(new Color(1, 0, 0, 0));

            // overrides
            if (overrideCameraFOV)
            {
                Prosthesis.Instance.Camera.fieldOfView = headset.GetFieldOfView(Axis.Vertical);
            }

            if (overrideRefreshRate)
            {
                Application.targetFrameRate = headset.GetRefreshRate();
            }

            // cache texture-related variables
            lastHeadset = headset;
            lastPattern = pattern;
            lastLayout  = layout;

            // initialise eye gaze tracking
            EyeGaze.Initialise(eyeGazeSource, headset);

            // initialise camera target eye
            UpdateCameraTargetEye();
        }
        /*
         * Private methods
         */

        private void UpdatePerFrameData()
        {
            // pulse
            phos.SetInt(SP.pulse, Pulse ? 1 : 0);

            if (useFading)
            {
                // fading (can safely be uploaded every frame because it is just a RenderTexture pointer)
                phos.SetTexture(SP.fadeTexture, fadeRT.Back);
            }

            // eye gaze
            var eyeGaze = EyeGaze.Get(eyeGazeSource, headset);

            phos.SetVector(SP.eyeGaze, eyeGaze);
            tail.SetVector(SP.eyeGaze, eyeGaze);

            // eye gaze delta
            phos.SetVector(SP.eyeGazeDelta, EyeGaze.GetDelta(eyeGazeSource, headset));
        }