function TwoBitFlickerTest
% TwoBitFlickerTest
% Alternately displays two patterns, at 8 Hz, that differ in only the
% lowest bits. Some are designed to flicker visibly if the low bits work,
% some are null tests that will not flicker if the low bits work. This
% test is primarily of interest in assessing whether a card with 10-bit
% DACs actually achieves 10-bit performance, as driver problems limit many
% such cards to 8-bit accuracy. However, it will work with cards with DACs 
% of any precision. This program exercises the lowest two bits. 
% web http://psychtoolbox.org/mac.html#drivers
% 
% We have several tests (ScreenTest, ClutTest, etc) based on GetClut that
% rigorously test the card and driver, but there is always the lingering
% possibility that the values reported by GetClut are not what is actually
% sent to the DAC. E.g. it may report correct function when the actual
% values are wrong, or it may spuriously report errors that are not there.
% As of 8/10/02 it appears that the Radeon 7500 Mobility driver does
% achieve 10 bit performance despite spurious errors reported by GetClut.
% 
% The tests are all alternations. There is a background staircase pattern,
% going from black and the left to white at the right. All the steps in
% the staircase have DAC values that are a multiple of 4. We add
% increments to that staircase. "+0 / +3" means that we alternately add 0
% or 3 to each step. The alternation frequency is 8 Hz. This comparison
% shows whether wiggling the bottom two bits has any visual consequence.
% "+0 / +1" wiggles just the bottom bit. "+0 +4 / +2 +2" is a null test to
% see whether the "2" bit is worth half of a "4" bit. If it's right then
% there should be no luminance flicker.
% 
% Inspired by prior efforts of Jack Nachmias, Ben Singer, Doug Taylor, 
% David Brainard, and Bob Dougherty.

% 8/10/02 dgp Wrote it. 

textSize=16;
fprintf('TwoBitFlickerTest\n');
for s=screen('screens')
	PrepareScreen(s);
	if ScreenDacBits(s)>=8 % change from ">=" to ">" to skip 8-bit DAC cards.
		bits=ScreenDacBits(s);
		white=2^Screen(s,'PixelSize')-1;
		DescribeScreen(s);
		w=screen(s,'openwindow');
		
		steps=ScreenClutSize(s)/2/2; % divide into two cluts, each with 2 entries per "step".
		clut=(0:steps-1)*2^bits/steps;
		clut=expand(clut',3,2);
		clut=min(clut,2^bits-8);
		LoadClut(s,clut);
		r=screen(w,'rect');
		ramp=0:RectWidth(r)-1;
		ramp=2*round((steps-1)*ramp/ramp(end)); % uses bottom half of CLUT
		ramp2=ramp+ScreenClutSize(s)/2;         % uses top half of CLUT;
		
		bands=2;
		bandHeight=4*round(RectHeight(r)/bands/4);
		textUp=(bandHeight-textSize)/2;
		ww=screen(w,'openoffscreenwindow');
		screen(ww,'TextSize',textSize);
		
		% alternate 0/3
		screen(ww,'putimage',ramp,r);
		rBand=[0 0 r(3) bandHeight];
		screen(ww,'DrawText','Steady: +0 / +0',16,rBand(4)-textUp,white);
		rBand=OffsetRect(rBand,0,RectHeight(rBand));
		screen(ww,'putimage',ramp2,rBand);
		screen(ww,'DrawText','Flicker? +0 / +3',16,rBand(4)-textUp,white);
		cluts{1}=[clut; clut];
		cluts{2}=[clut; clut+3];
		show(s,w,ww,cluts);
		
		% alternate 0/1
		screen(ww,'putimage',ramp,r);
		rBand=[0 0 r(3) bandHeight];
		screen(ww,'DrawText','Steady: +0 / +0',16,rBand(4)-textUp,white);
		rBand=OffsetRect(rBand,0,RectHeight(rBand));
		screen(ww,'putimage',ramp2+1,rBand);
		screen(ww,'DrawText','Flicker? +0 / +1',16,rBand(4)-textUp,white);
		cluts{1}=[clut; clut];
		cluts{2}=[clut; clut+1];
		show(s,w,ww,cluts);
		
		% alternate  +0 +4 / +2 +2 for null
		screen(ww,'putimage',ramp,r);
		rBand=[0 0 r(3) bandHeight];
		screen(ww,'DrawText','Steady: +0 / +0',16,rBand(4)-textUp,white);
		rBand=OffsetRect(rBand,0,RectHeight(rBand));
		screen(ww,'putimage',repmat([ramp2;ramp2+1],bandHeight/2,1),rBand);
		screen(ww,'DrawText','Steady? +0 +4 / +2 +2',16,rBand(4)-textUp,white);
		cluts{1}=[clut; clut+repmat([0;4],length(clut)/2,3)];
		cluts{2}=[clut; clut+repmat([2;2],length(clut)/2,3)];
		show(s,w,ww,cluts);
		
		% alternate  +0 +0 +0 +4 / +1 +1 +1 +1 for null
		screen(ww,'putimage',ramp,r);
		rBand=[0 0 r(3) bandHeight];
		screen(ww,'DrawText','Steady: +0 / +0',16,rBand(4)-textUp,white);
		rBand=OffsetRect(rBand,0,RectHeight(rBand));
		screen(ww,'putimage',repmat([ramp2;ramp2;ramp2;ramp2+1],bandHeight/4,1),rBand);
		screen(ww,'DrawText','Steady? +0 +0 +0 +4 / +1 +1 +1 +1',16,rBand(4)-textUp,white);
		cluts{1}=[clut; clut+repmat([0;4],length(clut)/2,3)];
		cluts{2}=[clut; clut+repmat([1;1],length(clut)/2,3)];
		show(s,w,ww,cluts);
		
		screen('closeall');
		DescribeScreen(-2);
		string=sprintf('If all your answers were "yes" then the lowest two bits of your %d bit CLUT and DAC are working correctly.\n',bits);
		fprintf('%s',WrapString(string));
	end
end
DescribeScreen(-1);

function show(s,w,ww,cluts)
	bits=ScreenDacBits(s);
	white=2^Screen(s,'PixelSize')-1;
	r=Screen(w,'Rect');
	string=sprintf('TwoBitFlickerTest. %d-bit DACs, range 0:%d. Hopefully all your answers are "yes". Hit any key to continue.',bits,2^bits-1);
	screen(ww,'DrawText',string,5,r(4)-5,white);
	screen('copywindow',ww,w);
	frames=round(FrameRate(w)/16);
	while charavail
		getchar;
	end
	while ~charavail
		% flicker at 8 Hz until key is presses
		LoadClut(w,cluts{1});
		screen(w,'waitblanking',frames);
		LoadClut(w,cluts{2});
		screen(w,'waitblanking',frames);
	end
	getchar;
return
