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