function WaitBlankingTest
% WaitBlankingTest tests the Screen 'WaitBlanking' function. It does this
% by timing the rate of a loop that calls 'WaitBlanking'. It does this for
% each screen. It also times a loop that directly calls SetClut.
% 
% Depending on Screen Preference settings and processor priority,
% WaitBlanking relies on either the blanking interrupt or SetClut to synch
% to the display. See "Screen WaitBlanking?".
% 
% When doing SetClut (i.e. cscSetEntries or cscDirectSetEntries) some
% video drivers first wait for blanking. Some don't. A few wait or not,
% depending on priority and the number of entries you are setting in the
% CLUT. We test all the important cases.
% 
% See also SetClutTest, PeekBlankingTest, FlickerTest, RushTest, and ScreenTest.

% 1/15/98 dgp Wrote it.
% 1/19/98 dgp Tried to make the printout more understandable.
% 2/12/98 dgp Don't disable WaitForVBLInterrupt when ~SetClutDriverWaitsForVBL.
% 3/14/98 dgp Test at all priorities.
% 3/17/98 dgp Updated.
% 7/25/98 dgp Updated for new Pref name.
% 2/18/02 dgp Enhanced to test at several priorities. Distinguish use of interrupt and SetClut.
% 2/18/02 dgp Cosmetic.
% 2/18/02 dgp Force SetClutDriverWaitsForBlanking to 1.
% 2/19/02 dgp Cosmetic.
% 2/20/02 dgp Rewrote the timing loop to produce useful results even if SetClut doesn't wait.
% 3/30/02 dgp Enhanced to try both polarities of AskSetClutDriverToWaitForBlanking.
% 4/24/02 awi Exit on PC with message.
% 5/5/02  dgp Cosmetic enhancement of the report.
% 6/12/02 awi Wrote Win section.  
% 6/29/02 dgp Enhanced handling of WaitBlankingAlwaysCallsSetClut to prevent error 
%             reported by Chris Chase <chris_chase@mac.com>

% WIN
if strcmp('PCWIN',computer)
    fprintf('WaitBlankingTest\n');
    fprintf('We time a loop that calls Screen WaitBlanking or SetClut. If the loop\n');
    fprintf('is thus synched to the display blanking, the iteration rate (Hz) of\n');
    fprintf('our loop will match the official FrameRate.\n\n');
    
    numIts=60; %number of times we loop under each condition.
    [w,wRect] = Screen(0,'OpenWindow'); 
    wPriorities = [0:MaxPriority('WaitBlanking')];
    originalPriority=Priority;
    originalClut = Screen(w,'GetClut');
    nominalFrameRate = Screen(w,'FrameRate');
    nominalFramePeriod = 1/nominalFrameRate;
    
    %preload functions
    GetSecs;Screen;
    
    %test 'WaitBlanking' too see if it waits for blanking
    for p = 1:length(wPriorities)
        Priority(wPriorities(p));
        for i = 1:numIts;
            Screen(w,'WaitBlanking');
            tWB(p,i)=GetSecs;
        end
    end
    %test 'SetClut' too see if it waits for blanking
    for p = 1:length(wPriorities)
        Priority(wPriorities(p));
        for i = 1:numIts;
            Screen(w,'SetClut',originalClut);
            tSC(p,i)=GetSecs;
        end
    end
    Screen(w,'Close');
    
    %calculate summary
    for i = 1:length(wPriorities) %iterate over priorities
        tWBDiffs(i,:) = diff(tWB(i,:));
        tWBmin(i)=min(tWBDiffs(i,:));
        tWBmax(i)=max(tWBDiffs(i,:));
        tWBmedian(i)=median(tWBDiffs(i,:));
        tWBmean(i)=mean(tWBDiffs(i,:));
        
        tSCDiffs(i,:) = diff(tSC(i,:));
        tSCmin(i)=min(tSCDiffs(i,:));
        tSCmax(i)=max(tSCDiffs(i,:));
        tSCmedian(i)=median(tSCDiffs(i,:));
        tSCmean(i)=mean(tSCDiffs(i,:));   
    end  
    priority(originalPriority);
    
    %display results
    DescribeScreen(-2);
    fprintf('%sWaitBlanking%s\n',char(39),char(39));
    fprintf('%-10s','priority');fprintf('%-10s','min'); fprintf('%-10s','max'); fprintf('%-10s','median'); fprintf('%-10s','mean');fprintf('%-10s','nominal');
    fprintf('\n');
    for i = 1:length(wPriorities) %iterate over priorities
          fprintf('%-10d',wPriorities(i));         
          fprintf('%-10.4f',tWBmin(i)); 
          fprintf('%-10.4f',tWBmax(i)); 
          fprintf('%-10.4f',tWBmedian(i)); 
          fprintf('%-10.4f',tWBmean(i)); 
          fprintf('%-10.4f',nominalFramePeriod);
          fprintf('\n');
    end
    
    DescribeScreen(-2);
    fprintf('%sSetClut%s\n',char(39),char(39));
    fprintf('%-10s','priority');fprintf('%-10s','min'); fprintf('%-10s','max'); fprintf('%-10s','median'); fprintf('%-10s','mean');fprintf('%-10s','nominal');
    fprintf('\n');
    for i = 1:length(wPriorities) %iterate over priorities
          fprintf('%-10d',wPriorities(i));         
          fprintf('%-10.4f',tSCmin(i)); 
          fprintf('%-10.4f',tSCmax(i)); 
          fprintf('%-10.4f',tSCmedian(i)); 
          fprintf('%-10.4f',tSCmean(i)); 
          fprintf('%-10.4f',nominalFramePeriod);
          fprintf('\n');
    end
    DescribeScreen(-2);
    return;
end

% MAC
fprintf('WaitBlankingTest\n');
fprintf('We time a loop that calls Screen WaitBlanking or SetClut. If the loop\n');
fprintf('is thus synched to the display blanking, the iteration rate (Hz) of\n');
fprintf('our loop will match the official FrameRate.\n');
clear Screen
DescribeComputer;
for whichScreen=Screen('Screens')
	DescribeScreen(whichScreen);
	DescribeScreenPrefs(whichScreen);
% 	fprintf('%g MaxPriorityForBlankingInterrupt. \n',Screen(whichScreen,'Preference','MaxPriorityForBlankingInterrupt'));
% 	fprintf('%g SetClutDriverWaitsForBlanking. \n',Screen(whichScreen,'Preference','SetClutDriverWaitsForBlanking'));
	setClutDriverWaitsForBlanking=Screen(whichScreen,'Preference','SetClutDriverWaitsForBlanking');
	for ww=0:1
		DescribeScreen(-2);
		Screen(whichScreen,'Preference','WaitBlankingAlwaysCallsSetClut',ww);
		if ~ww
			fprintf('WaitBlanking is using blanking interrupt to synch with %.0f Hz display:\n',FrameRate(whichScreen));
			pmax=MaxPriority(whichScreen,'BlankingInterrupt','GetSecs');
		else
			fprintf('WaitBlanking is using SetClut to synch with %.0f Hz display:\n',FrameRate(whichScreen));
			Screen(whichScreen,'Preference','SetClutDriverWaitsForBlanking',1);
			Screen(whichScreen,'Preference','WaitBlankingAlwaysCallsSetClut',1);
			if ~setClutDriverWaitsForBlanking
				fprintf('NOTE: SetClutDriverWaitsForBlanking was zero, so this test will probably run too fast.\n');
			end
			pmax=MaxPriority(whichScreen,'WaitBlanking','GetSecs');
		end
		s0=0;s=0;GetSecs;
		for p=unique(min([0 0.5 1 2 7],pmax))
			loop={
				'Screen(whichScreen,''WaitBlanking'');'
				's0=GetSecs;'
				'Screen(whichScreen,''WaitBlanking'',10);'
				's=GetSecs;'
			};
			Rush(loop,p);
			r=10/(s-s0);
			fprintf('%3.0f Hz: priority %3g\n',r,p);
		end
	end
	DescribeScreen(-2);
	Screen(whichScreen,'Preference','SetClutDriverWaitsForBlanking',setClutDriverWaitsForBlanking);
	fprintf('Directly calling SetClut to synch with %.0f Hz display:\n',FrameRate(whichScreen));
	for askDriver=[1 0]
		has=Screen(whichScreen,'Preference','DriverFlags');
		if isfinite(askDriver) & ~has.cscGetClutBehavior
			break;
		end
		Screen(whichScreen,'Preference','SetClutCallsWaitBlanking',0); % rely on driver, not interrupt
		Screen(whichScreen,'Preference','SetClutDriverWaitsForBlanking',1); % tell psychtoolbox that driver waits
		Screen(whichScreen,'Preference','WaitBlankingAlwaysCallsSetClut',1); % 
		Screen(whichScreen,'Preference','AskSetClutDriverToWaitForBlanking',askDriver);
		fprintf('AskSetClutDriverToWaitForBlanking set to %d.\n',Screen(whichScreen,'Preference','AskSetClutDriverToWaitForBlanking'));
		if ~setClutDriverWaitsForBlanking
			fprintf('NOTE: SetClutDriverWaitsForBlanking was zero, so this test will probably run too fast.\n');
		end
		Screen(whichScreen,'Preference','SetClutDriverWaitsForBlanking',1); % tell psychtoolbox that driver waits
		Screen(whichScreen,'Preference','WaitBlankingAlwaysCallsSetClut',1); % 
		clut=ClutDefault(whichScreen);
		for p=[0 1 7]
			[s,pr]=SetClutSecs(whichScreen,clut,0,3,p);
			fprintf('%3.0f Hz: priority %3g, SetClut(%3d entries).\n',1/s,pr,size(clut,1));
			s=SetClutSecs(whichScreen,clut(1,:));
			fprintf('%3.0f Hz: priority %3g, SetClut(  1 entry).\n',1/s,pr);
			s=SetClutSecs(whichScreen,clut(1,:),0.005);
			fprintf('%3.0f Hz: priority %3g, SetClut(  1 entry) and 5 ms delay.\n',1/s,pr);
		end
	end
end
DescribeScreen(-1);

function [s,p]=SetClutSecs(w,clut,delaySecs,reps,p)
% Times the period of a loop that calls SetClut plus a delay.
% If SetClut waits for end-of-frame blanking then the period should always be a multiple of the frame period.
% If SetClut doesn't wait then the period will be a fixed time plus delaySecs.
if nargin<2 | isempty(clut)
	clut=Screen(w,'GetClut');
	if isempty(clut)
		clut=ClutDefault(w);
	end
end
if nargin<3 | isempty(delaySecs)
	delaySecs=0;
end
if nargin<4 | isempty(reps)
	reps=3;
end
if nargin<5 | isempty(p)
	p=7;
end
reps=reps+1;
p=min(p,MaxPriority(w,'WaitSecs','GetSecs'));
oldSetting=Screen(w,'Preference','SetClutCallsWaitBlanking',0);
t=1:reps;
loop={
	'for i=1:reps;'
		'WaitSecs(delaySecs);'
		'Screen(w,''SetClut'',clut);'
		't(i)=GetSecs;'
	'end;'
};
WaitSecs(0);Screen('Screens');GetSecs; % Make sure all Rushed functions are in memory.
Rush(loop,p);
Screen(w,'Preference','SetClutCallsWaitBlanking',oldSetting);
s=median(diff(t));
return

