function ExpandingRingsDemo(ringtype) % ExpandingRingsDemo([ringtype=0]) -- Generate an "expanding rings" % stimulus by use of GLSL shaders and Psychtoolbox procedural textures. % % This demo illustrates the use of "procedural textures" with Psychtoolbox. % A procedural texture is a texture that is not directly represented by an % image matrix in memory, but the image content of the texture is generated % on the fly during drawing of the texture by means of a small algorithm - % a shader. The shader implements some mathematical formula or model which % is evaluated to generate image content, or it reads content of a data % matrix and transforms it into a picture. Psychtoolbox supports both, % purely virtual textures of unlimited size that are purely algorithmically % generated, and hybrid textures where an algorithm transforms the textures % content into something to be drawn. % % The algorithm has to be implemented by a GLSL shader program - a vertex % shader, geometry shader, fragment shader or any combination of them. The % shader program is read from a file, compiled and then attached to the % texture at texture creation time. Procedural textures only work with % graphics hardware that has sufficiently advanced support for (at least) % hardware fragment shaders. % % This demo implements a procedural texture which shows a set of rings that % can expand and move. The shader gets attached to a purely virtual % texture. The texture is drawn via the standard Screen('DrawTexture') % command -- your graphics processor generates the image content of the % stimulus on the fly during drawing of the texture via execution of the % shader at each output pixel location. % % The optional 'ringtype' parameter allows to select between different ring % shapes. Default type is zero: % % 0 = Hard transitions between red and yellow rings. % 1 = Transitions are modeled as a smooth sine wave, softly fading from % yellow to red and back. % % History: % 19.05.2007 Written. (MK) % 24.04.2011 Updated to support 'ringtype' parameter and sine-wave rings. (MK) % 12.06.2011 Updated to use ProceduralShadingAPI for cleaner M-file code. (MK) % Acquire a handle to OpenGL, so we can use OpenGL commands in our code: global GL; % Make sure this is running on OpenGL Psychtoolbox: AssertOpenGL; if nargin < 1 || isempty(ringtype) ringtype = 0; end % Choose screen with maximum id - the secondary display: screenid = max(Screen('Screens')); % Open a fullscreen onscreen window on that display, choose a background % color of 128 = gray with 50% max intensity: win = Screen('OpenWindow', screenid, 128); % Switch color specification to use the 0.0 - 1.0 range instead of the 0 - % 255 range. This is more natural for these kind of stimuli: Screen('ColorRange', win, 1); % Query window size: Need this to define center and radius of expanding % disk stimulus: [tw, th] = Screen('WindowSize', win); % Load the 'ExpandingRingsShader' fragment program from file, compile it, % return a handle to it: rshader = [PsychtoolboxRoot 'PsychDemos/ExpandingRingsShader.vert.txt']; if ringtype == 0 expandingRingShader = LoadGLSLProgramFromFiles({ rshader, [PsychtoolboxRoot 'PsychDemos/ExpandingRingsShader.frag.txt'] }, 1); % Width of a single ring (radius) in pixels: ringwidth = 20; else expandingRingShader = LoadGLSLProgramFromFiles({ rshader, [PsychtoolboxRoot 'PsychDemos/ExpandingSinesShader.frag.txt'] }, 1); % Width of a single ring (radius) / Period of a single color sine wave in pixels: ringwidth = 200; end % Create a purely virtual texture 'ringtex' of size tw x th virtual pixels, i.e., the % full size of the window. Attach the expandingRingShader to it, to define % its "appearance": ringtex = Screen('SetOpenGLTexture', win, [], 0, GL.TEXTURE_RECTANGLE_EXT, tw, th, 1, expandingRingShader); % Bind the shader: After binding it, we can setup some constant parameters % for our stimulus, so called GLSL 'uniform' variables. These are % parameters that are constant during the whole session, or at least only % change infrequently. They are set outside the fast stimulus rendering % loop and potentially optimized by the graphics driver for fast execution: glUseProgram(expandingRingShader); % Set the 'RingCenter' parameter to the center position of the ring % stimulus [tw/2, th/2]: glUniform2f(glGetUniformLocation(expandingRingShader, 'RingCenter'), tw/2, th/2); % Done with setup, disable shader. All other stimulus parameters will be % set at each Screen('DrawTexture') invocation to allow fast dynamic change % of the parameters during each stimulus redraw: glUseProgram(0); % Define first and second ring color as RGBA vector with normalized color % component range between 0.0 and 1.0: firstColor = [1 0 0 1]; secondColor = [1 1 0 1]; % Initial shift- and radius value is zero: We start with a point in the % center of the screen which will then expand and scroll by one pixel at % each redraw until it fills the whole screen, continuing to scroll % periodically: shiftvalue = 0; count = 0; % Retrieve monitor refresh duration: ifi = Screen('GetFlipInterval', win); % Perform initial flip to gray background and sync us to the retrace: vbl = Screen('Flip', win); ts = vbl; % Animation loop: Run until keypress: while ~KbCheck % Increase shift and radius of stimulus: shiftvalue = shiftvalue + 1; radius = shiftvalue; count = count + 1; % Draw the stimulus with its current parameter settings. We simply draw % the procedural texture as any other texture via 'DrawTexture'. We % leave all draw settings to their defaults, except the base drawing % colors, which in our case defines the colors of the rings and the % ringWidth, Radius and Shift parameters: % The vector with additional parameters must have a length which is a % multiple of four. Therefore - as we only have 7 parameters here - we % pad the vector with an additional meaningless zero value to end up % with a total length of 8 vector components. % % We use 'firstColor' for the even rings, 'secondColor' for the odd % rings. Screen('DrawTexture', win, ringtex, [], [], [], [], [], firstColor, [], [], [secondColor(1), secondColor(2), secondColor(3), secondColor(4), shiftvalue, ringwidth, radius, 0]); % Request stimulus onset at next video refresh: vbl = Screen('Flip', win, vbl + ifi/2); end % Done. Print some fps stats: avgfps = count / (vbl - ts); fprintf('Average redraw rate in Hz was: %f\n', avgfps); % Close window, release all ressources: sca;