function HighColorPrecisionDrawingTest(testconfig, maxdepth, testblocks, plotit)
% HighColorPrecisionDrawingTest([testconfig][, maxdepth][, testblocks][, plotit=0])
%
% Test for numeric drawing precision of your graphics card (GPU). Exercises
% a number of tests, where some 2D drawing primitive(s) is drawn in some
% well defined color, then the content of the framebuffer is read back and
% compared against expected results computed via Matlab code in double
% precision. The Matlab code emulates the expected behaviour of an ideal
% GPU that works at a precision higher than that of any real GPU.
% Difference between exptected and actual results is calculated for each
% tested pixel location and the maximum error is stored. Each test case
% plots that maximum error to the Matlab window. From the maximum error
% value, we also derive the maximum bitdepths of an output device for which
% the error would be negligible, ie., the difference would not show up in
% any measurable way because the deviation is too small to have any effect
% on the display device.
%
% The actual results of the tests depends on a number of conditions like
% tested precision range, selected precision of the framebuffer,
% alpha-blending mode (if any), texture filtering mode (if textures are
% used as drawing primitive), mode of operation of PTB, operating system
% etc. For that reason you can specify the exact test conditions via a
% number of arguments to this function -- Displayed test results will only
% be valid for that exact configuration, so if you want to evaluate
% suitability of your hardware+OS combo for a given type of visual
% stimulus, make sure you choose a test configuration to closely matches
% the one used in your stimulus presentation script.
%
% The test itself is currently in a BETA stage! While many of the test
% cases seem to work reliably on a variety of tested hardware, there may be
% (untested) hardware+OS combos for which the tests display effective
% precision numbers that are lower than the ones really achieved by your
% hardware -- the test is too pessimistic. So USE WITH CARE, DON'T TRUST
% THE TEST BLINDLY and APPLY COMMON SENSE when looking at the results.
%
% Optional parameters and their meaning:
%
% 'testconfig' is a vector that defines the framebuffer and PTB
% configuration to use. All elements have defaults:
%
% Colorclamping (aka high-precision vertex colors vs. standard vertex colors)
% -1 / 0 / 1 = Unclamped high-res via shader / Unclamped high-res / Clamped.
%              Default is 0 == Let PTB auto-select opmode with highest
%              precision. A setting of -1 overrides PTB's choice to always
%              use an internal implementation -- If auto-detection works
%              perfectly this should not give better results than mode 0,
%              but no automatic is perfect, so it doesn't hurt to test that
%              mode. 1 is "low-precision, clamped" mode: It shouldn't ever
%              give better results than -1 or 0 and is normally only used
%              for standard 8 bit precision output of standard stimuli
%              where bit-accurate output doesnt' matter too much.
%
% Framebuffer: (aka bit-depth and format of framebuffer)
% 0 / 1 / 2 / 3/ 4 = 8 bpc fixed, 16bpc float, 32bpc float, 32bpc float
%              if possible while alpha-blending enabled 16bpc otherwise,
%              16bpc fixed point (on ATI hardware only).
%
%              Precision with which the framebuffer operates: 8 bpc fixed
%              is a standard 8 bits per precision 256 levels framebuffer.
%              16bpc float allows for an effective 10-11 bit precision,
%              32bpc float allows for an effective 23 bit precision, 16 bpc
%              fixed is only supported on ATI hardware and allows for an
%              effective 16 bit precision, but without alpha-blending
%              support.
%
%              Selected precision is a accuracy vs. speed & functionality
%              tradeoff. Higher resolution means higher output precision
%              and higher precision for calculation of intermediate
%              results. However it means also more memory usage, slower
%              processing and drawing speed and - on some hardware - it
%              means that alpha-blending and anti-aliasing doesn't work or
%              works only very slowly. Therefore you need to select a mode
%              that is good enough for your purpose. Direct3D 10 compliant
%              hardware from NVidia and ATI (Geforce 8000 and later, Radeon
%              HD 2000 and later) is supposed to have no relevant
%              limitations wrt. to functionality or precision anymore -- It
%              can carry out all operations including alpha-blending etc.
%              at highest precision (32 bpc float). If you happen to have
%              such hardware then the only reason to choose less than
%              maximum precision is speed -- Lower precision is still
%              processed faster.
%
%              Please also note that the attainable precision of all the
%              test cases in this script is of course limited by the
%              precision of the framebuffer. E.g., if you chose a 16bpc
%              float framebuffer, none of the tests will be able to attain
%              more than about 10-11 bits of precision.
%
%
% Textures: (aka texture precision)
% 0 / 1 / 2 = 8 bpc fixed, 16bpc float, 32bpc float.
%
%              Precision with which textures are represented -- and
%              ultimately drawn. Same explanations apply as with
%              'Framebuffer'. However, there is no limitation in
%              functionality associated with high texture precision: Should
%              the hardware have some limitations, PTB will work-around
%              them. Higher precision textures still incur higher storage
%              requirements and lower drawing speeds though, so don't use
%              higher precision than you really need!
%
% Samplers: (aka texture sampling method)
% 0 / 1 / 2 = Hardware / PTB-Auto / PTB-Shaders.
%
%              Method employed for texture drawing: PTB-Auto is the
%              preferred choice -- Let PTB auto-select best method for
%              given texture precision and other requirements. However you
%              can manually override to always use PTB-Shaders (built-in
%              workarounds for less capable hardware) or Hardware
%              implementation of your GPU -- usually faster, but maybe less
%              precise.
%
% Filters: (aka texture filtering method)
% 0 / 1     = Nearest-Neighbour / Bilinear filtering.
%
%             Method of filtering texture pixels before drawing:
%             Nearest-Neighbour just uses pixels as they are -- blocky or
%             aliased appearance if you draw rotated textures, textures
%             where the 'srcRect' and 'dstRect' parameters in
%             Screen('DrawTexture') don't exactly match in size, and no way
%             of "scrolling" or positioning textures with subpixel
%             accuracy. However, also no loss in precision due to filtering
%             artifacts caused by low precision filtering hardware.
%             'Bilinear filtering' always provides perfectly anti-aliased
%             and smooth looking textures due to use of bilinear filtering,
%             but may introduce a slight loss of precision if your hardware
%             doesn't sample accurately. See DriftTexturePrecisionTest for
%             an extra test of filter accuracy of your GPU.
%
%
% 'maxdepth' parameter: Choose the maximum precision for which the
% test-cases test your hardware. Defaults to 16 bits and is restricted to
% about 18 bits by the current implementation of this test script. 16 bits
% is chosen because there aren't any display devices with more than 14 bit
% output precision available on the market, so 16 bits is "good enough" for
% most purposes. Plese note that even if your GPU is able to provide much
% more than 'maxdepth' bits of precision, the test won't detect that -- it
% will only test up to 'maxdepth' bits of precision!
%
%
% 'testblocks' parameter: A vector of tests to carry out. By default all
% tests are carried out and each single test is interruptible by holding
% down the left mouse button for a while. However this may take quite long
% -- dozens of minutes, maybe even over an hour! For that reason you can
% specify your own 'testblocks' vector to only run a subset of all test
% cases and save some time.
%
% The following test cases are currently implemented:
%
% 1  = Test precision of clearing of the framebuffer to selected
%      'clearcolor'. 'clearcolor' is set in Screen('OpenWindow') or
%      PsychImaging('OpenWindow') etc. or via Screen('FillRect', window,
%      clearcolor). Framebuffer clearing is performed after each
%      Screen('Flip') or Screen('FillRect', window, clearcolor) command and
%      this test tests precision of that operation.
%
% 2  = Test precision of Screen 2D drawing commands like FillRect,
%      FrameRect, FillOval, FrameOval, DrawLine etc.
%
% 3  = Test precision of Screen 2D batch-drawing commands, ie. commands
%      that allow to draw multiple primitives per command, e.g., DrawDots,
%      DrawLines and the batch versions of FillRect, FrameRect, FillOval
%      etc.
%      Also tests precision of the 'DrawTexture' command and of the
%      built-in gamma correction mechanism of PTB when used with the PTB
%      imaging pipeline, e.g., in Mono++ or Color++ mode of a CRS Bits++
%      box.
%
% 4  = Test precision of texture drawing commands when the special
%      'globalAlpha' or 'modulateColor' arguments are used to modulate the
%      textures pixel values during drawing -- for example for contrast
%      selection.
%
% 5  = Test of precision of alpha-blending: Does use of the alpha-blending
%      function via Screen('BlendFunction') introduce any loss of numeric
%      stimulus precision - and if so, how much?
%
%      This testcase 5 is incomplete and under development!
%
% 6  = Test of color precision of text drawing. This only tests to a fixed
%      precision of 8 bits for 256 levels at the moment, as our the text
%      renderer doesn't have a higher precision.
%
% 'plotit' parameter: If set to 1, output some error plots after tests where it
% makes sense. No plotting happens by default.
%

% History:
% 04/20/08  Written (MK).
% 10/22/10  Refined to account for small differences between GPU's (MK).
% 06/20/15  Add case 6 for testing 'DrawText' (MK).
% 10/04/15  Use PsychGPURasterizerOffsets() to compensate for driver flaws (MK).
% 10/21/15  Fix the fixes for rasterizer offsets, fix Octave-4 warnings, add
%           hint to new ConserveVRAMSetting to work around OSX 10.11 AMD bugs.
% 01/31/16  Change filterMode to zero for gamma correction test. Better matches
%           real world use conditions. (MK)
% 05/09/16  Abort tests via left mouse button on top of screen edge, suppress most
%           Screen output - only errors - to not drown results in debug clutter.
%           Use GUI window, so it can be repositioned or hidden. (MK)

global win;

close all;
drivername = mfilename;
maybeSamplerbug = 0;

% Octave's new plotting backend 'fltk' interferes with Screen(),
% due to internal use of OpenGL. Problem is it changes the
% bound OpenGL rendering context behind our back and we
% don't protect ourselves against this yet. Switch plotting backend
% to good'ol gnuplot to work around this issue until we fix it properly
% inside Screen():
if IsOctave && exist('graphics_toolkit')
    graphics_toolkit ('gnuplot');
end

if nargin < 1 || isempty(testconfig)
    % Empty 'testconfig' or missing: Do all tests.
    testconfig = [0 2 2 1 1]
end

if length(testconfig) ~=5
    error('testconfig vector must have 5 elements!');
end

if nargin < 2 || isempty(maxdepth)
    maxdepth = 16;
end

if maxdepth > 24
    maxdepth = 24;
    fprintf('Max bit depths value clamped to 24 bits -- More bits are not meaningful on current hardware...\n');
end

if nargin < 3 || isempty(testblocks)
    testblocks = [1, 2, 3, 4, 5, 6];
end
fprintf('Executing the following tests: %i.\n', testblocks);

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

screenid = max(Screen('Screens'));

ColorClamping = testconfig(1)
Framebuffer = testconfig(2)
Textures = testconfig(3)
Samplers = testconfig(4)
Filters = testconfig(5)

if Framebuffer == 0
    fbdef = 'UseVirtualFramebuffer';
end

if Framebuffer == 1
    fbdef = 'FloatingPoint16Bit';
end

if Framebuffer == 2
    fbdef = 'FloatingPoint32Bit';
end

if Framebuffer == 3
    fbdef = 'FloatingPoint32BitIfPossible';
end

if Framebuffer == 4
    fbdef = 'FixedPoint16Bit';
end

fprintf('Selected framebuffer mode: %s\n', fbdef);
fprintf('\n\nMove mouse cursor to top edge of screen, then hold down the left mouse button\n');
fprintf('for a while to skip or abort tests. Results of aborted tests will be wrong though!\n\n');

resstring = '';

% Disable sync tests for this script:
oldsync = Screen('Preference', 'SkipSyncTests', 2);
oldVerbosity = Screen('Preference', 'Verbosity', 1);

% Generate testvector of all color values to test. We test the full
% intensity range from 0.0 (black) to 1.0 (white) divided into 2^maxdepth steps
% -- the finest reasonable number of steps for any display device.
% We start with value 1 and decrement to 0, because the largest errors are
% expected at high values (due to characteristics of IEEE floating point
% format), so if user exits test early, he will get useable results:
testcolors = 1 - linspace(0,1,2^maxdepth);

if ismember(1, testblocks)
    invalidated = 0;
    PsychImaging('PrepareConfiguration');
    PsychImaging('AddTask', 'General', fbdef);

    % Open window with black background color:
    [win rect] = PsychImaging('OpenWindow', screenid, 0, [0 0 125 125], [], [], [], [], [], kPsychGUIWindow);

    % Set color clamping (and precision) for standard 2D draw commands:
    Screen('ColorRange', win, 1, ColorClamping);

    Screen('Flip', win);

    drawncolors = zeros(1, length(testcolors));
    i=1;

    % Test 1: Precision of backbuffer clears during Screen('Flip'):
    for tc = testcolors
        [xm ym buttons] = GetMouse;
        if buttons(1) && (ym == 0)
            invalidated = 1;
            break;
        end

        % Set clear color: Incidentally this also fills the backbuffer with the
        % cleared color:
        Screen('FillRect', win, tc);

        % Overdraw a smallish 20 x 20 patch in top-left corner with something
        % else, just to make sure the 'FillRect' above doesn't bleed through:
        Screen('FillRect', win, rand, [0 0 20 20]);

        % Flip the buffers - this will fill the backbuffer with 'clear' color
        % after flip: We don't sync to retrace to speed things up a bit...
        Screen('Flip', win, 0, 0, 2);

        % Readback drawbuffer, top-left 10x10 area, with float precision, only
        % the red/luminance channel:
        patch = Screen('GetImage', win, [0 0 10 10], 'drawBuffer', 1, 1);

        % Store result:
        drawncolors(i) = patch(5,5);
        i=i+1;

        if mod(i, 1000)==0
            fprintf('At %i th testvalue of %i...\n', i, 2^maxdepth);
            beep; drawnow;
        end
    end

    Screen('CloseAll');

    % Test done.
    if ~invalidated
        deltacolors = single(testcolors(1:i-1)) - drawncolors(1:i-1);
        minv = min(abs(deltacolors));
        maxv = max(abs(deltacolors));
        goodbits = floor(-(log2(maxv))) - 1;
        if goodbits < 0
            goodbits = 0;
        end

        if goodbits <= maxdepth
            resstring = [resstring sprintf('Clearbuffer test: Mindiff.: %1.17f, Maxdiff.: %1.17f --> Accurate to %i bits out of max tested bitdepths %i.  --> PROBLEMATIC\n', minv, maxv, goodbits, maxdepth)];
        else
            resstring = [resstring sprintf('Clearbuffer test: Mindiff.: %1.17f, Maxdiff.: %1.17f --> Accurate to full tested bitdepth range of %i bits.   --> GOOD\n', minv, maxv, maxdepth)];
        end

        if plotit, plot(deltacolors); end;
        drawnow;
    end
end % Of Test 1.

if ismember(2, testblocks)
    invalidated = 0;
    % Test 2: Precision of non-batched Screen 2D drawing commands.
    % This tests how well assignment and interpolation of vertex
    % colors across primitives works - or how well the generic 'varying'
    % interpolators work in case that our own shader based solution is active:
    PsychImaging('PrepareConfiguration');
    PsychImaging('AddTask', 'General', fbdef);

    % Open window with black background color:
    [win rect] = PsychImaging('OpenWindow', screenid, 0, [0 0 350 350], [], [], [], [], [], kPsychGUIWindow);

    % Set color clamping (and precision) for standard 2D draw commands:
    Screen('ColorRange', win, 1, ColorClamping);

    Screen('Flip', win);

    drawncolors = zeros(10, length(testcolors));
    i=1;

    for tc = testcolors
        [xm ym buttons] = GetMouse;
        if buttons(1) && (ym == 0)
            invalidated = 1;
            break;
        end

        % FillRect test: Top-left 10x10 patch:
        Screen('FillRect', win, tc, [0 0 10 10]);

        % FrameRect test:
        Screen('FrameRect', win, tc, [0 0 15 15], 2);

        % FillOval test:
        Screen('FillOval', win, tc, [20 0 30 10]);

        % FrameOval test:
        Screen('FrameOval', win, tc, [20 20 35 41], 4);
        
        % FillArc test: Don't need extra tests for FrameArc or DrawArc as internal codepath
        % in Screen is nearly identical...
        Screen('FillArc', win, tc, [40 0 55 21], 0, 320);
        
        % DrawLine test:
        Screen('DrawLine', win, tc, 0, 24, 10, 24, 2);

        % FramePoly test:
        Screen('FramePoly', win, tc, [0 28 ; 5 28 ; 10 28], 2);
        
        % FillPoly test:
        Screen('FillPoly', win, tc, [0 32 ; 10 32 ; 5 35 ]);
        
        % glPoint test: 
        Screen('glPoint', win, tc, 58, 5, 3);
        
        % gluDisk test:
        Screen('gluDisk', win, tc, 58, 15, 3);
        
        % Flip the buffers - We don't sync to retrace to speed things up a
        % bit. We also don't clear the drawbuffer, as we're overwriting it in
        % next loop iteration at the same location anyway -- saves some time.
        % N.B.: Technically we don't need to flip at all, as we're reading from
        % the 'drawBuffer' anyway which is unaffected by flips.
        if mod (i, 1000) == 0
            % Ok, only do it every 1000th trial to visualize...
            Screen('Flip', win, 0, 2, 2);
        end
        
        % Readback drawbuffer with float precision, only
        % the red/luminance channel:
        patch = Screen('GetImage', win, [0 0 60 40], 'drawBuffer', 1, 1);

        % Store result: FillRect
        drawncolors(1,i) = patch(5,5);

        % Store result: FrameRect
        drawncolors(2,i) = patch(15,10);

        % Store result: FillOval
        drawncolors(3,i) = patch(5,25);

        % Store result: FrameOval
        drawncolors(4,i) = patch(30, 35-1);

        % Store result: FillArc
        drawncolors(5,i) = patch(10, 50-1);

        % DrawLine:
        drawncolors(6,i) = patch(24, 5);
        
        % FramePoly:
        drawncolors(7,i) = patch(28, 5);

        % FillPoly:
        drawncolors(8,i) = patch(33, 5);

        % glPoint:
        drawncolors(9,i) = patch(5, 58);

        % gluDisk:
        drawncolors(10,i) = patch(15, 58);

        if mod(i, 1000)==0
            fprintf('At %i th testvalue of %i...\n', i, 2^maxdepth);
            beep; drawnow;
        end

        i=i+1;
    end

    Screen('CloseAll');

    if ~invalidated
        % Test done.
        primname = {'FillRect', 'FrameRect', 'FillOval', 'FrameOval', 'FillArc', 'DrawLine', 'FramePoly', 'FillPoly', 'glPoint', 'gluDisk'};

        for j=1:size(drawncolors, 1)
            deltacolors = single(testcolors(1:i-1)) - drawncolors(j, 1:i-1);
            minv = min(abs(deltacolors));
            maxv = max(abs(deltacolors));
            goodbits = floor(-(log2(maxv))) - 1;
            if goodbits < 0
                goodbits = 0;
            end

            testname = char(primname{j});

            if goodbits <= maxdepth
                resstring = [resstring sprintf('2D drawing test: %s : Mindiff.: %1.17f, Maxdiff.: %1.17f --> Accurate to %i bits out of max tested bitdepths %i.  --> PROBLEMATIC\n', testname, minv, maxv, goodbits, maxdepth)];
            else
                resstring = [resstring sprintf('2D drawing test: %s : Mindiff.: %1.17f, Maxdiff.: %1.17f --> Accurate to full tested bitdepth range of %i bits.   --> GOOD\n', testname, minv, maxv, maxdepth)];
            end

            if plotit, plot(deltacolors); end;
            drawnow;
        end
    end
end % Test 2.

if ismember(3, testblocks)
    invalidated = 0;
    % Test 3: Precision of Screen 2D batch drawing commands.
    % This tests how well assignment and interpolation of vertex
    % colors across primitives works - or how well the generic 'varying'
    % interpolators work in case that our own shader based solution is
    % active. Batch drawing commands use a different code-path inside
    % Screen for efficient submission of many primitives and colors values,
    % that's why we need to test them extra.
    PsychImaging('PrepareConfiguration');
    PsychImaging('AddTask', 'General', fbdef);

    % Open window with black background color:
    winsize = 2^(ceil(maxdepth / 2)) + 2 + 100;
    [win rect] = PsychImaging('OpenWindow', screenid, 0, [0 0 winsize winsize], [], [], [], [], [], kPsychGUIWindow);

    % Test GPU output positioning, report trouble:
    [rpfx, rpfy, rpix, rpiy, vix, viy, vfx, vfy] = PsychGPURasterizerOffsets(win, drivername);

    % Set color clamping (and precision) for standard 2D draw commands:
    Screen('ColorRange', win, 1, ColorClamping);

    Screen('Flip', win);

    % Convert testcolors to matrix of RGBA quadruples -- Most batch drawing
    % commands only accept RGB or RGBA, not pure Luminance:
    rgbacolors = [ repmat(testcolors, 3, 1) ; ones(1, length(testcolors)) ];

    % Compute corresponding 'xy' matrix of output positions:
    [outx , outy] = meshgrid(0:2^floor(maxdepth/2)-1, 0:2^floor(maxdepth/2)-1);
    
    % Build 2-row matrix of (x,y) pixel positions:
    xy = [outx(:)' ; outy(:)'];
        
    % Compute reference patch:
    refpatch = reshape(rgbacolors(1,:), length(outy), length(outx));

    % Readout region of framebuffer:
    fbrect = [0, 0, max(xy(1,:))+1, max(xy(2,:))+1];

    % DrawDots test: All pixels in a rectangular block in top-left corner,
    % each with a different color:
    Screen('DrawDots', win, xy, 1, rgbacolors, [-vfx, -vfy], 0);
    testname = 'DrawDots';

    % Evaluate and log:
    [resstring2, minv, maxv, goodbits] = comparePatches(plotit, '', testname, maxdepth, refpatch, fbrect);
    if goodbits == 0
        fprintf ('DrawDots result is nonsense. Retrying with a slight twist...\n');
        dyOffset = 1;
        Screen('DrawDots', win, xy, 1, rgbacolors, [-vfx, -vfy + dyOffset], 0);
        testname = 'DrawDots';

        % Evaluate and log:
        [resstring2, minv, maxv, goodbits] = comparePatches(plotit, '', testname, maxdepth, refpatch, fbrect);
    end
    resstring = [resstring resstring2];

    % Visualize and clear buffer back to zero aka black:
    Screen('Flip', win, 0, 0, 2);
    
    % DrawLines test: All pixels in a rectangular block in top-left corner,
    % each with a different color:

    % Need to replicate each column in 'xy', as consecutive columns define
    % a pair of start- and endpoints for a single line-segment. Actually,
    % we need to add a horizontal x offset of 1 to the end-point, as using
    % exactly the same start- and endpoint would create a line of zero
    % length -- ie. no line at all.
    lxy(:, 1:2:(2*size(xy,2))-1) = xy;    
    lxy(:, 2:2:(2*size(xy,2))-0) = xy + repmat([1;0], 1, size(xy, 2));

    % Same replication for color values:
    cxy(:, 1:2:(2*size(xy,2))-1) = rgbacolors;
    cxy(:, 2:2:(2*size(xy,2))-0) = rgbacolors;
    
    % We draw the lines without line-smoothing (=0), as it only works with
    % alpha-blending and we do not want to use alpha-blending in this test
    % block:
    Screen('DrawLines', win, lxy, 1, cxy, [0, 0], 0);
    testname = 'DrawLines';

    % Evaluate and log:
    [resstring2, minv, maxv, goodbits] = comparePatches(plotit, '', testname, maxdepth, refpatch, fbrect);
    if goodbits == 0
        fprintf ('DrawLines result is nonsense. Retrying with a slight twist...\n');
        Screen('DrawLines', win, lxy, 1, cxy, [-vfx, -vfy + 1], 0);
        testname = 'DrawLines';

        % Evaluate and log:
        [resstring2, minv, maxv, goodbits] = comparePatches(plotit, '', testname, maxdepth, refpatch, fbrect);
    end
    resstring = [resstring resstring2];

    % Visualize and clear buffer back to zero aka black:
    Screen('Flip', win, 0, 0, 2);

    % FillRect test:
    fxy = [xy ; xy + repmat([1;1], 1, size(xy, 2))];
    Screen('FillRect', win, rgbacolors, fxy);
    testname = 'FillRect';

    % Evaluate and log:
    [resstring, minv, maxv, goodbits] = comparePatches(plotit, resstring, testname, maxdepth, refpatch, fbrect);

    % Visualize and clear buffer back to zero aka black:
    Screen('Flip', win, 0, 0, 2);

    % FrameRect test:
    fxy = [xy ; xy + repmat([1;1], 1, size(xy, 2))];
    Screen('FrameRect', win, rgbacolors, fxy);
    testname = 'FrameRect';

    % Evaluate and log:
    [resstring, minv, maxv, goodbits] = comparePatches(plotit, resstring, testname, maxdepth, refpatch, fbrect);

    % Visualize and clear buffer back to zero aka black:
    Screen('Flip', win, 0, 0, 2);

    % FillOval test:
    foxy = [xy ; xy + repmat([2;2], 1, size(xy, 2))];
    Screen('FillOval', win, rgbacolors, foxy);
    testname = 'FillOval';

    % Evaluate and log:
    [resstring, minv, maxv, goodbits] = comparePatches(plotit, resstring, testname, maxdepth, refpatch, fbrect);

    % Visualize and clear buffer back to zero aka black:
    Screen('Flip', win, 0, 0, 2);

    % DrawTexture test: Need only 1 texture draw to test all values
    % simultaneously:
    foxy = [xy ; xy + repmat([2;2], 1, size(xy, 2))];
    teximg = refpatch;
    if Textures <=0
        % Integer texture instead of float texture: Now expected range of
        % values is 0-255 instead of 0.0 - 1.0. Need to rescale:
        teximg = uint8(refpatch * 255);
    end

    tex = Screen('MakeTexture', win, teximg, [], [], Textures);
    Screen('DrawTexture', win, tex, [], OffsetRect(fbrect, 0, 0), [], Filters);
    Screen('Close', tex);
    testname = 'DrawTexture';

    % Evaluate and log:
    [resstring, minv, maxv, goodbits] = comparePatches(plotit, resstring, testname, maxdepth, refpatch, fbrect);

    if goodbits < maxdepth
        maybeSamplerbug = 1;
    end

    % Visualize and clear buffer back to zero aka black:
    Screen('Flip', win, 0, 0, 2);

    % Test of gamma correction shader: This is the
    % 'SimpleGamma' shader used by the imaging
    % pipeline, setup by PsychColorCorrection():
    gamma = 1/2.374238462047320;
    gammaShader = LoadGLSLProgramFromFiles({'GammaCorrectionShader.frag.txt' , 'ICMSimpleGammaCorrectionShader.frag.txt'});
    glUseProgram(gammaShader);
    glUniform3f(glGetUniformLocation(gammaShader, 'ICMEncodingGamma'), gamma, gamma, gamma);
    % Default min and max luminance is 0.0 to 1.0, therefore reciprocal 1/range is also 1.0:
    glUniform3f(glGetUniformLocation(gammaShader, 'ICMMinInLuminance'), 0.0, 0.0, 0.0);
    glUniform3f(glGetUniformLocation(gammaShader, 'ICMMaxInLuminance'), 1.0, 1.0, 1.0);
    glUniform3f(glGetUniformLocation(gammaShader, 'ICMReciprocalLuminanceRange'), 1.0, 1.0, 1.0);
    % Default gain to postmultiply is 1.0:
    glUniform3f(glGetUniformLocation(gammaShader, 'ICMOutputGain'), 1.0, 1.0, 1.0);
    % Default bias to is 0.0:
    glUniform3f(glGetUniformLocation(gammaShader, 'ICMOutputBias'), 0.0, 0.0, 0.0);

    glUniform2f(glGetUniformLocation(gammaShader, 'ICMClampToColorRange'), 0.0, 1.0);
    glUseProgram(0);

    teximg = refpatch;
    if Textures <=0
        % Integer texture instead of float texture: Now expected range of
        % values is 0-255 instead of 0.0 - 1.0. Need to rescale:
        teximg = uint8(refpatch * 255);
    end

    tex = Screen('MakeTexture', win, teximg, [], [], Textures);
    % Use filterMode 0 aka nearest neighbour sampling for the gamma test. The reason is that
    % shader based gamma correction is typically done with the image postprocessing pipeline
    % with nearest neighbour sampling, or when done manually with filterMode 0. A bilinear
    % filter usually doesn't make much sense. Some gfx hardware and drivers do not sample
    % precise enough here in bilinear mode, and that will cause this test to report a gamma
    % precision much lower than what one would actually get in real world use. So lets adapt
    % the test to the real world use conditions here. Problems with precision will still show
    % up in tests where they matter to users under real world conditions, e.g., the tests for
    % texture drawing with alpha-blending and 1+1 overdraws alpha-blending.
    Screen('DrawTexture', win, tex, [], OffsetRect(fbrect, 0, 0), [], 0, [], [], gammaShader);
    Screen('Close', tex);
    testname = 'GammaCorrection';

    % Evaluate and log:
    gammapatch = refpatch .^ gamma;
    [resstring, minv, maxv, goodbits] = comparePatches(plotit, resstring, testname, maxdepth, gammapatch, fbrect);

    if goodbits < 16
        maybeSamplerbug = 1;
    end

    % Visualize and clear buffer back to zero aka black:
    Screen('Flip', win, 0, 0, 2);
    
    Screen('CloseAll');

end % Test 3.

if ismember(4, testblocks)
    invalidated = 0;
    % Test 4: Precision of texture drawing commands.
    PsychImaging('PrepareConfiguration');
    PsychImaging('AddTask', 'General', fbdef);

    % Open window with black background color:
    winsize = max(514, 2^(ceil(maxdepth / 2)) + 2) + 100;
    [win rect] = PsychImaging('OpenWindow', screenid, 0, [0 0 winsize winsize], [], [], [], [], [], kPsychGUIWindow);

    % Test GPU output positioning, report trouble:
    [rpfx, rpfy, rpix, rpiy, vix, viy, vfx, vfy] = PsychGPURasterizerOffsets(win, drivername);

    % Set color clamping (and precision) for standard 2D draw commands. In
    % our case here, this affects the precision and clamping of the
    % 'modulateColor' and 'globalAlpha' parameters of 'DrawTexture'
    % commands:
    Screen('ColorRange', win, 1, ColorClamping);

    Screen('Flip', win);

    % Convert testcolors to matrix of RGBA quadruples -- Most batch drawing
    % commands only accept RGB or RGBA, not pure Luminance:
    rgbacolors = [ repmat(testcolors, 3, 1) ; ones(1, length(testcolors)) ];

    % Compute corresponding 'xy' matrix of output positions:
    [outx , outy] = meshgrid(0:2^floor(maxdepth/2)-1, 0:2^floor(maxdepth/2)-1);
    
    % Build 2-row matrix of (x,y) pixel positions:
    xy = [outx(:)' ; outy(:)'];
        
    % Compute color patch:
    colpatch = reshape(rgbacolors(1,:), length(outy), length(outx));
    colpatch = [colpatch , -colpatch];
    
    
    % Readout region of framebuffer:
    fbrect = [0, 0, max(xy(1,:))+1, max(xy(2,:))+1];

    % DrawTexture test: Need only 1 texture draw to test all values inside
    % the texture simultaneously:
    foxy = [xy ; xy + repmat([2;2], 1, size(xy, 2))];
    teximg = colpatch;
    if Textures <=0
        % Integer texture instead of float texture: Now expected range of
        % values is 0-255 instead of 0.0 - 1.0. Need to rescale:
        teximg = uint8(colpatch * 255);
    end
    
    tex = Screen('MakeTexture', win, teximg, [], [], Textures);
    fbrect = Screen('Rect', tex);

    % Now we test modulation of drawn texture pixels with the
    % 'modulateColor' argument:
    
    i=0;
    mingoodbits = inf;
    % Step through range 1 down to -1, in 1/1000th decrements:
    for mc = 1.0:-0.001:-1.0
        i=i+1;

        if mod(i, 100)==0
            beep; drawnow;
            [xm ym buttons] = GetMouse;
            if buttons(1) && (ym == 0)
                invalidated = 1;
                break;
            end
        end

        % DrawTexture, modulateColor == [mc mc mc 1] modulated:
        Screen('DrawTexture', win, tex, [], OffsetRect(fbrect, 0, 0), [], Filters, [], mc);

        % While the GPU does its thing, we compute the Matlab reference
        % patch:
        refpatch = colpatch * mc;

        testname = 'DrawTexture-modulateColor';

        % Evaluate and log:
        [dummy, minv, maxv(i), goodbits] = comparePatches(plotit, [], testname, maxdepth, refpatch, fbrect);
        mingoodbits = min([mingoodbits, goodbits]);

        % Visualize and clear buffer back to zero aka black:
        Screen('Flip', win, 0, 0, 2);
    end

    if ~invalidated
        resstring = [resstring sprintf('%s : Maxdiff.: %1.17f --> Accurate to at least %i bits.\n', testname, max(maxv), mingoodbits)];

        if mingoodbits < 16
            maybeSamplerbug = 1;
        end
    end

    Screen('Close', tex);
    Screen('CloseAll');
end % Test 4.

if ismember(5, testblocks)
    invalidated = 0;
    % Test 5: Precision of alpha blending commands.
    PsychImaging('PrepareConfiguration');
    PsychImaging('AddTask', 'General', fbdef);

    % Open window with black background color:
    winsize = max(514, 2^(ceil(maxdepth / 2)) + 2) + 100;
    [win rect] = PsychImaging('OpenWindow', screenid, [0 0 0 0], [0 0 winsize winsize], [], [], [], [], [], kPsychGUIWindow);

    % Test GPU output positioning, report trouble:
    [rpfx, rpfy, rpix, rpiy, vix, viy, vfx, vfy] = PsychGPURasterizerOffsets(win, drivername);

    % Set color clamping (and precision) for standard 2D draw commands. In
    % our case here, this affects the precision and clamping of the
    % 'modulateColor' and 'globalAlpha' parameters of 'DrawTexture'
    % commands:
    Screen('ColorRange', win, 1, ColorClamping);

    Screen('Flip', win);

    % Enable alpha blending in additive mode: Let's see how precise
    % addition is carried out:
    Screen('Blendfunction', win, GL_ONE, GL_ONE);
    
    % Convert testcolors to matrix of RGBA quadruples -- Most batch drawing
    % commands only accept RGB or RGBA, not pure Luminance:
    rgbacolors = [ repmat(testcolors, 3, 1) ; ones(1, length(testcolors)) ];

    % Compute corresponding 'xy' matrix of output positions:
    [outx , outy] = meshgrid(0:2^floor(maxdepth/2)-1, 0:2^floor(maxdepth/2)-1);

    % Build 2-row matrix of (x,y) pixel positions:
    xy = [outx(:)' ; outy(:)'];

    % Compute color patch:
    colpatch = reshape(rgbacolors(1,:), length(outy), length(outx));
    colpatch = [colpatch , -colpatch];


    % Readout region of framebuffer:
    fbrect = [0, 0, max(xy(1,:))+1, max(xy(2,:))+1];

    % DrawTexture test: Need only 1 texture draw to test all values inside
    % the texture simultaneously:
    foxy = [xy ; xy + repmat([2;2], 1, size(xy, 2))];
    teximg = colpatch;
    if Textures <=0
        % Integer texture instead of float texture: Now expected range of
        % values is 0-255 instead of 0.0 - 1.0. Need to rescale:
        teximg = uint8(colpatch * 255);
    end

    tex = Screen('MakeTexture', win, teximg, [], [], Textures);
    fbrect = Screen('Rect', tex);

    % Now we test modulation of drawn texture pixels with the
    % 'modulateColor' argument:
    nroverdraws = 2;
    alpha=1;
    i=0;
    mingoodbits = inf;
    % Step through range 1 down to -1, in 1/1000th decrements:
    for mc = 1.0:-0.001:-1.0
        i=i+1;

        if mod(i, 100)==0
            beep; drawnow;
            [xm ym buttons] = GetMouse;
            if buttons(1) && (ym == 0)
                invalidated = 1;
                break;
            end
        end

        % DrawTexture, modulateColor == [mc mc mc 1] modulated:
        for odc=1:nroverdraws
            Screen('DrawTexture', win, tex, [], OffsetRect(fbrect, 0, 0), [], Filters, [], [mc mc mc alpha]);
        end
        
        % While the GPU does its thing, we compute the Matlab reference
        % patch:
        
        refpatch = zeros(size(colpatch));
        
        for odc=1:nroverdraws
            % MK: This is needed for ATI X1600 under Tiger to emulate the known color clamping bug: refpatch = max(0, min(1, (refpatch + colpatch * mc * alpha)));
            refpatch = (refpatch + colpatch * mc * alpha);
        end

        testname = 'DrawTexture-modulateColor&Blend1+1';

        % Evaluate and log:
        [dummy, minv, maxv(i), goodbits] = comparePatches(plotit, [], testname, maxdepth, refpatch, fbrect);
        mingoodbits = min([mingoodbits, goodbits]);
        
        % Visualize and clear buffer back to zero aka black:
        Screen('Flip', win, 0, 0, 2);
    end

    if ~invalidated
        resstring = [resstring sprintf('%s : Maxdiff.: %1.17f --> Accurate to at least %i bits with %i overdraws.\n', testname, max(maxv), mingoodbits, nroverdraws)];
        if mingoodbits < 16
            maybeSamplerbug = 1;
        end
    end

    Screen('Close', tex);
        
    Screen('CloseAll');
end % Test 5.

if ismember(6, testblocks)
    % Test 6: Precision of DrawText.
    invalidated = 0;
    PsychImaging('PrepareConfiguration');
    PsychImaging('AddTask', 'General', fbdef);

    % Open window with black background color:
    [win rect] = PsychImaging('OpenWindow', screenid, 0, [0 0 228 228], [], [], [], [], [], kPsychGUIWindow);

    % Set color clamping (and precision) for standard 2D draw commands:
    Screen('ColorRange', win, 1, ColorClamping);

    Screen('Flip', win);

    textmaxdepth = 8;
    texttestcolors = 1 - linspace(0,1,2^textmaxdepth);
    drawncolors = zeros(1, length(texttestcolors));
    i=1;

    Screen('TextSize', win, 128);
    Screen('TextFont', win, 'Arial');

    for tc = texttestcolors
        [xm ym buttons] = GetMouse;
        if buttons(1) && (ym == 0)
            invalidated = 1;
            break;
        end

        Screen('DrawText', win, '+', 0, 0, tc);
        [nbox, bbox] = Screen('TextBounds', win, '+', 0, 0);
        [patchx, patchy] = RectCenter(bbox);

        % Flip the buffers - We don't sync to retrace to speed things up a
        % bit. We also don't clear the drawbuffer, as we're overwriting it in
        % next loop iteration at the same location anyway -- saves some time.
        % N.B.: Technically we don't need to flip at all, as we're reading from
        % the 'drawBuffer' anyway which is unaffected by flips.
        if 1 || mod (i, 1000) == 0
            % Ok, only do it every 1000th trial to visualize...
            Screen('Flip', win, 0, 2, 2);
        end

        % Readback drawbuffer with float precision, only
        % the red/luminance channel:
        patch = Screen('GetImage', win, [], 'drawBuffer', 1, 1);

        % Store result: FillRect
        drawncolors(1,i) = patch(patchy,patchx);

        if mod(i, 1000)==0
            fprintf('At %i th testvalue of %i...\n', i, 2^textmaxdepth);
            beep; drawnow;
        end

        i=i+1;
    end

    Screen('CloseAll');

    % Test done.
    primname = {'DrawText'};

    for j=1:size(drawncolors, 1)
        deltacolors = single(texttestcolors(1:i-1)) - drawncolors(j, 1:i-1);
        minv = min(abs(deltacolors));
        maxv = max(abs(deltacolors));
        goodbits = floor(-(log2(maxv))) - 1;
        if goodbits < 0
            goodbits = 0;
        end

        testname = char(primname{j});
        if ~invalidated
            if goodbits <= textmaxdepth
                resstring = [resstring sprintf('2D drawing test: %s : Mindiff.: %1.17f, Maxdiff.: %1.17f --> Accurate to %i bits out of max tested bitdepths %i.  --> PROBLEMATIC!\n', testname, minv, maxv, goodbits, textmaxdepth)];
            else
                resstring = [resstring sprintf('2D drawing test: %s : Mindiff.: %1.17f, Maxdiff.: %1.17f --> Accurate to full tested bitdepth range of %i bits.   --> GOOD!\n', testname, minv, maxv, textmaxdepth)];
            end
        end
        if plotit, plot(deltacolors); end;
        drawnow;
    end
end % Test 6.

fprintf('\n\nTest summary:\n');
fprintf('-------------\n\n');
fprintf('%s\n\n', resstring);

% Check if this is the samplerbug we found on OSX 10.11 El Capitan with AMD gpus
% and also to a lesser degree on KUbuntu 16.04.0 LTS with Mesa 11.2.0 on modern AMD gpus.
if maybeSamplerbug && (Filters > 0) && (ColorClamping == 0)
    fprintf('The precision of some test results involving texture drawing with bilinear filtering\n');
    fprintf('enabled and color clamping disabled is suspiciously low. This hints at a graphics driver\n');
    fprintf('bug or low precision hardware wrt. shader based texture filtering. You may be able to work\n');
    fprintf('around this issue by using the Screen ConserveVRAMSetting kPsychAssumeGfxCapVCGood. See the help of\n');
    fprintf('''help ConserveVRAMSettings'' for more info. If you do not need filtered texture drawing, then it\n');
    fprintf('would be advisable to use the Screen DrawTexture command with a filterMode of 0 on your system.\n\n');

    if bitand(Screen('Preference','ConserveVRAM'), 2^28) == 0
        fprintf('I will rerun the problematic tests now with that ConserveVRAMSetting...\n\n');
        oldConserveVRAM = Screen('Preference','ConserveVRAM', 2^28);
        HighColorPrecisionDrawingTest(testconfig, maxdepth, intersect(testblocks, [3,4,5]));
        Screen('Preference','ConserveVRAM', oldConserveVRAM);
    else
        fprintf('Ok, retesting did not improve it to perfection. Retrying with filterMode 0 to see how nearest neighbour filtering performs...\n\n');
        Screen('Preference','ConserveVRAM', 0);
        testconfig(5) = 0;
        HighColorPrecisionDrawingTest(testconfig, maxdepth, intersect(testblocks, [3,4,5]));
    end
end

% Restore synctest settings:
Screen('Preference', 'SkipSyncTests', oldsync);
Screen('Preference', 'Verbosity', oldVerbosity);

end

function [resstring, minv, maxv, goodbits] = comparePatches(plotit, resstring, testname, maxdepth, refpatch, fbrect)
    global win;

    % Readback drawbuffer with float precision, only the red/luminance channel:
    patch = Screen('GetImage', win, fbrect, 'drawBuffer', 1, 1);

    deltacolors = single(refpatch) - single(patch);
    if plotit
        imagesc(deltacolors);
    end

    minv = min(min(abs(deltacolors)));
    maxv = max(max(abs(deltacolors)));
    goodbits = floor(-(log2(maxv))) - 1;
    if goodbits < 0
        goodbits = 0;
    end

    if isempty(resstring)
        resstring = '';
    end

    if goodbits <= maxdepth
        resstring = [resstring sprintf('2D drawing test: %s : Mindiff.: %1.17f, Maxdiff.: %1.17f --> Accurate to %i bits out of max tested bitdepths %i.  --> PROBLEMATIC!\n', testname, minv, maxv, goodbits, maxdepth)];
    else
        resstring = [resstring sprintf('2D drawing test: %s : Mindiff.: %1.17f, Maxdiff.: %1.17f --> Accurate to full tested bitdepth range of %i bits.   --> GOOD!\n', testname, minv, maxv, maxdepth)];
    end
end
