function ClutTest(whichScreen)
% ClutTest([whichScreen])
% Usually, you'll call this with no arguments, to test all screens.
% ClutTest assesses loading of the CLUT in graphics cards with
% 8-or-more-bit DACs. It uses Screen 'Gamma' (cscGetGamma) and
% 'GetClut' (cscGetEntries) and compares the returned values with what's
% expected. When "randomize" is 1, we fill both the gamma and color tables
% with random numbers, and the expected CLUT contents are compared with
% what's returned by GetClut. This is a very stringent test, which most
% drivers pass. Please report to the forum any graphics card that fails.
% web mailto:psychtoolbox@yahoogroups.com ;
% 
% LoadClutTest and ClutTest are closely related. LoadClutTest assesses
% accuracy of the LoadClut function, which is the highest-level user
% interface we provide. ClutTest is aimed one step lower, assessing 
% performance of the various driver calls, especially cscSetEntries,
% cscSetGamma, and cscGetEntries, and does a rigorous test with random
% numbers in both the color and gamma tables.
% 
% fixATIRadeon7000: This fix tweaks the gamma table to nearly cancel the
% Radeon 7000 driver error. However, there are still occasional errors in
% the stringent "randomize" test. You may enable/disable this fix by
% commenting/uncommenting the line (near 126) in PrepareScreen.m that sets
% "screenGlobal(screenNumber+1).fixATIRadeon7000". As of late July 2002,
% we understand that ATI is considering a ROM upgrade for the Radeon 7000
% to fix this bug (and others). Check our web site:
% web http://psychtoolbox.org/mac.html#drivers ;
% 
% Also see LoadClutTest.

% 7/18/98 dgp Wrote it as GammaTest.
% 7/20/98 dgp enhanced, based on feedback from Steve Engel, running it on his Radius Thunder.
% 7/21/98 dgp improved error checking.
% 7/25/98 dgp add conclusion
% 8/26/98 dgp apply logical() to ok for logical indexing.
% 5/20/99 dgp Test most cards only at nominal dac size.
% 4/24/02 awi Exit on PC with message.
% 6/18/02 dgp test both polarities of UseHighGammaBits.
% 6/22/02 dgp Add fixATIRadeon7000.
% 6/22/02 dgp Renamed ClutTest. (Discarded old program with that name.)
% 6/25/02 dgp Cleaned up for release.
% 6/28/02 dgp Chris Chase <chris_chase@mac.com> reported a failure in Mac OS 
%              X Classic. There is a mismatch in gamma table size between 
% 			   GetGamma and SetGamma. So now we check for that possibility 
% 			   and deal with it as best we can.
% 7/26/02 dgp Fixed minor bug, we were reporting the wrong value for "GetGamma" bits.

global screenGlobal

if strcmp('PCWIN',computer)
    error('Win: ClutTest not yet supported.');
end
Screen('CloseAll');

if nargin==0
	fprintf('ClutTest\n');
	RestoreScreen;
	PrepareScreen; 
	DescribeComputer;
	for whichScreen = Screen('Screens')
		DescribeScreen(whichScreen);
		DescribeScreenPrefs(whichScreen);
		ClutTest(whichScreen);
	end
	DescribeScreen(-1);
	return
end

for s=whichScreen;
	s=Screen(s,'WindowScreenNumber');
 	oldUseHighGammaBits=Screen(s,'Preference','UseHighGammaBits'); % save
	DescribeScreen(-2);
	if isempty(screenGlobal)
		PrepareScreen(s);
	end
	[oldGammaTable,oldBits,gammaError]=Screen(s,'Gamma');
	possibleBits=unique([8 oldBits ScreenDacBits(s) 10]);
	for bits=possibleBits
		for pixelSize=Screen(s,'PixelSizes')
			Screen(s,'PixelSize',pixelSize);
			randomize=0;
			test=TestOneScreen(s,bits,randomize);
			if test.maxError>100 & test.bits~=8
				Screen(s,'Preference','UseHighGammaBits',~ScreenUsesHighGammaBits(s));
				test=TestOneScreen(s,bits,randomize);
			end
			if test.ok
				randomize=1;
				test=TestOneScreen(s,bits,randomize);
			end
			list(find(bits==possibleBits))=test;
		end
	end
	if any([list.ok])
		fprintf('Screen driver supports gamma tables with precisions:');fprintf(' %d',list(logical([list.ok])).bits);fprintf(' bits.\n');
	else
		fprintf('Errors encountered at all tested gamma table resolutions:');fprintf(' %d',list.bits);fprintf(' bits.\n');
	end
 	Screen(s,'Preference','UseHighGammaBits',oldUseHighGammaBits); % restore
end
return

function list=TestOneScreen(s,possibleBits,randomize)
global screenGlobal
for i=1:length(possibleBits)
	list(i).ok=logical(1);
	list(i).bits=possibleBits(i);
	list(i).maxError=0;
	list(i).rmsError=0;
end
for iBits=1:length(list)
	% 	fprintf('UseHighGammaBits %d. ',Screen(s,'Preference','UseHighGammaBits')); % boring
	fprintf('pixelSize %2d. ',Screen(s,'PixelSize'));
	fprintf('randomize %d. ',randomize);
	bits=list(iBits).bits;
	list(iBits).ok=1;
	gammaTable=bitshift(257*[0:255]',bits-16);
	if randomize
		gammaTable=randsample(0:2^bits-1,size(gammaTable));
	end
	fprintf('SetGamma %2d bits. ',bits);
	[oldGammaTable,oldBits,gammaError]=Screen(s,'Gamma',gammaTable,bits);
	[getGammaTable,getBits,getGammaError]=Screen(s,'Gamma');
	if gammaError.set
		fprintf('SetGamma error %d. \n',gammaError.set);
		list(iBits).ok=0;
	end
	if getGammaError.get
		fprintf('GetGamma error %d. \n',getGammaError.get);
		list(iBits).ok=0;
	end
	if ~any([gammaError.set getGammaError.get])
		fprintf('GetGamma %2d bits. ',getBits);
		if size(getGammaTable,2)==3 & size(gammaTable,2)==1 & size(getGammaTable,1)==size(gammaTable,1)
			g=getGammaTable(:,1);
			getGammaTable=g;
		end
		if any(size(getGammaTable)~=size(gammaTable))
			fprintf('GetGamma table %dx%d should be %dx%d.\n',size(getGammaTable),size(gammaTable));
			list(iBits).ok=0;
		else
			list(iBits).ok=all(gammaTable==getGammaTable);
			if ~list(iBits).ok
				fprintf('Error in gamma table.\n');
				for i=1:4
					g=gammaTable(i,1)-getGammaTable(i,1);
					fprintf('%4d !=%4d\n',gammaTable(i,1),getGammaTable(i,1))
				end
			end
		end
	end
	if screenGlobal(s+1).fixATIRadeon7000 & bits>8
		% +1 to correct error in ATI Radeon 7000 driver
		fprintf('fixATIRadeon7000. ');
		gt=min(gammaTable+1,2^bits-1);
		gt(find(gt==682))=681;
		[g,oldBits,gammaError]=Screen(s,'Gamma',gt,bits);
	end
	if list(iBits).ok
		clutSize=ScreenClutSize(s);
		colorTable=(0:clutSize-1)'*[1 1 1];
		colorTable=[(0:clutSize-1)' (clutSize-1:-1:0)' Shuffle(0:clutSize-1)']; % red goes up, green goes down, blue is shuffled
		if randomize
			colorTable=RandSample(0:clutSize-1,size(colorTable));
		end
		err=Screen(s,'SetClut',colorTable);
		if err
			fprintf('SetClut error %d. ',err);
		end
		clut=Screen(s,'GetClut',bits);
		if isempty(clut)
			fprintf('GetClut not available.\n');
		else
			expectedClut=[gammaTable(colorTable(:,1)+1,1) gammaTable(colorTable(:,2)+1,1) gammaTable(colorTable(:,3)+1,1)];
			list(iBits).ok=all(all(clut==expectedClut));
			if ~list(iBits).ok
				list(iBits).maxError=max(max(abs(clut-expectedClut)));
				list(iBits).rmsError=mean(mean((clut-expectedClut).^2)).^0.5;
				fprintf('GetClut != predicted. Max error %d. Rms error %.1f.\n',list(iBits).maxError,list(iBits).rmsError);
				[row,column]=find(clut~=expectedClut);
				row=unique(row)';
				if length(row)>8
					row1=row(1:4);
					row2=row(length(row)-3:length(row));
				else
					row1=row;
					row2=[];
				end
				for i=row1
					fprintf('%3d: GetClut:[%5d %5d %5d] vs. expected:[%5d %5d %5d]\n',i-1,clut(i,:),expectedClut(i,:))
				end
				if length(row2)>0
					fprintf('         . . .\n');
				end
				for i=row2
					fprintf('%3d: GetClut:[%5d %5d %5d] vs. expected:[%5d %5d %5d]\n',i-1,clut(i,:),expectedClut(i,:))
				end
			end
		end
	end
	if list(iBits).ok & ~isempty(clut)
		fprintf('GetClut == predicted.\n');
	end
end
Screen(s,'Gamma',[0:255]',8); % We're done, so go back to 8-bit gamma table for maximum compatibility.
Screen(s,'SetClut',ClutDefault(s));
