function remapCLUTId = PsychHelperCreateARGB2101010RemapCLUT
% remapCLUTId = PsychHelperCreateARGB2101010RemapCLUT;
%
% Helper function for Psychtoolbox imaging pipeline, called by
% PsychImaging(), not meant to be called by normal user code!
%
% Build a 3 rows by 1024 texels RGBA8 lookup texture for mapping HDR RGB
% pixel values in range 0-1023 (ie. 10 bpc resolution) to RGBA8 framebuffer
% pixels which will be suitable to drive a ARGB2101010 framebuffer scanout
% engine (CRTC). If a graphics cards framebuffer scanout engine CRTC is
% configured for 2101010 10 bpc framebuffer mode, this will make sure that
% HDR pixel data is shown properly onscreen.
%
% This texture is used as CLUT texture for the
% RGBMultiLUTLookupCombine_FormattingShader.frag.txt in the final output
% formatting chain of the imaging pipeline.
%
% The expected pixel layout in 10bpc mode is: ARGB2101010, ie.:
% A2R10G10B10 -- That's how a 32 bit pixel is interpreted for video
% scanout. However the OpenGL system on all current OS with standard
% drivers can only output A8R8G8B8 formatted pixels. We solve this via the
% imaging pipeline: Screen() and OpenGL drawing ops go to the imaging
% pipelines virtual framebuffer with 16bpc float or 32 bpc float precision.
% At Flip time, our shader remaps that 0.0 - 1.0 values to the range 0-1023
% ie. 10 bpc, then uses the CLUT texture created with this function to
% lookup corresponding ARGB8 color tuples for the framebuffer, combines
% them into a single ARGB8 tuple by addition (logical OR), then writes that
% ARGB tuple to the framebuffer. At display scanout time, the CRTC's will
% find 32 bit pixels properly formatted for ARGB2101010 scanout.
%
% History:
% 01/13/08  Written (MK).
% 08/29/11  Add caching for LUT in mat file to speed this up a bit (MK).

% This routine assumes that a mogl GL context is properly set up:
global GL;

% Try to get LUT from cached .mat file -- faster:
cachedFile = [PsychtoolboxConfigDir 'argb2101010remaplut.mat'];

if exist(cachedFile, 'file')
    % Get cached LUT from file:
    load(cachedFile);
else
    % Build lookup table: 3 LUT's (Red,Green,Blue) with 1024 slots with 4
    % output color components RGBA8 per entry, ie., a 32 bpp value:
    clut = uint8(zeros(1, 3 * 1024 * 4));

    % Startindex for 1st LUT entry:
    j=1;

    % First the RED LUT: Texrow 1
    for i=0:1023
        % Memory order is ARGB:

        % Nothing in blue:
        clut(j)= 0;
        j=j+1;
        % Nothing in green:
        clut(j)= 0;
        j=j+1;
        % 4 LSB of red into 4 MSB of red:
        clut(j)= bitshift(bitand(i, bin2dec('1111')), 4);
        j=j+1;
        % 6 MSB of red into 6 LSB of alpha:
        clut(j)= bitshift(bitand(i, bin2dec('1111110000')), -4);
        j=j+1;
        % 2 MSB of alpha are left to a constant zero. They would encode the 2
        % bit alpha channel, but that is unused in our configuration. Alpha is
        % handled at render time by the high-res drawbuffer and earlier part of
        % imaging pipeline...
    end

    % Then the GREEN LUT: Texrow 2
    for i=0:1023
        % Memory order is ARGB:

        % Nothing in blue
        clut(j)= 0;
        j=j+1;
        % LSB 6 bits of green into 6 MSB of green:
        clut(j)= bitshift(bitand(i, bin2dec('111111')), 2);
        j=j+1;
        % MSB 4 bits of green into 4 LSB of red:
        clut(j)= bitshift(bitand(i, bin2dec('1111000000')), -6);
        j=j+1;
        % Nothing in alpha:
        clut(j)= 0;
        j=j+1;
    end

    % Finally the BLUE LUT: Texrow 3
    for i=0:1023
        % Memory order is ARGB:

        % LSB 8 bits of blue into blue:
        clut(j)= bitand(i, bin2dec('11111111'));
        j=j+1;
        % MSB 2 bits of blue into 2 LSB of green:
        clut(j)= bitshift(bitand(i, bin2dec('1100000000')), -8);
        j=j+1;
        % Nothing in red:
        clut(j)= 0;
        j=j+1;
        % Nothing in alpha:
        clut(j)= 0;
        j=j+1;
    end

    % Cache computed LUT in users config dir:
    save(cachedFile, 'clut', '-mat', '-V6');
end

% Hostformat defines byte-order in host memory:
% This should work on any little-endian system (Windows/Linux On IntelPC,
% Linux on IntelMac, OS/X on IntelMac) - pretty much everything.
% On BigEndian systems (OS/X PowerPC) we may need to replace this by a
% GL.UNSIGNED_INT_8_8_8_8 instead to reverse byte order at conversion time.
hostformat = GL.UNSIGNED_INT_8_8_8_8_REV;

% Create and setup texture from 'clut':
remapCLUTId = glGenTextures(1);
glBindTexture(GL.TEXTURE_RECTANGLE_EXT, remapCLUTId);
glTexImage2D(GL.TEXTURE_RECTANGLE_EXT, 0, GL.RGBA, 1024, 3, 0, GL.BGRA, hostformat, clut);

% Make sure we use nearest neighbour sampling:
glTexParameteri(GL.TEXTURE_RECTANGLE_EXT, GL.TEXTURE_MIN_FILTER, GL.NEAREST);
glTexParameteri(GL.TEXTURE_RECTANGLE_EXT, GL.TEXTURE_MAG_FILTER, GL.NEAREST);

% And that we clamp to edge:
glTexParameteri(GL.TEXTURE_RECTANGLE_EXT, GL.TEXTURE_WRAP_S, GL.CLAMP);
glTexParameteri(GL.TEXTURE_RECTANGLE_EXT, GL.TEXTURE_WRAP_T, GL.CLAMP);

glBindTexture(GL.TEXTURE_RECTANGLE_EXT, 0);

% Ready.
return;
