function [maximumError, roundTypeStr, independentFlag]=AlphaMultiplicationAccuracyTest(screenNumber)

% [maximumError, roundTypeStr, independentFlag]=AlphaMultiplicationAccuracyTest([screenNumber])
%
% Test the accuracy of alpha blending multiplication. OpenGL guarantees
% perfect accuracy of alpha multiplication for values 0 and 1 only.
% AlphaMultiplicationAccuracyTest measures accuracy of intermediate values.
%
% Return argument "maximumError" is the maximum unsigned difference between
% OpenGL alpha multiplication and  simulated alpha blending in MATLAB using
% double-precisions floating point multiplication.
%
% Values of maximumError fall into three categories:
%
% 0 <= maximumError < 0.5   : OpenGL rounds to nearest integer.  No 
%                             accuracy loss for a single multiplication.
%
% 0.5 <= maximumError < 1   : OpenGL truncates or rounds up. For a single 
%                             multiplication, Accuracy is off  0.5 parts
%                             in 255 more than would multiplying luminances 
%                             using floating-point values and rounding.
%
% maximumError >= 1         : Something is wrong.  
% 
% 
% AlphaMultiplicationAccuracyTest tries to determine whether OpenGL alpha
% multiplication rounds to the nearest integer, rounds down or rounds up
% and returns in "roundTypeStr" a string indicating which, either, "round"
% "floor", or "ceil".  If AlphaMultiplicationAccuracyTest can not determine
% the rounding method, then it returns "unknown".
%
% AlphaMultiplicationAccuracyTest also tests that multiplication errors are
% independent of the choice of blending factor string and the blending
% surface, setting return argument "independentFlag" accordingly.     
%
% Because in OpenGL pixel color components are ultimately encoded as 8-bit
% integers in video RAM, the results of OpenGL alpha multiplicaion will be
% less accurate than those predicted by floating-point calculations.  If
% OpenGL rounds to the nearest integer then the alpha multiply error will
% be less than 0.5. This is the limit of precision of the color components
% of a pixel. Therefore, when alpha blending rounds to the nearest integer,
% no more accuracy is lost with OpenGL alpha blending than by calculating
% pixel values in MATLAB with floating point precision then rounding them
% to integer pixel values for display.
%
% Note that errors are cumulative and iterative alpha multiplication, in
% which a product of a prevoius multiplication becomes a factor in a
% subsequent multiplication, can produce large errors, even in the best
% case of rounding where 0 <= maximumError < 0.5.  Note also that A single
% alpha blending operation may result in two multipliations, because both
% source and destination surfaces may be multiplied before they are added. 
%    
% We test blending accuracy becasue OpenGL makes no gurantees.  The OpenGL
% policy on alpha multiplician is summarized here:  
% 
% http://msdn.microsoft.com/library/default.asp?url=/library/en-us/opengl/glfunc01_4vs3.asp
%
%         "Despite the apparent precision of the above equations, blending
%         arithmetic is not exactly specified, because blending operates with
%         imprecise integer color values. However, a blend factor that should be
%         equal to one is guaranteed not to modify its multiplicand, and a blend
%         factor equal to zero reduces its multiplicand to zero." 
%
% See also: AlphaBlendingTest, PsychAlphaBlending


% HISTORY
%
% mm/dd/yy
%
% 1/28/05   awi  Wrote it.


% BUGS
%
% There is possibility that an OpenGL implementation would round
% multiplication to the nearest integer but have a different policy for
% which direction to round 0.5 than does MATLAB.  In that case,
% AlphaMultiplicationAccuracyTest would return a maximumError of 0.5 and
% roundTypeStr 'unknown' instead of 'round'.  


exitOnError= nargout < 1;
if nargin==0
    screenNumber=max(Screen('Screens'));
end



%first test GetImage using PutImage because in later steps we rely on
%GetImage alpha blending.
background=127;
w=Screen('OpenWindow', screenNumber, [], [], [], 2);
Screen('FillRect', w, background);
wRect=Screen('Rect',w);


testPlane1=repmat(0:255, 256, 1);
testPlane2=rot90(testPlane1);
testImage1=repmat(testPlane1, [1, 1, 3]);
testImage2=repmat(testPlane2, [1,1,3]);
testImageRect=RectOfMatrix(testImage1);
imageMat1=testImage1;
imageMat1(:,:,4)=zeros(256);
zerosImage=repmat(zeros(256), [1,1,4]);
matRect=RectOfMatrix(testImage1);


sourceBlendFactors={ 'GL_ZERO', 'GL_ONE', 'GL_DST_COLOR', 'GL_ONE_MINUS_DST_COLOR', 'GL_SRC_ALPHA', 'GL_ONE_MINUS_SRC_ALPHA', 'GL_DST_ALPHA', 'GL_ONE_MINUS_DST_ALPHA', 'GL_SRC_ALPHA_SATURATE'};
destinationBlendFactors={'GL_ZERO','GL_ONE', 'GL_SRC_COLOR', 'GL_ONE_MINUS_SRC_COLOR', 'GL_SRC_ALPHA', 'GL_ONE_MINUS_SRC_ALPHA', 'GL_DST_ALPHA', 'GL_ONE_MINUS_DST_ALPHA'};
numSourceFactors=length(sourceBlendFactors);
numDestinationFactors=length(destinationBlendFactors);

sourceBlendVariableFactors={ 'GL_DST_COLOR', 'GL_ONE_MINUS_DST_COLOR', 'GL_SRC_ALPHA', 'GL_ONE_MINUS_SRC_ALPHA', 'GL_DST_ALPHA', 'GL_ONE_MINUS_DST_ALPHA', 'GL_SRC_ALPHA_SATURATE'};
destinationBlendVariableFactors={'GL_SRC_COLOR', 'GL_ONE_MINUS_SRC_COLOR', 'GL_SRC_ALPHA', 'GL_ONE_MINUS_SRC_ALPHA', 'GL_DST_ALPHA', 'GL_ONE_MINUS_DST_ALPHA'};
numSourceBlendVariableFactors=length(sourceBlendVariableFactors);
numDestinationBlendVariableFactors=length(destinationBlendVariableFactors);



% first, check that alpha multiplication gives the same result regardless of
% the mode. We do not test trivial cases of GL_ZERO and GL_ONE.  Those are tested
% elsewhere, by AlphaMultiplicationTest.  

%for source blending
for i=1:numSourceBlendVariableFactors
    %make source and destination images
    [sourceImagesA{i}, destinationImagesA{i}]=SetSourceAlpha(sourceBlendVariableFactors{i}, testPlane2, imageMat1, zerosImage);
    %write the destination matrix
    Screen('BlendFunction', w, 'GL_ONE', 'GL_ZERO');
    Screen('PutImage', w, destinationImagesA{i}, matRect);
    %write the source matrix.
    Screen('BlendFunction', w, sourceBlendVariableFactors{i}, 'GL_ZERO');
    Screen('PutImage', w, sourceImagesA{i}, matRect);
    sourceReadbacks{i}=Screen('GetImage',w,matRect, 'backBuffer');
    Screen('Flip', w);
end


%compare results from every mode to results from every other.  
sourceEqualityMat=zeros(numSourceBlendVariableFactors);
for i=1:numSourceBlendVariableFactors
    for j=1:numSourceBlendVariableFactors
        sourceEqualityMat(i,j)=isempty(find(sourceReadbacks{i}~=sourceReadbacks{j}));
    end
end


%for destination blending
for i=1:numDestinationBlendVariableFactors
    %make source and destination images
    [sourceImagesB{i}, destinationImagesB{i}]=SetDestinationAlpha(destinationBlendVariableFactors{i}, testPlane2, zerosImage, imageMat1);
    %write the destination matrix
    Screen('BlendFunction', w, 'GL_ONE', 'GL_ZERO');
    Screen('PutImage', w, destinationImagesB{i}, matRect);
    %write the source matrix.
    Screen('BlendFunction', w,  'GL_ZERO', destinationBlendVariableFactors{i});
    Screen('PutImage', w, sourceImagesB{i}, matRect);
    destinationReadbacks{i}=Screen('GetImage',w,matRect, 'backBuffer');
    Screen('Flip', w);
end

%on the destination surface, for each mode compare the product to the products for
%every other mode and for each comparison store a bit into a table indicating a match.  
destinationEqualityMat=zeros(numDestinationBlendVariableFactors);
for i=1:numDestinationBlendVariableFactors
    for j=1:numDestinationBlendVariableFactors
        destinationEqualityMat(i,j)=isempty(find(destinationReadbacks{i}~=destinationReadbacks{j}));
    end
end

%on the source surface, for each mode compare the product to the products for
%every other mode and for each comparison store a bit into a table indicating a match.  
sourceEqualityMat=zeros(numSourceBlendVariableFactors);
for i=1:numSourceBlendVariableFactors
    for j=1:numSourceBlendVariableFactors
        sourceEqualityMat(i,j)=isempty(find(sourceReadbacks{i}~=sourceReadbacks{j}));
    end
end


%As above, but combine both source and destination results into a larger
%table indicating whether source surface product matches destination surface product.
allReadbacks={sourceReadbacks{:} destinationReadbacks{:}};
equalityMat=zeros(length(allReadbacks));
for i=1:length(allReadbacks)
    for j=1:length(allReadbacks)
        equalityMat(i,j)=isempty(find(allReadbacks{i}~=allReadbacks{j}));
    end
end

%Test whether multliplication errors on the source surface are independent
%of the blending factor string
sourceIndependentFlag=isempty(find(sourceEqualityMat ~= 1));

%Test whether multliplication errors on the destination surface are independent
%of blending factor string
destinationIndependentFlag=isempty(find(destinationEqualityMat ~= 1));

%Test whether multliplication errors are independent of both the surface and the blending factor string 
independentFlag=isempty(find(equalityMat ~= 1));


% whatever the multiplication accuracy, we  now know from previous tests in
% this file if accuracy is the same for all modes.  It should be,
% therefore, in the next step when testing that accuracy, we need only test
% one mode and know that those results apply to all others.
predictedReadbackAll=AlphaSourceTerm(sourceBlendVariableFactors{1}, sourceImagesA{1}, destinationImagesA{1});
predictedReadback=predictedReadbackAll(:,:,1:3);
maxFloatDiff=max(max(max(abs(predictedReadback-double(sourceReadbacks{1})))));
maxRoundDiff=max(max(max(abs(round(predictedReadback)-double(sourceReadbacks{1})))));
maxFloorDiff=max(max(max(abs(floor(predictedReadback)-double(sourceReadbacks{1})))));
maxCeilDiff=max(max(max(abs(ceil(predictedReadback)-double(sourceReadbacks{1})))));

maximumError=maxFloatDiff;
if maxRoundDiff==0
    roundTypeStr='round';
elseif maxFloorDiff==0
    roundTypeStr='floor';
elseif maxCeilDiff==0
    roundTypeStr='ceil';
else
    roundTypeStr='unknown';
end


Screen('CloseAll');


