function FlickerTest(whichScreen)
% FlickerTest([whichScreen])
% 
% Usually you'll call this with no arguments, to test all screen.
% 
% FlickerTest does a visual cancellation test on each of your displays. 
% It's a quick test, but lots of things have to work right to achieve
% cancellation, so success rules out a wide variety of potential problems.
% We recommend it (along with ScreenTest) for checking out any new
% configuration of computer, software, and display.
% 
% FlickerTest displays a counterphase-flickered grating at maximum speed,
% half the frame rate, for 3 seconds. The alternating opposite-contrast
% gratings nearly cancel each other, providing a sensitive test for stable
% eye position and reliable synchronization of the program to the display.
% Normally, if you keep your eyes still, the alternating frames nearly
% cancel each other out when integrated in your eye. The visible residue
% is a faint static grating at twice the spatial frequency of the original
% gratings due to nonlinear compression of luminance in the display
% (gamma) and in your eye.
% 
% The test is done twice: first using the blanking interrupt to synch with
% the display, second using SetClut to synch with the display.
% 
% The movie should appear very faint unless you make a saccade (move your
% eyes past it quickly), at which point you'll see it momentarily as a
% static high-contrast grating.
% 
% This movie is a particularly good way to detect whether timing is
% reliable. If some other process running on your computer (e.g. the
% operating system) steals large amounts of time, or if blanking
% interrupts are occasionally delayed, so that drawing a frame
% occasionally takes more than a frame interval, then each doubled frame
% will appear as a high contrast flash of the original grating.
% 
% See also ScreenTest, SetClutTest, WaitBlankingTest, PeekBlankingTest, and RushTest.

% 2/17/00 dgp Wrote it as "SaccadeDemo".
% 2/16/02 dgp Expanded help. Simplified code. Renamed "FlickerTest".
% 2/18/02 dgp Enhanced to test separately with interrupts and SetClut.
% 2/18/02 dgp Force SetClutDriverWaitsForBlanking to 1.
% 2/18/02 dgp Test priority dipping.
% 2/18/02 dgp Test all screens.
% 2/19/02 dgp Cosmetic.
% 2/19/02 dgp Properly restore SetClutDriverWaitsForBlanking.
% 2/21/02 dgp Polished the help text. Test interrupts at priority zero.
% 2/24/02 dgp Run for 3 secs, even if WaitBlanking doesn't wait.
% 4/24/02 awi Exit on PC with message.

if strcmp('PCWIN',computer)
    error('Win: FlickerTest not yet supported');
end

if nargin==0
	fprintf('FlickerTest\n');
	for whichScreen=Screen('Screens')
		FlickerTest(whichScreen);
	end
	DescribeScreen(-1);
	return
end

% window
PrepareScreen(whichScreen);
DescribeScreen(whichScreen);
DescribeScreenPrefs(whichScreen);
window=Screen(whichScreen,'OpenWindow');
white=WhiteIndex(window);
black=BlackIndex(window);
gray=(white+black)/2;
if round(gray)==white
	gray=black;
end
inc=white-gray;
Screen(window,'FillRect',gray);

% compute each frame of the movie
frames=2; % period in time, of the drifting grating
for i=1:frames
	phase=(i/frames)*2*pi;
	% grating
	[x,y]=meshgrid(-200:200,-200:200);
	angle=0*pi/180; % 0 deg orientation.
	f=0.1*2*pi; % cycles/pixel
	a=cos(angle)*f;
	b=sin(angle)*f;
	m=exp(-((x/100).^2)-((y/100).^2)).*sin(a*x+b*y+phase);
	w(i)=Screen(whichScreen,'OpenOffscreenWindow',0,[0 0 size(m)]);
	Screen(w(i),'PutImage',gray+inc*m);
end

% show the movie
r=Screen(w(1),'Rect');
rr=CenterRect(r,screen(window,'Rect'));
Screen('Screens');	% Make sure all Rushed functions are in memory.
Screen(window,'FillRect',gray);
i=0;				% Allocate all Rushed variables.
n=round(3*FrameRate(window)); % 3 seconds
t=zeros(1000*n,1); % Allocate enough to cope with not waiting, e.g. 10000 Hz iteration of loop.
loop={
	'i=1;'
	'stop=GetSecs+3;'
	'while GetSecs<stop;'
		'Screen(window,''WaitBlanking'');'
		't(i)=GetSecs;'
		'i=i+1;'
		'Screen(''CopyWindow'',w(1+mod(i,frames)),window,r,rr);'
	'end;'
	'Screen(window,''FillRect'',gray);'
	'n=i-1;'
};

% Use interrupt
DescribeScreen(-2);
Screen(window,'Preference','WaitBlankingAlwaysCallsSetClut',0);
Screen(window,'Preference','SetClutCallsWaitBlanking',0);
priorityLevel=MaxPriority(window,'BlankingInterrupt');
fprintf('WaitBlanking is using blanking interrupt to synch with %.0f Hz display:\n',FrameRate(whichScreen));
message=sprintf('WaitBlanking is using blanking interrupt to synch with display:');
Screen(window,'TextSize',18);
for priorityLevel=[0 MaxPriority(window,'BlankingInterrupt')]
	m=[message sprintf(' Priority %g',priorityLevel)];
	Screen(window,'DrawText',m,10,30);
	Rush(loop,priorityLevel);
	fprintf('%3.0f Hz: priority %g\n',(n-1)/(t(n)-t(1)),priorityLevel);
end

% Use SetClut
DescribeScreen(-2);
fprintf('WaitBlanking is using SetClut to synch with %.0f Hz display:\n',FrameRate(whichScreen));
message=sprintf('WaitBlanking is using SetClut to synch with display:');
setClutDriverWaitsForBlanking=Screen(window,'Preference','SetClutDriverWaitsForBlanking',1);
Screen(window,'Preference','WaitBlankingAlwaysCallsSetClut',1);
Screen(window,'Preference','SetClutCallsWaitBlanking',0);
if ~setClutDriverWaitsForBlanking
	fprintf('NOTE: SetClutDriverWaitsForBlanking was zero, so this test will probably run too fast.\n');
end
for priorityLevel=[0 MaxPriority(window,'BlankingInterrupt') 2 7]
	m=[message sprintf(' Priority %g',priorityLevel)];
	Screen(window,'DrawText',m,10,30);
	Rush(loop,priorityLevel);
	fprintf('%3.0f Hz: priority %g\n',(n-1)/(t(n)-t(1)),priorityLevel);
end
Screen(window,'Preference','SetClutDriverWaitsForBlanking',setClutDriverWaitsForBlanking);

if ~setClutDriverWaitsForBlanking
	fprintf('\n');
	fprintf('As an experiment, let''s try priority dipping, which gets the ATI Rage128 driver to wait for blanking.\n')
	Screen(window,'Preference','MinimumSetClutPriority',2);
	Screen(window,'Preference','DipPriorityAfterSetClut',1);
	% Use SetClut
	message=sprintf('WaitBlanking is now using SetClut (with priority dipping) to synch with %.0f Hz display:',FrameRate(whichScreen));
	fprintf('%s\n',message);
	setClutDriverWaitsForBlanking=Screen(window,'Preference','SetClutDriverWaitsForBlanking',1);
	Screen(window,'Preference','WaitBlankingAlwaysCallsSetClut',1);
	Screen(window,'Preference','SetClutCallsWaitBlanking',0);
	if ~setClutDriverWaitsForBlanking
		fprintf('NOTE: SetClutDriverWaitsForBlanking was zero, so this test will probably run too fast.\n');
	end
	for priorityLevel=[0 MaxPriority(window,'BlankingInterrupt') 7]
		m=[message sprintf(' Priority %g',priorityLevel)];
		Screen(window,'DrawText',m,10,30);
		Rush(loop,priorityLevel);
		fprintf('%3.0f Hz: priority %g\n',(n-1)/(t(n)-t(1)),priorityLevel);
	end
	Screen(window,'Preference','SetClutDriverWaitsForBlanking',setClutDriverWaitsForBlanking);
end
Screen('CloseAll');
