function [rc, winRect] = PsychImaging(cmd, varargin)
% rc = PsychImaging(subcommand [,arg1][,arg2][....,argn]) - Control common
% functions of the Psychtoolbox GPU image processing pipeline.
%
% This function allows you to setup and control various aspects and common
% functions of the Psychtoolbox image processing pipeline in a simple way.
% Various standard scenarious can be conveniently set up with this routine,
% e.g., geometric transformations of your stimulus image, various types of
% display correction, ...
%
% If you want to perform less common, unusual or simply not yet supported tasks
% with the pipeline, use the low-level Screen('HookFunction', ...)
% interface instead and have a peek in the M-File code for the
% PsychImaging.m file to learn about the low-level interface.
% See "help PsychGLImageprocessing" for more info.
%
%
% Subcommands and their meaning:
%
% PsychImaging('PrepareConfiguration');
% - Prepare setup of imaging pipeline for onscreen window.
% This is the first step in the sequence of configuration steps.
%
%
% PsychImaging('AddTask', whichChannel, whichTask [,param1]...);
% - Add a specific task or processing requirement to the list of actions
% to be performed by the pipeline for the currently selected onscreen
% window. 'whichChannel' is a string with the name of the channel to
% configure:
%
% 'LeftView' applies the action to the processing channel
% for the left-eye view of a stereo configuration. 'RightView' applies the
% action to the right-eye view of a stereo configuration. 'AllViews' applies
% the action to both, left- and right eye view channels of a stereo
% configuration or to the single monoscopic channel of a mono display
% configuration. Other options are 'Compositor', 'FinalFormatting' and
% 'Finalizer' for special purpose channels. Set this to 'General' if the
% command doesn't apply to a specific view, but is a general requirement.
%
% 'whichTask' contains the name string of one of the supported
% actions:
%
% * 'UseGPGPUCompute' Enable use of GeneralPurposeGPU computing support.
%   This prepares use of Psychtoolbox functions which are meant to
%   interface with, or take advantage of, the general purpose computation
%   capabilities of modern graphics processing units and other massively
%   parallel compute acceleration hardware, e.g., DSP's, or multi-core
%   processors. Interfacing with such hardware is done via common standard
%   compute API's like NVidia's CUDA or the cross-platform OpenCL API.
%
%   Use of this function often requires specific modern GPU hardware and
%   the installation of additional driver software, e.g., NVidia's freely
%   available CUDA SDK and runtime, or the free and open-source GPUmat
%   toolbox. Read 'help PsychGPGPU' for further info.
%
%   This function just detects and selects supported GPU compute API's for
%   use with Psychtoolbox and initializes them and some Psychtoolbox
%   function to take advantage if appropriate. While you could use those
%   API's by themselves without calling this init function, Psychtoolbox
%   builtin processing functions would not be able to take advantage of the
%   API's or perform efficient and fast data exchange with them.
%
%   Usage: PsychImaging('AddTask', 'General', 'UseGPGPUCompute', apitype [, flags]);
%
%   'apitype' Allows selection of the compute API to use. The value 'Auto'
%   leaves the choice to Psychtoolbox. The value 'GPUmat' selects the
%   high-level, free and open-source GPUmat compute toolkit for Matlab.
%   Currently no other choices are supported, but this is expected to
%   change in the future.
%
%   'flags' An optional string of keyword flags to determine behaviour.
%   There aren't any flags defined yet.
%
%
% * 'SideBySideCompressedStereo' Ask for stereo display in a horizontally
%   compressed side-by-side format. Left and Right eye images are drawn at
%   full framebuffer resolution by usercode. Screen('Flip', ...) draws them
%   horizontally compressed side-by-side to each other. They are scanned
%   out to the display device this way and then the display device itself
%   uncompresses them back to full resolution and displays them
%   stereoscopically, typically via built-in alternating frame-sequential
%   stereo with stereo goggles, but other methods are conceivable. This is
%   one popular stereo frame packing format for stereo on HDMI display
%   devices. Once you've set up a stereo display mode via PsychImaging, you
%   can tweak its specific parameters by calling the function
%   SetCompressedStereoSideBySideParameters().
%
%   Usage: PsychImaging('AddTask', 'General', 'SideBySideCompressedStereo');
%
%
% * 'InterleavedColumnStereo' Ask for stereo display in interleaved mode.
%   The output image is composed from the lefteye and righteye stereo
%   buffers by interleaving their content: Even columns are filled with
%   content from the left buffer, odd columns are filled with content from
%   the right buffer, i.e., Col 0 = Left col 0, Col 1 = Right Col 0, Col 2
%   = Left col 1, Col 3 = Right col 1, ....
%
%   This mode is useful for driving some auto-stereoscopic displays. These
%   use either some vertical parallax barriers or vertical lenticular
%   lense sheets. These direct light from even columns to one eye, light
%   from odd columns to the other eye.
%
%   Usage: PsychImaging('AddTask', 'General', 'InterleavedColumnStereo', startright);
%
%   If 'startright' is zero, then even columns are taken from left buffer. If
%   'startright' is one, then even columns are taken from the right buffer.
%
%   You can use the RemapMouse() function to correct GetMouse() positions
%   for potential geometric distortions introduced by this function.
%
%
% * 'InterleavedLineStereo' Ask for stereo display in interleaved mode.
%   The output image is composed from the lefteye and righteye stereo
%   buffers by interleaving their content: Even lines are filled with
%   content from the left buffer, odd lines are filled with content from
%   the right buffer, i.e., Row 0 = Left row 0, Row 1 = Right row 0, Row 2
%   = Left row 1, Row 3 = Right row 1, ....
%
%   This mode is useful for driving some types of stereo devices and
%   goggles, e.g., the iGlasses 3D Video goggles in interleaved stereo
%   mode.
%
%   Usage: PsychImaging('AddTask', 'General', 'InterleavedLineStereo', startright);
%
%   If 'startright' is zero, then even lines are taken from left buffer. If
%   'startright' is one, then even lines are taken from the right buffer.
%
%   You can use the RemapMouse() function to correct GetMouse() positions
%   for potential geometric distortions introduced by this function.%
%
%
% * 'DualWindowStereo' Ask for stereo display in dual-window mode (stereomode 10)
%
%   Only use this function under MacOSX. If possible on your setup and OS,
%   rather use a single window, spanning both stereo display outputs, and use
%   stereomode 4 or 5 to display dual-display stereo. That is much more
%   efficient in terms of speed, computational load and memory consumption,
%   also potentially more robust with respect to visual stimulation timing.
%
%   Usage: PsychImaging('AddTask', 'General', 'DualWindowStereo', rightEyeScreen [, rightEyeWindowRect]);
%
%   The left-eye image will be displayed on the screen and at a location
%   specified as usual via PsychImaging('Openwindow', screenid, ..., rect);
%   The right eye image will be displayed on screen 'rightEyeScreen'. If
%   the optional 'rightEyeWindowRect' is specified, then the right eye image
%   is not displayed in a fullscreen window, but in a window with the bounding
%   rectangle 'rightEyeWindowRect'.
%
%
% * 'UseVirtualFramebuffer' Ask for support of virtual framebuffer, even if
%   it isn't strictly needed, given the remaining set of requirements. Most
%   of the tasks require such a framebuffer - it gets enabled anyway. In a
%   few cases, e.g., to simplify your code (no need for special cases), it
%   may be useful to activate such a framebuffer even if it isn't strictly
%   needed. This option activates a minimal buffer with 8 bits per color
%   cmponent fixed point precision.
%
%   Usage: PsychImaging('AddTask', 'General', 'UseVirtualFramebuffer');
%
%
% * 'UseFineGrainedTiming' Ask for use of fine-grained stimulus onset timing.
%   Ask Screen('Flip', window, when) to try to show the new visual stimulus
%   close to (= ideally exactly at) target time 'when', instead of showing the
%   stimulus at the next video refresh frame boundary with time t >= 'when', ie.
%   try to schedule stimuli with better timing granularity than what is given by
%   the multiples of a video refresh cycle duration of the connected display.
%
%   This uses a technique known as "Variable Refresh Rate" or shorthand "VRR" if
%   your operating system and display driver and graphics card and cable and
%   display device supports it at the current system configuration. Otherwise,
%   PsychImaging('OpenWindow', ...) will fail if this task can't be achieved.
%
%   For a list of requirements with respect to graphics cards, display devices,
%   operating systems, drivers and general system configuration to make this work,
%   read "help VRRSupport". This will also tell you about limitations and caveats
%   wrt. this task.
%
%   There may be different methods of implementing such fine-grained timing.
%   The optional 'method' parameter allows you to select a specific method.
%   Using the keyword 'Auto' or omitting the 'method' parameter ([]) will leave
%   the choice of optimal method to Screen(). Currently the methods 'Simple' and
%   'OwnScheduled' are implemented: 'Simple' follows the most naive approach of VRR,
%   simply requesting immediate flip, or in case a 'when' time is specified, waiting
%   until 'when', then requesting a flip. 'OwnScheduled' selects a more sophisticated
%   scheduling method that tries to take other constraints like minimum and maximum
%   refresh rate of the display, display system state, and a specified 'styleHint'
%   for the given visual stimulation paradigm into account, and also tries to
%   compensate for some of the system timing jitter. This may provide higher
%   precision or stability in some stimulation scenarios, but may also cause
%   higher overhead or added latency in other scenarios.
%
%   The optional parameter 'styleHint' allows to give some general high level clue
%   into the nature of the visual stimulation paradigm, as far as presentation
%   timing is concerned. This allows to tune the scheduling method for VRR for
%   maximum precision and reliability for a stimulation paradigm of the specified
%   nature. Omitting the parameter, or setting it to 'None', will ask the algorithm
%   to make almost no assumptions about the nature of the stimulation, but to choose
%   some "one size fits all ok'ish" setup, or try to auto-detect how to tune itself for
%   the running paradigm. The currently supported 'styleHint' settings are:
%
%   - 'None' = Make no assumptions, try some "One size fits all hopefully ok'ish" setup.
%
%   The optional parameter 'vrrMinRefreshHz' allows to specify the lowest video
%   refresh rate that your display can reliably run at. If the parameter is
%   omitted, Screen() will try to auto-detect this display property, or failing
%   that, it will use a reasonable default.
%
%   Usage: PsychImaging('AddTask', 'General', 'UseFineGrainedTiming' [, method='Auto'][, styleHint='None'][, vrrMinRefreshHz]);
%
%
% * 'UseSubpixelDrive' Ask to take advantage of the so-called "Subpixel Drive"
%   mode of certain monochromatic medical imaging displays like, e.g., the
%   "Eizo RadiForce GS-521". This monitor essentially has a RGB panel with
%   horizontal RGB subpixels, but with the color filters removed, so each
%   pixel is horizontally split up into 3 luminance subpixels and these can
%   be individually addressed by packing 3 horizontally adjacent 8 bit stimulus
%   luminance pixels into the "RGB" color channels of an output pixel. Use
%   of this task will create a virtual framebuffer three times the width of
%   the output framebuffer and then pack triplets of three horizontal luminance
%   pixels into one output pixel, to triple the effective horizontal resolution.
%
%   Usage: PsychImaging('AddTask', 'General', 'UseSubpixelDrive');
%
%
% * 'UseRetinaResolution' Ask to prefer a framebuffer with the full native
%   resolution of attached HiDPI "Retina" displays on OSX, instead of a scaled
%   down lower resolution framebuffer with typically half the horizontal
%   and vertical resolution of the Retina display. This setting will be
%   ignored if the onscreen window is not displayed on a HiDPI "Retina"
%   display in a scaled display mode, or if the panel fitter is in use by
%   specifying the 'UsePanelFitter' task. By default, Screen() would use a
%   downscaled framebuffer on a Retina display under OSX and scale that low
%   resolution buffer up to full display panel resolution, just as Apples
%   OSX operating system does it by default. This in order to reduce
%   computational load, improve graphics performance, and avoid problems
%   with backward compatibility of old code. If you want to make full use
%   of the resolution of your HiDPI display, specify this task to tell
%   Screen() to use the full display panel resolution on OSX, even if this may
%   introduce some compatibility issues into your code and causes a decrease
%   in graphics performance due to the higher graphics rendering load.
%
%   If 'UseRetinaResolution' is used with a non-fullscreen window, ie.
%   the 'rect' parameter in PsychImaging('OpenWindow', ...) is provided
%   to specify the screen position and size of the window, note that
%   the size of the window rect returned by Screen('GlobalRect') and
%   Screen('Rect'), as well as of the returned rect of PsychImaging('OpenWindow')
%   will differ from the size of the 'rect' passed to 'OpenWindow'. 'rect's
%   passed into OpenWindow for positioning and sizing the window, as well
%   as the global position rect returned by Screen('GlobalRect') for the
%   current size and position of the onscreen window are expressed in global
%   desktop coordinates, in somewhat arbitrary units of virtual "points".
%   How such a point translates into display pixels depends on the operating
%   system, possibly the desktop GUI in use (on other systems than OSX), the
%   set of connected displays and their Retina or non-Retina resolutions.
%   The aim is that the coordinate system is somewhat consistent and meaningful
%   across all connected displays, for varying definitions of "consistent" and
%   "meaningful" on different operating systems, but the mapping of points to
%   physical screen pixels can be different on each connected display, at the
%   discretion of the operating system. You may get especially "interesting"
%   results if you try to move an onscreen window between screens, or let it
%   span multiple displays of different type and resolution.
%   The rect returned by PsychImaging('Openwindow') and Screen('Rect'), as
%   well as sizes returned by Screen('WindowSize') define the net useable
%   size of the window in display pixels. It is affected by all kind of
%   PsychImaging operations, e.g., selection of stereo modes, high bit depth
%   modes etc., but also by scaling on Retina displays in high res mode.
%   If 'UseRetinaResolution' is used on a Retina/HiDPI display, one typical
%   result will be that the size of the window in pixels reported by these
%   functions will be higher than the size in points, as one virtual point will
%   get represented by more than 1 pixel on a Retina display. Observing twice
%   the window size in pixels than in points is quite typical, but other
%   scaling factors are possible. The take home message for you is to specify
%   location and size of your stimuli based on the sizes and rects returned
%   by Screen('Rect'), PsychImaging('Openwindow') and Screen('Windowsize'), as
%   these are in units of display pixels, and *not* based on the virtual points
%   returned by Screen('GlobalRect'). The 2nd take home message is that you
%   should mostly use fullscreen windows for visual stimulation to avoid such
%   and other pitfalls.
%
%   Usage: PsychImaging('AddTask', 'General', 'UseRetinaResolution');
%
%
% * 'UseDisplayRotation' Ask to use builtin panel fitter exclusively for
%   rotating the framebuffer. This is useful if you want to turn your
%   display device from landscape (= normal upright) orientation into
%   portrait orientation (= rotated by 90 degrees clockwise or
%   counterclockwise). In such a case you will want to rotate the
%   framebuffer by 90 degrees as well, but you should *not* use the "rotate
%   monitor" function of your operating system for this purpose, as this
%   will very likely interfere with visual stimulus presentation timing and
%   timestamping! Use this task instead. It will perform rotation in a
%   similar way, but without severe interference to timing. However, there
%   is one limitation to this method: Multisample anti-aliasing currently
%   does not work if you use our framebuffer rotation.
%
%   Usage: PsychImaging('AddTask', 'General', 'UseDisplayRotation', angle);
%
%   'angle' is the desired rotation angle. The only values which will give
%   well defined and useful results are multiples of 90 degrees, useful
%   values are essentially 0, +90, -90 and 180 degrees for no rotation,
%   clockwise rotation, counterclockwise rotation and upside down rotation.
%
%   This function is mutually exclusive with 'UsePanelFitter', but if you
%   need to use both, you can omit 'UseDisplayRotation' and pass the
%   'angle' parameter to 'UsePanelFitter' instead, which also accepts an
%   'angle' parameter with the same meaning.
%
%   This function is not very mature yet: If you want to use the
%   panelfitter for anything beyond simple framebuffer rotation by 90
%   degree increments, you will likely hit bugs or limitations which will
%   require significant tinkering by you.
%
%
% * 'UsePanelFitter' Ask to use builtin panel fitter. This allows you to
%   define a virtual size for your onscreen window. The window will behave
%   as if it had that virtual size wrt. all size queries and drawing
%   operations. However, at Screen('Flip') time, the visual content of the
%   window will be resized by a fast scaling operation to the real size of
%   the windows framebuffer, ie., its real onscreen size. Scaling uses
%   bilinear interpolation or better for high quality results. After
%   rescaling to the real size, post-processing and display of your
%   stimulus image will proceed at full resolution. This function is useful
%   if you want to display a stimulus designed for a specific display
%   resolution on a display device of different higher or lower resolution.
%   Given that size and shape of the virtual framebuffer and real display
%   window may not match, the function provides you with multiple possible
%   choices on how to rescale your stimulus image, e.g., to maximize
%   display area, or to preserve the aspect ratio of the original image,
%   trading off displayed area etc.
%
%   Usage: PsychImaging('AddTask', 'General', 'UsePanelFitter', size, strategy [, srcRect, dstRect][, angle]);
%
%   'size' is a [width, height] vector defining the width x height of the
%   virtual window in pixels.
%
%   'strategy' a text string selecting the scaling method. Following settings are possible:
%
%   'Full' - Scale to full window size. Aspect ratio is not preserved,
%            unless the virtual window and the real onscreen windows 'rect'
%            already have the same aspect ratio, in which case this will be
%            a simple scaling operation.
%
%   'Aspect' - Scale to maximum size possible while preserving aspect
%              ratio. This will center the stimulus and add black
%              horizontal or vertical borders as neccessary.
%
%   'AspectWidth' - Scale aspect ratio preserving to cover full display
%                   width. Cut off top and bottom content if neccessary.
%
%   'AspectHeight' - Scale aspect ratio preserving to cover full display
%                    height. Cut off left and right content if neccessary.
%
%   'Centered' - Center stimulus without any scaling, add black borders
%                around stimulus or cut away border regions to get a
%                one-to-one mapping.
%
%   'Custom' - This works like the 'srcRect' and 'dstRect' parameters of
%              Screen('DrawTexture'): Cut out a 'srcRect' region from the
%              virtual framebuffer and display it in the 'dstRect' region.
%              'srcRect' and 'dstRect' are given in typical [left, top, right, bottom]
%              format.
%
%   'angle' is an optional rotation angle. If provided and non-zero, the
%   panelfitter will also rotate the output framebuffer by the given
%   rotation angle. Note: This doesn't work very well yet with most
%   framebuffer sizes and scaling strategies. What does work is if the
%   specified 'size' is identical to the onscreen windows size, or is its
%   transposed size (ie., if window is width x height pixels, then height x
%   width pixels will work as 'size' parameter) and the rotation angle is a
%   multiple of 90 degrees. This is mostly useful for display rotation from
%   landscape orientation into portrait orientation. Your mileage with
%   other configurations or rotation angles will vary.
%
%   Example: Suppose your real window covers a 1920 x 1080 display.
%
%   PsychImaging('AddTask', 'General', 'UsePanelFitter', [800 600], 'Aspect');
%   -> This would give you a virtual window of 800 x 600 pixels to draw
%   into and would rescale the 800 x 600 stimulus image to 1440 x 1080
%   pixels and display it centered on the 1920 x 1080 pixels display.
%   Aspect ratio would be correct and the image would cover the full height
%   1080 pixels of the display, but only 1440 out of 1920 pixels of its
%   width, thereby leaving black borders on the left and right side of your
%   stimulus.
%
%   PsychImaging('AddTask', 'General', 'UsePanelFitter', [800 600], 'AspectHeight');
%   -> Would do the same as above.
%
%   PsychImaging('AddTask', 'General', 'UsePanelFitter', [800 600], 'AspectWidth');
%   -> Would create a final image of 1920 pixels width, as you asked to
%   cover the full display width, aspect ratio would be correct, but the
%   top and bottom 75 pixels of your original stimulus would get cut away,
%   because they wouldn't fit after scaling without distorting the image.
%
%
% * 'UseFastOffscreenWindows' Ask for support of fast Offscreen windows.
%   These use a more efficient storage, backed by OpenGL framebuffer
%   objects (FBO's). Drawing into them isn't faster, but *switching*
%   between drawing into onscreen- and offscreen windows, or switching
%   between drawing into different offscreen windows is faster. They also
%   support a couple of other advanced features and performance
%   improvements in conjunction with the imaging pipeline.
%   If you only specify this task, then you'll get the benefit of fast
%   windows, without the cost of other features of the pipeline you might
%   not need.
%
%   Usage: PsychImaging('AddTask', 'General', 'UseFastOffscreenWindows');
%
%
% * 'EnableCLUTMapping' Enable support for old-fashioned clut animation /
%   clut mapping. The drawn framebuffer image is transformed by applying a
%   color lookup table (clut). This is not done via the hardware gamma
%   tables as in the good ol' days, but by application of the clut via
%   image processing. Hardware gamma tables don't provide well defined
%   timing on modern hardware, therefore they aren't suitable anymore.
%
%   You can update the clut to be applied at the next Screen('Flip');
%   via the command Screen('LoadNormalizedGammatable', windowPtr, clut, 2);
%
%   'clut' needs to be a clutSize-by-3 matrix, with 'clutSize' slots and
%   one column for each of the red, green and blue color channels.
%
%   Setup command:
%
%   By default, a clut of 256 slots with (R,G,B) values is used, but you
%   can provide the optional 'clutSize' parameter to use clut's with more
%   slots. The maximum number depends on your GPU, but 2048 are typically
%   supported even on very low-end cards.
%
%   If you set 'highprecision' to 1, the clut will resolve values at more
%   than 8 bit per color channel on modern hardware. This usually only
%   makes sense if you also use a more than 8 bpc framebuffer with more
%   than 256 slots as clutSize.
%
%   Usage: PsychImaging('AddTask', whichView, 'EnableCLUTMapping' [, clutSize=256][, highprecision=0]);
%   Example: PsychImaging('AddTask', 'AllViews', 'EnableCLUTMapping');
%
%
% * 'FloatingPoint16Bit' Ask for a 16 bit floating point precision
%   framebuffer. This allows more than 8 bit precision for complex drawing,
%   compositing and image processing operations. It also allows
%   alpha-blending with signed color values and intermediate results that
%   are outside the displayable range, e.g., negative. Precision is about 3
%   digits behind the decimal point or 1024 discriminable displayable
%   levels. If you need higher precision, choose 'FloatingPoint32Bit'.
%
%   Usage: PsychImaging('AddTask', 'General', 'FloatingPoint16Bit');
%
%
% * 'FixedPoint16Bit' Ask for a 16 bit integer precision framebuffer.
%   On graphics hardware that supports this, a 16 bit signed integer
%   framebuffer will be created. Such a framebuffer can store intermediate
%   color values in the normalized range [-1.0 ; +1.0] with a precision of
%   15 bits per component. Only positive values between 0.0 and 1.0 are
%   displayable in the end though. If the graphics hardware does not support this,
%   a 16 bit unsigned integer framebuffer is tried instead. Such a framebuffer
%   allows for 16 bits of precision per color component. However, many graphics
%   cards do not support alpha-blending on such a framebuffer, and
%   intermediate out-of-range values (smaller than zero or bigger than one) aren't
%   supported either. Such values will be clamped to the representable [0.0 ; 1.0]
%   range instead. Additionally this mode is only supported on some graphics
%   hardware. It is a special purpose intermediate solution - more accurate
%   than 16 bit floating point, but less capable and less accurate than 32
%   bit floating point. If you need higher precision, choose 'FloatingPoint32Bit'.
%
%   The main sad reason this switch exists is because some graphics hardware or
%   graphics drivers do not support floating point precision textures and
%   framebuffers due to some ridiculous patent restrictions, but they do
%   support a 16 bit signed or unsigned integer precision format. The switch
%   is basically a workaround for the broken patent systems of many countries.
%
%   Usage: PsychImaging('AddTask', 'General', 'FixedPoint16Bit');
%
%
% * 'FloatingPoint32Bit' Ask for a 32 bit floating point precision
%   framebuffer. This allows more than 8 bit precision for complex drawing,
%   compositing and image processing operations. It also allows
%   alpha-blending with signed color values and intermediate results that
%   are outside the displayable range, e.g., negative. Precision is about
%   6.5 digits behind the dezimal point or 8 million discriminable displayable
%   levels. Be aware that only the most recent hardware (NVidia Geforce
%   8000 series, ATI Radeon HD 2000 series) is able to perform
%   alpha-blending at full speed in this mode. Enabling alpha-blending on
%   older hardware may cause a significant decrease in drawing performance,
%   or alpha blending may not work at all at this precision! If you'd like
%   to have both, the highest precision and support for alpha-blending,
%   specify 'FloatingPoint32BitIfPossible' instead. PTB will then try to
%   use 32 bit precision if this is possible in combination with alpha
%   blending. Otherwise, it will choose 16 bit precision for drawing &
%   blending, but 32 bit precision at least for the post-processing.
%
%   Usage: PsychImaging('AddTask', 'General', 'FloatingPoint32Bit');
%
%
% * 'FloatingPoint32BitIfPossible' Ask PTB to choose the highest precision
%   that is possible on your hardware without sacrificing functionality like,
%   e.g., alpha-blending. PTB will choose the best compromise possible for
%   your hardware setup.
%
%   Usage: PsychImaging('AddTask', 'General', 'FloatingPoint32BitIfPossible');
%
%
% * 'NormalizedHighresColorRange' Ask PTB to use a normalized range of
%   color and luminance intensity levels in the interval [0; 1], ie. values
%   between zero and one for minimum and maximum intensity. Also ask for
%   unclamped colors -- intermediate results are allowed to take on
%   arbitrary values, e.g., also negative values. All Screen() 2D drawing
%   commands should operate at maximum color/luminance precision.
%
%   Usage: PsychImaging('AddTask', 'General', 'NormalizedHighresColorRange' [, applyAlsoToMakeTexture]);
%
%   The command PsychImaging('AddTask', 'General', 'NormalizedHighresColorRange', 1);
%   is automatically executed if you used PsychDefaultSetup(featureLevel)
%   with a featureLevel of >= 2 at the top of your experiment script,
%   *except* that clamping is *not* disabled by default in this case! To
%   disable clamping you'd still need to add this task explicitely, as
%   unclamping may have unintended side effects on old graphics hardware.
%
%   The optional flag 'applyAlsoToMakeTexture' defaults to zero. If set to 1,
%   then a unit color range of expected input values in the [0; 1] range is
%   also applied to standard 8-Bit precision textures in Screen('MakeTexture')
%   if the provided Matlab imageMatrix is of double precision type instead of
%   uint8 type. This allows to specify standard textures in the same consistent
%   value range 0-1 as other drawing colors, for cleaner code. Such textures
%   will still be limited to 0-1 range and only resolved into 256 intensity
%   levels, unless you also set the optional 'floatprecision' flag in Screen('MakeTexture')
%   to a value of 1 or 2. We still apply this limitation, as high precision textures consume
%   more memory and other resources and are incompatible with very old graphics
%   hardware.
%
%   This is just a convenience shortcut for Screen('ColorRange', win, 1, 0, applyAlsoToMakeTexture);
%   with the added benefit of allowing to specify the background clear
%   color in normalized 0-1 range as well. This command is implied by use
%   of any of the high precision display device drivers (for attenuators,
%   Bits+ box etc.). It is only needed if you want to create the same
%   visual results on a 8 bit standard framebuffer without needing to
%   change your code, or if you want to set the 'applyAlsoToMakeTexture' flag to a
%   setting of non-zero, so unit colorrange also applies to Screen('MakeTexture').
%
%
% * 'StereoCrosstalkReduction' If a stereoMode is active or requested,
%   apply a shader first in the processing chain that for each eye aims to
%   reduce crosstalk from the other eye.
%
%   Usage:
%
%     PsychImaging('AddTask', 'LeftView', 'StereoCrosstalkReduction', method, crossTalkGain);
%     PsychImaging('AddTask', 'RightView', 'StereoCrosstalkReduction', method, crossTalkGain);
%
%   The 'method' parameter selects the method to use for crosstalk
%   reduction.
%
%   Currently only a method named 'SubtractOther' is implemented, which works as follows:
%
%   To reduce crosstalk, the contrast in the image of each eye, i.e., the
%   difference in color from the background level provided as background
%   clear color of the window is subtracted from the image of the other eye,
%   after scaling the contrast by 'crossTalkGain'. 'crossTalkGain' can be a
%   scalar, or a separate gain for each RGB channel. The background color
%   can be a scalar in the range 0-1, or a 3-element array to set the
%   backgroundlevel for each RGB channel separately. The background
%   color level should not be zero, as contrast then can't be inverted
%   around the background level. In general, the background level
%   should be high enough to allow unclamped inversion of the highest
%   contrast features of your stimulus at your 'crossTalkGain', or
%   artifacts will occur.
%
%
% * 'DisplayColorCorrection' Select a method for color correction to apply to
%   stimuli before output conversion and display. You have to specify a
%   color correction method 'methodname' to apply as parameter, see "help
%   PsychColorCorrection" for an overview of supported color correction
%   methods and their adjustable parameters. The imaging pipeline will be
%   set up to support the chosen color correction method. After you've
%   opened the onscreen window, you can use the different subcommands of
%   PsychColorCorrection() to change parameters of the color correction
%   algorithm at runtime.
%
%   Usage: PsychImaging('AddTask', whichView, 'DisplayColorCorrection', methodname);
%
%   Example: PsychImaging('AddTask', 'FinalFormatting', 'DisplayColorCorrection', 'SimpleGamma');
%   This would apply a simple power-law gamma correction to all view
%   channels of a stereo setup, or the single view of a monoscopic setup.
%   Later on you could use the methods of PsychColorCorrection() to
%   actually set the wanted gamma correction factors.
%
%   Please note that we use the channel 'FinalFormatting' instead of
%   'AllViews' as we'd usually do. Both specs will work, but a selection
%   of 'FinalFormatting' will lead to faster processing in many cases, so
%   this is preferred here if you want to apply the same setting to all
%   view channels - or to a single monoscopic display. Should you find
%   that things don't work as expected, you might try 'AllViews' instead
%   of 'FinalFormatting' - There are subtle differences in how they
%   process your instructions, which may matter in some corner cases.
%
%
% * 'EnablePseudoGrayOutput' Enable the high-performance driver for the
%   rendering of up to 1786 different levels of gray on a standard - but
%   well calibrated - color monitor and 8 bit graphics card. This is done
%   by applying an algorithn known as "Pseudo-Gray" or "Bit stealing".
%   Selecting this mode implies use of 32 bit floating point
%   framebuffers, unless you specify use of a 16 bit floating point
%   framebuffer via 'FloatingPoint16Bit' explicitely. The expected range
%   of input luminance values is between 0 and 1. See "help CreatePseudoGrayLUT"
%   for further explanation.
%
%   Usage: PsychImaging('AddTask', 'General', 'EnablePseudoGrayOutput');
%
%
% * 'EnableGenericHighPrecisionLuminanceOutput'
%   Setup Psychtoolbox for conversion of high precision luminance images
%   into a format suitable for special high precision luminance display
%   devices. This is a generic support routine that uses LUT based
%   conversion.
%
%   Usage: PsychImaging('AddTask', 'General', 'EnableGenericHighPrecisionLuminanceOutput', lut);
%
%
% * 'EnableVideoSwitcherSimpleLuminanceOutput'
%   Setup Psychtoolbox for conversion of high precision luminance images
%   into a format suitable for driving the "VideoSwitcher" high precision
%   luminance display device which was developed by Xiangrui Li et al.
%
%   This implements the simple converter, which only needs the
%   Blue-To-Red-Ratio of the device as input parameter and performs
%   conversion via a closed-form formula without any need for lookup
%   tables. This is supposed to be fast.
%
%   See "help VideoSwitcher" for more info about the device and its
%   options.
%
%   Usage: PsychImaging('AddTask', 'General', 'EnableVideoSwitcherSimpleLuminanceOutput' [, btrr] [, trigger]);
%
%   - The optional 'btrr' parameter is the Blue-To-Red-Ratio to use. If the
%   parameter is left out, the btrr value will be read from a global
%   configuration file.
%
%   - The optional 'trigger' parameter can be zero for "No trigger", or 1
%   for "Use trigger as configured". By default, trigger is off (==0).
%   Enabled, one can use the VideoSwitcher('SetTrigger', ...); function to
%   configure when and how a trigger signal should be emitted. Trigger
%   signals are simply specific pixel patterns in the green output channel.
%   That channel is recognized by the VideoSwitcher as a trigger signal
%   control channel.
%
%
% * 'EnableVideoSwitcherCalibratedLuminanceOutput'
%   Setup Psychtoolbox for conversion of high precision luminance images
%   into a format suitable for driving the "VideoSwitcher" high precision
%   luminance display device which was developed by Xiangrui Li et al.
%
%   This implements the simple converter, which only needs the
%   Blue-To-Red-Ratio of the device as input parameter and performs
%   conversion via a closed-form formula without any need for lookup
%   tables. This is supposed to be fast.
%
%   See "help VideoSwitcher" for more info about the device and its
%   options.
%
%   Usage: PsychImaging('AddTask', 'General', 'EnableVideoSwitcherCalibratedLuminanceOutput' [, btrr] [, lut] [, trigger]);
%
%   - The optional 'btrr' parameter is the Blue-To-Red-Ratio to use. If the
%   parameter is left out, the btrr value will be read from a global
%   configuration file.
%
%   - The optional 'lut' paramter is a 257 elements vector of luminance
%   values, which maps blue channel drive indices to luminance values. This
%   lut needs to be acquired via a calibration procedure by use of a
%   photometer. If 'lut' is left out, the table will be read from a global
%   configuration file.
%
%   - The optional 'trigger' parameter can be zero for "No trigger", or 1
%   for "Use trigger as configured". By default, trigger is off (==0).
%   Enabled, one can use the VideoSwitcher('SetTrigger', ...); function to
%   configure when and how a trigger signal should be emitted. Trigger
%   signals are simply specific pixel patterns in the green output channel.
%   That channel is recognized by the VideoSwitcher as a trigger signal
%   control channel.
%
%
% * 'EnableNative10BitFramebuffer' Enable support for output of stimuli
%   with 10 bit precision per color channel (10 bpc / 30 bpp / "Deep color")
%   on graphics hardware that supports native 10 bpc framebuffers.
%
%   Many graphics cards of the professional class AMD/ATI Fire series
%   (2008 models and later) and all current models of the professional class
%   NVidia Quadro series (2008 models and later), as well as all current models
%   of the consumer class NVidia GeForce series under Linux, do support 10 bpc
%   framebuffers under some circumstances. 10 bpc display on classic CRT monitors
%   which are connected via analog VGA outputs is supported. Support for digital
%   display devices like LCD/OLED panels or video projectors depends on the specific
%   type of display output connector used, the specific panels, and their video
%   settings. Consult manufacturer documentation for details. In general, 10 bpc
%   output may be supported on some graphics cards and displays via DisplayPort
%   or HDMI video outputs, but to our knowledge not via DVI-D outputs.
%
%   If such a combination of graphics card and display is present on your system
%   on Linux or Microsoft Windows, then Psychtoolbox will request native support
%   from the standard graphics drivers, ie., it won't need to use our own
%   homegrown, experimental box of tricks to enable this. You do need to enable/
%   unlock 10 bpc mode somewhere in the display driver settings though. On Linux you
%   can do this for supported cards and drivers via XOrgConfCreator + XOrgConfSelector,
%   on Windows the method is vendor specific.
%
%   Apple OSX, since version 10.11.2 "El Capitan", does support native 10 bpc video
%   output on some small subset of Apple hardware, as of May 2017 these are the MacPro
%   2013 "with some suitable displays" (Apple quotation), and the 27 inch iMac models
%   late 2014 and late 2015 with Retina 5k displays. We've confirmed this to be working
%   on the iMac 5k Retina 27 inch late 2014 model via photometer measurements. On OSX,
%   the OS will actually provide a 16 bit half-float framebuffer for our onscreen windows.
%   This buffer provides ~11 bpc effective linear precision in the displayable color
%   intensity range 0.0-1.0. The OS outputs this 11 bpc framebuffer as a native 10 bpc
%   video signal on suitable displays and uses some Apple proprietary software spatial
%   dithering algorithm to add 1 extra bit of simulated precision, so a photometer would
%   measure up to 11 bpc perceived/measured precision. On some other Mac models, which are
%   not in Apples list of 10 bit capable Macs, Apple uses a proprietary spatial dithering
%   algorithm implemented in software to fake a precision of 11 bpc on standard 8 bpc
%   framebuffers and displays, at least convincing enough for a photometer. The downside
%   of this proprietary dithering scheme is that visual stimulus onset timing precision
%   and timestamping precision is impaired, so Macs in "10 bit" framebuffer mode are not
%   suitable if trustworthy frame accurate visual timing is needed. Nothing we could do
%   about this. To summarize: EnableNative10BitFramebuffer mode on OSX will actually give
%   you a simulated 11 bpc framebuffer on some Mac hardware, plus severe visual timing
%   problems.
%
%   Psychtoolbox experimental 10 bpc framebuffer support:
%
%   Additionally we support ATI/AMD Radeon hardware of the X1000, HD2000 - HD8000,
%   series and later models (everything since at least the year 2006) under Linux
%   via our own low-level setup mechanisms. These graphics cards support a native
%   ARGB2101010 framebuffer, ie., a system framebuffer with 2 bits for the alpha
%   channel, and 10 bits per color channel.
%
%   As this is supported by the hardware, but not always by the standard AMD
%   graphics drivers, we follow a hybrid approach: We use special low-level
%   hardware access to reconfigure the hardware for 10 bpc framebuffer support.
%   Then we use a special imaging pipeline formatting plugin to convert 16 bpc or
%   32 bpc stimuli into the special data format required by this framebuffer
%   configuration.
%
%   On Linux you must have run PsychLinuxConfiguration at least once on your
%   system at some point. You'll need to have one of the supported AMD Radeon
%   gfx-cards (see above) for this to work. If you use Linux with the free and
%   open-source AMD graphics drivers, 10 bpc framebuffer support should work
%   reliably, so use of the open-source drivers on Linux is recommended for
%   reliable results.
%
%   Getting a 10 bpc framebuffer working is only the first half of what you need for
%   high color precision output. Your graphics card must also be able to transmit the
%   video signal at high precision to the display device and the display must be able
%   to faithfully reproduce the high precision image. 10 bpc output has been verified
%   to work for analog VGA connected CRT monitors and displays on both AMD and NVidia
%   graphics cards which do support 10 bpc framebuffers, so with a analog VGA CRT you
%   should be safe. Note that this only applies to native VGA output via VGA connectors
%   or passive DVI-I to VGA adapters connected to a DVI-I connector. Active DisplayPort
%   to VGA adapters or active HDMI to VGA adapters may be limited to maximum 8 bpc output.
%   The status of 10 bpc output to digital display devices differs a lot across devices
%   and OS'es. Output of 10 bpc framebuffers to standard 8 bpc digital laptop panels or
%
%   DVI/HDMI/DisplayPort panels via digital dithering is known to work, but that is not
%   the real thing, only a simulation of 10 bpc via dithering to 8 bpc. This may or may
%   not be good enough for your specific visual stimulation paradigm. On a DVI-D connected
%   standard digital display, this dithered output is the best you will ever get.
%
%   DisplayPort: Recent NVidia and AMD graphics cards can output to some suitable DisplayPort
%   displays with 10 bpc or higher precision on Linux, and maybe also on MS-Windows, but you
%   have to verify this carefully for your specific display.
%
%   HDMI: Recent Intel graphics cards can output up to 12 bpc precision to HDMI deep color
%   capable displays on Linux, and maybe also on MS-Windows. However, > 8 bpc framebuffers
%   are not yet supported, so this can only be used for gamma correction. All AMD graphics
%   cards of model Radeon HD-5000 or later (and equivalent Fire-Series models) can output to
%   HDMI deep color capable displays with 10 bpc real precision at least if you use a Linux
%   kernel of version 3.16 or later with the open-source AMD graphics drivers. Execute
%   PsychLinuxConfiguration to enable this >= 10 bpc deep color output mode, then reboot your
%   machine once to enable it.
%
%   The status with the proprietary AMD drivers on Linux or on MS-Windows is unknown.
%
%   Usage: PsychImaging('AddTask', 'General', 'EnableNative10BitFramebuffer' [, disableDithering=0]);
%
%   This function will setup a 32 bpc floating point resolution framebuffer by
%   default for Psychtoolbox drawing and stimulus processing. Output will happen
%   into a 10 bpc framebuffer. The function will also disable the graphics cards
%   gamma tables, so you'll need to use PsychImaging(...'DisplayColorCorrection'...)
%   for gamma and color correction if you need this.
%
%   The function will *not* disable dithering on digital displays by default,
%   but leave that decision to the operating system and graphics drivers of
%   your machine. A well working OS would disable dithering on a 10 bpc or
%   higher color depth display, if the display reports its capability to the
%   OS via its EDID info. It would enable dithering on < 10 bpc displays, so
%   you'd get a "pseudo 10 bpc" display where 10 bpc color depths is simulated
%   on a 6 bpc or 8 bpc display via the dithering.
%
%   You can disable dithering manually on some graphics cards by providing the
%   optional 'disableDithering' flag as 1. Currently mostly AMD cards allow this
%   control. NVidia or Intel cards require manual setup to force dithering off.
%
%
% * 'EnableNative11BitFramebuffer' Enable support for output of stimuli with (almost)
%   11 bit precision per color channel (11 bpc / 32 bpp / "Deep color") on graphics
%   hardware that supports native 11 bpc framebuffers. This will request an ~ 11 bpc
%   framebuffer from the operating system. If it can't get such a framebuffer on Linux
%   with AMD graphics hardware, it will use our own homegrown setup code to provide
%   such a framebuffer anyway on Radeon X1000, HD-2000 and later graphics cards and
%   equivalent Fire-Series graphics cards. On OSX 10.11.2 it will request and get a
%   ~11 bpc framebuffer on some Mac models. See the explanations above for 10 bpc on
%   OSX.
%
%   Read all the explanations in the section above for 'EnableNative10BitFramebuffer'
%   for capabilities, limitations and possible caveats on different systems.
%
%   Please note that on Linux this "11 Bit framebuffer" is not quite 11 bpc precision,
%   but only about ~ 10.6666 bpc precision. Specifically, the framebuffer can only
%   store at most 32 bits of color information per pixel, so it will store 11 bit
%   precision for the red channel (2048 distinct red intensity levels), 11 bit
%   (2048 levels) for the green channel, but only 10 bit (1024 levels) for the blue
%   channel, for a total number of 11 + 11 + 10 bits = 32 bits of color information
%   per pixel, or 4 billion different possible colors. A true 11 bpc framebuffer would
%   need 33 bits per pixel, and current graphics hardware can't handle that.
%
%   How many bits of precision of these ~ 11 bpc actually reach your display device?
%
%   - Analog VGA only provides for maximum 10 bpc output precision on all shipping
%     NVidia and AMD graphics cards at best. Intel graphics cards only allow for 8 bpc.
%
%   - DisplayPort or HDMI might allow for transfer of 11 bpc precision, in general they
%     support up to 12 bpc. However additional hardware restrictions for your graphics
%     card may limit precision to as low as 10 bpc. To our knowledge, only AMD graphics
%     cards support ~ 11 bpc framebuffers at all. Radeon HD-7000 and earlier can only
%     truly process up to 10 bpc, so 'EnableNative11BitFramebuffer' may not gain you any
%     precision over 'EnableNative10BitFramebuffer' in practice on these cards. AMD cards
%     of the "Sea Islands" family or later, mostly models from the year >= 2014, should be
%     able to process and output up to 12 bpc over HDMI or DisplayPort, so they'd be able
%     to output true ~11 bpc images. However, this hasn't been verified by us so far due to
%     lack of suitable hardware - we don't know if it really works.
%
%   So obviously: Measure very carefully on your setup what kind of precision you really
%   get and make sure not to be fooled by dithering if you need precise low-level control
%   of spatial stimulus properties, or per-pixel high precision, instead of just averaged
%   over larger clusters of pixels.
%
%   Usage: PsychImaging('AddTask', 'General', 'EnableNative11BitFramebuffer' [, disableDithering=0]);
%
%
% * 'EnableNative16BitFramebuffer' Enable 16 bpc, 64 bpp framebuffer on some setups.
%   This asks to enable a framebuffer with a color depth of 16 bpc for up to 65535 levels of intensity
%   per red, green and blue channel, ie. 48 bits = different 2^48 colors. Currently, as of January 2020,
%   this mode of operation is only supported on Linux when using the open-source amdgpu display driver
%   on modern AMD GCN 1.1+ graphics cards. This low-level hack works under specific conditions, but uses
%   a relatively large amount of graphics memory and compute resources to implement. If you can do with
%   less than 12 bpc, you are better off with the other high bit depth modes, as they are more efficient
%   and faster. On suitable setups this will establish a 16 bpc framebuffer which packs 3 * 16 bpc = 48
%   bit color info into 64 bpp pixels and the gpu's display engine will scan out that framebuffer at 16
%   bpc. However, effective output precision is further limited to < 16 bpc by your display, video cable
%   and specific model of graphics card. As of January 2020, the maximum effective output precision is
%   limited to at most 12 bpc (= 4096 levels of red, green and blue) by the graphics card, and this
%   precision is only attainable on AMD graphics cards of the so called "Sea Islands" (cik) family
%   (aka GraphicsCore Next GCN 1.1 or later) or later models when used with the amdgpu-kms display driver.
%
%   Older cards of type GCN 1.0 "Southern Islands" or earlier won't work, as they only support at most 10
%   bpc overall precision. AMD's latest discrete gpu's of the "Navi" gpu family and later, and AMD's latest
%   integrated gpus as part of AMD Ryzen processors / APU's are not yet supported. The specific requirement
%   for this to work is an AMD gpu with a DCE-8 to DCE-12 display engine which uses the new amdgpu-ddx /
%   amdgpu-kms driver and AMD's DisplayCore driver path. Cards older than "Sea Islands" don't have a DCE-8+
%   engine, and cards newer than "Vega" won't work, because their DCN-2 display engines are not yet supported
%   by our 16 bpc code, ditto for modern AMD APU's with DCN display engines.
%
%   Please note that actual 12 bpc output precision can only be attained on certain display devices and
%   software configurations. As of January 2020, the following hardware + software combo has been
%   verified with a CRS ColorCal2 colorimeter to provide 12 bpc per color channel precision:
%   The Apple MacBookPro 2017 15 inch with its built-in 10 bit Retina display, running under Ubuntu Linux
%   19.10 with Linux 5.5-rc, using the XFCE 4 desktop GUI. This is the only setup we have available for
%   testing, other setups should work under proper circumstances. Due to time limitations, so far only a
%   spot check of a fraction of the intensity range has been conducted, testing 128 steps out of the 4096
%   possible steps in the 12 bpc intensity range.
%
%   High bit depth 12 bpc output should work over high-end 12 bit displays connected via HDMI or DisplayPort.
%   Modern AMD gpus, e.g., AMD Polaris, are also able to emulate 12 bpc precision via spatial dithering on
%   8 bit display panels. Measure your results carefully with a photometer or colorimeter, this is the only
%   way to verify your specific setup really achieves 12 bpc! See the sections about 11 bpc and 10 bpc native
%   framebuffers above for further details.
%
%   Required manual one time setup:
%
%   -  Only three distinct display setups are allowed: Either a single display connected, or if multiple
%      displays are conected, all displays must mirror (aka clone) each other showing the same image,
%      or a dual display setup with both displays running at the same video resolution, one display
%      showing the left half of your onscreen window, the other showing the right half of your onscreen
%      window, ie., a typical setup for dual-display side-by-side stereo presentation. Pretty much any other
%      display setup will display undefined results, e.g., corrupted images or random pixel trash.
%
%   -  Also note that not all desktop GUI environments work: GNOME-3 desktop "Gnome shell" and "Ubuntu desktop"
%      as well as KDE-5 do not work as of Ubuntu 19.10, but the XFCE 4 desktop works.
%
%   1. Install a suitable desktop environment, e.g., XFCE 4 via "sudo apt install xfce4", log into it.
%
%   2. If you are using an old "Sea Islands" / "GraphicsCore Next 1.1" / "GCN 1.1" gpu, you must reboot
%      your machine with Linux kernel boot settings that select the new amdgpu kms driver and AMD DisplayCore,
%      instead of the old radeon kms driver that would be used by default. This requires adding the following
%      parameters to the kernel boot command line: "radeon.cik_support=0 amdgpu.cik_support=1 amdgpu.dc=1"
%
%   3. At least the Retina panel on the 2017 MacBookPro, while claiming it is a 10 bit panel, seems to work
%      with better precision if operated in 8 bit mode. Measurements in 'EnableNative16BitFramebuffer' mode
%      suggest better quality of the "dithering emulated" 11 bpc / 12 bpc framebuffers if the panel is
%      restricted to 8 bit mode. The assumption is that Apples 10 bit Retina panel is not actually a 10 bit
%      panel, but an 8 bit panel that uses built in spatial dithering to fake 10 bit panel behaviour. If the
%      gpu operates at > 10 bpc output precision, it employs dithering itself, and the panels dithering interferes
%      with the gpu's dithering, causing degraded results. Running the panel in (true native!) 8 bit mode leaves
%      all the dithering from 8 bit -> 10, 11 or 12 bit to the high quality dithering of the gpu, attaining
%      better results.
%
%      You can enforce 8 bit mode + up to 4 bit dithering for up to 12 bpc precision by executing the following
%      command in a terminal window, startup script, or via a system(); command:
%
%      xrandr --output eDP --set 'max bpc' 9
%
%      You can verify actual output bit depth for an output XXX by typing this command as root into a
%      terminal window:
%
%      cat /sys/kernel/debug/dri/0/XXX/output_bpc
%
%      E.g., for the internal laptop eDP flat panel of the MacBookPro 2017:
%
%      cat /sys/kernel/debug/dri/0/eDP-1/output_bpc
%
%   Once one time setup is done, adding the following command to your script will enable the 16 bpc framebuffer
%   with up to 12 bpc effective output precision:
%
%   Usage: PsychImaging('AddTask', 'General', 'EnableNative16BitFramebuffer' [, disableDithering=0][, bpc]);
%
%
% * 'EnableNative16BitFloatingPointFramebuffer' Enable support for output of
%   stimuli with up to 16 bit floating point precision per color channel on
%   graphics hardware and displays that support native 16 bpc floating point
%   framebuffers. Please note that the effective linear output precision of a
%   16 bit non-linear floating point framebuffer in the normalized range 0.0 - 1.0
%   (the "typical" output range for SDR standard dynamic range displays) is only
%   about 11 bits ~ 2048 levels of red, green, blue intensity. Also note that
%   actual display hardware will usually only resolve this at about 10 bpc, or
%   maybe simulated 11 bpc via dithering techniques. As of July 2019, only NVidia
%   graphics cards of the GeForce 1000 "Pascal" series or later under Windows-10
%   seem to support this mode properly. At least one combo of GeForce 1060 + 8 bit
%   panel was shown via photometer to reproduce about 11 bpc luminance via spatial
%   dithering. macOS does support this mode with what seems to be mostly software
%   rendering on most machines, ie. with very low performance and even worse timing.
%   Your mileage may vary.
%
%   Usage: PsychImaging('AddTask', 'General', 'EnableNative16BitFloatingPointFramebuffer');
%
%
% * 'UseDataPixx' Tell Psychtoolbox that additional functionality for
%   displaying the onscreen window on a VPixx Technologies DataPixx device
%   should be enabled.
%
%   This command is implied by enabling a DataPixx video mode by one of the
%   commands for the DataPixx in the following sections.
%
%   'UseDataPixx' mostly prepares use of a variety of subfunctions in the
%   DataPixxToolbox ("help DataPixxToolbox") and in the PsychDataPixx()
%   high-level driver ("help PsychDataPixx").
%
%
% * 'EnableDataPixxL48Output' Setup Psychtoolbox for L48 mode of the VPixx
%   Technologies DataPixx device. This loads the graphics hardwares gamma
%   table with an identity mapping so it can't interfere with DPixx video
%   processing. It also sets up automatic generation of control signals to
%   support the features of DPixx that are available via the functions in
%   PsychDataPixx(). You will be able to upload new CLUT's into the DPixx
%   by use of the Screen('LoadNormalizedGammaTable', window, clut, 2);
%   command. CLUT updates will be synchronized with Screen('Flip') commands.
%   Please note that while L48 CLUT mode works even with very old
%   graphics hardware, this is a pretty cumbersome way of driving the
%   DPixx. On recent hardware, you will want to use M16 or C48 mode
%   (see below). That allows to draw arbitrarily complex stimuli with as
%   many colors as you want and PTB will take care of conversion into the
%   M16 or C48 format for DataPixx.
%
%   Usage: PsychImaging('AddTask', 'General', 'EnableDataPixxL48Output');
%
%
% * 'EnableDataPixxM16Output' Enable the high-performance driver for M16
%   mode of the VPixx Technologies DataPixx device. This is the fastest and
%   most elegant way of driving the DPixx box with 16 bit luminance output
%   precision. See "help DataPixx" for more information. Selecting this
%   mode implies use of 32 bit floating point framebuffers, unless you
%   specify use of a 16 bit floating point framebuffer via
%   'FloatingPoint16Bit' explicitely. If you do that, you will not be able
%   to use the full 16 bit output precision, but only approximately 10 bits.
%
%   Usage: PsychImaging('AddTask', 'General', 'EnableDataPixxM16Output');
%
%   If you want to make use of the color overlay plane in M16 mode, then
%   call the function like this:
%
%   Usage: PsychImaging('AddTask', 'General', 'EnableDataPixxM16OutputWithOverlay');
%   See the explanation of color overlays in the section
%   'EnableBits++Mono++OutputWithOverlay' - behaviour of color overlays is
%   identical for the CRS Bits++ and the VPixx DataPixx.
%
%
% * 'EnableDataPixxC48Output' Enable the high-performance driver for the
%   C48 mode of VPixx technologies DataPixx box. This is the fastest and
%   most elegant way of driving the DataPixx box with 16 bit per color
%   channel output precision. See "help DataPixx" for more information.
%   Selecting this mode implies use of 32 bit floating point framebuffers,
%   unless you specify use of a 16 bit floating point framebuffer via
%   'FloatingPoint16Bit' explicitely. If you do that, you will not be able
%   to use the full 16 bit output precision, but only approximately 10 bits.
%
%   Usage: PsychImaging('AddTask', 'General', 'EnableDataPixxC48Output', mode);
%
%   See the section below about 'EnableBits++Color++Output' for the meaning
%   of the mandatory "mode" parameter.
%
%   You can use the RemapMouse() function to correct GetMouse() positions
%   for potential geometric distortions introduced by this function for
%   "mode" zero.
%
%
% * 'UseBits#' Tell Psychtoolbox that additional functionality for
%   displaying the onscreen window on a Cambridge Research Systems Bits#
%   device should be enabled.
%
%   This command is implied by enabling a Bits+ or Bits# video mode by one
%   of the commands for the Bits+/Bits# in the following sections, if the
%   driver can auto-detect a connected Bits# device. If it cannot auto-detect
%   a connected Bits# device and this command is omitted, Psychtoolbox will
%   instead assume that an older Bits+ is in use and only allow functionality
%   common to Bits# and Bits+, without automatic video mode switching.
%
%   If you provide this command, you can optionally specify the name of the
%   serial port to which your Bits# is connected, instead of leaving it to
%   the system to find this out (either via configuration file or via a
%   guess-o-matic).
%
%   Usage: PsychImaging('AddTask', 'General', 'UseBits#' [, BitsSharpSerialPort]);
%
%   'BitsSharpSerialPort' is optional and can be set to the name of a serial
%   port for your specific operating system and computer, to which the Bits#
%   is connected. If omitted, Psychtoolbox will look for the name in the first
%   line of text of a text file stored under the filesystem path and filename
%   [PsychtoolboxConfigDir 'BitsSharpConfig.txt']. If that file is empty, the
%   serial port is auto-detected (Good luck!).
%
%   'UseBits#' mostly prepares use of a variety of new Bits# subfunctions
%   in the BitsPlusPlus() high-level driver ("help BitsPlusPlus").
%
%
% * 'EnableBits++Bits++Output' Setup Psychtoolbox for Bits++ mode of the
%   Cambridge Research Systems Bits++ box. This loads the graphics
%   hardwares gamma table with an identity mapping so it can't interfere
%   with Bits++ T-Lock system. It also sets up automatic generation of
%   Bits++ T-Lock codes: You will be able to upload new CLUT's into the
%   Bits++ by use of the Screen('LoadNormalizedGammaTable', window, clut, 2);
%   command. CLUT updates will be synchronized with Screen('Flip')
%   commands, because PTB will generate and draw the proper T-Lock code
%   into the top line of your onscreen window. Please note that while
%   Bits++ CLUT mode works even with very old graphics hardware, this is a
%   pretty cumbersome way of driving the Bits++. On recent hardware, you
%   will want to use Mono++ or Color++ mode (see below). That allows to
%   draw arbitrarily complex stimuli with as many colors as you want and
%   PTB will take care of conversion into the Color++ or Mono++ format for
%   Bits++.
%
%   Usage: PsychImaging('AddTask', 'General', 'EnableBits++Bits++Output');
%
%
% * 'EnableBits++Mono++Output' Enable the high-performance driver for the
%   Mono++ mode of Cambridge Research Systems Bits++ box. This is the
%   fastest and most elegant way of driving the Bits++ box with 14 bit
%   luminance output precision. See "help BitsPlusPlus" for more
%   information. Selecting this mode implies use of 32 bit floating point
%   framebuffers, unless you specify use of a 16 bit floating point
%   framebuffer via 'FloatingPoint16Bit' explicitely. If you do that, you
%   will not be able to use the full 14 bit output precision of Bits++, but
%   only approximately 10 bits.
%
%   Usage: PsychImaging('AddTask', 'General', 'EnableBits++Mono++Output');
%
%   If you want to make use of the color overlay plane in Mono++ mode, then
%   call the function like this:
%
%   Usage: PsychImaging('AddTask', 'General', 'EnableBits++Mono++OutputWithOverlay');
%
%   Then you can query the window handle of the overlay window via:
%
%   overlayWin = PsychImaging('GetOverlayWindow', window);
%
%   'overlayWin' is the handle to the overlay window associated with the
%   overlay of onscreen window 'window'. The overlay window is a standard
%   offscreen window, so you can do anything with it that you would want to
%   do with offscreen windows. The only difference is that the window is a
%   pure index window: It only has one "color channel", which can be written
%   with color values between 0 and 255. Values 1 to 255 get mapped to the
%   corresponding color indices of the Bits++ overlay plane: A zero value is
%   transparent -- Content of the onscreen window is visible. Positive
%   non-zero color values map to the 255 indices available in overlay mode,
%   these get mapped by the Bits++ CLUT to colors. You can define the
%   mapping of indices to CLUT colors via the
%   Screen('LoadNormalizedGammaTable', window, clut, 2); command.
%
%   Updates of the overlay image are synchronized to Screen('Flip')
%   updates. If you draw into the overlay window, the changed overlay image
%   will become visible at Screen('Flip') time -- in sync with the changed
%   onscreen window content. The overlay plane is not automatically cleared
%   to background (or transparent) color after a flip, but its content
%   persists across flips. You need to clear it out manually via a
%   Screen('FillRect') command.
%
%
% * 'EnableBits++Color++Output' Enable the high-performance driver for the
%   Color++ mode of Cambridge Research Systems Bits++ box. This is the
%   fastest and most elegant way of driving the Bits++ box with 14 bit
%   per color channel output precision. See "help BitsPlusPlus" for more
%   information. Selecting this mode implies use of 32 bit floating point
%   framebuffers, unless you specify use of a 16 bit floating point
%   framebuffer via 'FloatingPoint16Bit' explicitely. If you do that, you
%   will not be able to use the full 14 bit output precision of Bits++, but
%   only approximately 10 bits.
%
%   Usage: PsychImaging('AddTask', 'General', 'EnableBits++Color++Output', mode);
%
%   "mode" is a mandatory numeric parameter which must be 0, 1 or 2. In
%   Color++ mode, the effective horizontal display resolution is only half
%   the normal horizontal resolution. To cope with this, multiple different
%   methods are implemented to squeeze your stimulus image horizontally by
%   a factor of two. The following options exist:
%
%   0 = This is the "classic" mode which was used in all Psychtoolbox
%   versions prior to 22nd September 2010. If you want to keep old code
%   working as is, select 0. In this mode, your script will only see a
%   framebuffer that is half the true horizontal resolution of your
%   connected display screen. Each drawn pixel will be stretched to cover
%   two pixels on the output display device horizontally. While this
%   preserves the content of your stimulus image exactly, it means that the
%   aspect ratio of all displayed text and stimuli will be 2:1. Text will
%   be twice as wide as its height. Circles or squares will turn into
%   horizontal ellipses or rectangles etc. You'll need to do extra work in
%   your code if you want to preserve aspect ratio properly.
%
%   You can use the RemapMouse() function to correct GetMouse() positions
%   for potential geometric distortions introduced by this function for
%   "mode" zero.
%
%   Example: A fine vertical grid with alternating vertical white and black
%   lines would display as expected, but each white or black stripe would be
%   two pixels wide on the display instead of one pixel wide.
%
%   1 = Subsample: Your framebuffer will appear at the same resolution as
%   your display device. Aspect ratio of drawn stimuli/text etc. will be
%   correct and as expected. However, every 2nd column of pixels in your
%   stimulus (ie., all odd-numbered x-coordinates 1,3,5,7,...) will be
%   completely ignored, only even columns are used!
%
%   Example: A fine vertical grid with alternating vertical white and black
%   lines would display as a purely white image, as only the white pixels
%   in the even columns would be used, whereas the black pixels in the odd
%   columns would be ignored.
%
%   2 = Average: Your framebuffer will appear at the same resolution as
%   your display device. Aspect ratio of drawn stimuli/text etc. will be
%   correct and as expected. However, each pair of adjacent even/odd pixel
%   columns will be averaged before output. Stimulus pixels 0 and 1 will
%   contribute the mean color for display pixel 0. Pixels 2 and 3 will be
%   averaged into display pixel 1 and so on. Visually this gives the most
%   pleasing and smooth results, but if adjacent even/odd pixels don't have
%   the same color value, you'll obviously get an output color that is
%   neither the color of the even pixel nor the odd pixel, but the average
%   of both.
%
%   Example: A fine vertical grid with alternating vertical white and black
%   lines would display as a 50% gray image, as the alternating white and
%   black columns would be averaged into the average of white and black,
%   which is 50% gray.
%
%
% * 'EnableDualPipeHDROutput' Enable EXPERIMENTAL high-performance driver
%   for HDR display devices which are composites of two separate displays.
%
%   EXPERIMENTAL proof-of-concept code with no real function yet!
%
%   This is meant for high-precision luminance or color output. It implies
%   use of 32 bpc floating point framebuffers unless otherwise specified by
%   other calls to PsychImaging().
%
%   The pair of specially encoded output images that are derived from
%   content of the onscreen window shall be output to both, the display
%   associated with the screen given to PsychImaging('OpenWindow',...); and
%   on the screen with the index 'pipe1Screen', using appropriate encoding
%   to drive the HDR device or similar composite device.
%
%   Usage: PsychImaging('AddTask', 'General', 'EnableDualPipeHDROutput', pipe1Screen [, pipe1Rectangle]);
%
%   Optionally you can pass a 'pipe1Rectangle' if the window with the
%   pipe1 image shall not fill the whole 'pipe1Screen', but only a
%   subregion 'pipe1Rectangle'.
%
%
% * 'AddOffsetToImage' Add a constant color- or intensity offset to the
%   drawn image, possibly also multiply with a gain, prior to all following
%   image processing and post-processing operations:
%
%   Outimage(x,y) = (Inimage(x,y) + preOffset) * gain + Offset.
%
%   If the framebuffer is in a color display mode, the same offset will be added
%   to all three color channels.
%
%   Usage: PsychImaging('AddTask', whichView, 'AddOffsetToImage', Offset [, gain=1][, preOffset=0]);
%   'gain' and 'preOffset' are optional, 'Offset' is required.
%
%   Example: PsychImaging('AddTask', 'AllViews', 'AddOffsetToImage', 0.5);
%
%
% * 'MirrorDisplayTo2ndOutputHead' Mirror the content of the onscreen
%   window to given 2nd screen, ie., to a 2nd output connector (head)
%   of a dualhead graphics card. This should give the same result as if one
%   switches the graphics card into "Mirror mode" or "Clone mode" via the
%   display settings panel of your operating system.
%
%   This function only works for monoscopic displays, ie., it can not be
%   used simultaneously with any stereo display mode. The reason is that it
%   internally uses stereomode 10 with a few modifications to get its job
%   done, so obviously neither mode 10 nor any other mode can be used
%   without interference.
%
%   Only use this function for mirroring onto the 2nd head of a dual-head
%   graphics card under MacOS/X, or if you need to mirror onto a 2nd head
%   on MS-Windows and can't use "desktop spanning" mode on Windows to
%   achieve dual display output. If possible on your setup and OS, rather use
%   'MirrorDisplayToSingleSplitWindow' (see below). That mode should work
%   well on dual-head graphics cards on MS-Windows or GNU/Linux, as well as
%   in conjunction with a hardware display splitter attached to a single
%   head on any operating system. It has the advantage of consuming less
%   memory and compute ressources, so it is potentially faster or provides
%   a more reliable overall timing.
%
%   Usage: PsychImaging('AddTask', 'General', 'MirrorDisplayTo2ndOutputHead', mirrorScreen [, mirrorRectangle]);
%
%   The content of the onscreen window shall be shown not only on the
%   display associated with the screen given to PsychImaging('OpenWindow',
%   ...); but also (as a copy) on the screen with the index 'mirrorScreen'.
%
%   Optionally you can pass a 'mirrorRectangle' if the window with the
%   mirror image shall not fill the whole 'mirrorScreen', but only a
%   subregion 'mirrorRectangle'.
%
%
% * 'MirrorDisplayToSingleSplitWindow' Mirror the content of the onscreen
%   window to the right half of the desktop (if desktop spanning on a
%   dual-display setup is enabled) or the right-half of the virtual screen
%   if a display splitter (e.g., Matrox Dualhead2Go (TM)) is attached to a
%   single head of a graphics card. This should give the same result as if one
%   switches the graphics card into "Mirror mode" or "Clone mode" via the
%   display settings panel of your operating system.
%
%   Usage: PsychImaging('AddTask', 'General', 'MirrorDisplayToSingleSplitWindow');
%
%   Optionally, you can add the command...
%   PsychImaging('AddTask', 'General', 'DontUsePipelineIfPossible');
%   ... if you don't intend to use the imaging pipeline for anything else
%   than display mirroring. This will allow further optimizations.
%
%
% * 'RestrictProcessing' Restrict stimulus processing to a specific subarea
%   of the screen. If your visual stimulus only covers a subarea of the
%   display screen you can restrict PTB's output processing to that
%   subarea. This may save some computation time to allow for higher
%   display redraw rates.
%
%   Usage: PsychImaging('AddTask', whichChannel, 'RestrictProcessing', ROI);
%
%   ROI is a rectangle defining the area to process ROI = [left top right bottom];
%   E.g., ROI = [400 400 800 800] would only create output pixels in the
%   screen area with top-left corner (400,400) and bottom-right corner
%   (800, 800).
%
%
% * 'FlipHorizontal' and 'FlipVertical' flip your output images
%   horizontally (left- and right interchanged) or vertically (upside down).
%
%   Usage: PsychImaging('AddTask', whichChannel, 'FlipHorizontal');
%   Usage: PsychImaging('AddTask', whichChannel, 'FlipVertical');
%
%   You can use the RemapMouse() function to correct GetMouse() positions
%   for potential geometric distortions introduced by this function.
%
%
% * 'GeometryCorrection' Apply some geometric warping operation during
%   rendering of the final stimulus image to correct for geometric
%   distortion of your physical display device. You need to measure the
%   geometric distortion of your display with a suitable calibration
%   procedure, then compute an inverse warp transformation to undo this
%   distortion, then provide that transformation to this function.
%
%   Usage: PsychImaging('AddTask', whichChannel, 'GeometryCorrection', calibfilename [, debugoutput] [, arg1], [arg2], ...);
%
%   'calibfilename' is the filename of a calibration file which specified
%   the type of undistortion to apply. Calibration files can be created by
%   interactive calibration procedures. See 'help CreateDisplayWarp' for a
%   list of calibration methods. One of the supported procedures is, e.g.,
%   "DisplayUndistortionBezier", read "help DisplayUndistortionBezier". The
%   recommended method for most cases is 'DisplayUndistortionBVL', read
%   "help DisplayUndistortionBVL" for help.
%
%   The optional flag 'debugoutput' if set to non-zero value will trigger
%   some debug output about the calibration with some calibration methods.
%
%   The optional 'arg1', 'arg2', ..., are optional parameters whose
%   meaning depends on the calibration method in use.
%
%   Use of geometry correction will break the 1:1 correspondence between
%   framebuffer pixel locations (x,y) and the mouse cursor position, ie. a
%   mouse cursor positioned at display position (x,y) will be no longer
%   pointing to framebuffer pixel (x,y). If you want to know which
%   pixel in your original stimulus image corresponds to a specific
%   physical display pixel (or mouse cursor position), use the function
%   RemapMouse() to perform the neccessary coordinate transformation.
%
%
% * 'UseVRHMD' Display this onscreen window on a "Virtual Reality" head mounted
%   display (HMD), e.g., the Oculus Rift DK1 or Rift DK2. This enables display of
%   stereoscopic visual stimuli on supported virtual reality headsets.
%   You need to have the neccessary vendor supplied VR runtimes installed for
%   this to work.
%
%   Simple usage:
%
%   The most simple way to setup a HMD for use is to add a call to
%   hmd = PsychVRHMD('AutoSetupHMD') instead of a call to
%   PsychImaging('AddTask', 'General', UseVRHMD', ...). The 'AutoSetupHMD'
%   call would detect the first supported HMD device on your computer, connect to
%   it, then set it up with reasonable default operating parameters. Then it would
%   call this PsychImaging task to perform all required setup steps.
%
%   Advanced usage:
%
%     1. Open a connection to a HMD and get a handle for the device:
%        For example, if you wanted to use a Oculus Rift DK1 or DK2, you could
%        do:
%
%        hmd = PsychOculusVR('Open' ...);
%
%     2. Perform basic configuration of the HMD via the HMD specific driver.
%
%     3. Add a PsychImaging task for the HMD and pass in its device handle 'hmd':
%        PsychImaging('AddTask', 'General', 'UseVRHMD', hmd);
%
%   This sequence will perform the necessary setup of panel fitter, stereo display
%   mode and image post-processing for geometry correction, color aberration
%   correction and vignette correction for a fullscreen window on the HMD.
%
%
% * 'UseVulkanDisplay' Display this onscreen window using a Vulkan-based display
%   backend. This only works on graphics card + operating system combinations
%   which support both the OpenGL and Vulkan rendering api's and OpenGL-Vulkan
%   interop. As of October 2020 this would be modern AMD and NVidia graphics cards
%   under modern GNU/Linux (Ubuntu 18.04-LTS and later) and Microsoft Windows-10.
%
%   At the moment 'UseVulkanDisplay' does not provide any advantages for standard
%   visual stimulus display tasks, quite the contrary! The current implementation
%   is *experimental* and may go through backwards incompatible changes which may
%   break your scripts if you rely on it! Only use if you really know what you are
%   doing!
%
%   Usage:
%
%   PsychImaging('AddTask', 'General', 'UseVulkanDisplay');
%
%   Psychtoolbox will try to display the onscreen window by using a Vulkan driver
%   with Vulkan/WSI backend, instead of the usual OpenGL windowing system backend.
%   This may fail if the given system setup does not support this.
%
%
% * 'EnableHDR' Display this onscreen window on a "High dynamic range" (HDR) display.
%   This requires a combination of operating-system, display drivers, graphics card,
%   video cables and display devices which are at least HDR-10 capable.
%
%   For hardware and system requirements, setup instructions, and further explanations
%   read "help PsychHDR".
%
%   Usage:
%
%   PsychImaging('AddTask', 'General', 'EnableHDR' [, unit='Nits'][, hdrMode='Auto'][, extraRequirements]);
%
%   Optional parameters:
%
%   'unit'  The unit in which color values are specified by the users drawing code.
%           Default value is 'Nits', ie. 1 unit = 1 Nit = 1 candela per square-meter.
%           '80Nits', ie. 1 unit = 80 Nits = 80 cd/sqm = Supposedly the SDR range.
%
%   'hdrMode' General mode of operation for HDR display:
%             Default is 'Auto' for auto-selection of optimal mode for given system
%             configuration, selected out of the following available op-modes:
%
%             'HDR10' Standard HDR-10, with 10 bpc color precision, ITU Rec 2020 input
%             color space (aka BT-2020), SMPTE ST-2084 PQ "Perceptual Quantizer" OETF
%             transfer function.
%
%             -> Currently 'Auto' will select 'HDR-10' as the only supported op-mode.
%
%   'extraRequirements' String with various keywords to specify special requirements.
%                       Default is empty, ie. no extra requirements. Currently supported
%                       keywords are:
%
%                       'Dummy' - Only simulate HDR on a SDR standard dynamic range display.
%                                 This only performs setup steps and processing possible on
%                                 a SDR display, to allow for basic script development and
%                                 testing. Visual results will be obviously wrong!
%
%
% * 'UseStaticHDRHack' Use a Linux + X11 only HDR display setup hack to allow multi-display
%   HDR presentation, e.g., for dual-HDR-monitors setups with two identical HDR monitors
%   or display devices for binocular or stereoscopic HDR visual stimulation.
%
%   Currently our Vulkan-based HDR display backend does not yet support proper dual-display/
%   multi-display stereo presentation in a proper way. As a stop-gap solution until we have
%   proper support implemented, this task requests use of a Linux + X11 specific hack to make
%   HDR stereo presentation work ok. The hack is a hack, not guaranteed to work on your setup,
%   or even if it works it is not guaranteed to work permanently, e.g. across operating system
%   updates. Use of this highly experimental hack is at your own risk, you have been warned!
%   Also note that using this PsychImaging tasks will make your script non-portable to other
%   operating systems than Linux or other display systems than X11 (e.g., Wayland) for the time
%   being. Another limitation is that this only works with 10 bpc HDR-10 precision framebuffers,
%   not with the higher precision 16 bit floating point framebuffers offered by standard HDR
%   mode. This hack will be limited permanently to HDR-10 with static HDR metadata and 10 bit
%   precision. It will eventually be superseded by a proper multi-display/stereo HDR implementation,
%   if and when we get funding to work on this.
%
%   To setup HDR-10 display with this hack:
%
%   0. You *must* install GNU/Octave and setup Psychtoolbox for use with GNU/Octave! Even if
%      you prefer to run your scripts in Matlab, Octave is needed as a background helper! In
%      general we recommend using Octave for this special hack, as Matlab's GUI is currently
%      incompatible with 10 bit display mode as of R2020b. It will misrender, crash or simply
%      hang! The only way to use Matlab here is without GUI in the terminal window by launching
%      Matlab via "matlab -nodesktop". In this case, using Octave in the first place may be
%      more convenient, as Octave's GUI works just fine!
%
%   1. Connect identical models of HDR monitors to your X-Screen 0, or at your leisure,
%      create a separate X-Screen for stimulation by use of XOrgConfCreator. It is important
%      that the X-Screen used for visual HDR stimulation only has HDR monitors connected, no
%      other monitors, iow. the same configuration as if you'd do multi-display stereo stimulation
%      for standard dynamic range stimuli under Linux.
%
%   2. Use XOrgConfCreator to create a separate X-Screen - or setup your one and only X-Screen 0
%      to use 10 bpc, color depth 30 bit mode. If the XOrgConfCreator asks you if you want to
%      setup advanced settings, answer "y"es, and then when it asks for 10 bpc / 30 bit deep
%      color support, answer "y"es and select all X-Screens which will be used for HDR stimulation.
%
%   3. Select this HDR-10 10 bit configuration via XOrgConfSelector, logout and login again to
%      get your single or multi-X-Screen setup with the X-Screen to which the HDR displays are
%      connected running in 10 bpc color depth.
%
%   4. Now you can use a regular HDR script, but add the PsychImaging('AddTask', 'General', 'UseStaticHDRHack')
%      task before opening the onscreen window. Also select a stereoMode of 4 for dual-display stereo
%      stimulation in PsychImaging('OpenWindow', ..., stereoMode);
%
%   5. All connected HDR monitors should switch to HDR mode and the onscreen window should be setup
%      for stereo stimulus display across HDR monitors, as usual.
%
%   If these steps don't work for you, too bad. If they work, great.
%
%   This mode will only allow slow switching of different HDR metadata settings via PsychHDR('HDRMetadata'),
%   and each switch of settings will be slow and accompanied by flicker. For this reason you can
%   also assign the meta-data as part of the 'UseStaticHDRHack' setup as optional hdrStaticMetadata
%   struct. The same HDR metadata will be applied to all connected HDR monitors for a window / X-Screen.
%   Other limitations may apply, and this functionality is so far only tested on one HDR setup.
%
%   Usage:
%
%   PsychImaging('AddTask', 'General', 'UseStaticHDRHack' [, hdrStaticMetadata]);
%
%
% * More actions will be supported in the future. If you can think of an
%   action of common interest not yet supported by this framework, please
%   file a feature request on our Wiki (Mainpage -> Feature Requests).
%
%
% After adding all wanted task specifications and other requirements,
% call...
%
% [windowPtr, windowRect] = PsychImaging('OpenWindow', screenid, [backgroundcolor], ....);
%
% - Finishes the setup phase for imaging pipeline, creates a suitable onscreen
% window and performs all remaining configuration steps. After this
% command, your onscreen window will be ready for drawing and display of
% stimuli. All specified imaging operations will get automatically applied
% to your stimulus before stimulus onset.
%
%
% After the window has been opened you can call the following commands any
% time at runtime:
%
% PsychImaging('RestrictProcessingToROI', window, whichChannel, ROI);
% - Restrict the processing area of viewChannel 'whichChannel' of onscreen
% window 'window' to the rectangular subarea defined by 'ROI'. See the
% explanation above for subtask 'RestrictProcessing'. This does exactly the
% same but allows a dynamic change of the restricted area at any point
% during your experiment script.
%
%
% PsychImaging('UnrestrictProcessing', window, whichChannel);
% - Remove a restriction of the processing area of viewChannel
% 'whichChannel' of onscreen window 'window' to a previously defined
% subarea. Can be called anytime during your scripts execution.
%
%
% [overlaywin, overlaywinRect] = PsychImaging('GetOverlayWindow', win);
% - Will return the handle to the 'overlaywin'dow associated with the
% given 'win'dow, if any. Will abort with an error message if the 'win'dow
% doesn't have an associated overylay window.
% Currently, only the CRS Bits+ box in Mono++ mode and the VPixx DataPixx
% box in M16 mode does support overlays. Other output drivers don't support
% such a feature. See "help BitsPlusPlus" for subfunction
% 'GetOverlayWindow' for more explanations of the purpose and properties of
% overlay windows. The explanations apply to the DPixx device as well if it
% is opened in videomode 'M16WithOverlay'.
%
%
%
% The following commands are only for specialists:
%
% [imagingMode, needStereomode] = PsychImaging('FinalizeConfiguration');
% - Finish the configuration phase for this window. This will compute an
% optimal configuration for all stages of the pipeline, but won't apply it
% yet. You'll have to call Screen('OpenWindow', windowPtr, ......,
% imagingMode, ...); with the returned 'imagingMode' + any other options
% you'd like to have for your window. After that, you'll have to call
% PsychImaging('PostConfiguration') to really apply and setup all your
% configuration settings. If you don't have unusual needs, you can simplify
% these steps by simply calling PsychImaging('OpenWindow', ....);
% with the same parameters that you'd pass to Screen('OpenWindow', ....);
% PsychImaging will perform all necessary steps to upon return, you'll have
% your window properly configured.
%
%
% PsychImaging('PostConfiguration', windowPtr [, clearcolor]);
% - To be called after opening the onscreen window 'windowPtr'.
% Performs all the setup work to be done after the window was created.
%
%

% Notes:
%
%
%

% History:
% 3.6.2007 Written. (MK)
%
% 19.7.2007 Added our own implementation mystrcmp() of Matlabs strcmp().
% The strcmp() of Octave is not as powerful as Matlab 7's strcmp(). We need
% a powerful implementation that works for both runtime environments, so we
% reimplemented in in a portable way.
%
% 19.7.2007   Added initial support for display geometry correction. (MK).
%
% 27.9.2007   Added support for floating point framebuffer, Bits++ and
%             Brightside-HDR. Documentation cleanup. (MK).
%
% 13.1.2008   Support for 10 bpc native framebuffer of ATI Radeons. (MK).
%
% 17.4.2008   Support for a few new subcommands, and description of overlay
%             planes setup with Bits++ in Mono++ mode. (MK).
%
% 18.5.2008   A few bug fixes and support for 'DisplayColorCorrection' setup
%             code: Now a central solution that will work for all current and
%             future output devices (hopefully). (MK).
%
% 02.7.2009   Add CLUT configuration support for ICM color correction (MK).
%
% 18.12.2009  Add support for VPixx Technologies DataPixx device for video
%             processing modes L48, M16, C48 and color overlays in M16
%             mode, mostly via calls into the new PsychDataPixx() driver.
%             Also support a new 'General' task 'UseDataPixx'. (MK)
% 04.03.2010  Bugfixes and workarounds to 'ColorCorrection' setup code. (MK)
%
% 26.04.2010  Disable workarounds from 04.03.2010, as Screen() is fixed now. (MK)
%
% 02.09.2010  Add support for 'InterleavedColumnStereo'- for auto-stereoscopic
%             displays, e.g., parallax barrier and lenticular sheets. (MK)
%
% 03.04.2011  Add support for 'EnableCLUTMapping' for old fashioned clut animation. (MK)
%
% 26.12.2011  Add support for ptb_geometry_inverseWarpMap inverse mapping
%             of geometry corrected displays. See RemapMouse.m. (MK)
%
% 11.09.2012  Add support for stereo sync line handling, and for
%             scanning backlight control of the ViewPixx in stereomode. (MK)
%
% 23.12.2012  Add support for 'SideBySideCompressedStereo' stereo mode. (MK)
%
% 23.12.2012  Add support for 'UsePanelFitter' Screen panelfitter setup. (MK)
%
% 15.04.2013  Add support for 'UseGPGPUCompute', currently via GPUmat. (MK)
%
% 03.07.2013  Call PsychJavaSwingCleanup via onscreen window close hook. (MK)
%
% 28.09.2013  Add support for 'UseDisplayRotation' via panelfitter. (MK)
%
% 06.03.2014  Add support for 'DualWindowStereo' and fixes to Native10BitFramebuffer mode. (MK)
%
% 26.06.2014  Add support for Native11BitFramebuffer mode, update our docs with what
%                     we learned about this 10/11 bpc business on HDMI so far. (MK)
% 16.09.2014  Add experimental 'StereoCrosstalkReduction' support. (MK/DCN)
% 17.09.2014  Add 'Native16BitFramebuffer' support for Linux + FOSS + AMD. (MK)
% 03.11.2014  Make panelfitter compatible with Retina displays. (MK)
% 04.11.2014  Add new task 'UseRetinaResolution' for Retina displays. (MK)
% 06.09.2015  Add basic support for "Client distortion rendering" on the Oculus VR
%             Rift DK1/DK2 virtual reality headsets. (MK)

persistent configphase_active;
persistent reqs;

% This global variable signals if a GPGPU compute api is enabled, and which
% one. 0 = None, 1 = GPUmat.
global psych_gpgpuapi;

% These flags are global - needed in subfunctions as well (ugly ugly coding):
global ptb_outputformatter_icmAware;
global isASideBySideConfig;
global screenRestoreCmd;
global maxreqarg;

if isempty(configphase_active)
    configphase_active = 0;
    ptb_outputformatter_icmAware = 0;
    maxreqarg = 10;
end

if nargin < 1 || isempty(cmd)
    error('You did not provide any subcommand to execute!');
end

rc = [];
winRect = [];

if strcmpi(cmd, 'PrepareConfiguration')
    % Prepare new configuration:
    if configphase_active
        % Huh? Configuration was already in progress. Warn user about reset of task specs:
        fprintf('Tried to prepare a new configuration phase via PsychImaging(''PrepareConfiguration''), but did not finalize the previous phase yet.\n');
        fprintf('You must call the PsychImaging(''OpenWindow'', ...); command at least once to open an onscreen\n');
        fprintf('window according to the provided settings, before you can specify settings for additional onscreen windows.\n');
        fprintf('\n');
        fprintf('The most likely reason you see this error message is because your script aborted with some error\n');
        fprintf('before it managed to open the onscreen window. In that case it is best practice to execute a ''clear all''\n');
        fprintf('command at the Matlab/Octave prompt before you restart your script.\n');
        fprintf('\n');
        fprintf('I will restart configuration now and forget the previously made PsychImaging(''AddTask'', ...); settings.\n');
        warning('Tried to prepare a new configuration phase, but you did not finalize the previous phase yet!');
    end

    % Enter configuration mode, accept 'AddTask' specifications:
    configphase_active = 1;

    % Reset old settings:

    % MK: This clear reqs causes malfunctions on Octave 3.2.0 for some reason, so don't use it! clear reqs;
    reqs = [];
    ptb_outputformatter_icmAware = 0;

    % Set GPGPU api type indicator to zero "none in use" default:
    if isempty(psych_gpgpuapi)
        psych_gpgpuapi = 0;
    end

    % Assign default success return code rc:
    rc = 0;

    return;
end

if strcmpi(cmd, 'AddTask')
    if nargin < 3 || isempty(varargin{1}) || isempty(varargin{2})
        error('Parameters missing: Need at least "whichChannel" and "whichTask"!');
    end

    if configphase_active ~= 1
        error('Call PsychImaging(''PrepareConfiguration''); first to prepare the configuration phase!');
    end

    reqs = AddTask(reqs, varargin{1:end});
    rc = 0;

    return;
end

if strcmpi(cmd, 'FinalizeConfiguration')
    if configphase_active ~= 1
        error('You tried to finalize configuration, but no configuration in progress!');
    end

    if isempty(reqs)
        error('You tried to FinalizeConfiguration, but you did not specify any requirements or tasks!');
    end

    configphase_active = 2;

    % Compute correct imagingMode - Settings for current configuration and
    % return it:
    [imagingMode, needStereoMode, reqs] = FinalizeConfiguration(reqs);
    rc = imagingMode;
    winRect = needStereoMode;

    return;
end

if strcmpi(cmd, 'PostConfiguration')
    if configphase_active ~= 2
        error('Tried to call PostConfiguration without calling FinalizeConfiguration before!');
    end

    if nargin < 2 || isempty(varargin{1}) || Screen('WindowKind', varargin{1})~=1
        error('No "windowPtr" or invalid "windowPtr" or non-onscreen window handle provided!');
    end

    if nargin < 3
        clearcolor = 0;
    else
        clearcolor = varargin{2};
    end

    rc = PostConfiguration(reqs, varargin{1}, clearcolor);

    configphase_active = 0;
    return;
end

if strcmpi(cmd, 'OpenWindow')

    % Allow 'OpenWindow' without task specs. Simply open with empty task requirements list:
    if ismember(configphase_active, [0, 2])
        PsychImaging('PrepareConfiguration');
    end

    if configphase_active ~= 1
        error('You tried to OpenWindow, but didn''t specify any imaging configuration!');
    end

    if nargin < 2
        error('You must supply at least a "screenId" for the screen on which the window should be opened');
    end

    % Final config phase:
    configphase_active = 2; %#ok<NASGU>

    screenid = varargin{1};
    if ~ismember(screenid, Screen('Screens'))
        error('Invalid screenId provided in ''OpenWindow'' - no such screen %i available.', screenid);
    end

    if nargin < 3 || isempty(varargin{2})
        clearcolor = [];
    else
        clearcolor = varargin{2};
    end

    if nargin < 4 || isempty(varargin{3})
        winRect = [];
    else
        winRect = varargin{3};
    end

    % Set override special flags to "none" by default:
    ovrSpecialFlags = [];

    % Set override framebuffer rect to "none" by default:
    ovrfbOverrideRect = [];

    % Running on a VR headset?
    if ~isempty(find(mystrcmp(reqs, 'UseVRHMD')));
        % Yes:
        floc = find(mystrcmp(reqs, 'UseVRHMD'));
        [rows cols] = ind2sub(size(reqs), floc(1));
        row = rows(1);

        % Extract first parameter - This should be the handle of the HMD device:
        hmd = reqs{row, 3};

        % Verify it is already open:
        if ~hmd.driver('IsOpen', hmd)
            error('PsychImaging(''OpenWindow''): Invalid HMD handle specified for UseVRHMD task. No such device opened.');
        end

        % Compute special OpenWindow overrides for winRect, framebuffer rect, and specialflags, as needed:
        [winRect, ovrfbOverrideRect, ovrSpecialFlags] = hmd.driver('OpenWindowSetup', hmd, screenid, winRect, ovrfbOverrideRect, ovrSpecialFlags);
    end

    % Override numbuffers -- always 2:
    numbuffers = 2;

    if nargin < 7 || isempty(varargin{6})
        stereomode = 0;
    else
        stereomode = varargin{6};
    end

    % Compute correct imagingMode - Settings for current configuration and return it:
    [imagingMode, needStereoMode, reqs] = FinalizeConfiguration(reqs, stereomode, screenid);

    if ~isempty(find(mystrcmp(reqs, 'EnableNative10BitFramebuffer')))
        % Request a pixelsize of 30 bpp to enable native 2101010
        % framebuffer support:
        pixelSize = 30;
    elseif ~isempty(find(mystrcmp(reqs, 'EnableNative11BitFramebuffer')))
        % Request a pixelsize of 33 bpp to enable native RGB11-11-10
        % framebuffer support. A value of 32 bpp would be appropriate but
        % that's already taken by old cruft code, so it's a no-no and we use the
        % weirdo 33 bpp value, to retain backwards compatibility.
        pixelSize = 33;
    elseif ~isempty(find(mystrcmp(reqs, 'EnableNative16BitFramebuffer')))
        % Request a pixelsize of 48 bpp to enable native up to RGB16-16-16
        % framebuffer support.
        pixelSize = 48;
    elseif ~isempty(find(mystrcmp(reqs, 'EnableNative16BitFloatingPointFramebuffer')))
        % Request a pixelsize of 64 bpp to enable native RGBA16F floating point
        % framebuffer support.
        pixelSize = 64;
    else
        % Ignore pixelSize:
        pixelSize = [];
    end

    floc = find(mystrcmp(reqs, 'UseVulkanDisplay'));
    if ~isempty(floc) && isempty(find(mystrcmp(reqs, 'UseStaticHDRHack')))
        [rows cols] = ind2sub(size(reqs), floc(1));
        row = rows(1);

        % Extract first parameter - This would be the optional video output name:
        outputName = reqs{row, 3};

        % Compute special OpenWindow overrides for winRect, framebuffer rect, and specialflags, as needed:
        [winRect, ovrfbOverrideRect, ovrSpecialFlags, outputName] = PsychVulkan('OpenWindowSetup', outputName, screenid, winRect, ovrfbOverrideRect, ovrSpecialFlags);

        % Reassign first parameter. Anything non-empty means to use fullscreen/
        % direct display mode:
        reqs{row, 3} = outputName;

        % Reset pixelSize to default 8 bpc, as we are handling potential deep color
        % in the Vulkan backend, not in the OpenGL WSI for the Screen onscreen window:
        pixelSize = [];
    end

    % Override stereomode derived from requirements?
    if needStereoMode ~= -1
        if needStereoMode == -2 && stereomode == 0
            % Stereo operation needed, but not set up by usercode:
            error('Your requirements demand a stereo presentation mode, but you didn''t specify one!');
        else
            if (needStereoMode > -1) && (stereomode ~= needStereoMode)
                % Need a specific mode: Override current setting by our needs:
                stereomode = needStereoMode;

                % Give feedback about stereomode override. If the user
                % didn't provide a stereomode, we just output an info.
                % Otherwise we output a warning about the conflict and our
                % override...
                if nargin < 7 || isempty(varargin{6})
                    fprintf('PsychImaging-Info: Stereomode %i required - Enabling it.\n', stereomode);
                else
                    warning('Your provided "stereomode" conflicts with required stereomode for imaging pipeline. Overriden...');
                end
            end
        end
    end

    if nargin < 8 || isempty(varargin{7})
        multiSample = 0;
    else
        multiSample = varargin{7};
    end

    if nargin < 9 || isempty(varargin{8})
        imagingovm = 0;
    else
        imagingovm = varargin{8};
    end

    imagingMode = mor(imagingMode, imagingovm);

    if nargin < 10 || isempty(varargin{9})
        specialFlags = ovrSpecialFlags;
    else
        specialFlags = varargin{9};
    end

    if nargin < 11 || isempty(varargin{10})
        clientRect = [];
    else
        clientRect = varargin{10};
    end

    if nargin < 12 || isempty(varargin{11})
        fbOverrideRect = [];
    else
        fbOverrideRect = varargin{11};
    end

    % Allow override of the fbOverrideRect from special clients like VR apps:
    if ~isempty(ovrfbOverrideRect)
        fbOverrideRect = ovrfbOverrideRect;
    end

    % Define fitRefRect (used for panel fitter setup) as the effective framebuffer rectangle:
    fitRefRect = winRect;
    if ~isempty(fbOverrideRect)
        fitRefRect = fbOverrideRect;
    end

    % VRR handling:
    ovrvrrParams = [];
    if ~isempty(find(mystrcmp(reqs, 'UseFineGrainedTiming'))) %#ok<*EFIND>
        % Yes. Extract parameters:
        floc = find(mystrcmp(reqs, 'UseFineGrainedTiming'));
        if length(floc) > 1
            error('PsychImaging: Multiple definitions of task "UseFineGrainedTiming"! There can be only one.');
        end

        [row cols] = ind2sub(size(reqs), floc); %#ok<NASGU>
        vrrMode = reqs{row, 3};

        if isempty(vrrMode) || strcmpi(vrrMode, 'Auto')
            vrrMode = 1;
        elseif strcmpi(vrrMode, 'Simple')
            vrrMode = 2;
        elseif strcmpi(vrrMode, 'OwnScheduled')
            vrrMode = 3;
        else
            error('PsychImaging: For task "UseFineGrainedTiming" invalid method argument specified. Must be ''Auto'', ''Simple'', or ''OwnScheduled''.');
        end

        vrrStyleHint = reqs{row, 4};

        if isempty(vrrStyleHint) || strcmpi(vrrStyleHint, 'None')
            vrrStyleHint = 0;
        elseif strcmpi(vrrStyleHint, 'XXX')
            vrrStyleHint = 1;
        else
            error('PsychImaging: For task "UseFineGrainedTiming" invalid styleHint argument specified. Must be ''None''.');
        end

        vrrMinRefreshHz = reqs{row, 5};
        if isempty(vrrMinRefreshHz)
            vrrMinRefreshHz = 0;
        elseif ~isnumeric(vrrMinRefreshHz) || ~isscalar(vrrMinRefreshHz) || vrrMinRefreshHz < 0
            error('PsychImaging: For task "UseFineGrainedTiming" invalid "vrrMinRefreshHz" argument specified.');
        else
            % Map to maximum frame duration:
            if vrrMinRefreshHz > 0
              vrrMinRefreshHz = 1 / vrrMinRefreshHz;
            else
              vrrMinRefreshHz = 0;
            end
        end

        % Build ovrvrrParams:
        ovrvrrParams = [vrrMode, vrrStyleHint, 0, vrrMinRefreshHz];
    end

    if nargin < 13 || isempty(varargin{12})
        vrrParams = ovrvrrParams;
    else
        vrrParams = varargin{12};
    end

    if ~isempty(find(mystrcmp(reqs, 'UseDisplayRotation'))) %#ok<*EFIND>
        % Yes. Extract parameters:
        floc = find(mystrcmp(reqs, 'UseDisplayRotation'));
        if length(floc) > 1
            error('PsychImaging: Multiple definitions of task "UseDisplayRotation"! There can be only one.');
        end

        % Check for collisions with mutually exclusive "UsePanelFitter" task:
        if ~isempty(find(mystrcmp(reqs, 'UsePanelFitter')))
            fprintf('\n\n');
            fprintf('PsychImaging: You can not use both "UseDisplayRotation" and "UsePanelFitter" at the same time. However, you can pass\n');
            fprintf('PsychImaging: the rotation angle you wanted to use for "UseDisplayRotation" to "UsePanelFitter" instead, so "UsePanelFitter"\n');
            fprintf('PsychImaging: will also do the job of "UseDisplayRotation" for you. This works because "UseDisplayRotation" is only\n');
            fprintf('PsychImaging: a simple convenience shortcut to "UsePanelFitter".\n');
            error('PsychImaging: Task "UsePanelFitter" also requested, but you can only use either "UsePanelFitter" or "UseDisplayRotation".');
        end

        [row cols] = ind2sub(size(reqs), floc); %#ok<NASGU>
        rotAngle = reqs{row, 3};

        if isempty(rotAngle) || ~isnumeric(rotAngle) || ~isscalar(rotAngle)
            error('PsychImaging: For task "UseDisplayRotation", required rotation angle parameter missing or not a scalar angle in degrees.');
        end

        % Get full size of output framebuffer:
        if isempty(fitRefRect)
            [clientRes(1), clientRes(2)] = Screen('WindowSize', screenid, 1);
        else
            clientRes = [RectWidth(fitRefRect), RectHeight(fitRefRect)];
        end

        % Rotation into a portrait orientation?
        if (round(rotAngle / 90) == (rotAngle / 90))
            if (mod(round(rotAngle / 90), 2) > 0)
                % Yes. Switch width and height of clientRes:
                clientRes = [clientRes(2), clientRes(1)];
            end
        else
            fprintf('PsychImaging: Provided rotation angle for task "UseDisplayRotation" is not a multiple of 90 degrees.\n');
            fprintf('PsychImaging: You are probably in for a bit of trouble for such rotation angles...\n');
        end

        % No-Op for rotation angle of 0 degrees, as that does nothing.
        if rotAngle ~= 0
            % Build a 'UsePanelFitter' task from our tasks parameters by
            % overwriting our own task spec:
            reqs{row, 2} = 'UsePanelFitter';
            reqs{row, 3} = clientRes;
            reqs{row, 4} = 'Full';
            reqs{row, 5} = [];
            reqs{row, 6} = [];
            reqs{row, 7} = rotAngle;
        end
    end

    % Use and high-level setup of panelfitter requested?
    if ~isempty(find(mystrcmp(reqs, 'UsePanelFitter'))) %#ok<*EFIND>
        % Yes. Extract parameters:
        floc = find(mystrcmp(reqs, 'UsePanelFitter'));
        if length(floc) > 1
            error('PsychImaging: Multiple definitions of task "UsePanelFitter"! There can be only one.');
        end

        [row cols] = ind2sub(size(reqs), floc); %#ok<NASGU>

        % Extract requested resolution of virtual framebuffer...
        clientRes = reqs{row, 3};
        if length(clientRes) ~= 2 || ~isnumeric(clientRes) || min(clientRes) < 1
            error('PsychImaging: Mandatory "size" parameter of task "UsePanelFitter" is missing or not a two component [width, height] size vector with positive width and height as expected.');
        end

        clientRes = round(clientRes);

        if ~isempty(clientRect)
            fprintf('PsychImaging: OpenWindow: Warning: User provided "clientRect" overriden by specification in PsychImaging task "UsePanelFitter".');
        end

        % ... and define clientRect accordingly:
        clientRect = [0, 0, clientRes(1), clientRes(2)];

        % Extract scaling strategy:
        fitterStrategy = reqs{row, 4};
        if isempty(fitterStrategy) || ~ischar(fitterStrategy)
            error('PsychImaging: Mandatory parameter "strategy" of task "UsePanelFitter" missing or not a string.');
        end

        % Define full size of output framebuffer:
        if isempty(fitRefRect)
            dstFit = Screen('Rect', screenid, 1);
        else
            dstFit = SetRect(0, 0, RectWidth(fitRefRect), RectHeight(fitRefRect));
        end

        % Adapt dstFit according to window size flags:

        % Apply half-height flag, if any:
        if bitand(imagingMode, kPsychNeedHalfHeightWindow)
            dstFit(RectBottom) = dstFit(RectBottom) / 2;
        end

        % Apply half-width flag, if any:
        if bitand(imagingMode, kPsychNeedHalfWidthWindow) || ismember(stereomode, [4, 5])
            dstFit(RectRight) = dstFit(RectRight) / 2;
        end

        % Apply twice-width flag, if any:
        if bitand(imagingMode, kPsychNeedTwiceWidthWindow)
            dstFit(RectRight) = dstFit(RectRight) * 2;
        end

        % Apply triple-width flag, if any:
        if bitand(imagingMode, kPsychNeedTripleWidthWindow)
            dstFit(RectRight) = dstFit(RectRight) * 3;
        end

        winCenter = [RectWidth(dstFit)/2, RectHeight(dstFit)/2];

        % Extract rotation angle to use for display rotation:
        rotX = [];
        rotY = [];
        rotAngle = reqs{row, 7};
        rot90Deg = 0;

        if isempty(rotAngle)
            % No rotation angle == zero rotation == no rotation.
            rotAngle = 0;
        else
            % Round to full degrees:
            rotAngle = round(rotAngle);

            if rotAngle ~= 0
                fprintf('PsychImaging: PanelFitter will apply a display rotation of %i degrees.\n', rotAngle);

                % Check if rotation angle is -90, +90, -270, +270, ... degrees,
                % ie. the image is effectively tilted by 90 degrees clockwise
                % or counter-clockwise:
                if (round(rotAngle / 90) == (rotAngle / 90)) && (mod(round(rotAngle / 90), 2) > 0)
                    % Yes. This is classic panel rotation. Exchange width and
                    % height of clientRect, so it is "rotated" accordingly and
                    % the various scaling and centering strategies will
                    % peacefully cooperate with display rotation via panel
                    % fitting:
                    rot90Deg = 1;
                    clientRect = [0, 0, clientRes(2), clientRes(1)];
                    fprintf('PsychImaging: Applying special setup for display rotation by 90 degrees into portrait orientation.\n');
                end
            end
        end

        % Which strategy to use?
        if strcmpi(fitterStrategy, 'Custom')
            % Custom scaling with provided srcRect and dstRect:
            srcFit = reqs{row, 5};
            dstFit = reqs{row, 6};
            if ~isnumeric(srcFit) || length(srcFit) ~= 4
                error('PsychImaging: Mandatory parameter "srcRect" of task "UsePanelFitter" for fitting strategy "Custom" missing or not a 4 element rect.');
            end

            if ~isnumeric(dstFit) || length(dstFit) ~= 4
                error('PsychImaging: Mandatory parameter "dstRect" of task "UsePanelFitter" for fitting strategy "Custom" missing or not a 4 element rect.');
            end
        elseif strcmpi(fitterStrategy, 'Centered')
            % Don't rescale but blit one-to-one. Center in target
            % framebuffer, crop if neccessary:

            % Try to center clientRect in destination framebuffer rect:
            srcFit = CenterRect(clientRect, dstFit);

            % Does it fully fit in?
            if any(srcFit < 0)
                % No. We need to crop/clip it to fit in:
                dstFit = ClipRect(srcFit, dstFit);
                srcFit = CenterRect(dstFit, clientRect);
                fprintf('PsychImaging: For centered fitting, i needed to crop the source framebuffer to central region [%i,%i,%i,%i]. Borders will be missing.\n', srcFit(1), srcFit(2), srcFit(3), srcFit(4));
            else
                % Yes: Center in destination framebuffer:
                dstFit = srcFit;
                srcFit = clientRect;
            end
        elseif strcmpi(fitterStrategy, 'Full')
            % Rescale source framebuffer to full target framebuffer, not
            % taking aspect ratio into account:
            srcFit = clientRect;

            if RectWidth(srcFit) / RectHeight(srcFit) ~= RectWidth(dstFit) / RectHeight(dstFit)
                fprintf('PsychImaging: Using full resolution fitting strategy. Scaling will not preserve aspect ratio of original stimulus!\n');
            else
                fprintf('PsychImaging: Using full resolution fitting strategy. Aspect ratio is preserved.\n');
            end
        elseif strcmpi(fitterStrategy, 'AspectWidth') || strcmpi(fitterStrategy, 'AspectHeight') || strcmpi(fitterStrategy, 'Aspect')
            % Rescale aspect ratio preserving:

            if strcmpi(fitterStrategy, 'AspectWidth')
                % Cover full width of window, maybe crop top and bottom:
                sf = RectWidth(dstFit) / RectWidth(clientRect);
                fprintf('PsychImaging: Using scaling to full width. Aspect ratio is preserved, top and bottom may be cut away.\n');
            end

            if strcmpi(fitterStrategy, 'AspectHeight')
                % Cover full width of window, maybe crop top and bottom:
                sf = RectHeight(dstFit) / RectHeight(clientRect);
                fprintf('PsychImaging: Using scaling to full height. Aspect ratio is preserved, left and right margins may be cut away.\n');
            end

            if strcmpi(fitterStrategy, 'Aspect')
                % Cover as much as possible, aspect ratio preserving, leaving
                % borders as neccessary:
                sfw = RectWidth(dstFit) / RectWidth(clientRect);
                sfh = RectHeight(dstFit) / RectHeight(clientRect);
                sf = min(sfw, sfh);
                fprintf('PsychImaging: Using scaling to the most maximal size which still preserves aspect ratio. There may be borders.\n');
            end

            % Compute scaled size target rectangle:
            scaleFit = ScaleRect(clientRect, sf, sf);

            % Center it in destination framebuffer dstFit:
            scaleFit = CenterRect(scaleFit, dstFit);

            % Clip it against dstFit's size, crop away borders if neccessary:
            % dstFit now contains the destination retangle in the window:
            dstFit = ClipRect(scaleFit, dstFit);

            % Compute originating source rectangle of original size for
            % 'dstFit' by undoing the scaling:
            scaleFit = SetRect(0, 0, RectWidth(dstFit)/sf, RectHeight(dstFit)/sf);

            % Center properly sized source rectangle in clientRect source
            % framebuffer to compute final srcRect for scaling blit:
            srcFit = CenterRect(scaleFit, clientRect);
        else
            error('PsychImaging: Mandatory parameter "strategy" of task "UsePanelFitter" has invalid setting ''%s''.', fitterStrategy);
        end

        if rotAngle ~= 0
            [rotX, rotY] = RectCenter(clientRect);
        end

        if rot90Deg
            % Offset compensation for multiple of 90 degrees rotations:
            degrad = 2 * pi * rotAngle / 360;
            rotOffset(1) = -(winCenter(2) - rotX) * sin(degrad);
            rotOffset(2) =  (winCenter(1) - rotY) * sin(degrad);
            dstFit = OffsetRect(dstFit, rotOffset(1), rotOffset(2));
        end

        % Build final fitterParams vector:
        fitterParams = [srcFit dstFit rotAngle rotX rotY];

        % Restore clientRect to original one:
        clientRect = [0, 0, clientRes(1), clientRes(2)];
    else
        % No panel fitter in use. Or at least, none we would set up:
        fitterParams = [];
    end

    % Custom color correction for display wanted on a Bits+ display in
    % Mono++ or Color++ mode or a DataPixx?
    if ~isempty(find(mystrcmp(reqs, 'DisplayColorCorrection')))
        if ~isempty(find(mystrcmp(reqs, 'EnableBits++Mono++Output'))) || ~isempty(find(mystrcmp(reqs, 'EnableBits++Mono++OutputWithOverlay'))) || ~isempty(find(mystrcmp(reqs, 'EnableBits++Color++Output')))
            % Yes. The BitsPlusPlus() setup routine implements its own
            % setup code for display color correction which is very
            % efficient for a single color correction plugin, but not
            % useable with multiple plugins! Need to handle both
            % cases specially.

            % More than one color correction plugin requested for pipeline?
            floc = find(mystrcmp(reqs, 'DisplayColorCorrection'));
            if length(floc) == 1
                % Single plugin. Use BitsPlusPlus internal setup code,
                % just provide proper method setting for it now:

                % Which channel?
                x=floc;
                [rows cols] = ind2sub(size(reqs), x); %#ok<NASGU>
                for row=rows'
                    % Extract first parameter - This should be the method of correction:
                    colorcorrectionmethod = reqs{row, 3};

                    if isempty(colorcorrectionmethod) || ~ischar(colorcorrectionmethod)
                        sca;
                        error('PsychImaging: Name of color correction method for ''DisplayColorCorrection'' missing or not of string type!');
                    end

                    % Select method:
                    PsychColorCorrection('ChooseColorCorrection', colorcorrectionmethod);
                end
            else
                % Multiple plugins: Select special method which won't be
                % harmful, a simple clamping to valid range, labeled with a
                % special name that can't clash with our own definition of
                % ICM shaders:
                PsychColorCorrection('ChooseColorCorrection', 'ClampedNoName');
            end
        end
    end

    % Open onscreen window with proper imagingMode and stereomode set up.
    % We have a couple of special cases here for VPixx devices and the CRS Bits++/Bits#...
    win = [];

    if ~isempty(find(mystrcmp(reqs, 'EnableBits++Bits++Output')))
        % Special case: Need to open Bits++ Bits++ driver. We delegate the
        % openwindow procedure to the BitsPlusPlus.m file:
        if ~isempty(win)
            error('You specified multiple conflicting output display device drivers! This will not work.');
        end

        if nargin >= 13
            [win, winRect] = BitsPlusPlus('OpenWindowBits++', screenid, clearcolor, winRect, pixelSize, numbuffers, stereomode, multiSample, imagingMode, specialFlags, clientRect, fbOverrideRect, vrrParams, varargin{13:end});
        else
            [win, winRect] = BitsPlusPlus('OpenWindowBits++', screenid, clearcolor, winRect, pixelSize, numbuffers, stereomode, multiSample, imagingMode, specialFlags, clientRect, fbOverrideRect, vrrParams);
        end
    end

    if ~isempty(find(mystrcmp(reqs, 'EnableBits++Mono++Output'))) || ~isempty(find(mystrcmp(reqs, 'EnableBits++Mono++OutputWithOverlay')))
        % Special case: Need to open Bits++ Mono++ driver. We delegate the
        % openwindow procedure to the BitsPlusPlus.m file:
        if ~isempty(win)
            error('You specified multiple conflicting output display device drivers! This will not work.');
        end

        if ~isempty(find(mystrcmp(reqs, 'EnableBits++Mono++OutputWithOverlay')))
            bpcom = 'OpenWindowMono++WithOverlay';
        else
            bpcom = 'OpenWindowMono++';
        end

        if nargin >= 13
            [win, winRect] = BitsPlusPlus(bpcom, screenid, clearcolor, winRect, pixelSize, numbuffers, stereomode, multiSample, imagingMode, specialFlags, clientRect, fbOverrideRect, vrrParams, varargin{13:end});
        else
            [win, winRect] = BitsPlusPlus(bpcom, screenid, clearcolor, winRect, pixelSize, numbuffers, stereomode, multiSample, imagingMode, specialFlags, clientRect, fbOverrideRect, vrrParams);
        end
    end

    if ~isempty(find(mystrcmp(reqs, 'EnableBits++Color++Output')))
        % Special case: Need to open Bits++ Color++ driver. We delegate the
        % openwindow procedure to the BitsPlusPlus.m file:
        if ~isempty(win)
            error('You specified multiple conflicting output display device drivers! This will not work.');
        end

        if nargin >= 13
            [win, winRect] = BitsPlusPlus('OpenWindowColor++', screenid, clearcolor, winRect, pixelSize, numbuffers, stereomode, multiSample, imagingMode, specialFlags, clientRect, fbOverrideRect, vrrParams, varargin{13:end});
        else
            [win, winRect] = BitsPlusPlus('OpenWindowColor++', screenid, clearcolor, winRect, pixelSize, numbuffers, stereomode, multiSample, imagingMode, specialFlags, clientRect, fbOverrideRect, vrrParams);
        end
    end

    if isempty(win)
        % Standard openwindow path:
        if nargin >= 13
            [win, winRect] = Screen('OpenWindow', screenid, clearcolor, winRect, pixelSize, numbuffers, stereomode, multiSample, imagingMode, specialFlags, clientRect, fbOverrideRect, vrrParams, varargin{13:end});
        else
            [win, winRect] = Screen('OpenWindow', screenid, clearcolor, winRect, pixelSize, numbuffers, stereomode, multiSample, imagingMode, specialFlags, clientRect, fbOverrideRect, vrrParams);
        end
    end

    % No secondary slave window by default:
    slavewin = [];

    % Display mirroring requested?
    if ~isempty(find(mystrcmp(reqs, 'MirrorDisplayTo2ndOutputHead')))
        % Yes. Need to open secondary slave window:
        floc = find(mystrcmp(reqs, 'MirrorDisplayTo2ndOutputHead'));
        [rows cols]= ind2sub(size(reqs), floc);

        % Extract first parameter - This should be the id of the slave
        % screen to which the display should get mirrored:
        slavescreenid = reqs{rows, 3};

        if isempty(slavescreenid)
            sca;
            error('In PsychImaging MirrorDisplayTo2ndOutputHead: You must provide the index of the secondary screen "slavescreen"!');
        end

        if ~any(ismember(Screen('Screens'), slavescreenid))
            sca;
            error('In PsychImaging MirrorDisplayTo2ndOutputHead: You must provide the index of a valid secondary screen "slavescreen"!');
        end

        if stereomode == 10
            fprintf('PsychImaging: WARNING! You simultaneously requested display mirroring to 2nd output head and dual display stereomode 10.\n');
            fprintf('PsychImaging: WARNING! These are mutually exclusive! Will choose stereomode 10 instead of mirroring.\n');
        end

        if stereomode == 1
            sca;
            error('In PsychImaging MirrorDisplayTo2ndOutputHead: Tried to simultaneously enable frame-sequential stereomode 1! This is not supported.');
        end

        % Extract optional 2nd parameter - The window rectangle of the slave
        % window on the slave screen to which the display should get mirrored:
        slavewinrect = reqs{rows, 4};
        if isempty(slavewinrect), slavewinrect = []; end

        % Open slave window on slave screen: Set the special dual window
        % output flag, so Screen('OpenWindow') initializes the internal blit
        % chain properly:
        slavewin = Screen('OpenWindow', slavescreenid, [255 0 0], slavewinrect, pixelSize, [], [], [], kPsychNeedDualWindowOutput);
    end

    % Dualwindow output requested? [Essentially the same as display
    % mirroring, but kept separate for now for simplicity]
    if ~isempty(find(mystrcmp(reqs, 'EnableDualPipeHDROutput')))
        % Yes. Need to open secondary slave window:
        floc = find(mystrcmp(reqs, 'EnableDualPipeHDROutput'));
        [rows cols]= ind2sub(size(reqs), floc);

        % Extract first parameter - This should be the id of the slave
        % screen to which the pipe 1 display should get displayed:
        slavescreenid = reqs{rows, 3};

        if isempty(slavescreenid)
            sca;
            error('In PsychImaging EnableDualPipeHDROutput: You must provide the index of the secondary screen "slavescreen"!');
        end

        if ~any(ismember(Screen('Screens'), slavescreenid))
            sca;
            error('In PsychImaging EnableDualPipeHDROutput: You must provide the index of a valid secondary screen "slavescreen"!');
        end

        if stereomode == 1
            sca;
            error('In PsychImaging EnableDualPipeHDROutput: Tried to simultaneously enable frame-sequential stereomode 1! This is not supported.');
        end

        if stereomode == 10
            sca;
            error('In PsychImaging EnableDualPipeHDROutput: Tried to simultaneously enable dual display output stereomode 10! This is not supported.');
        end

        % Extract optional 2nd parameter - The window rectangle of the slave
        % window on the slave screen to which the pipe 1 display should get outputted:
        slavewinrect = reqs{rows, 4};
        if isempty(slavewinrect), slavewinrect = []; end

        % Open slave window on slave screen: Set the special dual window
        % output flag, so Screen('OpenWindow') initializes the internal blit
        % chain properly:
        slavewin = Screen('OpenWindow', slavescreenid, [255 0 0], slavewinrect, pixelSize, [], [], [], kPsychNeedDualWindowOutput);
    end

    % DualWindow stereo output requested?
    if ~isempty(find(mystrcmp(reqs, 'DualWindowStereo')))
        % Yes. Need to open secondary slave window:
        floc = find(mystrcmp(reqs, 'DualWindowStereo'));
        [rows cols]= ind2sub(size(reqs), floc);

        % Extract first parameter - This should be the id of the slave
        % screen to which the right eye display should get displayed:
        slavescreenid = reqs{rows, 3};

        if isempty(slavescreenid)
            sca;
            error('In PsychImaging DualWindowStereo: You must provide the index of the secondary screen "slavescreen"!');
        end

        if ~any(ismember(Screen('Screens'), slavescreenid))
            sca;
            error('In PsychImaging DualWindowStereo: You must provide the index of a valid secondary screen "slavescreen"!');
        end

        % Extract optional 2nd parameter - The window rectangle of the slave
        % window on the slave screen:
        slavewinrect = reqs{rows, 4};
        if isempty(slavewinrect), slavewinrect = []; end

        % Open slave window on slave screen:
        slavewin = Screen('OpenWindow', slavescreenid, [], slavewinrect, pixelSize, [], 10);
    end

    % Matlab? Does the Java swing cleanup function exist?
    if exist('PsychJavaSwingCleanup', 'file')
        % Attach a window close callback for cleanup of Java's memory
        % management mess at window close time when Matlab with Java based
        % GUI is in use:
        Screen('Hookfunction', win, 'AppendMFunction', 'CloseOnscreenWindowPostGLShutdown', 'Shutdown window callback into PsychJavaSwingCleanup().', 'PsychJavaSwingCleanup;');
        Screen('HookFunction', win, 'Enable', 'CloseOnscreenWindowPostGLShutdown');

        % Some slave window opened?
        if ~isempty(slavewin)
            % Yes: Apply java cleanup there as well:
            Screen('Hookfunction', slavewin, 'AppendMFunction', 'CloseOnscreenWindowPostGLShutdown', 'Shutdown window callback into PsychJavaSwingCleanup().', 'PsychJavaSwingCleanup;');
            Screen('HookFunction', slavewin, 'Enable', 'CloseOnscreenWindowPostGLShutdown');
        end
    end

    glerr = glGetError;
    while glerr
        fprintf('PsychImaging-WARNING:OpenWindow: OpenGL error detected after Screen(''OpenWindow''): %s', gluErrorString(glerr));
        glerr = glGetError;
    end

    % Window open. Perform imaging pipe postconfiguration:
    PostConfiguration(reqs, win, clearcolor, slavewin);

    glerr = glGetError;
    while glerr
        fprintf('PsychImaging-WARNING:OpenWindow: OpenGL error detected after PostConfiguration(): %s', gluErrorString(glerr));
        glerr = glGetError;
    end

    % Perform double-flip, so both back- and frontbuffer get initialized to
    % background color:
    Screen('Flip', win);
    Screen('Flip', win);

    % Panel fitter in use and setup by us?
    if ~isempty(fitterParams)
        % Yes: Apply fitter parameters now, so the scaling method takes
        % effect at next flip. We only do it now, so the preceeding
        % Screen('Flip') ops after imaging pipeline initialization were
        % able to operate with the default "cover full framebuffer" fitter
        % params, ie., they applied their implicit "clear to background
        % color" ops to the full framebuffer and thereby initialized all
        % stages of the pipeline down to the real window backbuffer with
        % background clear color. This way, regardless which panel fitting
        % strategy is chosen by user code, potential top-bottom or
        % left-right borders will get initialized to the selected
        % background clear color, which should be the most well defined
        % choice:
        Screen('PanelFitter', win, round(fitterParams));

        % Now that the fitter is fully configured, perform an extra
        % double-flip to apply proper scaling and borders and such:
        Screen('Flip', win);
        Screen('Flip', win);
    end

    % One extra Flip to put the full imaging pipeline into initial state:
    Screen('Flip', win);

    glerr = glGetError;
    while glerr
        fprintf('PsychImaging-WARNING:OpenWindow: OpenGL error detected after init Flip: %s', gluErrorString(glerr));
        glerr = glGetError;
    end

    rc = win;

    % Done.
    configphase_active = 0;

    return;
end

if strcmpi(cmd, 'RestrictProcessingToROI')
    % Define a ROI in a processing chain/channel to which processing should
    % be restricted by internal use of glScissor() command. This is a
    % runtime function. Each invocation will search the given channel if
    % such a command already exists, then delete it if so. It will prepend
    % the new command with the new spec in any case, so that at any point
    % in time exactly one such ROI can be active for a chain:

    if nargin < 4
        sca;
        error('You must provide all parameters for subfunction "RestrictProcessingToROI!"');
    end

    % Extract window handle:
    win = varargin{1};

    if ~isscalar(win) || ~isnumeric(win) || Screen('WindowKind', win) ~= 1
        sca;
        error('Provided window parameter for subfunction "RestrictProcessingToROI!" is not the handle of a valid onscreen window!');
    end

    % Extract window information:
    winfo = Screen('GetWindowInfo', win);

    % Extract view channel:
    whichView = varargin{2};

    % Extract scissor rectangle:
    scissorrect = varargin{3};

    if size(scissorrect,1)~=1 || size(scissorrect,2)~=4
        sca;
        error('Command "RestrictProcessingToROI" in channel %s expects a 1-by-4 ROI rectangle to define the ROI, e.g, [left top right bottom]!', whichView);
    end

    ox = scissorrect(RectLeft);

    [winwidth, winheight] = InterBufferSize(win);
    oy = winheight - scissorrect(RectBottom);

    w  = RectWidth(scissorrect);
    h  = RectHeight(scissorrect);

    if mystrcmp(whichView, 'LeftView') || mystrcmp(whichView, 'AllViews')
        % Need to restrict left view processing:
        DoRemoveScissorRestriction(win, 'StereoLeftCompositingBlit');
        Screen('HookFunction', win, 'PrependBuiltin', 'StereoLeftCompositingBlit', 'Builtin:RestrictToScissorROI', sprintf('%i:%i:%i:%i', ox, oy, w, h));
    end

    if mystrcmp(whichView, 'RightView') || mystrcmp(whichView, 'AllViews')
        % Need to restrict right view processing:
        DoRemoveScissorRestriction(win, 'StereoRightCompositingBlit');
        Screen('HookFunction', win, 'PrependBuiltin', 'StereoRightCompositingBlit', 'Builtin:RestrictToScissorROI', sprintf('%i:%i:%i:%i', ox, oy, w, h));
    end

    if (mystrcmp(whichView, 'AllViews') || mystrcmp(whichView, 'Compositor')) && ismember(winfo.StereoMode, [6,7,8,9])
        % Needed to restrict both views processing and a
        % compositing mode is active. If both views are restricted
        % in their output area then it makes sense to restrict the
        % compositor to the same area. We also restrict the
        % compositor if that was requested.
        oy = RectHeight(Screen('Rect', win, 1)) - scissorrect(RectBottom);
        DoRemoveScissorRestriction(win, 'StereoCompositingBlit');
        Screen('HookFunction', win, 'PrependBuiltin', 'StereoCompositingBlit', 'Builtin:RestrictToScissorROI', sprintf('%i:%i:%i:%i', ox, oy, w, h));
    end

    if mystrcmp(whichView, 'FinalFormatting')
        % Need to restrict final formatting blit processing:
        oy = RectHeight(Screen('Rect', win, 1)) - scissorrect(RectBottom);
        DoRemoveScissorRestriction(win, 'FinalOutputFormattingBlit');
        Screen('HookFunction', win, 'PrependBuiltin', 'FinalOutputFormattingBlit', 'Builtin:RestrictToScissorROI', sprintf('%i:%i:%i:%i', ox, oy, w, h));
    end

    return;
end

if strcmpi(cmd, 'UnrestrictProcessing')
    % Remove a ROI in a processing chain/channel to which processing should
    % be restricted by internal use of glScissor() command. This is a
    % runtime function. Each invocation will search the given channel if
    % such a command exists, then delete it if so.
    if nargin < 3
        sca;
        error('You must provide all parameters for subfunction "UnrestrictProcessing!"');
    end

    % Extract window handle:
    win = varargin{1};

    if ~isscalar(win) || ~isnumeric(win) || Screen('WindowKind', win) ~= 1
        sca;
        error('Provided window parameter for subfunction "UnrestrictProcessing!" is not the handle of a valid onscreen window!');
    end

    % Extract window information:
    winfo = Screen('GetWindowInfo', win);

    % Extract view channel:
    whichView = varargin{2};

    if mystrcmp(whichView, 'LeftView') || mystrcmp(whichView, 'AllViews')
        % Need to restrict left view processing:
        DoRemoveScissorRestriction(win, 'StereoLeftCompositingBlit');
    end

    if mystrcmp(whichView, 'RightView') || mystrcmp(whichView, 'AllViews')
        % Need to restrict right view processing:
        DoRemoveScissorRestriction(win, 'StereoRightCompositingBlit');
    end

    if (mystrcmp(whichView, 'AllViews') || mystrcmp(whichView, 'Compositor')) && ismember(winfo.StereoMode, [6,7,8,9])
        % Needed to restrict both views processing and a
        % compositing mode is active. If both views are restricted
        % in their output area then it makes sense to restrict the
        % compositor to the same area. We also restrict the
        % compositor if that was requested.
        DoRemoveScissorRestriction(win, 'StereoCompositingBlit');
    end

    if mystrcmp(whichView, 'FinalFormatting')
        % Need to restrict final formatting blit processing:
        DoRemoveScissorRestriction(win, 'FinalOutputFormattingBlit');
    end

    return;
end

if strcmpi(cmd, 'GetOverlayWindow')
    % Pass this call through to BitsPlusPlus.m driver -- the only one which
    % currently supports such overlays.
    % MK: May need to do something more clever in the future...

    % rc is the 'win'dowhandle, winRect is its Screen('Rect'):
    [rc, winRect] = BitsPlusPlus('GetOverlayWindow', varargin{:});

    return;
end

% Catch all for unknown commands:
error('Unknown subcommand specified! Read "help PsychImaging" for usage info.');
return; %#ok<UNRCH>

% Internal helper routines:

% FinalizeConfiguration consolidates the current set of requirements and
% derives the needed stereoMode settings and imagingMode setting to pass to
% Screen('OpenWindow') for pipeline preconfiguration.
function [imagingMode, stereoMode, reqs] = FinalizeConfiguration(reqs, userstereomode, screenid)
global ptb_outputformatter_icmAware;
global psych_gpgpuapi;
global isASideBySideConfig;
global screenRestoreCmd;
global maxreqarg;

% Reset flag to "no":
isASideBySideConfig = 0;

if nargin < 2
    userstereomode = [];
end

if isempty(userstereomode)
    userstereomode = 0;
end

if nargin < 3 || isempty(screenid)
    screenid = max(Screen('Screens'));
end

% Set imagingMode to minimum: Pipeline disabled. All latter task
% requirements will setup imagingMode to fullfill their needs. A few
% tasks/requirements don't need the full pipeline at all. E.g, Support for
% fast offscreen windows only needs that, but not the full pipeline. Some
% of the "software based mirror modes" herein only need the finalizer blit
% chains, but not the imaging pipeline. Bits++ setup for pure CLUT imaging
% (Bits++ mode) doesn't need imaging pipe either...
imagingMode = 0;

% Set stereoMode to don't care:
stereoMode = -1;

% No datapixx by default:
datapixxmode = 0;

% No Bits+ or Bits# by default:
crsbitsdevice = 0; %#ok<NASGU>

% Request for GPGPU compute support?
floc = find(mystrcmp(reqs, 'UseGPGPUCompute'));
if ~isempty(floc)
    % Yes.
    [row cols] = ind2sub(size(reqs), floc); %#ok<NASGU>

    % Extract first mandatory parameter, the apitype to use:
    apitype = reqs{row, 3};
    if ~ischar(apitype) || (~strcmpi(apitype, 'Auto') && ~strcmpi(apitype, 'GPUmat'))
        % Missing or invalid apitype specified:
        sca;
        error('PsychImaging: Use of GPU compute device via UseGPGPUCompute was requested, but mandatory apitype parameter is missing or invalid!');
    end

    % Extract 2nd optional parameter, the compute flags:
    gpgpuflags = reqs{row, 4};
    if ~isempty(gpgpuflags) && ~ischar(gpgpuflags)
        % There ain't no valid flags yet, so providing anything but the empty string is invalid:
        sca;
        error('PsychImaging: Use of GPU compute device via UseGPGPUCompute was requested, but optional flags argument is invalid!');
    end

    % Ok, all parameters validated. Check if our only currently supported
    % GPU compute api, 'GPUmat' is installed and functional and start it,
    % if possible:
    if ~exist('GPUstart', 'file')
        % Unsupported:
        sca;
        error('PsychImaging: Use of GPU compute device via UseGPGPUCompute was requested, but the required GPUmat toolbox seems to be missing!');
    end

    % Available. Start it:
    psychlasterror('reset');
    try
        % Start/Initialize GPUmat GPU computing toolkit if not already started:
        if ~GPUstart(1)
            GPUstart;
        end
    catch %#ok<CTCH>
        fprintf('PsychImaging: Failed to start GPGPU compute toolkit GPUmat! See error message below:\n');
        err = psychlasterror('reset');
        disp(err.message);
        sca;
        error('PsychImaging: GPGPU init failed!');
    end

    % Ok, GPUmat is online. Set a global marker that it is running:
    fprintf('PsychImaging: GPGPU computing support via GPUmat toolbox enabled.\n');

    % Type 1 is GPUmat:
    psych_gpgpuapi = 1; %#ok<NASGU>
end

% Special setup for CRS Bits# next-generation devices:
% Is a Bits+ / Bits# specific video display mode requested? Or
% explicit use of a Bits# device?
floc = [ find(mystrcmp(reqs, 'EnableBits++Bits++Output')) ];
floc = [floc(:) ; find(mystrcmp(reqs, 'EnableBits++Mono++Output')) ; find(mystrcmp(reqs, 'EnableBits++Mono++OutputWithOverlay')) ];
floc = [floc(:) ; find(mystrcmp(reqs, 'EnableBits++Color++Output')) ; find(mystrcmp(reqs, 'UseBits#')) ];
if ~isempty(floc)
    % Explicit use of Bits# requested? Or only implicit by video mode?
    floc = find(mystrcmp(reqs, 'UseBits#'));
    if ~isempty(floc)
        % Use of Bits# requested. Try to retrieve any special Bits# parameters to
        % pass them to the OpenBits# function:
        [row cols] = ind2sub(size(reqs), floc);

        % Extract first parameter - This should be the serial port name, or [] empty:
        bitsSharpPortname = reqs{row, 3};
    else
        % No specific usage of Bits# requested. Leave it to auto-detection
        % if we work with a Bits# or with a Bits+:
        bitsSharpPortname = [];
    end

    % Initialize serial port connection to Bits#, if any such device present:
    if BitsPlusPlus('OpenBits#', bitsSharpPortname)
        % Connection to Bits# established. Do we need to explicitely
        % specify use of it? Only if it was not already done by usercode via
        % keyword UseBits#
        if isempty(floc)
            % Bits# connected. Makeit explicit by adding the reqs task UseBits#
            reqs(end+1, :) = cell(1, size(reqs, 2));
            reqs{end, 2} = 'UseBits#';
        end

        % Mark use of Bits#:
        crsbitsdevice = 2;

        fprintf('PsychImaging: Will use a connected CRS Bits# device instead of a Bits+ for this session - Connection established.\n');
    else
        % No connection to Bits#. Was one requested? If not, we just assume we are
        % operating against a good old Bits+ which does not support connections.
        % Otherwise, failure to connect to Bits# would be, well, a failure:
        if ~isempty(floc)
            % Bummer:
            sca;
            error('PsychImaging: Use of a CRS Bits# device was requested, but connecting to it failed. Disconnected or misconfigured?!?');
        else
            % Mark use of Bits+:
            crsbitsdevice = 1;

            fprintf('PsychImaging: Will use a CRS Bits+ device, which i assume is connected to target display output screen.\n');
        end
    end
end

% End of Bits# setup, start of DataPixx/ViewPixx/ProPixx setup:

% Remap Datapixx L48 mode to equivalent Bits++ mode:
floc = find(mystrcmp(reqs, 'EnableDataPixxL48Output'));
if ~isempty(floc)
    reqs{floc} = 'EnableBits++Bits++Output';
    reqs(end+1, :) = cell(1, size(reqs, 2));
    reqs{end, 2} = 'UseDataPixx';
    datapixxmode = 1;

    % Initialize connection, switch immediately to L48 mode:
    PsychDataPixx('Open');
    PsychDataPixx('SetVideoMode', 1);
end

% Remap Datapixx M16 mode to equivalent Mono++ mode:
floc = find(mystrcmp(reqs, 'EnableDataPixxM16Output'));
if ~isempty(floc)
    reqs{floc} = 'EnableBits++Mono++Output';
    reqs(end+1, :) = cell(1, size(reqs, 2));
    reqs{end, 2} = 'UseDataPixx';
    datapixxmode = 1;

    % Initialize connection, switch immediately to M16 mode:
    PsychDataPixx('Open');
    PsychDataPixx('SetVideoMode', 2);
end

% Remap Datapixx M16 mode to equivalent Mono++ mode with overlay:
floc = find(mystrcmp(reqs, 'EnableDataPixxM16OutputWithOverlay'));
if ~isempty(floc)
    reqs{floc} = 'EnableBits++Mono++OutputWithOverlay';
    reqs(end+1, :) = cell(1, size(reqs, 2));
    reqs{end, 2} = 'UseDataPixx';
    datapixxmode = 1;

    % Initialize connection, switch immediately to M16 mode:
    PsychDataPixx('Open');
    PsychDataPixx('SetVideoMode', 2);
end

% Remap Datapixx C48 mode to equivalent Color++ mode:
floc = find(mystrcmp(reqs, 'EnableDataPixxC48Output'));
if ~isempty(floc)
    reqs{floc} = 'EnableBits++Color++Output';
    reqs(end+1, :) = cell(1, size(reqs, 2));
    reqs{end, 2} = 'UseDataPixx';
    datapixxmode = 1;

    % Initialize connection, switch immediately to C48 mode:
    PsychDataPixx('Open');
    PsychDataPixx('SetVideoMode', 3);
end

% Assign opmode to BitsPlusPlus driver: It unifies code for Bits+ and
% Datapixx:
BitsPlusPlus('SetTargetDeviceType', datapixxmode);

% Are we setting up for a Datapixx display?
if ~isempty(find(mystrcmp(reqs, 'UseDataPixx')))
    % Yes. Device connection already open from video mode setup above?
    % If not, open connection now.
    if datapixxmode == 0
        % Open connection:
        PsychDataPixx('Open');

        % As no other special high precision output mode is requested, set
        % video mode to "normal passthrough":
        PsychDataPixx('SetVideoMode', 0);

        % Mark as online:
        datapixxmode = 1;
    end
end

% Want native Retina display resolution in a scaled HiDPI display mode?
if ~isempty(find(mystrcmp(reqs, 'UseRetinaResolution')))
    imagingMode = mor(imagingMode, kPsychNeedRetinaResolution);
end

% FBO backed framebuffer needed?
if ~isempty(find(mystrcmp(reqs, 'UseVirtualFramebuffer')))
    imagingMode = mor(imagingMode, kPsychNeedFastBackingStore);
end

% Use Eizo RadiForce et al. subpixel drive pixel packing?
if ~isempty(find(mystrcmp(reqs, 'UseSubpixelDrive')))
    imagingMode = mor(imagingMode, kPsychNeedFastBackingStore, kPsychNeedTripleWidthWindow, kPsychNeedOutputConversion);
    ptb_outputformatter_icmAware = 0;
end

% 16 bit integer precision framebuffer needed? This is only supported on
% ATI hardware...
if ~isempty(find(mystrcmp(reqs, 'FixedPoint16Bit')))
    imagingMode = mor(imagingMode, kPsychNeed16BPCFixed);
end

% Stereomode 6 for interleaved line stereo needed?
if ~isempty(find(mystrcmp(reqs, 'InterleavedLineStereo')))
    % Yes: Must use stereomode 6.
    stereoMode = 6;
    % We also request an effective window height that is only half the real
    % height. This affects all drawing and query commands of Screen:
    imagingMode = mor(imagingMode, kPsychNeedFastBackingStore, kPsychNeedHalfHeightWindow);
end

% Stereomode 6 for interleaved column stereo needed?
if ~isempty(find(mystrcmp(reqs, 'InterleavedColumnStereo')))
    % Yes: Must use stereomode 6.
    stereoMode = 6;
    % We also request an effective window width that is only half the real
    % width. This affects all drawing and query commands of Screen:
    imagingMode = mor(imagingMode, kPsychNeedFastBackingStore, kPsychNeedHalfWidthWindow);
end

% Stereomode 2 for side-by-side compressed stereo needed?
if ~isempty(find(mystrcmp(reqs, 'SideBySideCompressedStereo')))
    % Yes: Must use stereomode 2.
    stereoMode = 2;
    imagingMode = mor(imagingMode, kPsychNeedFastBackingStore);
end

% Stereomode 10 for dualwindow stereo needed?
if ~isempty(find(mystrcmp(reqs, 'DualWindowStereo')))
    % Yes: Must use stereomode 10.
    stereoMode = 10;
    imagingMode = mor(imagingMode, kPsychNeedFastBackingStore);
    imagingMode = mor(imagingMode, kPsychNeedDualWindowOutput);
end

% Does usercode or internal code request a stereomode?
if userstereomode > 0 || stereoMode > 0
    % Enable imaging pipeline based stereo,ie., kPsychNeedFastBackingStore:
    imagingMode = mor(imagingMode, kPsychNeedFastBackingStore);

    % Datapixx - if any - needs special setup:
    if datapixxmode
        % Datapixx device active:

        % Frame sequential style mode via top-down "sync-doubling" mode?
        if ismember(userstereomode, 2) || ismember(stereoMode, 2)
            % Switch Datapixx to sync-doubling stereo mode:
            PsychDataPixx('SetVideoVerticalStereo', 1);
            % Reduce height of virtual framebuffer to effective half height:
            % Nope... imagingMode = mor(imagingMode, kPsychNeedHalfHeightWindow);
        else
            % Switch Datapixx to non stereo mode:
            PsychDataPixx('SetVideoVerticalStereo', 0);
        end

        % Frame-Sequential stereo driven by GPU or us, instead of Datapixx?
        if ismember(userstereomode, [1,11]) || ismember(stereoMode, [1,11])
            % Ask Datapixx to interpret the blue-line-sync stereo sync line
            % generated by either Screen() itself, or the graphics card
            % stereo device driver. The device will drive its VESA Stereo
            % Mini-DIN connector accordingly:
            PsychDataPixx('EnableVideoStereoBlueline');
        else
            % Datapixx shall ignore stereo sync lines / treat the bottom
            % scanline as any other scanline:
            PsychDataPixx('DisableVideoStereoBlueline');
        end

        % Any frame-sequential stereo mode active?
        if ismember(userstereomode, [1,11,2]) || ismember(stereoMode, [1,11,2])
            % Ask ViewPixx to enable its scanning backlight for faster /
            % ghost-free response:
            PsychDataPixx('EnableVideoScanningBacklight');

            % Tell Screen() to tolerate a VBLANK interval that is up to 50%
            % the height of VACTIVE, ie. allow a max VTOTAL = 1.5 * VACTIVE.
            % This is needed because Screen's beamposition query startup
            % tests may otherwise falsely conclude broken beamposition
            % query support due to the unusually large VBLANK interval used
            % by ViewPixx - and possibly other VPixx devices - in frame
            % sequential stereo mode. Our normal rejection threshold is 25%
            % or 1.25, now we raise it to 50% or 1.5:
            Screen('Preference', 'VBLEndlineOverride', [], 1.5);
        else
            % ViewPixx shall disable scanning backlight by default:
            PsychDataPixx('DisableVideoScanningBacklight');
        end

        % Dual-Display stereo via left-right stereo?
        if ismember(userstereomode, [4,5]) || ismember(stereoMode, [4,5])
            % Switch Datapixx to stereo mode by splitting display
            % horizontally onto 2 displays:
            PsychDataPixx('SetVideoHorizontalSplit', 1);
        else
            % Switch Datapixx to non stereo mode, aka auto mode:
            PsychDataPixx('SetVideoHorizontalSplit', 2);
        end
    end
end

% Want to reduce crosstalk in stereo presentation modes?
if ~isempty(find(mystrcmp(reqs, 'StereoCrosstalkReduction')))
    % Yes: For now we only implement this experimentally and for attachment
    % of crosstalk reduction shaders to the image processing chains.
    % This will be suboptimal if other image processing ops are active,
    % but for a first usefully working prototype it should be good enough.
    %
    % We only request additional access to the other image channel, as setup
    % code above and below will already have activated the image processing
    % chains etc.
    imagingMode = mor(imagingMode, kPsychNeedOtherStreamInput);
end

% Want to use a VR Head mounted display (HMD)?
floc = find(mystrcmp(reqs, 'UseVRHMD'));
if ~isempty(floc)
    % Yes: We need a peculiar configuration, which involves the panelfitter
    % to allow for a custom resolution of the virtual framebuffers for left
    % eye and right eye - much higher than output resolution, so we have enough
    % excess information to deal with geometric undistortion warps, color aberration,
    % and dynamic display warping for head motion correction. We also need a
    % special stereo processing shader that does geometric distortion correction,
    % color aberration correction, vignetting correction, and dynamic display warping
    % in one go, as processing speed is crucial for VR experience.
    [rows cols] = ind2sub(size(reqs), floc(1));
    row = rows(1);

    % Extract first parameter - This should be the handle of the HMD device:
    hmd = reqs{row, 3};

    % Verify it is already open:
    if ~hmd.driver('IsOpen', hmd)
        error('UseVRHMD: Invalid HMD handle specified. No such device opened.');
    end

    % Get imagingMode flags and stereoMode to use for this HMD:
    [clientRes, imagingFlags, stereoMode] = hmd.driver('GetClientRenderingParameters', hmd);

    % Add imaging mode flags requested by HMD driver:
    imagingMode = mor(imagingMode, imagingFlags);

    % Do we need the PanelFitter?
    needPanelFitter = hmd.driver('GetPanelFitterParameters', hmd);
    if needPanelFitter
        x{1} = 'General';
        x{2} = 'UsePanelFitter';
        x{3} = clientRes;
        x{4} = 'Custom';
        x{5} = [0, 0, clientRes(1), clientRes(2)];
        x{6} = [0, 0, clientRes(1), clientRes(2)];

        % Pad to maxreqarg arguments:
        if length(x) < maxreqarg
            for i=length(x)+1:maxreqarg
                x{i}='';
            end
        end
        reqs = [reqs ; x];
    end
end

floc = find(mystrcmp(reqs, 'EnableHDR'));
if ~isempty(floc)
    % Get and validate input arguments:
    [rows cols] = ind2sub(size(reqs), floc(1));
    row = rows(1);
    hdrArguments = reqs(row, :);

    % Check if HDR is supported at all on this system:
    if ~PsychHDR('Supported') && isempty(strfind(hdrArguments{5}, 'Dummy'))
        % Failed/Unsupported.
        error('PsychImaging: Requested task ''EnableHDR'', but this system does not support High dynamic range (HDR) at all.');
    end

    if ~isempty(find(mystrcmp(reqs, 'UseStaticHDRHack')))
        if ~(ismember(userstereomode, [0, 1, 4, 5, 11]) && IsLinux && ~IsWayland)
            error('PsychImaging: Requested task ''UseStaticHDRHack'' is incompatible with this system setup.');
        end

        % Add special requirements marker statichdrhack to signal to PsychHDR()
        % that this hack is requested:
        reqs{row, 5} = [reqs{row, 5} ' statichdrhack'];

        % Add the EnableNative10BitFramebuffer task if it doesn't exist already,
        % as our UseStaticHDRHack HDR support requires a 10 bpc native OpenGL
        % framebuffer:
        if isempty(find(mystrcmp(reqs, 'EnableNative10BitFramebuffer')))
            reqs = AddTask(reqs, 'General', 'EnableNative10BitFramebuffer');
        end
    end

    % Parse user provided parameters, and return imagingMode flags and other
    % flags/constraints needed for FinalizeConfiguration() stage:
    hdrImagingModeFlags = PsychHDR('GetClientImagingParameters', hdrArguments);
    imagingMode = mor(imagingMode, hdrImagingModeFlags);

    % Mark use of HDR:
    useHDR = 1;

    % Add the UseVulkanDisplay task if it doesn't exist already, as our HDR support
    % is implemented on top of Vulkan:
    floc = find(mystrcmp(reqs, 'UseVulkanDisplay'));
    if isempty(floc)
        reqs = AddTask(reqs, 'General', 'UseVulkanDisplay');
    end

    % No frame-sequential stereo yet:
    if ismember(userstereomode, [1, 11])
        error('PsychImaging: Requested task ''EnableHDR'' is incompatible with frame-sequential stereo mode %i.', userstereomode);
    end

    % Dual-window would turn into dual-stream:
    if ismember(userstereomode, [10])
        % Remap dual-window stereo to dual-stream stereo:
        stereoMode = 12;
    end
else
    % No HDR in use:
    useHDR = 0;
end

% Want to use the Vulkan/WSI display backend?
floc = find(mystrcmp(reqs, 'UseVulkanDisplay'));
if ~isempty(floc)
    [rows cols] = ind2sub(size(reqs), floc(1));
    row = rows(1);

    % Check if Vulkan/WSI based display output is supported at all, ie. if a
    % Vulkan loader library is installed, so PsychVulkanCore can be loaded and
    % linked, and if at least one Vulkan gpu is available on this system setup.
    % This will also perform driver init and Vulkan instance init, which is an
    % important thing to do as the very first thing before opening an onscreen
    % window (ie. before OpenGL context creation) on AMD + Windows-10, otherwise
    % the AMD proprietary OpenGL driver might crash due to AMD driver bugs!
    if ~PsychVulkan('Supported')
        % Failed/Unsupported.
        error('PsychImaging: Requested task ''UseVulkanDisplay'', but this system does not support Vulkan at all.');
    end

    imagingMode = mor(imagingMode, kPsychNeedFastBackingStore);

    % Special static HDR dual-display stereo hack requested and supported?
    if ~useHDR || isempty(find(mystrcmp(reqs, 'UseStaticHDRHack')))
        % No -> Standard operations: Add imaging mode flags for handing rendered images to Vulkan:
        imagingMode = mor(imagingMode, kPsychNeedFinalizedFBOSinks);

        % Mark full use of Vulkan:
        useVulkan = 1;
    else
        % We abuse Vulkan for HDR setup in UseStaticHDRHack, but not as primary
        % display backend:
        useVulkan = 0;
    end

    if ismember(userstereomode, [1, 11])
        error('PsychImaging: Requested task ''UseVulkanDisplay'' is incompatible with frame-sequential stereo mode %i.', userstereomode);
    end

    if ismember(userstereomode, [10])
        % Remap dual-window stereo to dual-stream stereo:
        stereoMode = 12;
    end
else
    useVulkan = 0;
end

% Display replication needed?
if ~isempty(find(mystrcmp(reqs, 'MirrorDisplayTo2ndOutputHead')))
    % Yes: Must use dual window output mode. This implies
    % kPsychNeedFastBackingStore, automatically set by Screen('OpenWindow')
    % itself, so no need to do it here.
    imagingMode = mor(imagingMode, kPsychNeedDualWindowOutput);
end

% Custom color correction for display wanted?
if ~isempty(find(mystrcmp(reqs, 'DisplayColorCorrection')))
    % Color correction in output chain?
    if (~isempty(find(mystrcmp(reqs, 'AllViews'))) || ~isempty(find(mystrcmp(reqs, 'FinalFormatting'))))
        % Yes. Need full pipeline in any case, ie fast backing store and output conversion:
        imagingMode = mor(imagingMode, kPsychNeedFastBackingStore, kPsychNeedOutputConversion);
    else
        % No. Fast backing store is enough, per-view chains will get enabled below for Left/RightView:
        imagingMode = mor(imagingMode, kPsychNeedFastBackingStore);
    end
end

% Replication of left half of window into right half needed?
% This is used for a software implementation of mirror mode displays,
% e.g., in conjunction with desktop-spanning display mode on MS-Windows or
% in conjunction with a display splitter on a single output head:
if ~isempty(find(mystrcmp(reqs, 'MirrorDisplayToSingleSplitWindow')))
    % We simply request that window size is reported and handled as if the
    % window would be only half the width --> right half remains empty and
    % can be used as target for the cloning op of the left half.
    % This works even without imaging pipe enabled, only uses finalizer
    % blit chains:
    imagingMode = mor(imagingMode, kPsychNeedHalfWidthWindow);
end

% 16 bpc float framebuffers needed?
if ~isempty(find(mystrcmp(reqs, 'FloatingPoint16Bit')))
    imagingMode = mor(imagingMode, kPsychNeedFastBackingStore);
    imagingMode = mor(imagingMode, kPsychNeed16BPCFloat);
end

% 32 bpc float framebuffers needed?
if ~isempty(find(mystrcmp(reqs, 'FloatingPoint32Bit')))
    imagingMode = mor(imagingMode, kPsychNeedFastBackingStore);
    imagingMode = mor(imagingMode, kPsychNeed32BPCFloat);
end

if ~isempty(find(mystrcmp(reqs, 'FloatingPoint32BitIfPossible')))
    imagingMode = mor(imagingMode, kPsychNeedFastBackingStore);
    imagingMode = mor(imagingMode, kPsychUse32BPCFloatAsap);
end

if ~isempty(find(mystrcmp(reqs, 'EnableBits++Mono++Output'))) || ~isempty(find(mystrcmp(reqs, 'EnableBits++Mono++OutputWithOverlay')))
    imagingMode = mor(imagingMode, kPsychNeedFastBackingStore);
    imagingMode = mor(imagingMode, kPsychNeedOutputConversion);
    % The Mono++ formatter is icm aware - Capable of internal color
    % correction, but not setup here -- special case: Set flag to zero:
    ptb_outputformatter_icmAware = 0;
end

if ~isempty(find(mystrcmp(reqs, 'EnableGenericHighPrecisionLuminanceOutput'))) || ~isempty(find(mystrcmp(reqs, 'EnablePseudoGrayOutput')))
    imagingMode = mor(imagingMode, kPsychNeedFastBackingStore);
    imagingMode = mor(imagingMode, kPsychNeedOutputConversion);

    % The Luminance LUT based formatter is icm aware - Capable of internal color correction:
    ptb_outputformatter_icmAware = 1;

    % Request 32bpc float FBO unless already a 16 bpc FBO or similar has
    % been explicitely requested:
    if ~bitand(imagingMode, kPsychNeed16BPCFloat) && ~bitand(imagingMode, kPsychUse32BPCFloatAsap) && ~bitand(imagingMode, kPsychNeed16BPCFixed)
        imagingMode = mor(imagingMode, kPsychNeed32BPCFloat);
    end
end

if ~isempty(find(mystrcmp(reqs, 'EnableVideoSwitcherSimpleLuminanceOutput'))) || ~isempty(find(mystrcmp(reqs, 'EnableVideoSwitcherCalibratedLuminanceOutput')))
    imagingMode = mor(imagingMode, kPsychNeedFastBackingStore);
    imagingMode = mor(imagingMode, kPsychNeedOutputConversion);

    % The VideoSwitcher formatter is icm aware - Capable of internal color correction:
    ptb_outputformatter_icmAware = 1;

    % Request 32bpc float FBO unless already a 16 bpc FBO or similar has
    % been explicitely requested:
    if ~bitand(imagingMode, kPsychNeed16BPCFloat) && ~bitand(imagingMode, kPsychUse32BPCFloatAsap) && ~bitand(imagingMode, kPsychNeed16BPCFixed)
        imagingMode = mor(imagingMode, kPsychNeed32BPCFloat);
    end
end

if ~isempty(find(mystrcmp(reqs, 'EnableBits++Color++Output')))
    floc = find(mystrcmp(reqs, 'EnableBits++Color++Output'));
    [rows cols] = ind2sub(size(reqs), floc(1));
    row = rows(1);
    % Extract first parameter - This should be the colorConversionMode:
    colorConversionMode = reqs{row, 3};
    BitsPlusPlus('SetColorConversionMode', colorConversionMode);

    % These settings are mildly redundant, as the dedicated
    % OpenWindowColor++ code in the BitsPlusPlus.m helper file will do all
    % neccessary setup, especially deciding of kPsychNeedHalfWidthWindow is
    % needed or not:
    imagingMode = mor(imagingMode, kPsychNeedFastBackingStore);
    imagingMode = mor(imagingMode, kPsychNeedOutputConversion);

    % The Color++ formatter is icm aware - Capable of internal color
    % correction, but not setup here -- special case: Set flag to zero:
    ptb_outputformatter_icmAware = 0;
end

% Request for native 10 bit per color component ARGB2101010 framebuffer,
% or native almost 11 bit per color component RGB111110 framebuffer?
if ~isempty(find(mystrcmp(reqs, 'EnableNative10BitFramebuffer'))) || ...
   ~isempty(find(mystrcmp(reqs, 'EnableNative11BitFramebuffer')))

    if ~useVulkan
        % Enable output formatter chain:
        imagingMode = mor(imagingMode, kPsychNeedFastBackingStore);
        imagingMode = mor(imagingMode, kPsychNeedOutputConversion);
    end

    % Request 32bpc float FBO unless already a 16 bpc FBO or similar has
    % been explicitely requested: In principle, a 16 bpc FBO would be
    % sufficient for a native 10 to 11 bpc framebuffer...
    if ~bitand(imagingMode, kPsychNeed16BPCFloat) && ~bitand(imagingMode, kPsychUse32BPCFloatAsap) && ~bitand(imagingMode, kPsychNeed16BPCFixed)
        imagingMode = mor(imagingMode, kPsychNeed32BPCFloat);
    end

    % The ATI 10/11bpc formatter is not yet icm aware - Incapable of internal color correction!
    % Additionally native 10/11 bpc framebuffers, e.g., on Fire-Series or NVidia cards also don't
    % have icm aware output formatting, so a 'false' setting would be mandatory. However, we leave
    % the setting at whatever it currently is, as it defaults to 0 / false anyway, and the PseudoGray
    % or EnableGenericHighPrecisionLuminanceOutput might have requested a 1 / true setting and we do
    % not want to override that. This to allow to stack such a perceptual precision boosting trick
    % on top of a 10/11 bpc framebuffer.
    % ptb_outputformatter_icmAware = 0;
end

% Request for native 16 bit per color component RGBA16161616 framebuffer under Vulkan?
if ~isempty(find(mystrcmp(reqs, 'EnableNative16BitFramebuffer'))) && useVulkan
    % Request 32bpc float FBO unless already a 16 bpc fixed point FBO
    % has been explicitely requested. 16 bpc fixed point is obviously just
    % quite sufficient for 16 bpc linear output, 32 bpc float provides 23 bpc
    % effective linear precision in the meaningful output intensity range, so
    % leaves some numerical headroom for post processing and roundoff errors:
    if ~bitand(imagingMode, kPsychUse32BPCFloatAsap) && ~bitand(imagingMode, kPsychNeed16BPCFixed)
        imagingMode = mor(imagingMode, kPsychNeed32BPCFloat);
    end
end

% Request for native 16 bit per color component ARGB16161616 framebuffer under OpenGL (== not Vulkan)?
if ~isempty(find(mystrcmp(reqs, 'EnableNative16BitFramebuffer'))) && ~useVulkan
    % Enable output formatter chain:
    imagingMode = mor(imagingMode, kPsychNeedFastBackingStore);
    imagingMode = mor(imagingMode, kPsychNeedOutputConversion);

    % Validate current config to make sure it makes sense for this stunt:
    if ~IsLinux || IsWayland
        error('PsychImaging: Native 16 bpc framebuffer requested, but not running on Linux X11. This is unsupported.');
    end

    % We need to disable color tiling for rendering and display on the AMD gpu, as this hack needs a linear
    % renderbuffer and scanoutbuffer. All suitable AMD gpu's are GCN gpus and therefore use the Mesa radeonsi
    % gallium OpenGL driver which supports a special debug environment variable setting R600_DEBUG=notiling to
    % achieve this. However, the variable setting is only picked up by Mesa during driver reinit time, and that
    % only happens at first use of OpenGL in a session:
    if isempty(strfind(getenv('R600_DEBUG'), 'notiling')) && isempty(strfind(getenv('AMD_DEBUG'), 'notiling'))
        % Env setting and Screen reinit needed. This will not work on Matlab, and only sometimes on
        % Octave, so give customized advise:
        if IsOctave
            setenv('R600_DEBUG', 'notiling');
            fprintf('PsychImaging: Had to set the R600_DEBUG environment variable myself for setup of native 16 bpc framebuffer mode.\n');
            fprintf('PsychImaging: This may not work if any OpenGL graphics or Screen() was already used in this session. If you see\n');
            fprintf('PsychImaging: corrupted visual stimuli then abort the script, type ''clear all'' and then retry. If that does not\n');
            fprintf('PsychImaging: help, then exit Octave and restart it from a terminal via this command:\n');
            fprintf('PsychImaging: R600_DEBUG=notiling octave\n');
            fprintf('\n\n');
        else
            fprintf('PsychImaging: The R600_DEBUG environment variable is not set to ''notiling'' as needed for native 16 bpc framebuffer\n');
            fprintf('PsychImaging: mode. To fix this, the variable must be assigned before Matlab is started - or during Matlab startup.\n');
            fprintf('PsychImaging: If you see corrupted visual stimuli then quit Matlab and restart it from a terminal via this command:\n');
            fprintf('PsychImaging: R600_DEBUG=notiling matlab\n');
            fprintf('\n\n');
        end
    end

    % Get number of attached video outputs (aka scanout engines) and properties
    % of the first output, which acts as a reference for all other outputs, if any:
    numOutputs = Screen('ConfigureDisplay', 'NumberOutputs', screenid);
    refOutput = Screen('ConfigureDisplay', 'Scanout', screenid, 0);

    % First output must have x/y offset 0,0 ie. top-left corner of framebuffer.
    if (refOutput.xStart ~= 0) || (refOutput.yStart ~= 0)
        error('PsychImaging: First video output in native 16 bpc framebuffer mode not starting at (x,y)=(0,0)! This is unsupported.');
    end

    % Screen must have twice the width and height of the viewport of the first output:
    [swidth, sheight] = Screen('WindowSize', screenid, 1);
    if (2 * refOutput.width ~= swidth) || (2 * refOutput.height ~= sheight)
        fprintf('PsychImaging: Screen width and height is not twice the width and height of the first video output in native 16 bpc framebuffer mode. Adapting...\n');
        oldres = Screen('Resolution', screenid, 2 * refOutput.width, 2 * refOutput.height, [], [], 2);
        for i=1:10
            [swidth, sheight] = Screen('WindowSize', screenid, 1);
            if (2 * refOutput.width == swidth) && (2 * refOutput.height == sheight)
                % Change applied. Carry on!
                break;
            end
            fprintf('PsychImaging: Screen resize to %i x %i pixels for 16 bpc mode still in progress. Waiting...\n', 2 * refOutput.width, 2 * refOutput.height);
            WaitSecs(1);
        end

        if i == 10
            % Restore old screen setting if we changed it:
            Screen('Resolution', screenid, oldres.width, oldres.height, [], [], 2);
            fprintf('PsychImaging: Failed to change X-Screen width and height for native 16 bpc framebuffer mode. Likely this is because your\n');
            fprintf('PsychImaging: desktop GUI environment prevents it. Modern versions of the GNOME desktop and Ubuntu desktop are known to\n');
            fprintf('PsychImaging: prevent this mode (as tested with Ubuntu 19.10). Try switching to a different desktop GUI to see if that\n');
            fprintf('PsychImaging: makes it work. E.g., XFCE-4 worked on Ubuntu 19.10, KDE may work.\n');
            error('PsychImaging: Switch to 16 bpc framebuffer failed, probably due to wrong desktop GUI environment in use. See message above.');
        end
    else
        oldres = [];
    end

    % More than one output? If so, make sure that all outputs have the same
    % horizontal and vertical resolution / viewport size as the 1st reference
    % output and that they either clone the first output, aka x/y start offset is (0,0),
    % or just right of the first output, so we have a classic dual-display side-by-side
    % configuration for ultra wide-screen viewing or dual-display stereo:
    if numOutputs > 1
        for outputId=1:(numOutputs-1)
            % Get this outputs settings:
            testOutput = Screen('ConfigureDisplay', 'Scanout', screenid, outputId);

            if (testOutput.width ~= refOutput.width) || (testOutput.height ~= refOutput.height) || (testOutput.yStart ~= 0)
                % Mismatch in viewport size or vertical start location:
                if ~isempty(oldres)
                    % Restore old screen setting if we changed it:
                    Screen('Resolution', screenid, oldres.width, oldres.height, [], [], 2);
                end
                error('PsychImaging: At least one secondary video output in native 16 bpc framebuffer mode does not have the same viewport size as the first output! This is unsupported.');
            end

            % Secondary outputs must either clone the 1st output, or be located directly to its right edge:
            if (testOutput.xStart ~= 0) && (testOutput.xStart ~= refOutput.width)
                % Mismatch in horizontal start location:
                if ~isempty(oldres)
                    % Restore old screen setting if we changed it:
                    Screen('Resolution', screenid, oldres.width, oldres.height, [], [], 2);
                end
                error('PsychImaging: At least one secondary video output in native 16 bpc framebuffer mode is not located right of the first output, or cloning the first output! This is unsupported.');
            end

            % At least one output establishing a dual-display side-by-side config?
            if testOutput.xStart == refOutput.width
                isASideBySideConfig = 1;
            end
        end
    end

    % If we made it up to here, then the display output configuration and framebuffer size etc.
    % is at least compatible with 16 bpc 64 bpp scanout.
    if ~isempty(oldres)
        % Setup command sequence to restore settings after window close. Restore original X-Screen resolution:
        screenRestoreCmd{screenid+1} = sprintf('Screen(''Resolution'', %i, %i, %i, [], [], 1+2);', screenid, oldres.width, oldres.height);
    else
        screenRestoreCmd{screenid+1} = [];
    end

    % As of January 2020, none of the commercially available gpu's has a graphics
    % driver which would support 16 bpc / 64 bpp fixed point linear framebuffers.
    % However, all recent AMD gpu's do support linear 16 bpc / 64 bpp framebuffers
    % in their scanout hardware, ie., storing 16 bpc formatted framebuffer
    % content and scanning it out. The actual display encoders do limit output
    % precision to way less than 16 bpc though, ie. not the whole display pipeline is
    % 16 bpc. So what we do on Linux with the FOSS AMD graphics drivers on
    % X11 + radeon/amdgpu-kms is we reprogram the scanout engine (crtc) to treat a
    % 32 bpp framebuffer of twice the width and height of the crtc's viewport
    % as a 64 bpp framebuffer of exactly the width and height of the crtc's
    % viewport. From the perspective of Linux and its graphics stack, what we
    % have is an oversized X-Screen twice the width and height of the selected
    % display resolution. From the perspective of the scanout hw we have an
    % exactly matching X-Screen with 64 bpp color format for 16 bpc scanout,
    % and half of all scanlines are dead/ignored. PTB needs to render into the
    % jumbo-size onscreen window/framebuffer/x-screen as if it is a 64 bpp fb,
    % using GLSL conversion shaders to convert its floating point framebuffers
    % content into a 64 bpp encoding. Usercode however should see an onscreen
    % window with the effective output size/resolution of the display, not the over-
    % sized resolution of the x-screen/fb. Therefore instruct Screen() to pretend
    % the onscreen window is only half the width/height of the true system back-
    % buffer: half the width/height of a twice width/height system fb == cancels
    % each other out for effective width/height == display width/height, and all
    % is good:
    if ~isASideBySideConfig
        % Only half-width if no secondary video output can cover the "right half"
        % of the framebuffer for side-by-side (e.g., stereoscopic) display:
        imagingMode = mor(imagingMode, kPsychNeedHalfWidthWindow);
    end

    % Always half-height:
    imagingMode = mor(imagingMode, kPsychNeedHalfHeightWindow);

    % Request 32bpc float FBO unless already a 16 bpc fixed point FBO
    % has been explicitely requested. 16 bpc fixed point is obviously just
    % quite sufficient for 16 bpc linear output, 32 bpc float provides 23 bpc
    % effective linear precision in the meaningful output intensity range, so
    % leaves some numerical headroom for post processing and roundoff errors:
    if ~bitand(imagingMode, kPsychUse32BPCFloatAsap) && ~bitand(imagingMode, kPsychNeed16BPCFixed)
        imagingMode = mor(imagingMode, kPsychNeed32BPCFloat);
    end

    % The AMD 16 bpc formatter is not icm aware - Incapable of internal color correction!
    % See 'EnableNative10BitFramebuffer' above, for why we don't touch the ptb_outputformatter_icmAware
    % setting instead of forcing it to 0 / false:
    % ptb_outputformatter_icmAware = 0;
end

% Request for dual display pipeline custom HDR system?
if ~isempty(find(mystrcmp(reqs, 'EnableDualPipeHDROutput')))
    % Enable imaging pipeline ...
    imagingMode = mor(imagingMode, kPsychNeedFastBackingStore);
    % ... final device output formatter chain(s) ...
    imagingMode = mor(imagingMode, kPsychNeedOutputConversion);
    % ... and dual stream processing and output to two displays ...
    imagingMode = mor(imagingMode, kPsychNeedDualWindowOutput);

    % Request 32bpc float FBO unless already a 16 bpc FBO or similar has
    % been explicitely requested:
    if ~bitand(imagingMode, kPsychNeed16BPCFloat) && ~bitand(imagingMode, kPsychUse32BPCFloatAsap) && ~bitand(imagingMode, kPsychNeed16BPCFixed)
        imagingMode = mor(imagingMode, kPsychNeed32BPCFloat);
    end

    % The dual-pipeline HDR output formatter is not yet icm aware -
    % Incapable of internal color correction. Well, technically it is, but
    % that code-path is disabled for now. It is probably computationally
    % more efficient to perform one generic ICM pass on the input buffer
    % and then feed into the formatters for the two pipes instead of
    % letting each pipe's formatter apply the same color correction, ie.,
    % do the same work twice. This needs to be found out in the future. For
    % now we go for the simple solution:
    ptb_outputformatter_icmAware = 0;
end

if ~isempty(find(mystrcmp(reqs, 'LeftView'))) || ~isempty(find(mystrcmp(reqs, 'RightView')))
    % Specific eye channel requested: Need a stereo display mode.
    if stereoMode == -1
        % None set yet. Just channel the request to the caller:
        stereoMode = -2;
    end

    imagingMode = mor(imagingMode, kPsychNeedFastBackingStore);

    % Also need image processing stage, because only it can provide
    % separate processing for both eyes:
    imagingMode = mor(imagingMode, kPsychNeedImageProcessing);
else
    % Not a single eye specific command requested: Check if there's any
    % other spec that would require the image processing stage:
    % Any command that applies to 'AllViews' naturally needs the image
    % processing:
    if ~isempty(find(mystrcmp(reqs, 'AllViews')))
        imagingMode = mor(imagingMode, kPsychNeedFastBackingStore);
        imagingMode = mor(imagingMode, kPsychNeedImageProcessing);
    end
end

% Image processing stage needed?
if bitand(imagingMode, kPsychNeedImageProcessing)
    % Yes. How many commands per chain?
    nrslots = max(length(find(mystrcmp(reqs, 'LeftView'))), length(find(mystrcmp(reqs, 'RightView'))));
    nrslots = nrslots + length(find(mystrcmp(reqs, 'AllViews')));

    % More than one slot per chain? Otherwise we use the default
    % single-pass chain:
    if nrslots > 1
        % More than two slots per chain?
        if nrslots > 2
            % Need full blown multistage chain:
            imagingMode = mor(imagingMode, kPsychNeedMultiPass);
        else
            % Only two slots. More efficient dual-pass chain is sufficient:
            imagingMode = mor(imagingMode, kPsychNeedDualPass);
        end
    end
end

% Final output formatting stage needed?
if ~isempty(find(mystrcmp(reqs, 'FinalFormatting')))
    imagingMode = mor(imagingMode, kPsychNeedFastBackingStore);
    imagingMode = mor(imagingMode, kPsychNeedOutputConversion);
end

% Native 16 bit per color component RGBA16F floating point framebuffer requested?
if ~isempty(find(mystrcmp(reqs, 'EnableNative16BitFloatingPointFramebuffer')))
    % Yes. Pipeline active?
    if bitand(imagingMode, kPsychNeedFastBackingStore)
        % Native 16 bpc float doesn't need imaging pipeline by itself, but if the
        % pipeline is enabled then it needs to provide sufficient >= 16 bpc float
        % precision throughout the pipeline. Make sure we request that.

        % Request 32bpc float FBO unless already a 16 bpc FBO or similar has
        % been explicitely requested: In principle, a 16 bpc FBO would be
        % sufficient for a native 16 bpc float framebuffer...
        if ~bitand(imagingMode, kPsychNeed16BPCFloat) && ~bitand(imagingMode, kPsychUse32BPCFloatAsap) && ~bitand(imagingMode, kPsychNeed16BPCFixed)
            imagingMode = mor(imagingMode, kPsychNeed32BPCFloat);
        end

        % Not needed, as it is default: ptb_outputformatter_icmAware = 0;
    end
end

% Validate if everything is right for use of HDR displays:
if useHDR
    % For HDR we absolutely need a floating point virtual framebuffer on the
    % input side, ie. for user-scripts to draw into, as most HDR color values
    % will be far outside (exceeding) the unorm range 0 - 1:
    if ~bitand(imagingMode, kPsychNeed16BPCFloat) && ~bitand(imagingMode, kPsychUse32BPCFloatAsap) && ~bitand(imagingMode, kPsychNeed32BPCFloat)
        % No floating point input drawbuffer format requested yet.
        % Play it safe and request 32 bit single precision float to prefer
        % maximum accuracy over performance:
        imagingMode = mor(imagingMode, kPsychNeed32BPCFloat);
    end

    % Assume that all HDR output formatters are ICM aware, ie. allow/require linking
    % an ICM shader into them for colorspace conversion, clamping etc., for efficient
    % single-pass colorspace conversion and HDR OETF mapping:
    ptb_outputformatter_icmAware = 1;
end

% Support for fast offscreen windows (aka FBO backed offscreen windows)
% needed?
if ~isempty(find(mystrcmp(reqs, 'UseFastOffscreenWindows')))
    % Need fast offscreen windows. They are included if any non-zero imagingMode
    % is set, so we only request'em if imagingMode is still zero:
    if imagingMode == 0
        imagingMode = kPsychNeedFastOffscreenWindows;
    end
end

return;

% End of FinalizeConfiguration subroutine.

% PostConfiguration is called after the onscreen window is open: Performs
% actual pipeline setup of the hook chains:
function rc = PostConfiguration(reqs, win, clearcolor, slavewin)
global ptb_outputformatter_icmAware;
global GL;
global ptb_geometry_inverseWarpMap;
global psych_gpgpuapi; %#ok<NUSED>
% Default requested colormode: Set by PsychDefaultSetup(), if at all.
global psych_default_colormode;

% At least two video outputs scanning out in dual-display side-by-side configuration?
global isASideBySideConfig;
global screenRestoreCmd;

if isempty(GL)
    % Perform minimal OpenGL init, so we can call OpenGL commands and use
    % GL constants. We do not activate a full 3D rendering context:
    InitializeMatlabOpenGL([], [], 1);
end

% Identity CLUT in graphics hardware required?
needsIdentityCLUT = 0;

% Should dithering be disabled if 'needsIdentityCLUT'?
% By default we disable in such a case:
disableDithering = 1;

% 0.0 - 1.0 colorrange without color clamping required?
needsUnitUnclampedColorRange = 0;
applyAlsoToMakeTexture = [];

% Number of used slots in left- and right processing chain:
leftcount = 0;
rightcount = 0;
outputcount = 0;
outputcount0 = 0;
outputcount1 = 0;

% Flags for horizontal/vertical flip operations:
leftUDFlip = 0;
rightUDFlip = 0;
leftLRFlip = 0;
rightLRFlip = 0;

% Stereomode?
winfo = Screen('GetWindowInfo', win);
[winwidth, winheight] = InterBufferSize(win);

% Setup inverse warp map matrices for this window handle:
ptb_geometry_inverseWarpMap{win} = [];
ptb_geometry_inverseWarpMap{win}.gx = 1;
ptb_geometry_inverseWarpMap{win}.gy = 1;
ptb_geometry_inverseWarpMap{win}.mx = winwidth;
ptb_geometry_inverseWarpMap{win}.my = winheight;

if ismember(winfo.StereoMode, [2,3])
    ptb_geometry_inverseWarpMap{win}.gy = 2;
end

% Determine early if Vulkan display backend is to be used:
floc = find(mystrcmp(reqs, 'UseVulkanDisplay'));
if ~isempty(floc)
    useVulkan = 1;

    % Default to SDR standard dynamic range display mode:
    vulkanHDRMode = 0;

    % Default color precision: In SDR mode this is standard 8 bpc RGBA8 unorm
    % fixed point color precision. In vulkanHDRMode > 0 it may get automatically
    % overriden to something higher precision:
    vulkanColorPrecision = 0;

    % Default color space: Overriden in vulkanHDRMode > 0.
    vulkanColorSpace = 0;

    % Default color pixel format: Overriden in vulkanHDRMode > 0 as part of
    % override of vulkanColorPrecision, or explicit override:
    vulkanColorFormat = 0;
else
    useVulkan = 0;
end

% Determine early if HDR display is to be used:
floc = find(mystrcmp(reqs, 'EnableHDR'));
if ~isempty(floc)
    % Assign all user provided arguments in hdrArguments for later use:
    [rows cols] = ind2sub(size(reqs), floc(1));
    row = rows(1);
    hdrArguments = reqs(row, :);

    % Get HDR setup parameters for imaging pipeline and Vulkan:
    [useVulkan, vulkanHDRMode, vulkanColorPrecision, vulkanColorSpace, vulkanColorFormat] = PsychHDR('GetVulkanHDRParameters', win, hdrArguments);

    %if ~isempty(find(mystrcmp(reqs, 'UseStaticHDRHack')))
    % Better with useVulkan to suppress our own 10/11/16 bpc output formatters atm. useVulkan = 1;
    %end

    % Mark HDR as in use:
    useHDR = 1;
else
    hdrArguments = [];
    useHDR = 0;
end

% --- First action in pipe is a horizontal- or vertical flip, if any ---

% Any flip horizontal requested?
floc = find(mystrcmp(reqs, 'FlipHorizontal'));
if ~isempty(floc)
    % Which channel?
    for x=floc
        [rows cols]= ind2sub(size(reqs), x);
        for row=rows'
            if mystrcmp(reqs{row, 1}, 'LeftView') || mystrcmp(reqs{row, 1}, 'AllViews')
                % LeftView horizontal flip.
                leftLRFlip = 1;
            end

            if mystrcmp(reqs{row, 1}, 'RightView') || mystrcmp(reqs{row, 1}, 'AllViews')
                % LeftView horizontal flip.
                rightLRFlip = 1;
            end
        end
    end
end

% Any flip vertical requested?
floc = find(mystrcmp(reqs, 'FlipVertical'));
if ~isempty(floc)
    % Which channel?
    for x=floc
        [rows cols]= ind2sub(size(reqs), x);
        for row=rows'
            if mystrcmp(reqs{row, 1}, 'LeftView') || mystrcmp(reqs{row, 1}, 'AllViews')
                % LeftView vertical flip.
                leftUDFlip = 1;
            end

            if mystrcmp(reqs{row, 1}, 'RightView') || mystrcmp(reqs{row, 1}, 'AllViews')
                % LeftView vertical flip.
                rightUDFlip = 1;
            end
        end
    end
end

% Left channel flipping needed?
if leftLRFlip || leftUDFlip
    % Yes.
    sx = 1;
    ox = 0;
    sy = 1;
    oy = 0;

    if leftLRFlip
        sx = -1;
        ox = RectWidth(InterBufferRect(win));
        hv = winwidth-1:-1:0;
    else
        hv = 0:winwidth-1;
    end

    if leftUDFlip
        sy = -1;
        oy = RectHeight(InterBufferRect(win));
        vv = winheight-1:-1:0;
    else
        vv = 0:winheight-1;
    end

    % Enable left imaging chain:
    Screen('HookFunction', win, 'Enable', 'StereoLeftCompositingBlit');
    % Append blitter for LR/UD flip:
    Screen('HookFunction', win, 'AppendBuiltin', 'StereoLeftCompositingBlit', 'Builtin:IdentityBlit', sprintf('Offset:%i:%i:Scaling:%f:%f', ox, oy, sx, sy));
    leftcount = leftcount + 1;

    clear curmap;
    [xg,yg] = meshgrid(hv, vv);
    curmap(:,:,1) = xg;
    curmap(:,:,2) = yg;
    ptb_geometry_inverseWarpMap{win}.(reqs{row, 1}) = int16(curmap);
end

if winfo.StereoMode > 0
    % Stereomode enabled: Need to possibly handle right channel as
    % well. In mono-mode there would be only a left channel...

    % Right channel flipping needed?
    if rightLRFlip || rightUDFlip
        % Yes.
        sx = 1;
        ox = 0;
        sy = 1;
        oy = 0;

        if rightLRFlip
            sx = -1;
            ox = RectWidth(InterBufferRect(win));
            hv = winwidth-1:-1:0;
        else
            hv = 0:winwidth-1;
        end

        if rightUDFlip
            sy = -1;
            oy = RectHeight(InterBufferRect(win));
            vv = winheight-1:-1:0;
        else
            vv = 0:winheight-1;
        end

        % Enable right imaging chain:
        Screen('HookFunction', win, 'Enable', 'StereoRightCompositingBlit');
        % Append blitter for LR/UD flip:
        Screen('HookFunction', win, 'AppendBuiltin', 'StereoRightCompositingBlit', 'Builtin:IdentityBlit', sprintf('Offset:%i:%i:Scaling:%f:%f', ox, oy, sx, sy));
        rightcount = rightcount + 1;

        clear curmap;
        [xg,yg] = meshgrid(hv, vv);
        curmap(:,:,1) = xg;
        curmap(:,:,2) = yg;
        ptb_geometry_inverseWarpMap{win}.(reqs{row, 1}) = int16(curmap);
    end
end

% --- End of the flipping stuff ---


% --- Implementation of CLUT animation via clut remapping of colors ---
floc = find(mystrcmp(reqs, 'EnableCLUTMapping'));
% Is a display mode on a CRS Bits+/Bits# or VPixx DataPixx/ViewPixx/ProPixx requested which requires use
% and setup of the devices hardware CLUT? If so we must turn 'EnableCLUTMapping' into a no-op, as it
% would clash with the hardware clut update - and is also superseded by it. Detect the namestrings of
% Bits++ CLUT palette display mode and Mono++ CLUT overlay palette mode. These Bits+ namestrings also
% cover VPixx devices due to the remapping of VPixx names into CRS reqs:
if ~isempty(find(mystrcmp(reqs, 'EnableBits++Bits++Output'))) || ~isempty(find(mystrcmp(reqs, 'EnableBits++Mono++OutputWithOverlay')))
    % Yep. We must no-op this 'EnableCLUTMapping' request:
    floc = [];
end
if ~isempty(floc)
    % Which channel?
    for x=floc
        [rows cols]= ind2sub(size(reqs), x);
        for row=rows'
            % Extract first parameter - This should be the number of clut slots:
            nClutSlots = reqs{row, 3};

            % Default to 256 slot clut, as most commonly used:
            if isempty(nClutSlots)
                nClutSlots = 256;
            end

            if ~isnumeric(nClutSlots)
                sca;
                error('PsychImaging: Number of clut slots parameter for ''EnableCLUTMapping'' missing or not of numeric type!');
            end

            % Extract high precision flag:
            highprec = reqs{row, 4};
            if isempty(highprec)
                highprec = 0;
            end

            % Use our reformatter shader for mapping RGB indices to RGB
            % triplets.

            % Load shader:
            pgshader = LoadGLSLProgramFromFiles('RGBMultiLUTLookupCombine_FormattingShader', 1);

            % Init the shader:
            glUseProgram(pgshader);

            % Assign mapping of input image and clut to texture units:
            glUniform1i(glGetUniformLocation(pgshader, 'Image'), 0);
            glUniform1i(glGetUniformLocation(pgshader, 'CLUT'),  1);

            % Assign number of clut slots to use:
            glUniform1f(glGetUniformLocation(pgshader, 'Prescale'), nClutSlots - 1);
            glUseProgram(0);

            % Use helper routine to build a proper RGBA lookup texture:
            pglutid = PsychHelperCreateRemapCLUT(0, nClutSlots, highprec);
            pgconfig = sprintf('TEXTURERECT2D(1)=%i', pglutid);

            % Setup the callback function which is only called if the clut
            % texture needs to be updated because
            % Screen('LoadNormalizedGammatable', ..., 2); was called to
            % provide a new clut. We attach this to the left image
            % processing chain, as this chain is almost always used anyway.
            % It needs to execute only once per flip, as it updates state
            % global to all views (in a stereo setup):
            rclutcmd = sprintf('PsychHelperCreateRemapCLUT(1, %i, IMAGINGPIPE_GAMMATABLE);', pglutid);
            Screen('HookFunction', win, 'AppendMFunction', 'StereoLeftCompositingBlit', 'Upload new clut into shader callback', rclutcmd);

            % Enable left chain unconditionally, so the above clut setup
            % code gets executed:
            Screen('HookFunction', win, 'Enable', 'StereoLeftCompositingBlit');

            % Attach shaders and slots to proper processing chain.
            % These perform the clut color conversion blit of each input
            % image into a transformed output image. They're executed at
            % each flip, irrespective if the clut changed or not:
            if mystrcmp(reqs{row, 1}, 'LeftView') || mystrcmp(reqs{row, 1}, 'AllViews')
                % Need to attach to left view:
                if leftcount > 0
                    % Need a bufferflip command:
                    Screen('HookFunction', win, 'AppendBuiltin', 'StereoLeftCompositingBlit', 'Builtin:FlipFBOs', '');
                end
                Screen('HookFunction', win, 'AppendShader', 'StereoLeftCompositingBlit', 'CLUT image transformation shader', pgshader, pgconfig);
                Screen('HookFunction', win, 'Enable', 'StereoLeftCompositingBlit');
                leftcount = leftcount + 1;
            end

            if mystrcmp(reqs{row, 1}, 'RightView') || (mystrcmp(reqs{row, 1}, 'AllViews') && winfo.StereoMode > 0)
                % Need to attach to right view:
                if rightcount > 0
                    % Need a bufferflip command:
                    Screen('HookFunction', win, 'AppendBuiltin', 'StereoRightCompositingBlit', 'Builtin:FlipFBOs', '');
                end
                Screen('HookFunction', win, 'AppendShader', 'StereoRightCompositingBlit', 'CLUT image transformation shader', pgshader, pgconfig);
                Screen('HookFunction', win, 'Enable', 'StereoRightCompositingBlit');
                rightcount = rightcount + 1;
            end
        end
    end
end
% --- End of CLUT animation via clut remapping of colors ---


% --- Addition of offsets / scales etc. to input image ---
floc = find(mystrcmp(reqs, 'AddOffsetToImage'));
if ~isempty(floc)
    % Which channel?
    for x=floc
        [rows cols]= ind2sub(size(reqs), x);
        for row=rows'
            % Extract first parameter - This should be the offset:
            PixelOffset = reqs{row, 3};

            if isempty(PixelOffset) || ~isnumeric(PixelOffset)
                sca;
                error('PsychImaging: Parameter for ''AddOffsetToImage'' missing or not of numeric type!');
            end

            % Further (optional) parameters passed?
            % 2nd parameter, if any, would be a gain value to apply before
            % applying the PixelOffset:
            PixelGain = reqs{row, 4};
            if isempty(PixelGain)
                % No such flag: Default to 1:
                PixelGain = 1;
            else
                if ~isnumeric(PixelGain)
                    sca;
                    error('PsychImaging: Optional Gain-Parameter for ''AddOffsetToImage'' not of numeric type!');
                end
            end

            % 3rd parameter, if any, would be an Offset value to apply before
            % applying the gain:
            PixelPreOffset = reqs{row, 5};
            if isempty(PixelPreOffset)
                % No such flag: Default to 0:
                PixelPreOffset = 0;
            else
                if ~isnumeric(PixelPreOffset)
                    sca;
                    error('PsychImaging: Optional "Offset before Gain"- PrescaleParameter for ''AddOffsetToImage'' not of numeric type!');
                end
            end

            % Load and build shader:
            shader = LoadGLSLProgramFromFiles('ScaleAndBiasShader', 1);

            % Init the shader: Assign mapping of input image and offsets, gains:
            glUseProgram(shader);

            glUniform1i(glGetUniformLocation(shader, 'Image'), 0);
            glUniform1f(glGetUniformLocation(shader, 'postscaleoffset'), PixelOffset);
            glUniform1f(glGetUniformLocation(shader, 'prescaleoffset'), PixelPreOffset);
            glUniform1f(glGetUniformLocation(shader, 'scalefactor'), PixelGain);

            glUseProgram(0);

            % Ok, 'gld' should contain a valid OpenGL display list for
            % geometry correction. Attach proper shader to proper chain:
            if mystrcmp(reqs{row, 1}, 'LeftView') || mystrcmp(reqs{row, 1}, 'AllViews')
                % Need to attach to left view:
                if leftcount > 0
                    % Need a bufferflip command:
                    Screen('HookFunction', win, 'AppendBuiltin', 'StereoLeftCompositingBlit', 'Builtin:FlipFBOs', '');
                end
                Screen('HookFunction', win, 'AppendShader', 'StereoLeftCompositingBlit', 'ScaleAndOffsetShader', shader);
                Screen('HookFunction', win, 'Enable', 'StereoLeftCompositingBlit');
                leftcount = leftcount + 1;
            end

            if mystrcmp(reqs{row, 1}, 'RightView') || (mystrcmp(reqs{row, 1}, 'AllViews') && winfo.StereoMode > 0)
                % Need to attach to right view:
                if rightcount > 0
                    % Need a bufferflip command:
                    Screen('HookFunction', win, 'AppendBuiltin', 'StereoRightCompositingBlit', 'Builtin:FlipFBOs', '');
                end
                Screen('HookFunction', win, 'AppendShader', 'StereoRightCompositingBlit', 'ScaleAndOffsetShader', shader);
                Screen('HookFunction', win, 'Enable', 'StereoRightCompositingBlit');
                rightcount = rightcount + 1;
            end

            if mystrcmp(reqs{row, 1}, 'FinalFormatting')
                % Need to attach to final formatting:
                if outputcount > 0
                    % Need a bufferflip command:
                    Screen('HookFunction', win, 'AppendBuiltin', 'FinalOutputFormattingBlit', 'Builtin:FlipFBOs', '');
                end
                Screen('HookFunction', win, 'AppendShader', 'FinalOutputFormattingBlit', 'ScaleAndOffsetShader', shader);
                Screen('HookFunction', win, 'Enable', 'FinalOutputFormattingBlit');
                outputcount = outputcount + 1;
            end
        end
    end
end
% --- End of addition of offsets / scales etc. to input image ---


% --- Geometry correction via warped blit ---
floc = find(mystrcmp(reqs, 'GeometryCorrection'));
if ~isempty(floc)
    % Which channel?
    for x=floc
        [rows cols]= ind2sub(size(reqs), x);
        for row=rows'
            % Extract first parameter - This should be the name of a
            % calibration file:
            calibfilename = reqs{row, 3};

            if isempty(calibfilename)
                sca;
                error('PsychImaging: Parameter for ''GeometryCorrection'' missing!');
            end

            % Is 'calibfilename' a function handle or a final warpstruct?
            if (~isstruct(calibfilename) && ~ischar(calibfilename)) || ...
               (isstruct(calibfilename) && isfield(calibfilename, 'gld') && isfield(calibfilename, 'glsl'))
                % Functionhandle or final warpstruct passed: This
                % assignment will either assign the warpstruct, or call the
                % function referenced by the functionhandle and assign the
                % returned warpstruct:
                if ~isstruct(calibfilename)
                    [warpstruct, filterMode] = calibfilename();
                else
                    warpstruct = calibfilename;
                    filterMode = ':Bilinear';
                end
            else
                % Either calibration input parameter struct, or filename of
                % calibration file: Just pass it to CreateDisplayWarp(),
                % after some parameter validation:
                if ischar(calibfilename) && ~exist(calibfilename, 'file')
                    sca;
                    error('PsychImaging: Passed an argument to ''GeometryCorrection'' which is not a valid name of an accessible calibration file!');
                end

                % Filename or calibstruct valid. Further (optional) parameters passed?
                % 2nd parameter, if any, would be a 'visualize' flag that
                % asks for plotting of some calibration info and additional
                % output to the console:
                showCalibOutput = reqs{row, 4};
                if isempty(showCalibOutput)
                    % No such flag: Default to "silence":
                    showCalibOutput = 0;
                end

                % Additional parameters provided? Pass 'em along. Currently
                % defined are up to additional 6 parameters 5 to 10. These
                % default to empty if not provided by user-code.

                % Use helper function to read the calibration file or
                % parameter struct and build a proper warp-function:
                [warpstruct, filterMode] = CreateDisplayWarp(win, calibfilename, showCalibOutput, reqs{row, 5:10});
            end

            % Is it a display list handle?
            if ~isempty(warpstruct.gld)
                % This must be a display list handle for display list
                % blitting, potentially with an additional GLSL shader
                % attached:
                gld = warpstruct.gld;
                if ~glIsList(gld)
                    % Game over:
                    sca;
                    error('PsychImaging: Passed a handle to ''GeometryCorrection'' which is not a valid OpenGL display list!');
                end

                if ~isempty(warpstruct.glsl)
                    glsl = warpstruct.glsl;
                else
                    glsl = 0;
                end

                % Ok, 'gld' should contain a valid OpenGL display list for
                % geometry correction.

                % Before we setup the image warping ops for real in the pipeline, we
                % do a "cold run" to compute a 2D reverse lookup table that allows to
                % map warped 2D screen positions back to their originating pre-warp pixels.
                % This is useful, e.g., if one wants to map 2D mouse click
                % positions on the geometry corrected display back to the
                % originating pixel positions in the uncorrected stimulus
                % image.
                %
                % This works by creating a float texture whose texels
                % color-code their spatial (x,y) locations in the R and G
                % channels, then warping this texture with the same
                % operations that the GPU will apply to the stimulus
                % images, then reading back the warp-blitted texture into a
                % 2-layer 2D matrix, where layer 1 (former red channel)
                % encodes originating x-position of each "pixel", layer 2
                % encodes y-position, Undefined positions are mapped to (0,0):

                % At least 32 bpc float or 16 bit snorm textures/fbo's
                % supported? Otherwise this is a no-go:
                if (winfo.GLSupportsTexturesUpToBpc >= 32) || ~isempty(strfind(glGetString(GL.EXTENSIONS), '_texture_snorm'))
                    % Yes.

                    % Check if previous code already defined some inverse
                    % mapping:
                    if ~isempty(ptb_geometry_inverseWarpMap{win}) && isfield(ptb_geometry_inverseWarpMap{win}, reqs{row, 1})
                        % Yes: Extract it and use it as starting point for
                        % geometry inverse mapping:
                        premap = double(ptb_geometry_inverseWarpMap{win}.(reqs{row, 1}));
                        xg = premap(:,:,1);
                        yg = premap(:,:,2);
                    else
                        % No: Create a default identity mapping as starting
                        % point:
                        [xg,yg] = meshgrid(0:winwidth-1, 0:winheight-1);
                    end

                    % Need to use snorm 16 bit textures because 32 bpc
                    % float textures unavailable?
                    invmap_needs_snorm = (winfo.GLSupportsTexturesUpToBpc < 32);

                    % We always normalize to range 0..1, so it works for
                    % both floating point textures and 16 bit snorm
                    % textures:
                    inmap = zeros(winheight, winwidth, 3);
                    inmap(:,:,1) = xg / winwidth;
                    inmap(:,:,2) = yg / winheight;

                    if invmap_needs_snorm
                        % Need to use 16 bit snorm textures. We request 16
                        % bit floating point precision on this hw that
                        % doesn't support it, but does support 16 bit
                        % snorm. Screen() will choose 16 bit snorm as
                        % fallback, so we get what we want and can properly
                        % process mappings for up to 32k x 32k pixels aka 1
                        % Gigapixel:
                        premaptex = Screen('MakeTexture', win, inmap, [], [], 1);
                        postmaptex = Screen('OpenOffscreenWindow', win, 0, Screen('Rect', premaptex), 64);
                    else
                        % We have 32 bpc float texture support: Use it.
                        premaptex = Screen('MakeTexture', win, inmap, [], [], 2);
                        postmaptex = Screen('OpenOffscreenWindow', win, 0, Screen('Rect', premaptex), 128);
                    end
                    warpoperator = CreateGLOperator(win);
                    AddImageUndistortionToGLOperator(warpoperator, premaptex, warpstruct);
                    postmaptex = Screen('TransformTexture', premaptex, warpoperator, [], postmaptex);
                    glerr = glGetError;
                    if glerr
                        % We get this error on some NVidia binary blob graphics driver on Linux, e.g., v295.49. Swallow it, it seems to cause no consequences:
                        fprintf('PsychImaging: GeometryCorrection: Spurious benign gl error [%s] after computing postmap texture detected.\n', gluErrorString(glerr));
                    end
                    curmap = Screen('GetImage', postmaptex, [], [], 1, 3);
                    Screen('Close', [premaptex, postmaptex, warpoperator]);
                    curmap(:,:,1) = curmap(:,:,1) * winwidth;
                    curmap(:,:,2) = curmap(:,:,2) * winheight;
                    curmap = round(curmap(:,:,1:2));

                    % Assign inverse warp mapping tables for selected view. We
                    % assume that 16 bit signed integer is enough - Can cope
                    % with a framebuffer of up to 32768 * 32768 pixels.
                    % ptb_geometry_inverseWarpMap{} is a global variable shared
                    % with the RemapMouse() functions that uses these mapping
                    % matrices:
                    ptb_geometry_inverseWarpMap{win}.(reqs{row, 1}) = int16(curmap);
                else
                    % No: Cannot create remap textures at required
                    % precision, inverse mapping won't work:
                    fprintf('PsychImaging GeometryCorrection:Warning: GPU does not support features needed for RemapMouse() command.\n');
                end

                % Setup imaging pipeline - Attach proper blitters to proper chains:
                if mystrcmp(reqs{row, 1}, 'LeftView') || mystrcmp(reqs{row, 1}, 'AllViews')
                    % Need to setup left view warp:
                    if leftcount > 0
                        % Need a bufferflip command:
                        Screen('HookFunction', win, 'AppendBuiltin', 'StereoLeftCompositingBlit', 'Builtin:FlipFBOs', '');
                    end

                    % Must clear target buffer, because a geometrically
                    % warped blit for geometry correction may not cover the
                    % whole buffer area, and "uninitialized pixel trash"
                    % may shine through otherwise:
                    Screen('Hookfunction', win, 'AppendMFunction', 'StereoLeftCompositingBlit', 'Clear target buffer', 'glClear(16384);');

                    if glsl
                        Screen('HookFunction', win, 'AppendShader', 'StereoLeftCompositingBlit', 'GeometricWarpShader', glsl, sprintf('Blitter:DisplayListBlit:Handle:%i%s', gld, filterMode));
                    else
                        Screen('HookFunction', win, 'AppendBuiltin', 'StereoLeftCompositingBlit', 'Builtin:IdentityBlit', sprintf('Blitter:DisplayListBlit:Handle:%i%s', gld, filterMode));
                    end
                    Screen('HookFunction', win, 'Enable', 'StereoLeftCompositingBlit');
                    leftcount = leftcount + 1;
                end

                if mystrcmp(reqs{row, 1}, 'RightView') || (mystrcmp(reqs{row, 1}, 'AllViews') && winfo.StereoMode > 0)
                    % Need to setup right view warp:
                    if rightcount > 0
                        % Need a bufferflip command:
                        Screen('HookFunction', win, 'AppendBuiltin', 'StereoRightCompositingBlit', 'Builtin:FlipFBOs', '');
                    end

                    Screen('Hookfunction', win, 'AppendMFunction', 'StereoRightCompositingBlit', 'Clear target buffer', 'glClear(16384);');

                    if glsl
                        Screen('HookFunction', win, 'AppendShader', 'StereoRightCompositingBlit', 'GeometricWarpShader', glsl, sprintf('Blitter:DisplayListBlit:Handle:%i%s', gld, filterMode));
                    else
                        Screen('HookFunction', win, 'AppendBuiltin', 'StereoRightCompositingBlit', 'Builtin:IdentityBlit', sprintf('Blitter:DisplayListBlit:Handle:%i%s', gld, filterMode));
                    end
                    Screen('HookFunction', win, 'Enable', 'StereoRightCompositingBlit');
                    rightcount = rightcount + 1;
                end

                if mystrcmp(reqs{row, 1}, 'FinalFormatting')
                    % Need to setup final formatting warp:
                    if outputcount > 0
                        % Need a bufferflip command:
                        Screen('HookFunction', win, 'AppendBuiltin', 'FinalOutputFormattingBlit', 'Builtin:FlipFBOs', '');
                    end

                    Screen('Hookfunction', win, 'AppendMFunction', 'FinalOutputFormattingBlit', 'Clear target buffer', 'glClear(16384);');

                    if glsl
                        Screen('HookFunction', win, 'AppendShader', 'FinalOutputFormattingBlit', 'GeometricWarpShader', glsl, sprintf('Blitter:DisplayListBlit:Handle:%i%s', gld, filterMode));
                    else
                        Screen('HookFunction', win, 'AppendBuiltin', 'FinalOutputFormattingBlit', 'Builtin:IdentityBlit', sprintf('Blitter:DisplayListBlit:Handle:%i%s', gld, filterMode));
                    end
                    Screen('HookFunction', win, 'Enable', 'FinalOutputFormattingBlit');
                    outputcount = outputcount + 1;
                end
            else
                % Game over:
                sca;
                error('PsychImaging: Passed a handle for a not yet implemented display undistortion method!');
            end
        end
    end
end
% --- End of geometry correction via warped blit ---

% --- Interleaved line stereo wanted? ---
if ~isempty(find(mystrcmp(reqs, 'InterleavedLineStereo')))
    % Yes: Load and setup compositing shader.
    shader = LoadGLSLProgramFromFiles('InterleavedLineStereoShader', 1);

    floc = find(mystrcmp(reqs, 'InterleavedLineStereo'));
    [rows cols]= ind2sub(size(reqs), floc);
    % Extract first parameter - This should be the mapping of odd- and even
    % lines: 0 = even lines == left image, 1 = even lines == right image.
    startright = reqs{rows, 3};

    if startright~=0 && startright~=1
        sca;
        error('PsychImaging: The "startright" parameter must be zero or one!');
    end

    % Init the shader: Assign mapping of left- and right image:
    glUseProgram(shader);
    glUniform1i(glGetUniformLocation(shader, 'Image1'), 1-startright);
    glUniform1i(glGetUniformLocation(shader, 'Image2'), startright);

    glUniform2f(glGetUniformLocation(shader, 'Offset'), 0, 0);
    glUseProgram(0);

    % Reset compositor chain: It got initialized inside Screen() with an
    % unsuitable shader for our purpose:
    Screen('HookFunction', win, 'Reset', 'StereoCompositingBlit');

    % Append our new shader and enable chain:
    Screen('HookFunction', win, 'AppendShader', 'StereoCompositingBlit', 'StereoCompositingShaderInterleavedLineStereo', shader, 'Blitter:IdentityBlit:Offset:0:0:Scaling:1.0:2.0');
    Screen('HookFunction', win, 'Enable', 'StereoCompositingBlit');

    % Correct mouse position via proper gain:
    ptb_geometry_inverseWarpMap{win}.gy = ptb_geometry_inverseWarpMap{win}.gy * 0.5;
end
% --- End of interleaved line stereo setup code ---

% --- Interleaved column stereo wanted? ---
if ~isempty(find(mystrcmp(reqs, 'InterleavedColumnStereo')))
    % Yes: Load and setup compositing shader.
    shader = LoadGLSLProgramFromFiles('InterleavedColumnStereoShader', 1);

    floc = find(mystrcmp(reqs, 'InterleavedColumnStereo'));
    [rows cols]= ind2sub(size(reqs), floc);
    % Extract first parameter - This should be the mapping of odd- and even
    % columns: 0 = even cols == left image, 1 = even cols == right image.
    startright = reqs{rows, 3};

    if startright~=0 && startright~=1
        sca;
        error('PsychImaging: The "startright" parameter must be zero or one!');
    end

    % Init the shader: Assign mapping of left- and right image:
    glUseProgram(shader);
    glUniform1i(glGetUniformLocation(shader, 'Image1'), 1-startright);
    glUniform1i(glGetUniformLocation(shader, 'Image2'), startright);

    glUniform2f(glGetUniformLocation(shader, 'Offset'), 0, 0);
    glUseProgram(0);

    % Reset compositor chain: It got initialized inside Screen() with an
    % unsuitable shader for our purpose:
    Screen('HookFunction', win, 'Reset', 'StereoCompositingBlit');

    % Append our new shader and enable chain:
    Screen('HookFunction', win, 'AppendShader', 'StereoCompositingBlit', 'StereoCompositingShaderInterleavedColumnStereo', shader, 'Blitter:IdentityBlit:Offset:0:0:Scaling:2.0:1.0');
    Screen('HookFunction', win, 'Enable', 'StereoCompositingBlit');

    % Correct mouse position via proper gain:
    ptb_geometry_inverseWarpMap{win}.gx = ptb_geometry_inverseWarpMap{win}.gx * 0.5;
end
% --- End of interleaved column stereo setup code ---

% --- SideBySideCompressedStereo wanted? ---
if ~isempty(find(mystrcmp(reqs, 'SideBySideCompressedStereo')))
    % Yes: Call external setup routine with its default parameters to
    % modify our default "stereomode 2" top-bottom compressed stereo
    % shader, which was automatically generated by Screen('Openwindow'),
    % into a left-right side-by-side compressed shader.
    SetCompressedStereoSideBySideParameters(win);

    % Correct mouse position via proper gain:
    % Need to apply a 2x gain to horizontal cursor position to compensate
    % for horizontal compression...
    ptb_geometry_inverseWarpMap{win}.gx = ptb_geometry_inverseWarpMap{win}.gx * 2;
    % ... need to undo the 2x gain automatically applied at the top of this
    % function when a stereomode of 2 is used, as we do to implement our
    % stereo method:
    ptb_geometry_inverseWarpMap{win}.gy = ptb_geometry_inverseWarpMap{win}.gy / 2;
end
% --- End of SideBySideCompressedStereo setup code ---

% --- "Mouse" remapping needed for half-width Color++ or C48 mode? ---
if ~isempty(find(mystrcmp(reqs, 'EnableBits++Color++Output')))
    floc = find(mystrcmp(reqs, 'EnableBits++Color++Output'));
    [rows cols] = ind2sub(size(reqs), floc(1));
    row = rows(1);
    % Extract first parameter - This should be the colorConversionMode:
    colorConversionMode = reqs{row, 3};

    % Only mode 0 needs remapping:
    if colorConversionMode == 0
        % Correct mouse position via proper gain:
        ptb_geometry_inverseWarpMap{win}.gx = ptb_geometry_inverseWarpMap{win}.gx * 0.5;
    end
end

% --- Custom color correction for display wanted? ---
%
% This *MUST* be immediately before the final output formatters for
% special display devices. If this is done in the output conversion chain
% it must be the last corrective operation before data is fed into the
% formatter plugins. If it is applied to the image processing chains for
% stereo display setups, it must be the absolutely last operation in that
% processing chains before data is fed into output conversion or into the
% stereo compositor.
%
% If we need per view correction for any stereo output mode except
% anaglyph stereo, it needs to happen at end of per view pipeline, so
% things like gamma-correction are applied to final stims, not
% intermediate results. In any other case, there will be only one physical
% output device, so correction is handled best at the end of output
% conversion.
icmshader = [];
icmstring = [];
icmconfig = [];
icmformatting_downstream = 0;

floc = find(mystrcmp(reqs, 'DisplayColorCorrection'));
if ~isempty(floc)
    numColorCorrections = length(floc);

    handlebitspluplus = 0;

    % Bits+ Mono++ or Color++ mode active?
    if ~isempty(find(mystrcmp(reqs, 'EnableBits++Mono++Output'))) || ~isempty(find(mystrcmp(reqs, 'EnableBits++Mono++OutputWithOverlay'))) || ~isempty(find(mystrcmp(reqs, 'EnableBits++Color++Output')))
        % Only one 'DisplayColorCorrection' plugin in the whole pipeline?
        if length(floc) == 1
            % Yes: Nothing to do. Full setup for that single plugin has
            % been already done inside our OpenWindow routine. The single
            % plugin has been merged as downstream formatter into the
            % Bits++ output formatting shader via special setup code inside
            % BitsPlusPlus() driver M-File.
            floc = [];
            handlebitspluplus=0;
        else
            % No: No downstream formatting for Bits++ possible whatsoever:
            % Need to do our setup work -- The Bitsplus output formatter
            % just contains a simple neutral clamping shader. However, we
            % need to be careful where to insert our shader(s) if the target
            % is the output conversion chain, as the last slot of that
            % chain is already occupied by the Bits++ shader.
            handlebitspluplus=1;
        end
    end

    % Which channel?
    for x=floc
        [rows cols]= ind2sub(size(reqs), x);
        for row=rows'
            % Extract first parameter - This should be the method of correction:
            colorcorrectionmethod = reqs{row, 3};

            if isempty(colorcorrectionmethod) || ~ischar(colorcorrectionmethod)
                sca;
                error('PsychImaging: Name of color correction method for ''DisplayColorCorrection'' missing or not of string type!');
            end

            % Select method:
            PsychColorCorrection('ChooseColorCorrection', colorcorrectionmethod);

            % Load and build shader objects: icmshader is the compiled
            % color correction shader:
            [icmshader icmstring icmconfig icmoverrideMain] = PsychColorCorrection('GetCompiledShaders', win, 1);

            % Output formatter with built-in ICM capabilities selected? And
            % color correction for final formatting chain insted of
            % per-viewchannel chains?
            if (ptb_outputformatter_icmAware > 0) && (numColorCorrections == 1) && isempty(icmoverrideMain) && (mystrcmp(reqs{row, 1}, 'FinalFormatting') || mystrcmp(reqs{row, 1}, 'AllViews'))
                % Yes. These formatters can use the icm shader internally for
                % higher efficiency if wanted. We can only do that if color
                % correction shall happen in 'AllViews' or 'FinalFormatting', ie.,
                % if this is a monoscopic window or a stereo window where all views
                % display to the same physical output device and therefore the same
                % color correction can be applied to both views.
                %
                % Additionally there must be only 1 color correction stage be present,
                % as multiple stages could can't be done downstream.
                %
                % Additionally there must be no need for a non-standard
                % main() routine for color correction shader.

                % Good. We create the icmshader here according to specs,
                % but then pass it along downstream to the output formatter
                % setup code which will attach it.
                icmformatting_downstream = 1;

            else
                % Downstream color correction not possible due to use of
                % either a per viewchannel correction, or due to use of
                % either no output formatter at all, or not of an icm aware
                % one, or because multi-pass color correction needed, or
                % non-standard main routine needed:
                icmformatting_downstream = 0;

                % Need to build full standalone shader, including main()
                % stub routine and full link and post-link:
                if isempty(icmoverrideMain)
                    % No special override main routine provided. Use our
                    % standard one:
                    shBody = 'uniform sampler2DRect Image; vec4 icmTransformColor(vec4 incolor); void main(void){gl_FragColor = icmTransformColor(texture2DRect(Image, gl_FragCoord.xy));}';
                else
                    % Use provided override routine from
                    % PsychColorCorrection():
                    shBody = icmoverrideMain;
                end

                % shMain is the main() routine which needs to get compiled into
                % a valid shader object:
                shMain = sprintf('\n#extension GL_ARB_texture_rectangle : enable \n\n%s', shBody);
                mainShader = glCreateShader(GL.FRAGMENT_SHADER);
                glShaderSource(mainShader, shMain);
                glCompileShader(mainShader);

                % Link together mainShader and icmshader into a GLSL program
                % object:
                shader = glCreateProgram;
                glAttachShader(shader, icmshader);
                glAttachShader(shader, mainShader);

                % Link the program:
                glLinkProgram(shader);

                % Init the shader: Assign mapping of input image and offsets, gains:
                glUseProgram(shader);
                glUniform1i(glGetUniformLocation(shader, 'Image'), 0);
                glUseProgram(0);
            end

            if ~icmformatting_downstream
                % Ok, shader is our final color correction shader, properly
                % setup. Attach it to proper chain:
                % MK Resolved 26.4.2010: HACK FIXME BUG: 'AllViews' -> Move back to
                % 'FinalFormatting' below, once Screens() pipeline is
                % fixed!!
                if mystrcmp(reqs{row, 1}, 'LeftView') %|| mystrcmp(reqs{row, 1}, 'AllViews')
                    % Need to attach to left view:
                    if leftcount > 0
                        % Need a bufferflip command:
                        Screen('HookFunction', win, 'AppendBuiltin', 'StereoLeftCompositingBlit', 'Builtin:FlipFBOs', '');
                    end
                    Screen('HookFunction', win, 'AppendShader', 'StereoLeftCompositingBlit', icmstring, shader, icmconfig);
                    Screen('HookFunction', win, 'Enable', 'StereoLeftCompositingBlit');
                    leftcount = leftcount + 1;
                end

                if mystrcmp(reqs{row, 1}, 'RightView')
                    % Need to attach to right view:
                    if rightcount > 0
                        % Need a bufferflip command:
                        Screen('HookFunction', win, 'AppendBuiltin', 'StereoRightCompositingBlit', 'Builtin:FlipFBOs', '');
                    end
                    Screen('HookFunction', win, 'AppendShader', 'StereoRightCompositingBlit', icmstring, shader, icmconfig);
                    Screen('HookFunction', win, 'Enable', 'StereoRightCompositingBlit');
                    rightcount = rightcount + 1;
                end

                % MK Resolved 26.4.2010: HACK FIXME BUG: 'AllViews' -> Move back to
                % 'FinalFormatting' below, once Screens() pipeline is
                % fixed!!
                if mystrcmp(reqs{row, 1}, 'FinalFormatting') || mystrcmp(reqs{row, 1}, 'AllViews')
                    % Need to attach to final formatting:
                    if ~handlebitspluplus
                        % Standard case:
                        if outputcount > 0
                            % Need a bufferflip command:
                            Screen('HookFunction', win, 'AppendBuiltin', 'FinalOutputFormattingBlit', 'Builtin:FlipFBOs', '');
                        end
                        Screen('HookFunction', win, 'AppendShader', 'FinalOutputFormattingBlit', icmstring, shader, icmconfig);
                    else
                        % Special case: A BitsPlusPlus output formatter has
                        % been attached at the end of queue already. We need
                        % to insert our new slot + some FlipFBO commands just
                        % before the last occupied slot - which is the output formatter slot.
                        % Let's simply count the number of occupied slots and
                        % then insert at that location:
                        insertPos = 0;
                        while(1)
                            if Screen('Hookfunction', win, 'Query', 'FinalOutputFormattingBlit', insertPos)~=-1
                                insertPos = insertPos + 1;
                            else
                                break;
                            end
                        end
                        % insertPos points to first slot after the end of the
                        % chain, ie., where one could append new slots. We want
                        % to insert just at the location of the last slot, so
                        % the last slot gets pushed back one element:
                        insertPos = insertPos - 1;

                        % This insertPos >= 0 check makes sure we also work
                        % in some dummy emulation modes, where no
                        % actual slot is attached:
                        if insertPos >= 0
                            % Need to prepend a bufferflip command in front of
                            % Bitsplusplus:
                            insertSlot = sprintf('InsertAt%iBuiltin', insertPos);
                            Screen('HookFunction', win, insertSlot, 'FinalOutputFormattingBlit', 'Builtin:FlipFBOs', '');
                        else
                            % No real output formatter due to emulation
                            % mode, if any.
                            % Force insertPos to 0, so at least
                            % colorcorrection applies:
                            insertPos = 0;
                        end

                        % Then need to prepend our shader in front of that
                        % FlipFBO's:
                        insertSlot = sprintf('InsertAt%iShader', insertPos);
                        Screen('HookFunction', win, insertSlot, 'FinalOutputFormattingBlit', icmstring, shader, icmconfig);

                        % If we're not the first, we need to prepend a
                        % FlipFBO's for ourselves, unless there is already
                        % such a command at the current insertPos:
                        if outputcount > 0
                            % Need to test slot right before us:
                            insertPos = insertPos - 1;

                            % Test what's there at the moment:
                            [dummy testNameString ] = Screen('HookFunction', win, 'Query', 'FinalOutputFormattingBlit', insertPos);
                            if (dummy == - 1) || ~mystrcmp(testNameString, 'Builtin:FlipFBOs')
                                % Need a bufferflip command:
                                insertSlot = sprintf('InsertAt%iBuiltin', insertPos);
                                Screen('HookFunction', win, insertSlot, 'FinalOutputFormattingBlit', 'Builtin:FlipFBOs', '');
                            end
                        end
                    end

                    % One more slot occupied by us, so increment
                    % outputcount:
                    outputcount = outputcount + 1;

                    % And enable the chain if it ain't enabled already:
                    Screen('HookFunction', win, 'Enable', 'FinalOutputFormattingBlit');
                end

                % Perform post-link setup of color correction method after
                % shader attached to pipe:
                PsychColorCorrection('ApplyPostGLSLLinkSetup', win, reqs{row, 1});

                % Screen('HookFunction', win, 'Dump', 'FinalOutputFormattingBlit');
            end
        end
    end
end

% Any output formatter to follow which is icmAware, ie., needs to have an
% icmshader as input, either a real one, or a dummy pass-through one?
if ptb_outputformatter_icmAware
    % Yes. To be created output formatter needs an icmshader.
    % Downstream attachment of (already created) icmshader?
    % If so, nothing to do, icmshader and icmstring already setup:
    if ~icmformatting_downstream
        % No. The output formatter is icm aware and needs an icmshader, but
        % none yet created because downstream correction not possible. We
        % need to create a dummy icmshader which just passes through all
        % values uncorrected - This way we make sure that the link
        % operation of the output formatter doesn't fail:
        icmshader = LoadShaderFromFile('ICMPassThroughShader.frag.txt', [], 1);
        icmstring = '';
        icmconfig = '';
    else
        % Nothing to do. Just perform some sanity check here to catch
        % possible future implementation bugs:
        if isempty(icmshader) || isempty(icmstring)
            error('In DisplayColorCorrection setup: Downstream formatting for icmAware output formatter requested, but icmshader and/or icmstring undefined! This is an implementation bug!!!');
        end
    end
end

% --- End of Custom color correction for display wanted ---

% --- User code wants to use unclamped, high precision 0-1 range colors? ---
if ~isempty(find(mystrcmp(reqs, 'NormalizedHighresColorRange')))
    % Use unit color range, without clamping, but in high-precision mode:
    needsUnitUnclampedColorRange = 1;

    % Extract first parameter - This should be the applyAlsoToMakeTexture flag:
    floc = find(mystrcmp(reqs, 'NormalizedHighresColorRange'));
    [rows cols] = ind2sub(size(reqs), floc(1));
    row = rows(1);
    applyAlsoToMakeTexture = reqs{row, 3};
    if ~isempty(applyAlsoToMakeTexture)
        if ~isnumeric(applyAlsoToMakeTexture) || ~ismember(applyAlsoToMakeTexture, [0, 1])
            sca;
            error('In NormalizedHighresColorRange: Invalid applyAlsoToMakeTexture flag specified. Must be 0 or 1.');
        end
    else
        applyAlsoToMakeTexture = [];
    end
end
% --- End of setup for unclamped, high precision 0-1 range colors ---

% --- Native 16 bit per color component RGBA16F floating point framebuffer requested? ---
if ~isempty(find(mystrcmp(reqs, 'EnableNative16BitFloatingPointFramebuffer')))
    % Use unit color range, without clamping, but in high-precision mode:
    needsUnitUnclampedColorRange = 1;
end
% --- End of Native 16 bit per color component RGBA16F floating point framebuffer setup ---

% --- Setup stereo crosstalk reduction ---
floc = find(mystrcmp(reqs, 'StereoCrosstalkReduction'));
if ~isempty(floc)
    if winfo.StereoMode == 0
        sca;
        error('PsychImaging task ''StereoCrosstalkReduction'' requested, but no suitable stereomode active?! Aborted.');
    end

    % Which channel?
    for x=floc
        [rows cols]= ind2sub(size(reqs), x);
        for row=rows'
            crosstalkMethod = reqs{row, 3};
            if isempty(crosstalkMethod) || ~strcmpi(crosstalkMethod, 'SubtractOther')
                sca;
                error('In StereoCrosstalkReduction: Crosstalk reduction method parameter missing or unsupported method requested.');
            end

            crosstalkGain = reqs{row, 4};
            if isempty(crosstalkGain)
                sca;
                error('In StereoCrosstalkReduction: The crosstalk reduction gain must be provided.');
            end

            if isscalar(crosstalkGain)
                % Same gain for all three color channels:
                crosstalkGain = [crosstalkGain crosstalkGain crosstalkGain];
            else
                if numel(crosstalkGain)~=3
                    sca;
                    error('In StereoCrosstalkReduction: provided gain should be a scalar or a 3-element vector.');
                end
            end

            % Background clear color as specified by PsychImaging('Openwindow', ...) call is reference for
            % zero-contrast:
            crosstalkBackGroundClr = clearcolor;
            if isempty(crosstalkBackGroundClr) || ~isnumeric(crosstalkBackGroundClr)
                sca;
                error('In StereoCrosstalkReduction: You did not provide the mandatory background clear color for crosstalk reduction in ''OpenWindow''.');
            end

            if isscalar(crosstalkBackGroundClr)
                % Same background luminance level for all three color channels:
                crosstalkBackGroundClr = [crosstalkBackGroundClr, crosstalkBackGroundClr, crosstalkBackGroundClr];
            else
                if numel(crosstalkBackGroundClr) < 3
                    sca;
                    error('In StereoCrosstalkReduction: Provided background clear color should be a scalar or an at least 3-element RGB(A) vector.');
                end
                crosstalkBackGroundClr = crosstalkBackGroundClr(1:3);
            end

            if min(crosstalkBackGroundClr) <= 0 || max(crosstalkBackGroundClr) >= 1
                sca;
                error('In StereoCrosstalkReduction: Provided background clear color is not in the normalized range > 0 and < 1 as required.');
            end

            % Load and build shader from files StereoCrosstalkReductionShader.vert.txt and/or
            % StereoCrosstalkReductionShader.frag.txt in the shader directory:
            shader = LoadGLSLProgramFromFiles('StereoCrosstalkReductionShader', 1);

            % Init the shader: Assign mapping of images:
            glUseProgram(shader);

            % Image1 will contain the input image for the currently processed target eye:
            glUniform1i(glGetUniformLocation(shader, 'Image1'), 0);

            % Image2 will contain the input image for the to-be-suppressed other eye:
            glUniform1i(glGetUniformLocation(shader, 'Image2'), 1);

            % Just as example. Assign scalar float parameter crosstalkReductionParameter1 to the
            % shader variable 'uniform float crossTalkParam1' for use as a input constant in shader:
            glUniform3fv(glGetUniformLocation(shader, 'crosstalkGain'), 1, crosstalkGain);
            glUniform3fv(glGetUniformLocation(shader, 'backGroundClr'), 1, crosstalkBackGroundClr);

            % Shader setup done:
            glUseProgram(0);

            % Setup specific to left eye output:
            if mystrcmp(reqs{row, 1}, 'LeftView') || mystrcmp(reqs{row, 1}, 'AllViews')
                % Need to attach to left view:
                if leftcount > 0
                    % Need a bufferflip command:
                    Screen('HookFunction', win, 'PrependBuiltin', 'StereoLeftCompositingBlit', 'Builtin:FlipFBOs', '');
                end
                Screen('HookFunction', win, 'PrependShader', 'StereoLeftCompositingBlit', 'StereoCrosstalkReductionShader', shader);
                Screen('HookFunction', win, 'Enable', 'StereoLeftCompositingBlit');
                leftcount = leftcount + 1;
            end

            % Setup specific to right eye output:
            if mystrcmp(reqs{row, 1}, 'RightView') || mystrcmp(reqs{row, 1}, 'AllViews')
                % Need to attach to right view:
                if rightcount > 0
                    % Need a bufferflip command:
                    Screen('HookFunction', win, 'PrependBuiltin', 'StereoRightCompositingBlit', 'Builtin:FlipFBOs', '');
                end
                Screen('HookFunction', win, 'PrependShader', 'StereoRightCompositingBlit', 'StereoCrosstalkReductionShader', shader);
                Screen('HookFunction', win, 'Enable', 'StereoRightCompositingBlit');
                rightcount = rightcount + 1;
            end
        end
    end
end
% --- End of setup for stereo crosstalk reduction ---

% --- Custom processing setup for the stereo compositor ---

% --- VR Headset support ---
floc = find(mystrcmp(reqs, 'UseVRHMD'));
if ~isempty(floc)
    [row col] = ind2sub(size(reqs), floc);

    % Extract first parameter - This should be the handle of the VR device:
    hmd = reqs{row, 3};

    % Verify it is already open:
    if ~hmd.driver('IsOpen', hmd)
        sca;
        error('In UseVRHMD: Invalid HMD handle specified. No such device opened.');
    end

    % Ok, perform setup after onscreen window is open, e.g., setting up the special
    % shaders for the stereo compositor:
    if ~hmd.driver('PerformPostWindowOpenSetup', hmd, win, clearcolor)
        sca;
        error('In UseVRHMD: Failed to setup image post-processing for the VR HMD.');
    end

    % Ready to rock the HMD!
end
% --- End of VR Headset support code. ---


% --- FROM HERE ON ONLY OUTPUT FORMATTERS, NOTHING ELSE!!! --- %


% --- Final output formatter for generic LUT based luminance framebuffer requested? ---
% --- OR Final output formatter for Pseudo-Gray processing requested? ---
floc = find(mystrcmp(reqs, 'EnableGenericHighPrecisionLuminanceOutput'));
if isempty(floc)
    floc = find(mystrcmp(reqs, 'EnablePseudoGrayOutput'));
end
if ~isempty(floc)
    [row col]= ind2sub(size(reqs), floc);

    % Get native depth in bits per color (bpc) of active framebuffer:
    nativeBPC = Screen('Pixelsize', win) / 3;

    if mystrcmp(reqs{row, 2}, 'EnablePseudoGrayOutput')
        % PseudoGray mode: We create the lut ourselves via helper function:
        lut = CreatePseudoGrayLUT(nativeBPC);

        % For proper pseudo-gray output the gfx gamma-tables must not be
        % touched by us!
        needsIdentityCLUT = 0;
    else
        % Generic "attenuator" driver mode:

        % Attenuator-style devices need an identity clut:
        needsIdentityCLUT = 1;

        % Extract first parameter - This should be the lookup table 'lut' to use:
        lut = reqs{row, 3};
    end

    if isempty(lut) || ~isnumeric(lut)
        sca;
        error('PsychImaging: Mandatory lookup table parameter lut for ''%s'' missing or not of numeric type!', reqs{floc});
    end

    % Load output formatting shader for GenericHighPrecisionLuminanceOutput:
    % 'icmshader' is a handle to a compiled fragment shader, provided by
    % upstream, that implements the display color correction function:
    pgshader = LoadGLSLProgramFromFiles('GenericLuminanceToRGBA8_FormattingShader', 1, icmshader);

    % Init the shader: Assign mapping texture units etc.:
    glUseProgram(pgshader);
    glUniform1i(glGetUniformLocation(pgshader, 'Image'), 0);
    glUniform1i(glGetUniformLocation(pgshader, 'LUT'),  1);
    glUniform1f(glGetUniformLocation(pgshader, 'MaxIndex'),  size(lut, 2)-1);
    glUseProgram(0);

    % Use helper routine to build a proper RGBA Lookup texture for
    % conversion of HDR luminance pixels to output framebuffer pixels:
    if isa(lut, 'uint8')
        % uint8 classic lut: Create RGBA8 lookup texture:
        fprintf('PsychImaging-%s: Creating LUT suitable for precision boosting of a 8 bpc native framebuffer and display or DAC.\n', reqs{floc});
        pglutid = PsychHelperCreateGenericLuminanceToRGBA8LUT(lut);
    elseif isa(lut, 'uint16')
        % uint16 input lut: Store as texture of suitable depths (8, 10 or 16 bpc):
        fprintf('PsychImaging-%s: Creating %i slot LUT suitable for precision boosting of a %i bpc native framebuffer and display or DAC.\n', ...
                reqs{floc}, size(lut, 2), nativeBPC);
        pglutid = PsychHelperCreateGenericLuminanceToRGBA16MaxLUT(lut, nativeBPC, win);
    else
        sca;
        error('PsychImaging: Mandatory lookup table parameter lut for ''%s'' not of uint8 or uint16 type, as required!', reqs{floc});
    end

    if outputcount > 0
        % Need a bufferflip command:
        Screen('HookFunction', win, 'AppendBuiltin', 'FinalOutputFormattingBlit', 'Builtin:FlipFBOs', '');
    end
    pgconfig = sprintf('TEXTURERECT2D(1)=%i %s', pglutid, icmconfig);
    pgidstring = sprintf('Generic high precision luminance output formatting shader: %s', icmstring);
    Screen('HookFunction', win, 'AppendShader', 'FinalOutputFormattingBlit', pgidstring, pgshader, pgconfig);
    Screen('HookFunction', win, 'Enable', 'FinalOutputFormattingBlit');
    outputcount = outputcount + 1;

    % Use unit color range, without clamping, but in high-precision mode:
    needsUnitUnclampedColorRange = 1;
end
% --- End of output formatter for generic LUT based luminance framebuffer ---

% --- Final output formatter for VideoSwitcher attenuator device requested? ---
VideoSwitcherTriggerflag = 0;

floc = find(mystrcmp(reqs, 'EnableVideoSwitcherSimpleLuminanceOutput'));
if isempty(floc)
    floc = find(mystrcmp(reqs, 'EnableVideoSwitcherCalibratedLuminanceOutput'));
end
if ~isempty(floc)
    [row col]= ind2sub(size(reqs), floc);

    if mystrcmp(reqs{row, 2}, 'EnableVideoSwitcherSimpleLuminanceOutput')
        simpleVideoSwitcher = 1;
    else
        simpleVideoSwitcher = 0;
    end

    % Extract optional first parameter - This should be the 'btrr' ratio to use:
    btrr = reqs{row, 3};

    if isempty(btrr)
        % btrr empty: Get it from config file:
        btrr = PsychVideoSwitcher('GetDefaultConfig', win);
    else
        if ~isnumeric(btrr) || ~isscalar(btrr)
            sca;
            error('PsychImaging: Optional "btrr" parameter for VideoSwitcher output not of numeric scalar type!');
        end

        if btrr < 0
            sca;
            error('PsychImaging: Optional "btrr" parameter for VideoSwitcher output is negative -- Impossible!');
        end
    end

    if simpleVideoSwitcher
        % Extract optional 2nd parameter - This should be the 'trigger' flag:
        VideoSwitcherTriggerflag = reqs{row, 4};
    else
        % Extract optional 3rd parameter - This should be the 'trigger' flag:
        VideoSwitcherTriggerflag = reqs{row, 5};
    end

    if isempty(VideoSwitcherTriggerflag)
        % triggerflag empty: Default to off:
        VideoSwitcherTriggerflag = 0;
    else
        if ~isnumeric(VideoSwitcherTriggerflag) || ~isscalar(VideoSwitcherTriggerflag)
            sca;
            error('PsychImaging: Optional "trigger" parameter for VideoSwitcher output not of numeric scalar type!');
        end

        if VideoSwitcherTriggerflag > 0
            VideoSwitcherTriggerflag = 1;
        else
            VideoSwitcherTriggerflag = 0;
        end
    end

    if simpleVideoSwitcher
        % Load output formatting shader for simple VideoSwitcher output:
        % 'icmshader' is a handle to a compiled fragment shader, provided by
        % upstream, that implements the display color correction function:
        pgshader = LoadGLSLProgramFromFiles('VideoSwitcherSimpleLuminanceToRB8_FormattingShader', 1, icmshader);

        % Init the shader: Assign mapping texture units etc.:
        glUseProgram(pgshader);
        glUniform1i(glGetUniformLocation(pgshader, 'Image'), 0);
        glUniform1f(glGetUniformLocation(pgshader, 'btrr'),  btrr);
        glUniform1f(glGetUniformLocation(pgshader, 'btrrPlusOne'),  btrr + 1);
        glUniform1f(glGetUniformLocation(pgshader, 'btrrFractionTerm'), ((btrr + 1) / btrr));
        glUseProgram(0);

        pgidstring = sprintf('VideoSwitcher simple high precision luminance output formatting shader: %s', icmstring);
        pgconfig = icmconfig;
    else
        % LUT calibrated VideoSwitcher setup:

        % Extract optional 2nd parameter - This should be the 'lut':
        lut = reqs{row, 4};

        if isempty(lut)
            % lut empty: Get it from config file:
            [dummy, lut] = PsychVideoSwitcher('GetDefaultConfig', win);
        else
            if ~isa(lut, 'double') || ~isvector(lut) || length(lut)~=257
                sca;
                error('PsychImaging: Lookup table parameter lut for VideoSwitcher output invalid: Must be a vector of double values with 257 elements!');
            end
        end

        % Load output formatting shader for lut calibrated VideoSwitcher output:
        % 'icmshader' is a handle to a compiled fragment shader, provided by
        % upstream, that implements the display color correction function:
        pgshader = LoadGLSLProgramFromFiles('VideoSwitcherCalibratedLuminanceToRB8_FormattingShader', 1, icmshader);

        % Init the shader: Assign mapping texture units etc.:
        glUseProgram(pgshader);
        glUniform1i(glGetUniformLocation(pgshader, 'Image'), 0);
        glUniform1i(glGetUniformLocation(pgshader, 'LUT'), 1);
        glUniform1f(glGetUniformLocation(pgshader, 'btrr'),  btrr);

        % Disable luminance hint color by default by setting it to the
        % luminance key -1, which shouldn't ever match in a regular
        % stimulus:
        glUniform3f(glGetUniformLocation(pgshader, 'BackgroundPixel'), 0, -1, 0);
        glUseProgram(0);

        % Convert 'lut' into lookup table texture:
        pglutid = PsychVideoSwitcher('GetLUTTexture', win, lut, btrr, pgshader);

        pgidstring = sprintf('VideoSwitcher calibrated high precision luminance output formatting shader: %s', icmstring);
        pgconfig = sprintf('TEXTURERECT2D(1)=%i %s', pglutid, icmconfig);
    end

    if outputcount > 0
        % Need a bufferflip command:
        Screen('HookFunction', win, 'AppendBuiltin', 'FinalOutputFormattingBlit', 'Builtin:FlipFBOs', '');
    end
    Screen('HookFunction', win, 'AppendShader', 'FinalOutputFormattingBlit', pgidstring, pgshader, pgconfig);
    Screen('HookFunction', win, 'Enable', 'FinalOutputFormattingBlit');
    outputcount = outputcount + 1;

    % VideoSwitcher devices need an identity clut:
    needsIdentityCLUT = 1;

    % Use unit color range, without clamping, but in high-precision mode:
    needsUnitUnclampedColorRange = 1;
end

% Setup of trigger for VideoSwitcher device needed?
if VideoSwitcherTriggerflag > 0
    % Yes. Attach a proper slot to the chain: The slot calls back into the
    % VideoSwitcher.m M-File, with the window handle as argument.
    pgconfig = sprintf('PsychVideoSwitcher(%i);', win);
    Screen('HookFunction', win, 'AppendMFunction', 'FinalOutputFormattingBlit', 'VideoSwitcher trigger control callback.', pgconfig);
end

% --- End of output formatters for VideoSwitcher attenuator device ---


% --- Final output formatter for native 10 bpc ARGB2101010 or 11 bpc RGB11-11-10framebuffer requested?
enableNative11BpcRequested = 0;
enableNative16BpcRequested = 0;
floc = find(mystrcmp(reqs, 'EnableNative10BitFramebuffer'));
if isempty(floc)
    enableNative11BpcRequested = 1;
    floc = find(mystrcmp(reqs, 'EnableNative11BitFramebuffer'));
end

if isempty(floc)
    enableNative11BpcRequested = 0;
    enableNative16BpcRequested = 1;
    floc = find(mystrcmp(reqs, 'EnableNative16BitFramebuffer'));
end

if ~isempty(floc)
    [row col]= ind2sub(size(reqs), floc);

    % Our special shader-based 10 bpc output formatter is only applicable on Linux
    % with AMD Radeon hardware, or with FireGL/FirePro with override mode bit set.
    % Our 11 bpc and 16 bpc shader-based output formatters are only for Linux + AMD GCN-1.1+.
    % specialFlags setting 1024 signals that our own low-level 10/11/16 bit framebuffer
    % hack on AMD hardware is active, so we also need our own GLSL output formatters.
    % Otherwise setup was (hopefully) done by the regular graphics drivers and we don't
    % need this GLSL output formatter, as system OpenGL takes care of it.
    % If the Vulkan display backend is requested then this also does not apply as
    % the Vulkan/WSI backend must do whatever neccessary to provide the requested
    % fixed point unorm precision - or we simply fail if it can't:
    if bitand(winfo.SpecialFlags, 1024) && ~useVulkan
        % AMD/ATI gpu on Linux with our 10/11/16 bit hack. Use our reformatters:
        if enableNative16BpcRequested
            % Extract optional 2nd parameter - This should be the 'encodingBPC' depth:
            encodingBPC = reqs{row, 4};

            % Assign maximum bit depth default for given GPU, if no specific depth requested:
            if isempty(encodingBPC)
                if winfo.GPUMinorType >= 80
                    % DCE-8.0 or later display engine of "Sea Islands Family" or later: Does 12 bpc,
                    % but due to hw changes apparently needs an encodingBPC of 16. See PTB forum
                    % message 21600 and predecessors in that thread for reference.
                    encodingBPC = 16;
                else
                    % Older engine. Only does a maximum of 10 bpc, so using this mode
                    % is pointless and only good for debugging.
                    encodingBPC = 10;
                end
            end

            fprintf('PsychImaging: EnableNative16BitFramebuffer: Framebuffer resolution set to %i bpc. Further video output specific limitations may apply, check your results with a photometer!\n', encodingBPC);

            % Load algorithmic 9 bpc - 16 bpc shader for packing 9-16 bpc content into a 64 bpp
            % framebuffer:
            pgshader = LoadGLSLProgramFromFiles('AMD16bpc_FormattingShader', 1);
        else
            % Load output formatting shader for multi-LUT based 10 bpc or 11 bpc formatting:
            pgshader = LoadGLSLProgramFromFiles('RGBMultiLUTLookupCombine_FormattingShader', 1);
        end

        % Init the shader:
        glUseProgram(pgshader);

        % Assign mapping of input image:
        glUniform1i(glGetUniformLocation(pgshader, 'Image'), 0);

        if enableNative16BpcRequested
            % Scale from 0.0 - 1.0 to 0.0 - (2^n - 1) with n being encoding bit depth: n = encodingBPC
            glUniform1f(glGetUniformLocation(pgshader, 'Prescale'), bitshift(1, encodingBPC) - 1);
            % Pass true half-width of framebuffer/x-screen/onscreen window to shader for proper handling
            % of dual-display side-by-side "stereo style" configurations:
            glUniform1f(glGetUniformLocation(pgshader, 'halfFBWidth'), Screen('WindowSize', win, 1) / 2);
        else
            % CLUT based mapping:
            glUniform1i(glGetUniformLocation(pgshader, 'CLUT'), 1);
            glUniform1f(glGetUniformLocation(pgshader, 'Prescale'), bitshift(1024, enableNative11BpcRequested) - 1);
        end
        glUseProgram(0);

        if enableNative16BpcRequested
              % Setup 16 bpc formatter further:
              pgshadername = 'Native RGBA16161616 framebuffer output formatting shader';
              if isASideBySideConfig
                  % Only scale vertically to cover whole vertical framebuffer height:
                  pgconfig = 'Scaling:1.0:2.0';
              else
                  % Scale horizontally and vertically to cover whole framebuffer width x height:
                  pgconfig = 'Scaling:2.0:2.0';
              end
        elseif enableNative11BpcRequested
              % Use helper routine to build a proper RGBA Lookup texture for
              % conversion of RGB pixels to ARGB0-11-11-10 pixels.
              % DCE-8 and later (tested on DCE-8 and DCE-10) need a different
              % format:
              if winfo.GPUMinorType >= 80
                  % DCE-8+
                  pglutid = PsychHelperCreateRGB111110RemapCLUTAMDDCE8;
              else
                  % Pre DCE-8, e.g., tested on DCE-4:
                  pglutid = PsychHelperCreateRGB111110RemapCLUTOldAMD;
              end
              pgshadername = 'Native RGB111110 framebuffer output formatting shader';
              pgconfig = sprintf('TEXTURERECT2D(1)=%i', pglutid);
        else
              % Use helper routine to build a proper RGBA Lookup texture for
              % conversion of RGBA pixels to ARGB2101010 pixels:
              pglutid = PsychHelperCreateARGB2101010RemapCLUT;
              pgshadername = 'Native ARGB2101010 framebuffer output formatting shader';
              pgconfig = sprintf('TEXTURERECT2D(1)=%i', pglutid);
        end

        if outputcount > 0
            % Need a bufferflip command:
            Screen('HookFunction', win, 'AppendBuiltin', 'FinalOutputFormattingBlit', 'Builtin:FlipFBOs', '');
        end

        Screen('HookFunction', win, 'AppendShader', 'FinalOutputFormattingBlit', pgshadername, pgshader, pgconfig);
        Screen('HookFunction', win, 'Enable', 'FinalOutputFormattingBlit');
        outputcount = outputcount + 1;

        % For 16 bpc mode, we need to restore original X-Screen size at window close:
        if enableNative16BpcRequested && ~isempty(screenRestoreCmd) && ~isempty(screenRestoreCmd{Screen('WindowScreenNumber', win)+1})
            Screen('HookFunction', win, 'PrependMFunction', 'CloseOnscreenWindowPostGLShutdown', 'Cleanup for 16 bpc Linux X11 mode.', screenRestoreCmd{Screen('WindowScreenNumber', win)+1});
            Screen('HookFunction', win, 'Enable', 'CloseOnscreenWindowPostGLShutdown');
            screenRestoreCmd{Screen('WindowScreenNumber', win)+1} = [];
        end

        % AMD framebuffer devices - Identity CLUT not needed, as internal clut is bypassed anyway,
        % but we do it nonetheless, so we can decide about dithering setup and get things like
        % degamma and other colorspace conversions disabled / bypassed:
        needsIdentityCLUT = 1;
    else
        % Everything else: Windows OS or macOS, or AMD FireGL/FirePro without override,
        % or AMD with amdgpu DisplayCore, or any NVidia or Intel GPU. Also on Vulkan.
        % Do not request an identity lut. Modern Intel, NVidia and AMD gpu's have
        % hw LUT's with an output width of potentially more than 10 bpc, so we
        % can potentially benefit from a higher precision gamma correction via
        % hw lut. E.g., Intel Icelake has up to 16 bit output precision lut's,
        % NVidia up to 14 bit, AMD greater than 10 bit - typically 12 bit.
        % Going through our identity lut setup code could even load a "identity lut"
        % that truncates output precision to 8 bit, e.g., on Linux + Intel gpu's,
        % as our LoadIdentityClut() function is optimized/targeted at 8 bpc passthrough.
        needsIdentityCLUT = 0;
    end

    % Extract optional first parameter - This should be the 'disableDithering' flag:
    disableDithering = reqs{row, 3};

    if isempty(disableDithering)
        % Control of output dithering on digital >= 10 bit panels should be left to
        % the OS + graphics driver by default. For example, the OS can configure
        % the encoders for 10 bpc no-dithering if it detects a truly 10 bpc capable display,
        % based on EDID information. DisplayPort and HDMI provides infos about >= 10 bpc
        % capabilities in their EDID info. If the OS detects a <= 8 bpc digital panel, it
        % can dither so we get pseudo-10bpc, similar to a bit stealing approach or other
        % perceptual high bit depths tricks. The same is true for driving 10 bpc panels with
        % 12 bpc or more precision, using dithering on the 10 bpc signal:
        disableDithering = 0;
    else
        % User provided disableDithering flag. Valid?
        if ~ismember(disableDithering, [0, 1])
            sca;
            error('Optional disableDithering flag with invalid value provided! Valid is 0 or 1!');
        end

        % Yes, use it.
    end

    % Use unit color range, without clamping, but in high-precision mode:
    needsUnitUnclampedColorRange = 1;
end
% --- End of output formatter for native 10 bpc ARGB2101010 framebuffer ---

% --- Experimental output formatter for Dual-Pipeline HDR display ---
floc = find(mystrcmp(reqs, 'EnableDualPipeHDROutput'));
if ~isempty(floc)
    [row col]= ind2sub(size(reqs), floc);

    % outputcount should be zero, i.e., the unified output formatting chain
    % should be disabled, as we use separate per channel chains:
    if outputcount > 0
        fprintf('PsychImaging: WARNING! In setup for task "EnableDualPipeHDROutput": Unified output formatting chain was active (count = %i)!\n', outputcount);
        fprintf('PsychImaging: WARNING! This conflicts with need for separate output formatting chains! Overriding: Unified chain disabled!\n');
        fprintf('PsychImaging: WARNING! Check your output stimulus carefully for artifacts!\n');

        % Disable unified output formatting chain and hope for the best:
        Screen('HookFunction', win, 'Disable', 'FinalOutputFormattingBlit');
        % Screen('HookFunction', win, 'Disable', 'RightFinalizerBlitChain');
    end

    % Setup shader for pipe 0:
    pipe0shader = LoadGLSLProgramFromFiles('DualPipeHDRPipe0_FormattingShader', 1, icmshader);

    if outputcount0 > 0
        % Need a bufferflip command:
        Screen('HookFunction', win, 'AppendBuiltin', 'FinalOutputFormattingBlit0', 'Builtin:FlipFBOs', '');
    end

    Screen('HookFunction', win, 'AppendShader', 'FinalOutputFormattingBlit0', 'HDRPipe0 - Output Formatter', pipe0shader, '');
    Screen('HookFunction', win, 'Enable', 'FinalOutputFormattingBlit0');
    outputcount0 = outputcount0 + 1;

    % Setup shader for pipe 1:
    pipe1shader = LoadGLSLProgramFromFiles('DualPipeHDRPipe1_FormattingShader', 1, icmshader);

    if outputcount1 > 0
        % Need a bufferflip command:
        Screen('HookFunction', win, 'AppendBuiltin', 'FinalOutputFormattingBlit1', 'Builtin:FlipFBOs', '');
    end

    Screen('HookFunction', win, 'AppendShader', 'FinalOutputFormattingBlit1', 'HDRPipe1 - Output Formatter', pipe1shader, '');
    Screen('HookFunction', win, 'Enable', 'FinalOutputFormattingBlit1');
    outputcount1 = outputcount1 + 1;

    % Device need an identity clut in the GPU gamma tables:
    needsIdentityCLUT = 1;

    % Use unit color range, without clamping, but in high-precision mode:
    needsUnitUnclampedColorRange = 1;
end
% --- End of experimental output formatter for Dual-Pipeline HDR display ---

% --- Output formatter for Eizo RadiForce style 8 bit luminance subpixel drive ---
floc = find(mystrcmp(reqs, 'UseSubpixelDrive'));
if ~isempty(floc)
    [row col]= ind2sub(size(reqs), floc);

    if outputcount > 0
        % Need a bufferflip command:
        Screen('HookFunction', win, 'AppendBuiltin', 'FinalOutputFormattingBlit', 'Builtin:FlipFBOs', '');
    end

    pgshader = LoadGLSLProgramFromFiles('SubpixelDrive_FormattingShader', 1);

    % Init the shader: Assign mapping texture units etc.:
    glUseProgram(pgshader);
    glUniform1i(glGetUniformLocation(pgshader, 'Image'), 0);
    glUseProgram(0);

    Screen('HookFunction', win, 'AppendShader', 'FinalOutputFormattingBlit', 'Subpixel drive - Output Formatter', pgshader, '');
    Screen('HookFunction', win, 'Enable', 'FinalOutputFormattingBlit');
    outputcount = outputcount + 1;
end
% --- End of output formatter for Eizo RadiForce style 8 bit luminance subpixel drive ---

% --- GPU based mirroring of left half of onscreen window to right half requested? ---
if ~isempty(find(mystrcmp(reqs, 'MirrorDisplayToSingleSplitWindow')))

    % Simply set up the left finalizer chain with a glCopyPixels command
    % that copies the left half of the system backbuffer to the right half
    % of the system backbuffer. Query the real backbuffer width x height,
    % but use half the width as source region and destination region
    % offset, as the right half of the backbuffer shall be a copy of the
    % left half:
    [w, h] = Screen('WindowSize', win, 1);
    w = w / 2;

    % Imaging pipeline fully enabled? Specific offsets used for blitter
    % commands depend on this:
    if bitand(winfo.ImagingMode, kPsychNeedFastBackingStore) > 0
        % Yes: Use proper offsets for active imaging pipeline:
        myblitstring = sprintf('glRasterPos2f(%f, %f); glCopyPixels(0, 0, %f, %f, 6144);', w, h, w, h);
    else
        % No: Need different x-offset for glRasterPos2f, because the good
        % ol' fixed function pipeline uses different viewport / projection
        % matrix etc.:
        myblitstring = sprintf('glRasterPos2f(%f, %f); glCopyPixels(0, 0, %f, %f, 6144);', w/2, h, w, h);
    end

    % Attach blit command sequence to finalizer chain:
    Screen('Hookfunction', win, 'AppendMFunction', 'LeftFinalizerBlitChain', 'MirrorSplitWindowToSplitWindow', myblitstring);
    Screen('HookFunction', win, 'Enable', 'LeftFinalizerBlitChain');
end
% --- End of GPU based mirroring of left half of onscreen window to right half requested? ---

% --- GPU based mirroring of onscreen window to secondary display head requested? ---
if ~isempty(find(mystrcmp(reqs, 'MirrorDisplayTo2ndOutputHead')))
    % Yes: We need to replicate the framebuffer of the master onscreen
    % window to the slave windows framebuffer.

    % What we do: We use the right finalizer blit chain to copy the
    % contents of the master window's system backbuffer (which is bound
    % during execution of the right finalizer blit chain) to the
    % colorbuffer texture of the special finalizedFBO[1] - the shadow
    % framebuffer FBO of the slave window. Once we did this, the processing
    % code of kPsychNeedDualWindowOutput in Screens
    % PsychPreFlipOperations() routine will take care of the rest -->
    % Blitting that FBO's and its texture to the system backbuffer of the
    % slave window, thereby cloning the master windows framebuffer to the
    % slave windows framebuffer:
    % TODO FIXME: We assume that texture handle '1' denotes the color
    % attachment texture of finalizedFBO[1]. This is true if this is the
    % first opened onscreen window (ie., 99% of the time). If that
    % assumption doesn't hold, we will guess the wrong texture handle and
    % bad things will happen!
    [w, h] = Screen('WindowSize', win, 1);
    myblitstring = sprintf('glBindTexture(34037, 1); glCopyTexSubImage2D(34037, 0, 0, 0, 0, 0, %i, %i); glBindTexture(34037, 0);', w, h);
    Screen('Hookfunction', win, 'AppendMFunction', 'RightFinalizerBlitChain', 'MirrorMasterToSlaveWindow', myblitstring);
    Screen('HookFunction', win, 'Enable', 'RightFinalizerBlitChain');
end
% --- End of GPU based mirroring of onscreen window to secondary display head requested? ---

% --- Datapixx in use? ---
if ~isempty(find(mystrcmp(reqs, 'UseDataPixx')))
    % Yes: Need to call into high level DataPixx driver for final setup:
    PsychDataPixx('PerformPostWindowOpenSetup', win);
end
% --- End of Datapixx in use? ---

% --- Bits# in use? ---
if ~isempty(find(mystrcmp(reqs, 'UseBits#')))
    % Yes: Need to call into high level BitsPlusPlus driver for final setup:
    BitsPlusPlus('PerformPostWindowOpenSetup', win);
end
% --- End of Bits# in use? ---

if useHDR && needsIdentityCLUT
    % We want identity lut's in HDR, but at maximum lut precision, so output
    % does not get truncated to 8 bpc. Therefore we can't use LoadIdentityClut()
    %
    % There we have identity lut setup code in PsychVulkan() to handle this, and
    % therefore useHDR  && needsIdentityCLUT should never happen:
    warning('needsIdentityCLUT set in HDR mode! Bug!?!');
end

% Do we need identity gamma tables / CLUT's loaded into the graphics card?
if needsIdentityCLUT && ~useHDR
    % Yes. Use our generic routine which is adaptive to the quirks of
    % specific gfx-cards:
    LoadIdentityClut(win, [], [], disableDithering);

    % Is there a slave window associated for some dual-window output mode,
    % HDR mode or stereo mode?
    if ~isempty(slavewin)
        % Yes: Apply identity LUT setup there as well:
        LoadIdentityClut(slavewin, [], [], disableDithering);
    end
end

% Special Vulkan display backend in use?
floc = find(mystrcmp(reqs, 'UseVulkanDisplay'));
if ~isempty(floc)
    [rows cols] = ind2sub(size(reqs), floc(1));
    row = rows(1);

    % Extract first parameter - This would be the optional video output name:
    outputName = reqs{row, 3};

    if ~isempty(outputName)
        isFullscreen = 1;
    else
        isFullscreen = 0;
    end

    % 10 bpc linear unorm output framebuffer requested?
    if ~isempty(find(mystrcmp(reqs, 'EnableNative10BitFramebuffer')))
        % Request code 1 - RGB10A2 / BGR10A2 format on Vulkan side:
        % This also overrides potential automatic choice  by 'EnableHDR' setup.
        vulkanColorPrecision = 1;
    end

    % 11 bpc linear unorm output framebuffer requested? Or 16 bpc float framebuffer?
    if ~isempty(find(mystrcmp(reqs, 'EnableNative11BitFramebuffer'))) || ...
       ~isempty(find(mystrcmp(reqs, 'EnableNative16BitFloatingPointFramebuffer')))
        % Request code 2 - RGBA16F format on Vulkan side:
        % This also overrides potential automatic choice  by 'EnableHDR' setup.
        % fp16 request obviously matches exactly RGBA16F / VK_FORMAT_R16G16B16A16_SFLOAT.
        %
        % The same format works for RGB11 unorm, because it so happens that inside the 0.0 - 1.0
        % unsigned normalized (unorm) color value range of RGB11 / EnableNative11BitFramebuffer,
        % a 16 bit half-float floating point value provides the equivalent of at least 11 bpc
        % linear precision:
        vulkanColorPrecision = 2;
    end

    % 16 bpc linear unorm output framebuffer requested?
    if ~isempty(find(mystrcmp(reqs, 'EnableNative16BitFramebuffer')))
        % Request code 3 - RGBA16 unorm fixed point format on Vulkan side:
        % This also overrides potential automatic choice  by 'EnableHDR' setup.
        vulkanColorPrecision = 3;
    end

    % Default flags:
    flags = 0;

    % Default: Auto-select optimal Vulkan driver+gpu combo:
    gpuIndex = 0;

    % Special static HDR stereo hack for Linux/X11 + OpenGL requested?
    floc = find(mystrcmp(reqs, 'UseStaticHDRHack'));
    if isempty(floc)
        % No, standard Vulkan display mode: Perform Vulkan onscreen window creation and setup of Vulkan and OpenGL interop on our side:
        vwin = PsychVulkan('PerformPostWindowOpenSetup', win, Screen('GlobalRect', win), isFullscreen, outputName, vulkanHDRMode, vulkanColorPrecision, vulkanColorSpace, vulkanColorFormat, gpuIndex, flags);
    else
        % Yes: Perform one-time dual-display HDR setup via special hack in PsychHDR():

        % Extract static HDR metadata to use, if any:
        [rows cols] = ind2sub(size(reqs), floc(1));
        row = rows(1);
        staticHDRMetadata = reqs{row, 3};

        % Perform one-time setup via Vulkan, with static assignment of staticHDRMetadata
        % for the duration of the whole session:
        PsychHDR('ExecuteStaticHDRHack', win, 1, vulkanHDRMode, gpuIndex, flags, staticHDRMetadata);
    end
end

if useHDR
    % Perform post OpenWindow setup for HDR stuff, e.g., setting up appropriate
    % HDR post-processing shaders (Optional tone-mapping, gamut-remapping,
    % color-space conversion and clamping. Mandatory OETF mapping, e.g., PQ):
    [hdrShader, hdrShaderString] = PsychHDR('PerformPostWindowOpenSetup', win, hdrArguments, icmshader, icmstring);

    % If any hdrShader is needed, append it to the final output formatter chain:
    if ~isempty(hdrShader)
        if outputcount > 0
            % Need a bufferflip command:
            Screen('HookFunction', win, 'AppendBuiltin', 'FinalOutputFormattingBlit', 'Builtin:FlipFBOs', '');
        end

        Screen('HookFunction', win, 'AppendShader', 'FinalOutputFormattingBlit', hdrShaderString, hdrShader, '');
        Screen('HookFunction', win, 'Enable', 'FinalOutputFormattingBlit');
        outputcount = outputcount + 1;
    end

    % Make sure that regular double() input images, even if only in the standard
    % unorm range for creation of 8 bpc RGBA8 textures, do get their input values
    % used "as is", ie. valid values are 0.0 - 1.0:
    applyAlsoToMakeTexture = 1;

    % Disable input value scaling and clamping, so color values can be passed through as
    % arbitrary floating point values, for encoding in Nits, 80Nits, negative values,
    % chromaticities etc.:
    needsUnitUnclampedColorRange = 1;
end

% --- END OF ALL OUTPUT FORMATTERS AND SPECIAL OUTPUT DEVICE/BACKEND SETUP ---

% --- This must be after setup of all output formatter shaders! ---
% Downstream icm color correction shader linked into an icmAware output
% formatter. We must perform post-link setup for it:
if ptb_outputformatter_icmAware && icmformatting_downstream
    % Perform post-link setup of color correction method after
    % shader attached to pipe. We know it is the
    % 'FinalOutputFormattingBlit' chain, as only in that case, downstream
    % formatting is performed at all.
    PsychColorCorrection('ApplyPostGLSLLinkSetup', win, 'FinalFormatting');
end

% --- Restriction of processing area ROI requested? ---

% This should be at the end of hookchain setup, so we can reliably prepend the
% command to each chain to guarantee that restriction applies to all
% processing:
floc = find(mystrcmp(reqs, 'RestrictProcessing'));
if ~isempty(floc)
    % Which channel?
    for x=floc
        [rows cols]= ind2sub(size(reqs), x);
        for row=rows'
            % Extract scissor rectangle:
            scissorrect = reqs{row, 3};
            if size(scissorrect,1)~=1 || size(scissorrect,2)~=4
                error('Task "RestrictProcessing" in channel %s expects a 1-by-4 ROI rectangle to define the ROI, e.g, [left top right bottom]!', reqs{row,1});
            end

            ox = scissorrect(RectLeft);
            oy = winheight - scissorrect(RectBottom);

            w  = RectWidth(scissorrect);
            h  = RectHeight(scissorrect);

            if mystrcmp(reqs{row, 1}, 'LeftView') || mystrcmp(reqs{row, 1}, 'AllViews')
                % Need to restrict left view processing:
                Screen('HookFunction', win, 'PrependBuiltin', 'StereoLeftCompositingBlit', 'Builtin:RestrictToScissorROI', sprintf('%i:%i:%i:%i', ox, oy, w, h));
            end

            if mystrcmp(reqs{row, 1}, 'RightView') || mystrcmp(reqs{row, 1}, 'AllViews')
                % Need to restrict right view processing:
                Screen('HookFunction', win, 'PrependBuiltin', 'StereoRightCompositingBlit', 'Builtin:RestrictToScissorROI', sprintf('%i:%i:%i:%i', ox, oy, w, h));
            end

            if (mystrcmp(reqs{row, 1}, 'AllViews') || mystrcmp(reqs{row, 1}, 'Compositor')) && ismember(winfo.StereoMode, [6,7,8,9])
                % Needed to restrict both views processing and a
                % compositing mode is active. If both views are restricted
                % in their output area then it makes sense to restrict the
                % compositor to the same area. We also restrict the
                % compositor if that was requested.
                oy = RectHeight(Screen('Rect', win, 1)) - scissorrect(RectBottom);
                Screen('HookFunction', win, 'PrependBuiltin', 'StereoCompositingBlit', 'Builtin:RestrictToScissorROI', sprintf('%i:%i:%i:%i', ox, oy, w, h));
            end

            if mystrcmp(reqs{row, 1}, 'FinalFormatting')
                % Need to restrict final formatting blit processing:
                oy = RectHeight(Screen('Rect', win, 1)) - scissorrect(RectBottom);
                Screen('HookFunction', win, 'PrependBuiltin', 'FinalOutputFormattingBlit', 'Builtin:RestrictToScissorROI', sprintf('%i:%i:%i:%i', ox, oy, w, h));
            end

        end
    end
end

% --- End of Restriction of processing area ROI ---

% Is a default colormode specified via psych_default_colormode variable and
% the level is at least 1? If so, switch to be created onscreen window to a
% [0;1] colorrange with clamping by default, and apply input scaling to
% Screen('MakeTexture') as well. This is like 'NormalizedHighresColorRange'
% aka needsUnitUnclampedColorRange, except it doesn't unclamp the
% framebuffer, but keeps it clamped to 0 - 1 range, unless a previous
% 'ColorRange' call changed this. Why? To accomodate OpenGL hw without
% clamp extension:
if ~isempty(psych_default_colormode) && (psych_default_colormode >= 1)
    applyAlsoToMakeTexture = 1;
end

if ~needsUnitUnclampedColorRange && ~isempty(psych_default_colormode) && (psych_default_colormode >= 1)
    Screen('ColorRange', win, 1, [], 1);
    applyAlsoToMakeTexture = 1;

    % Set Screen background clear color, in normalized 0.0 - 1.0 range:
    if ~isempty(clearcolor) && (max(clearcolor) > 1) && (all(round(clearcolor) == clearcolor))
        % Looks like someone's feeding old style 0-255 integer values as
        % clearcolor. Output a warning to tell about the expected 0.0 - 1.0
        % range of values:
        fprintf('\n\nPsychImaging-Warning: You specified a ''clearcolor'' argument for the OpenWindow command that looks \nlike an old 0-255 value instead of the wanted value in the 0.0-1.0 range.\nPlease update your code for correct behaviour.\n\n');
    end

    % Set the background clear color via old fullscreen 'FillRect' trick,
    % followed by a flip:
    Screen('FillRect', win, clearcolor);

    % Double-flip to be on the safe side:
    Screen('Flip', win);
    Screen('Flip', win);
end

% Do we need a normalized [0.0 ; 1.0] color range mapping with unclamped
% high precision colors?
if needsUnitUnclampedColorRange
    % Set color range to 0.0 - 1.0: This makes more sense than the normal
    % 0-255 values. Try to disable color clamping. This may fail and
    % produce a PTB warning, but if it succeeds then we're better off for
    % the 2D drawing commands...
    Screen('ColorRange', win, 1, 0, applyAlsoToMakeTexture);

    % Set Screen background clear color, in normalized 0.0 - 1.0 range:
    if ~isempty(clearcolor) && (max(clearcolor) > 1) && (all(round(clearcolor) == clearcolor)) && ~useHDR
        % Looks like someone's feeding old style 0-255 integer values as
        % clearcolor. Output a warning to tell about the expected 0.0 - 1.0
        % range of values:
        fprintf('\n\nPsychImaging-Warning: You specified a ''clearcolor'' argument for the OpenWindow command that looks \nlike an old 0-255 value instead of the wanted value in the 0.0-1.0 range.\nPlease update your code for correct behaviour.\n\n');
    end

    % Set the background clear color via old fullscreen 'FillRect' trick,
    % followed by a flip:
    Screen('FillRect', win, clearcolor);

    % Double-flip to be on the safe side:
    Screen('Flip', win);
    Screen('Flip', win);
end

% Return reqs array, for whatever reason...
rc = reqs;
return;

% End of PostConfiguration subroutine.

function rcmatch = mystrcmp(myhaystack, myneedle)

if isempty(myhaystack) || isempty(myneedle)
    rcmatch = logical(0); %#ok<LOGL>
    return;
end

if ~iscell(myhaystack) && ~ischar(myhaystack)
    error('First argument to mystrcmp must be a cell-array or a character array (string)!');
end

if iscell(myhaystack)
    % Cell array of strings: Check each element, return result matrix:
    rcmatch=logical(zeros(size(myhaystack))); %#ok<LOGL>
    rows = size(myhaystack, 1);
    cols = size(myhaystack, 2);
    for r=1:rows
        for c=1:cols
            if iscellstr(myhaystack(r,c))
                rcmatch(r,c) = logical(strcmpi(char(myhaystack(r,c)), myneedle));
            else
                rcmatch(r,c) = logical(0); %#ok<LOGL>
            end
        end
    end
else
    % Single character string: Do single check and return result:
    rcmatch=logical(strcmpi(myhaystack, myneedle));
end

return;

% Helper: Search chain 'hookname' in window 'win' for scissor restriction
% slots and remove all of them:
function DoRemoveScissorRestriction(win, hookname)
    while 1
        slot = Screen('HookFunction', win, 'Query', hookname, 'Builtin:RestrictToScissorROI');
        if slot~=-1
            Screen('Hookfunction', win, 'Remove', hookname, slot);
        else
            break;
        end
    end
return;

% Helper: Calculate and return bounding rectangle of intermediate
% framebuffers inside the imaging pipeline. These intermediates don't have
% the size of the client framebuffer (aka Screen('Rect', win);) and don't
% have the size of the windows backbuffer (aka Screen('Rect', win, 1);),
% but some size derived from the backbuffer size and various flags:
function rect = InterBufferRect(win)
    % Get window info flags about possible size transformations:
    winfo = Screen('GetWindowInfo', win);

    % Get raw rectangle of true window backbuffer size as baseline:
    % Left and Top entry is always zero, due to normalized rect.
    rect = Screen('Rect', win, 1);

    % Apply half-height flag, if any:
    if bitand(winfo.SpecialFlags, kPsychNeedHalfHeightWindow)
        rect(RectBottom) = rect(RectBottom) / 2;
    end

    % Apply half-width flag, if any:
    if bitand(winfo.SpecialFlags, kPsychNeedHalfWidthWindow)
        rect(RectRight) = rect(RectRight) / 2;
    end

    % Apply twice-width flag, if any:
    if bitand(winfo.SpecialFlags, kPsychNeedTwiceWidthWindow)
        rect(RectRight) = rect(RectRight) * 2;
    end

    % Apply triple-width flag, if any:
    if bitand(winfo.SpecialFlags, kPsychNeedTripleWidthWindow)
        rect(RectRight) = rect(RectRight) * 3;
    end
return;

function [w, h] = InterBufferSize(win)
    w = RectWidth(InterBufferRect(win));
    h = RectHeight(InterBufferRect(win));
return;

function reqs = AddTask(reqs, varargin)
    global maxreqarg;

    if length(varargin) < 2 || isempty(varargin{1}) || isempty(varargin{2})
        error('Parameters missing: Need at least "whichChannel" and "whichTask"!');
    end

    % Variable length input:
    x = varargin;

    % Store requirement in our cell array of requirements. We need to
    % extend each requirement vector to some number of max elements, so all
    % rows in the cell array have the same length:
    if length(x) < maxreqarg
        for i=length(x)+1:maxreqarg
            x{i}='';
        end
    end

    % First use of 'reqs' array?
    if isempty(reqs)
        % Yes: Initialize the array with content of 'x':
        reqs = x;
    else
        % No: Just concatenate new line with requirements 'x' to existing
        % array 'reqs':
        reqs = [reqs ; x];
    end
return;
