function VBLSyncTest(n, numifis, loadjitter, clearmode, stereo, flushpipe, synchronous, usedpixx, usevulkan, gpumeasure, screenNumber)
% VBLSyncTest([n=600][, numifis=0][, loadjitter=0][, clearmode=0][, stereo=0][, flushpipe=0][, synchronous=0][, usedpixx=0][, usevulkan=0][, gpumeasure=0][, screenNumber=max])
%
% Tests syncing of Psychtoolbox to the vertical retrace (VBL) and demonstrates
% how to implement the old Screen('WaitBlanking') behaviour with
% Screen('Flip')...
%
% This script provides a means to test, how well PTB synchronizes
% stimulus onset and execution of Matlab/Octave with the vertical retrace
% (also known as vertical blank or VBL) on your specific hardware setup.
%
% The script first opens a double-buffered fullscreen window. Then it
% performs a monitor calibration timing loop to estimate the real monitor refresh
% interval (aka IFI): While the formula ifi = 1.0 / Screen('NominalFramerate') will
% return an ifi that is close to the real IFI, it will be a little bit off
% from the real value. The reason is unavoidable jitter in the manufacturing of
% graphics cards internal clock circuits, as well as some drift and jitter
% caused by instabilities and change in the power supply and operating
% temperature of your machine. To be on the safe side, we use a timing-loop
% to compute the real IFI as an average of the IFI's of a number of consecutive
% monitor refresh intervals.
%
% After the calibration you'll see a simple animation: A flashing rectangle
% moving from the top-left corner of the screen to the bottom-right corner.
% During this animation, which lasts 'n' frames, it collects information
% about the timing behaviour.
%
% At the end, a couple of graphs are shown to you that should allow you to
% assess the timing behaviour of your setup.
%
% The following parameters can be changed in order to simulate different
% loads and stimulus presentation timings to assess PTB's behaviour under
% different conditions.
%
%
% PARAMETERS:
%
% n = Number of samples to take. E.g., n=1000 == Draw an animation
% consisting of 1000 frames.
%
%
%
% numifis = Number of monitor refresh intervals (IFIs) between flips:
% 0 == Flip at each vertical retrace: This is the old PTB 1.0.50 behaviour.
% Values of numifis>0 will cause Screen('Flip') to wait for 'numifis'
% monitor refresh intervals before flipping the back- and front buffers in
% sync with the vertical retrace.
%
% This would be roughly equivalent to the following snippet of code in the old
% MacOS9-PTB:
% ...
% Screen('WaitBlanking', windowPtr, numifis);
% Screen('CopyWindow', windowPtr, myOffscreenWindowwithStimulusPtr);
% ...
%
%
%
% loadjitter = Simulated load with a random duration between 0 ms
% and loadjitter monitor refresh intervals: We wait for the specified
% amount of time to simulate the execution of other Matlab-code, e.g.,
% KbChecks, GetMouse, Matlab calculations ...
%
%
%
% clearmode = Change the behaviour of Flip after flipping.
% clearmode = 0 will clear your stimulus drawing surface to background color after flip.
% This is the behaviour as found in PTB 1.0.50. After Flip you start with an
% empty image and can draw a completely new stim.
% 
% clearmode = 1 will not clear after a flip, but keep the contents of your stimulus
% image after the Flip: This allows you to incrementally update/draw stimuli.
%
% clearmode = 2 will neither clear nor keep the drawing surface after Flip, but leave the
% cleanup work to you. To be precise: The drawing surface will contain the
% stimulus image that was *just shown* on the screen. Think of Flip as if it would
% flip the front- and back-side of a sheet of paper: The current front side
% shows the stim to your subject, the current back side is where you draw.
% clearmode 2 will allow you to update the back side, which was the front
% side before the flip happened! This mode is useful if you want to save
% about 0.5-2 ms of time needed for mode 1 or 2 if you draw stimuli on very
% tight deadlines. 
%
%
% stereo = Test timing of display of stereoscopic stimuli.
% stereo = 0 will show you a standard monoscopic display.
% stereo = 1 will use the OS-X stereo output facilities to show stereoscopic
% stimuli: OS-X will quickly alternate between two images at each monitor refresh,
% one for the left-eye, one for the right-eye, while generating proper
% control signals for LCD shutter glasses. This should work with MacOS-X
% compatible stereo display hardware, e.g., CrystalEyes shutter glasses.
%
%
% flushpipe = Mark end of drawing commands to improve presentation timing.
% PTB knows a new command Screen('DrawingFinished') which, when properly used,
% will give PTB hints on how to optimize drawing of stimuli: This allows to draw
% more complex stimuli at higher monitor refesh intervals with reliable presentation
% timing. flushpipe enables/disables use of this new command.
%
% flushpipe = 0 Don't mark end of drawing commands.
% flushpipe = 1 Mark end of drawing commands to improve timing.
%
%
% synchronous = 0 Don't wait for drawing completion in Screen('DrawingFinished')
% synchronous = 1 Wait for completion - Useful for benchmarking and debugging,
% but degrades performance significantly in real experiments.
%
%
% usedpixx = 1 Use a DataPixx/ViewPixx/ProPixx device for external
% timestamping of stimulus onset, as a correctness test for Screen('Flip')
% timestamping. Disabled (0) by default.
% usedpixx = 2 Additionally correct for the clock skew between the computer
% and DataPixx device.
%
%
% 'usevulkan' If 1, try to use a Vulkan display backend instead of the
% OpenGL display backend. See 'help PsychVulkan'.
%
% 'gpumeasure' If 1, try to measure GPU time spent for all OpenGL processing
% for a given frame.
%
% screenNumber =  Use a screen other than the default (max) for testing .
%
%
% EXAMPLES:
%
% VBLSyncTest(1000, 0, 0.6, 0, 0, 1, 0) -- Render 1000 consecutive frames,
% flip at each retrace , pausing for 0.6 frame durations after each flip.
% Clear the framebuffer after flip, don't use stereo output, but use the new
% "DrawingFinished" command.
%
% VBLSyncTest(100, 10, 6, 1, 0, 1, 0) -- Render 100 frames,
% flip only every 10th monitor refresh interval ("WaitBlanking" for 10 refresh intervals),
% pause for 6 frame durations after each flip. Don't clear the framebuffer after
% flip, don't use stereo output, but use the new "DrawingFinished" command.
%
% VBLSyncTest(100, 10, 6, 1, 1, 1, 0) -- Render 100 frames,
% flip only every 10th monitor refresh interval ("WaitBlanking" for 10 refresh intervals),
% pause for 6 frame durations after each flip. Don't clear the framebuffer after
% flip, use stereo output via "frame-sequential stereo", use the new "DrawingFinished" command.
%
%
%
% THE PLOTS:
%
% Explanation of how to read the plots:
%
% Figure 1 shows the time delta between start of the VBL of successive
% Flip's: This value should be close to the requested delta as specified to
% Flip, e.g., you set numifis=0 or numifis=1 on a 100 Hz monitor. Then
% delta should be close to 1000 ms / 100 Hz = 10 ms. If numifis=2, it
% should be close to two monitor refresh intervals = 20 ms...
% 
% The green horizontal line denotes the proper delta value for your monitor
% refresh rate and 'numifis' value. The blue graph shows measured deltas. A
% jitter of less than +/- 1 ms indicates proper stimulus presentation timing -
% no skipped frames.
%
%
% Figure 2 shows the rasterbeam positions when flip took its internal
% timestamp: The values should be usually above the screen height, e.g., on
% a monitor resolution of 1200 x 1024 pixels, values should be above 1024.
% Values way below 1024 are also ok (e.g., < 100). This just means that
% your computer is either pretty slow, or connected to a flat-panel.
% Lots of randomly distributed values between 0 and 1024 would indicate sync trouble.
%
%
% Figure 3: Shows the estimated difference between requested presentation
% deadline and the real presentation deadline (start of VBL). Positive
% values indicate a deadline-miss and give you an indication of how much
% the deadline has been missed. Negative (or zero) values indicate that the
% deadline has been met. While the sign of this value is useful for assessing
% timing, the value itself is only meaningful for people who can read and
% fully understand the C source code and logic of 'Flips' implementation...
%
% Figure 4: Shows the difference (in milliseconds) between estimated
% start of VBL and return of the Flip command to Matlab. This is some
% indication of the processing overhead of OpenGL, the Operating system and
% Psychtoolbox when executing 'Flip'. It's also a lower bound for the
% timing delay when trying to synchronize start of acquisition devices to
% VBL.
%
% Figure 5: Shows the difference (in milliseconds) between estimated
% stimulus onset (aka end of vertical retrace, scanning beam starts
% at top of screen) and the end of Flip. This is the crucial value, if you
% want to sync something like sound-playback, triggering of some
% data-acquisition device (fMRI, MEG, EEG, ...) to stimulus onset. It
% should give you a feeling of how well you can sync. It's possible that
% negative values are reported on fast machines - Flip returns ahead of
% time (while monitor is still in retrace state). This is fine because your
% Matlab-Code for triggering something will add additional delays... Values
% should be below 1-2 milliseconds on reasonably modern and correctly
% configured hardware.
%
% Figure 6: Is only displayed when flag 'synchronous=1' This figure shows
% the total accumulated time for all drawing commands from the last Flip to the
% DrawingFinished command. It allows you to get a feeling on how hard the
% graphics hardware has to work for drawing your stim - and if it is
% possible at all to draw the stim on your hardware, given your time
% constraints. During a normal experiment (without sync-flag), the
% execution times of your Matlab code (as measured, e.g., by tic and toc)
% and of the drawing commands don't add up, because the graphics hardware
% works in parallel to the Matlab code.
%
%
% Please read the code of this M-File carefully as an example of how to get
% the best possible presentation timing on PTB-OSX.
%

%
%
% Date:   05/09/05
% Author: Mario Kleiner  (mario.kleiner.de@gmail.com)
%

%%% VBLSyncTest(1000, 0, 0.6, 0, 0, 1, 0)

if nargin < 1 || isempty(n)
    n = 600;
end

if nargin < 2 || isempty(numifis)
    numifis = 1;
end

if nargin < 3 || isempty(loadjitter)
    loadjitter = 0;
end

if nargin < 4 || isempty(clearmode)
    clearmode = 0;
end

if nargin < 5 || isempty(stereo)
    stereo = 0;
end

if nargin < 6 || isempty(flushpipe)
    flushpipe = 0;
end

if nargin < 7 || isempty(synchronous)
    synchronous = 0;
end

if nargin < 8 || isempty(usedpixx)
    usedpixx = 0;
end

if nargin < 9 || isempty(usevulkan)
    usevulkan = 0;
end

if nargin < 10 || isempty(gpumeasure)
    gpumeasure = 0;
end

if nargin < 11
    screenNumber = [];
end

try
    PsychDefaultSetup(2);
    RestrictKeysForKbCheck(KbName('ESCAPE'));
    
    % Get the list of Screens and choose the one with the highest screen number.
    % Screen 0 is, by definition, the display with the menu bar. Often when 
    % two monitors are connected the one without the menu bar is used as 
    % the stimulus display.  Chosing the display with the highest display number is 
    % a best guess about where you want the stimulus displayed.  
    screens=Screen('Screens');
    if isempty(screenNumber)
        screenNumber=max(screens);
    end
    screensize=Screen('Rect', screenNumber);

    % Query size of screen:
    screenheight=screensize(4);

    % Open double-buffered window: Optionally enable stereo output if
    % stereo == 1.
    PsychImaging('PrepareConfiguration');
    PsychImaging('AddTask', 'General', 'UseRetinaResolution');

    if usedpixx
        % Use DataPixx for external timestamping for quick basic correctness
        % tests.
        PsychImaging('AddTask', 'General', 'UseDataPixx');
    end

    if usevulkan
        % Use PsychVulkan display backend instead of standard OpenGL:
        PsychImaging('AddTask', 'General', 'UseVulkanDisplay');
    end

    w = PsychImaging('OpenWindow',screenNumber, 0, [], [], [], stereo);

    % Query effective stereo mode, as Screen() could have changed it behind our
    % back, e.g., if we asked for mode 1 but Screen() had to fallback to
    % mode 11:
    winfo = Screen('GetWindowInfo', w);
    stereo = winfo.StereoMode;
    
    % Clear screen to black background color: If in stereo mode, we only
    % clear the left-eye buffer...
    Screen('SelectStereoDrawBuffer', w, 0);
    Screen('FillRect', w, 0);
    Screen('Flip', w);
    
    % Switch to realtime-priority to reduce timing jitter and interruptions
    % caused by other applications and the operating system itself:
    Priority(MaxPriority(w));

    % Query nominal framerate as returned by Operating system:
    % If OS returns 0, then we assume that we run on a flat-panel with
    % fixed 60 Hz refresh interval.
    framerate=Screen('NominalFramerate', w);
    if framerate == 0
        framerate = 60;
    end

    ifinominal = 1 / framerate;
    fprintf('The refresh interval reported by the operating system is %2.5f ms.\n', ifinominal*1000);
    
    % Perform a calibration loop to determine the "real" interframe interval
    % for the given gfx-card + monitor combination:
    Screen('TextSize', w, 24);
    Screen('DrawText', w, 'Measuring monitor refresh interval... This can take up to 20 seconds...', 10, 10, 1);
    
    if stereo > 0
        % Show something for the right eye as well in stereo mode:
        Screen('SelectStereoDrawBuffer', w, 1);
        Screen('FillRect', w, 0);
        Screen('DrawText', w, 'Stereo yeah!!!', 10, 40, 1);
    end
    
    % Measure monitor refresh interval again, just for fun...
    % This will trigger a calibration loop of minimum 100 valid samples and return the
    % estimated ifi in 'ifi': We require an accuracy of 0.1 ms == 0.0001
    % secs. If this level of accuracy can't be reached, we time out after
    % 20 seconds...
    %[ ifi nvalid stddev ]= Screen('GetFlipInterval', w, 100, 0.0001, 5);
    [ ifi, nvalid, stddev ]= Screen('GetFlipInterval', w);
    fprintf('Measured refresh interval, as reported by "GetFlipInterval" is %2.5f ms. (nsamples = %i, stddev = %2.5f ms)\n', ifi*1000, nvalid, stddev*1000);
    
    % Init data-collection arrays for collection of n samples:
    ts=zeros(1,n);
    beampos=ts;
    missest=ts;
    flipfin=ts;
    dpixxdelay=ts;
    td=ts;
    so=ts;
    sodpixx = ts;
    boxTime = ts;
    gpudur = ts;
    
    % Compute random load distribution for provided loadjitter value:
    wt=rand(1,n)*(loadjitter*ifi);

    % Perform some initial Flip to get us in sync with retrace:
    % tvbl is the timestamp (system time in seconds) when the retrace
    % started. We need it as a reference value for our WaitBlanking
    % emulation:
    tvbl=Screen('Flip', w);

    if gpumeasure
        % Start GPU timer: gpumeasure will be true if this
        % is actually supported and will return valid results:
        gpumeasure = Screen('GetWindowInfo', w, 5);
    end

    % Test-loop: Collects n samples.
    for i=1:n
        % Presentation time calculation for waiting 'numifis' monitor refresh
        % intervals before flipping front- and backbuffer:
        % This formula emulates the old PTB-MacOS9 Screen('WaitBlanking', numifis)
        % behaviour as closely as possible.
        % The 'Flip' command takes a presentation timestamp 'tdeadline' as
        % optional argument: If tdeadline == 0 or is left out, Flip will
        % flip at the next possible retrace (PTB 1.0.5 and earlier behaviour).
        % If tdeadline is > 0, then Flip will wait until the system time
        % 'tdeadline' is reached and then flip the buffers
        % at the next possible VBL. This allows to specify absolute points in time
        % at which flip should occur. If you want the old behaviour of
        % Screen('WaitBlanking', w, numifis) back, then just calculate a
        % proper presentation timestamp "tdeadline" relative to the time of last flip
        % 'tvbl', as demonstrated here:        
        tdeadline=tvbl + numifis * ifi - 0.5 * ifi;
        
        % If numifis == 0, flip on next retrace. This should be the same
        % as numifis == 1, but might make a difference in robustness if
        % the stimulus is **very** complex and the load for the system is at
        % the limit that it can handle in a single video-refresh interval.
        if numifis==0
            % If user supplied numifis=0, we force tdeadline=0, so Flip
            % will actually ignore the deadline and just Flip at the next
            % possible retrace...
            tdeadline=0;
        end
        
        if usedpixx
            % Ask for a Datapixx onset timestamp for next 'Flip':
            PsychDataPixx('LogOnsetTimestamps', 1);
        end
        
        % Flip: The clearmode argument specifies if flip should clear the
        % drawing buffer after flip (=0 - default), keep it "as is"
        % for incremental drawing/updating of stims (=1) or don't do
        % anything to the framebuffer at all (=2).
        % We return the timestamp, when VBL starts in tvbl: This is when
        % the front- and back drawing surfaces get exchanged and it is the
        % crucial reference value for computing the 'tdeadline'
        % presentation deadline for the next 'Flip' command.
        % The rasterbeam-position (scanline) when the measurement was taken is returned in beampos(i),
        % the time when flip returned to Matlab is returned in flipfin(i),
        % estimated stimulus onset time aka end of VBL is returned in so(i).
        %
        % The first value "tvbl" is needed for tdeadline calculation if
        % one wants to emulate WaitBlanking of old PTB - see formula above.
        % beampos > screen height means that flip returned during the VBL
        % interval. Small values << screen height are also ok,
        % they just indicate either a slower machine or some types of flat-panels...

        [ tvbl, so(i), flipfin(i), missest(i), beampos(i)]=Screen('Flip', w, tdeadline, clearmode);

        if usedpixx
            % Ask for a Datapixx onset timestamp from last 'Flip':
            [boxTime(i), sodpixx(i)] = PsychDataPixx('GetLastOnsetTimestamp');
            dpixxdelay(i) = GetSecs;
        end

        if gpumeasure
            % Retrieve results from GPU load measurement:
            % Need to poll, as this is asynchronous and non-blocking,
            % so it may return a zero time value at first invocation(s),
            % depending on how deep the rendering pipeline is:
            while 1
                winfo = Screen('GetWindowInfo', w);
                if winfo.GPULastFrameRenderTime > 0
                    break;
                end
            end
    
            % Store it:
            gpudur(i) = winfo.GPULastFrameRenderTime;

            % Restart GPU timer: gpumeasure will be true if this
            % is actually supported and will return valid results:
            gpumeasure = Screen('GetWindowInfo', w, 5);
        end

        % Record timestamp for later use:
        ts(i) = tvbl;
        
        % Draw some simple stim for next frame of animation: We draw a
        % simple flashing rectangle that moves over the screen. The same
        % rectangle is drawn with some offset for the right-eye if stereo
        % display is requested:
        Screen('SelectStereoDrawBuffer', w, 0);
        pos=mod(i, screenheight);
        Screen('FillRect', w, 1 - mod(i, 100)/100, [pos+20 pos+20 pos+400 pos+400]);

        if usevulkan && IsOSX
            Screen('TextSize', w, 60);
            Screen('DrawText', w, num2str(i), pos+40, pos+300, [1, 0, 0]);
        end

        if stereo
            % Show something for the right eye as well in stereo mode:
            Screen('SelectStereoDrawBuffer', w, 1);
            Screen('FillRect', w, 1 - mod(i, 100)/100, [pos+40 pos+20 pos+420 pos+400]);
        end

        if flushpipe==1
            % Give a hint to PTB that no further drawing commands will
            % follow before the next Flip-command. This can be used by PTB
            % to optimize drawing of very demanding stimuli in order to decrease the
            % chance of deadline misses due to overload. The "clearmode"
            % argument should be the same as the one passed to Flip. It is
            % another hint. If synchronous is set == 1, then
            % DrawingFinished will return an estimate of the time needed by
            % the graphics hardware to draw your stimulus. This is useful
            % for finding the cause of skipped frames. If the value is
            % close to the video refresh interval of your monitor, then you
            % are drawing too much/too complex stims for your graphics
            % hardware -> Reduce complexity, reduce monitor refresh rate or
            % buy faster graphics hardware. Don't set synchronous == 1 for
            % real experiments as it will significantly degrade
            % performance and can *cause* deadline misses.
            td(i)=Screen('DrawingFinished', w, clearmode, synchronous);
        end

        % Sleep a random amount of time, just to simulate some work being
        % done in the Matlab loop:
        WaitSecs(wt(i));

        % And give user a chance to abort the test by pressing any key:
        if KbCheck
            break;
        end
    end % Draw next frame...

    % calculate clock skew corrected Datapixx onset timestamps
    if usedpixx>1
        sodpixx = PsychDataPixx('BoxsecsToGetsecs', boxTime);
    end

    RestrictKeysForKbCheck([]);

    % Shutdown realtime scheduling:
    Priority(0);

    % Close display: If we skipped/missed any presentation deadline during
    % Flip, Psychtoolbox will automatically display some warning message on the Matlab
    % console:
    sca;

    % Restrict to actual number of collected samples:
    n = i;
    ts = ts(1:n);
    so = so(1:n);
    flipfin = flipfin(1:n);
    missest = missest(1:n);
    beampos = beampos(1:n);
    td = td(1:n);
    dpixxdelay = dpixxdelay(1:n);
    sodpixx = sodpixx(1:n);
    boxTime = boxTime(1:n); %#ok<NASGU>
    gpudur = gpudur(1:n);

    % Count and output number of missed flip on VBL deadlines:
    numbermisses=0;
    numberearly=0;

    if numifis > 0
        if (stereo == 11) && (numifis == 1)
            % Special case: Stereomode 11 can't do better than one swap
            % every two refresh cycles, so take this into account:
            ifi = ifi * 2;
        end

        for i=2:n
            if (ts(i)-ts(i-1) > ifi*(numifis+0.5))
                numbermisses=numbermisses+1;
            end

            if (ts(i)-ts(i-1) < ifi*(numifis-0.5))
                numberearly=numberearly+1;
            end
        end
    else
        if stereo == 11
            % Special case: Stereomode 11 can't do better than one swap
            % every two refresh cycles at best, so take this into account:
            ifi = ifi * 2;
        end

        for i=2:n
            if (ts(i)-ts(i-1) > ifi*1.5)
                numbermisses=numbermisses+1;
            end

            if (ts(i)-ts(i-1) < ifi*(numifis-0.5))
                numberearly=numberearly+1;
            end
        end
    end

    % Plot all our measurement results:

    % Figure 1 shows time deltas between successive flips in milliseconds:
    % This should equal the product numifis * ifi:
    figure
    hold on
    plot((ts(2:n) - ts(1:n-1)) * 1000);
    ni = numifis;
    if numifis < 1
        ni = 1;
    end
    
    if (ni < 2 ) && (stereo == 11)
        % Special case: Stereomode 11 can't do better than one swap
        % every two refresh cycles, so take this into account:
        ni = 2;
    end
    
    plot(ones(1,n)*ifi*ni*1000);
    title('Delta between successive Flips in milliseconds:');
    hold off
    
    % Figure 2 shows the recorded beam positions:
    if winfo.VBLEndline > -1
        hasbeampos = 1;
    else
        hasbeampos = 0;
    end

    if hasbeampos
        figure
        plot(beampos);
        title('Rasterbeam position when timestamp was taken (in scanlines):');
    end

    if ((numbermisses > 1) || (numberearly > 0)) && any(missest)
        % Figure 3 shows estimated size of presentation deadline-miss in
        % milliseconds:
        figure
        hold on
        plot(missest*1000);
        plot(zeros(1,n));
        title('Estimate of missed deadlines in milliseconds (negative == no miss):');
        hold off
    end

    if isequal(ts, so)
        % Same info in vbltime and stimulus onset time. Only
        % do one plot and label it in a less confusing manner:
        figure
        plot((flipfin - so)*1000);

        if IsLinux && (Screen('Preference', 'VBLTimestampingmode') == 4)
            % Linux mode 4: OpenML or Wayland presentation feedback, so is stimulus onset:
            title('Time delta between stimulus onset and return of Flip in milliseconds:');
        elseif (IsLinux || IsOSX) && ismember(Screen('Preference', 'VBLTimestampingmode'), [1, 3]) && (winfo.VBLCount > 0)
            % Linux or OSX, vbl timestamping requested and apparently working. so is vblank time:
            title('Time delta between start of VBL and return of Flip in milliseconds:');
        else
            % Windows or other os'es without working high precision timestamping: so is raw timestamp:
            title('Time delta between return from swap completion and return of Flip in milliseconds:');
        end
    else
        % Figure 4 shows difference in ms between finish of Flip and estimated
        % start of VBL time:
        figure
        plot((flipfin - ts)*1000);
        title('Time delta between start of VBL and return of Flip in milliseconds:');

        % Figure 5 shows difference in ms between finish of Flip and estimated
        % stimulus-onset:
        figure
        plot((flipfin - so)*1000);
        title('Time delta between stimulus onset and return of Flip in milliseconds:');
    end

    % Figure 6 shows duration of drawing commands when calling
    % "DrawingFinished" in synchronous mode.
    if synchronous==1
        figure
        plot(td*1000);
        title('Total duration of all drawing commands in milliseconds:');
    end

    % Plot gpu OpenGL processing time if available:
    if gpumeasure
        figure
        plot(gpudur*1000);
        title('Total duration of all OpenGL GPU rendering in milliseconds:');
    end

    if usedpixx
        figure;
        plot((so - sodpixx) * 1000);
        title('Time delta in msecs onset according to Flip - onset according to DataPixx:');
        fprintf('Average discrepancy between Flip timestamping and DataPixx is %f msecs, stddev = %f msecs.\n', mean((so - sodpixx) * 1000), std((so - sodpixx) * 1000));

        figure;
        plot((dpixxdelay - so) * 1000);
        title('Time delta between stimulus onset and return of Datapixx timestamping in milliseconds:');
    end

    % Output some summary and say goodbye...
    fprintf('PTB missed %i out of %i stimulus presentation deadlines.\n', numbermisses, n);
    fprintf('One missed deadline is ok and an artifact of the measurement.\n');
    
    fprintf('PTB completed %i stimulus presentations before the requested target time.\n', numberearly);
    if numberearly > 0
        fprintf('CAUTION: Completing flips too early should *never ever happen*! Your system has\n');
        fprintf('CAUTION: a serious bug or misconfiguration in its graphics driver!!!\n');
    end
    fprintf('Have a look at the plots for more details...\n');
    
    % Done.
catch %#ok<*CTCH>
    % This "catch" section executes in case of an error in the "try" section
    % above. Importantly, it closes the onscreen window if its open and
    % shuts down realtime-scheduling of Matlab:
    RestrictKeysForKbCheck([]);

    sca;
    
    % Disable realtime-priority in case of errors.
    Priority(0);
    psychrethrow(psychlasterror);
end %try..catch..

% Reset to cross-platform default with old color encoding:
PsychDefaultSetup(1);

return
