function SetGammaWaitTest(whichScreen)
% SetGammaWaitTest(whichScreen)
% Thoroughly tests essentially all possible ways of calling SetGamma and
% succinctly reports which modes (if any) wait for blanking. SetGamma is
% our name for the Apple driver control call cscSetGamma. This is provided
% in the Psychtoolbox as Screen 'Gamma'. SetGammaWaitTest is intended
% primarily as a subroutine for ScreenTest.m. As a convenience to the
% general user, calling SetGammaWaitTest with no arguments will produce a
% complete report on all screens. Also see ScreenTest and SetClutWaitTest.

% 6/6/02 dgp Wrote it, based on SetClutWaitTest.m.
% 6/8/02 dgp ScreenTest now calls SetGammaWaitTest. Suppress preface when it's redundant.
% 8/1/02 dgp Fixed bug in FOR i=FIND statement, reported by Chris Chase.

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

% setGamma.rate(prioritySequence,wholeClut,synchMode,pixelSize) = iteration rate in Hz
% setGamma.waits(prioritySequence,wholeClut,synchMode,pixelSize) = Waits for blanking?
% relevant(d) = Does "waits" depend on parameter d?
% setGamma.parameter{d} = name of parameter d
% setGamma.value{d}{i} = i-th value of parameter d
setGamma.parameter{1}='prioritySequence';
setGamma.parameter{2}='clutEntries';
setGamma.parameter{3}='pixelSize';
setGamma.parameter{4}='synchMode';
setGamma.value{1}={'0/0','7/0','7/7'};
setGamma.value{2}={'all'};
setGamma.value{3}={'8','16','32'};
setGamma.value{4}={'sync'};
setGamma.ndims=3;
setGamma.rate=TimeGamma(whichScreen,preface);
waits=abs(log10(setGamma.rate/framerate(whichScreen)))<0.2;
relevant=RelevantDims(waits);
relevantDims=find(relevant);
if ~relevant(1)
	waits=waits(1,:,:,:);
end
if ~relevant(2)
	waits=waits(:,1,:,:);
end
if length(relevant)>=3 & ~relevant(3)
	waits=waits(:,:,1,:);
end
if length(relevant)>=4 & ~relevant(4)
	waits=waits(:,:,:,1);
end
sizeWaits(1:ndims(waits))=size(waits);
if any(waits(:))
	fprintf('SetGamma waits for blanking (i.e. ~%.0f Hz) if parameters have these values:\n',FrameRate(whichScreen));
	fprintf('%10s',' ');
	for d=find(relevant)
		fprintf('%17s ',setGamma.parameter{d});
	end
	fprintf('\n');
	for i=find(waits(:))'
		[s(1) s(2) s(3) s(4)]=ind2sub(sizeWaits,i);
		fprintf('%5.0f Hz: ',setGamma.rate(s(1),s(2),s(3),s(4)));
		for d=find(relevant)
			fprintf('%17s ',setGamma.value{d}{s(d)});
		end
		fprintf('\n');
	end
end	
if any(~waits(:))
	fprintf('SetGamma doesn''t wait for blanking if parameters have these values:\n');
	fprintf('%10s',' ');
	for d=find(relevant)
		fprintf('%17s ',setGamma.parameter{d});
	end
	fprintf('\n');
	for i=find(~waits(:))'
		[s(1) s(2) s(3) s(4)]=ind2sub(sizeWaits,i);
		fprintf('%5.0f Hz: ',setGamma.rate(s(1),s(2),s(3),s(4)));
		for d=find(relevant)
			fprintf('%17s ',setGamma.value{d}{s(d)});
		end
		fprintf('\n');
	end
end	
if any(~relevant)
 	fprintf('The following parameters don''t affect whether SetGamma waits for blanking:\n');
	fprintf('%10s',' ');
	for d=find(~relevant)
		fprintf('%17s ',setGamma.parameter{d});
	end
	fprintf('\n');
end
return

function relevant=RelevantDims(array)
% relevant=RelevantDim(array)
% Returns a logical vector "relevant" with length ndims(array). 
% Each element indicates whether 
% the supplied array has any variation along that dimension.
for d=1:ndims(array)
	relevant(d)=0;
	a=shiftdim(array,d-1);
	for j=2:size(a,1)
		if ~all(a(1,:)==a(j,:))
			relevant(d)=1;
			break;
		end
	end
end
return

function rate=TimeGamma(whichScreen,preface)
saiString={'Sync','Async','Immed'};
for s=whichScreen;
	oldPixelSize=Screen(s,'PixelSize');
	
	if preface
		DescribeScreen(-2);
		% Report support for new driver calls
		csc=Screen(s,'Preference','DriverFlags');
		if csc.cscGetClutBehavior | csc.cscGetTimingRanges | csc.cscGetDetailedTiming
			fprintf('Supports ');
			if csc.cscGetClutBehavior
				fprintf('cscGetClutBehavior ');
			end
			if csc.cscGetTimingRanges
				fprintf('cscGetTimingRanges ');
			end
			if csc.cscGetDetailedTiming
				fprintf('cscGetDetailedTiming ');
			end
			fprintf('\n');
		end
	
		fprintf('%d SetClutDriverWaitsForBlanking\n',Screen(s,'Preference','SetClutDriverWaitsForBlanking'));
	end

	% Turn off the customization, and save the old settings for restoration below.
	oldSetClutCallsWaitBlanking=Screen(s,'Preference','SetClutCallsWaitBlanking',0);
	oldDipPriorityAfterSetClut=Screen(s,'Preference','DipPriorityAfterSetClut',0);
	oldMinimumSetClutPriority=Screen(s,'Preference','MinimumSetClutPriority',0);
	oldMaximumSetClutPriority=Screen(s,'Preference','MaximumSetClutPriority',7);
	if preface & oldSetClutCallsWaitBlanking
		fprintf('NOTE: Disabling SetClutCallsWaitBlanking while testing driver.\n');
	end
	if preface & oldDipPriorityAfterSetClut
		fprintf('NOTE: Disabling automatic priority dipping while testing driver.\n');
	end
	DescribeScreen(-2);
	
	for pixelSize=Screen(s,'PixelSizes')
		Screen(s,'PixelSize',pixelSize);
		Screen(s,'Preference','SetClutCallsWaitBlanking',0);
		gamma=Screen(s,'Gamma');
		for p=[0 7]
			for otherPriority=unique([0 p])
				for entries=size(gamma,1) % "entries" has only this one value, i.e. all.
					for sai=0 % select Sync, Async, and Immed. Doesn't seem to make any difference.
						Screen(s,'Preference','SetClutSAI',sai);
						GetSecs;WaitSecs(0);Priority(0);i=0;t0=0;t1=0;t2=0; % load funs and vars
						tGamma=0;tDeferred=0;
						n=30;
						loop={
						't=GetSecs;'
						'for i=1:n;'
						't0=GetSecs;'
						'Screen(s,''Gamma'',gamma);'
						't1=GetSecs;'
						'Priority(otherPriority);'
						'Priority(p);'
						't2=GetSecs;'
						'tGamma=tGamma+t1-t0;'
						'tDeferred=tDeferred+t2-t1;'
						'WaitSecs(0.003);'
						'end;'
						't=(GetSecs-t)/n;'
						};
						Rush(loop,p);
						tGamma=tGamma/n;
						tDeferred=tDeferred/n;
						%  	fprintf('%3.0f Hz: priority %d/%d, %3d entries, %-5s, %2d bits\n',1/t,p,otherPriority,entries,saiString{sai+1},pixelSize);
						if p==0
							prioritySequence=1;
						else
							if otherPriority==0
								prioritySequence=2;
							else
								prioritySequence=3;
							end
						end
						clutEntries=1;
						synchMode=sai+1;
						pixelSizeIndex=log(pixelSize)/log(2)-2;
						rate(prioritySequence,clutEntries,pixelSizeIndex,synchMode)=1/t;
					end
				end
			end
		end
		Screen(s,'Preference','SetClutSAI',0);
	end
	Screen(s,'PixelSize',oldPixelSize);
	% Restore the customization.
	Screen(s,'Preference','DipPriorityAfterSetClut',oldDipPriorityAfterSetClut);
	Screen(s,'Preference','MinimumSetClutPriority',oldMinimumSetClutPriority);
	Screen(s,'Preference','MaximumSetClutPriority',oldMaximumSetClutPriority);
	Screen(s,'Preference','SetClutCallsWaitBlanking',oldSetClutCallsWaitBlanking);
end
return
