File: SetStereoSideBySideParameters.m

package info (click to toggle)
psychtoolbox-3 3.0.19.14.dfsg1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 86,796 kB
  • sloc: ansic: 176,245; cpp: 20,103; objc: 5,393; sh: 2,753; python: 1,397; php: 384; makefile: 193; java: 113
file content (224 lines) | stat: -rw-r--r-- 8,649 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
function SetStereoSideBySideParameters(win, leftOffset, leftScale, rightOffset, rightScale, shaders, offsetUnit)
% Change parameters for side-by-side stereo display modes (4 and 5).
%
% SetStereoSideBySideParameters(win [, leftOffset][, leftScale][, rightOffset][, rightScale][, shaders][, offsetUnit='windowsizes'])
%
% Call this function after the win = PsychImaging('OpenWindow',...); call on an
% onscreen window in side-by-side stereo mode to change the parameters
% of drawing the stereo views.
%
% All parameters except the onscreen 'win'dowhandle are optional and have
% reasonable builtin defaults:
%
% 'leftOffset' = Top-Left [x,y] offset of left eye framebuffer in relative
% coordinates [0,0] == top-left of framebuffer, [1,0] == 1 stereo window
% width to the right, [2,0] == 2 stereo window width to the right etc. This
% is the case if the optional parameter 'offsetUnit' is omitted, empty, or
% set to 'windowsizes'. Otherwise the unit for 'leftOffset' and 'rightOffset'
% is selected by the 'offsetUnit' parameter.
%
% 'leftScale' = Scaling of left eye image buffer. E.g., [1,1] == Don't
% scale. [0.75, 0.5] scale to 75% of original width, 50% of original
% height.
%
% 'rightOffset', 'rightScale' == Ditto for right eye image.
%
% 'shaders' a vector containing the GLSL handles of a new pair of shaders to replace
% the standard builtin side-by-side compositing shaders. shaders(1) for left eye
% view, shaders(2) for right eye view. The old shaders are deleted if new shaders
% are assigned.
%
% 'offsetUnit' is a string, specifying the unit used for specifying 'leftOffset'
% and 'rightOffset'. If omitted, by default it is 'windowsizes'. Following values
% are currently supported:
%
% 'windowsizes' - 1 Unit for x or y is one width or height of the onscreen window,
%                 as described above for 'leftOffset'.
% 'pixels'      - 1 Unit for x or y is one pixel, so you can specify offsets in
%                 absolute pixel coordinates. The pixels are likely framebuffer
%                 pixels, ie. after potential application of Retina/HiDPI scaling
%                 or the panelfitter, but other correction tasks like 'GeometryCorrection'
%                 may affect which 'pixels' are meant, if such tasks change
%                 imaging geometry further.
%

% History:
% 03-Dec-2012 mk   Written.
% 22-Jul-2021 mk   Add optional override 'shaders' parameter.
% 01-Oct-2022 mk   Add optional 'offsetUnit' parameter to allow offsets in pixels.
% 02-Oct-2022 mk   Add RemapMouse() support for 'LeftView' and 'RightView'.

global ptb_geometry_inverseWarpMap;
persistent initial_inverseWarpMap;

% Test if a windowhandle is provided...
if nargin < 1
    error('You must provide the windowhandle for the onscreen window as 1st parameter!');
end

% ... and if it is a valid onscreen window in frame-sequential stereo mode:
if Screen('WindowKind', win) ~= 1
    error('Provided windowhandle is not a valid and open onscreen window!');
end

winfo = Screen('GetWindowInfo', win);
if ~ismember(winfo.StereoMode, [4,5])
    % No side-by-side mode -> No operation.
    fprintf('SetStereoSideBySideParameters: Info: Provided onscreen window is not switched to side-by-side stereo mode. Call ignored.\n');
    return;
end

% Query size of onscreen window in pixels w x h:
[w, h] = Screen('WindowSize', win);

% Parse other arguments, assign defaults if none passed:
if nargin < 2 || isempty(leftOffset)
    leftOffset = [0, 0];
end

if nargin < 3 || isempty(leftScale)
    leftScale = [1, 1];
end

if nargin < 4 || isempty(rightOffset)
    rightOffset = [1, 0];
end

if nargin < 5 || isempty(rightScale)
    rightScale = [1, 1];
end

if nargin < 6
    shaders = [];
end

if nargin < 7 || isempty(offsetUnit)
    offsetUnit = 'windowsizes';
else
    if ~ischar(offsetUnit)
        error('Optional parameter offsetUnit is not a character string, as required.');
    end
end

switch (lower(offsetUnit))
    case {'windowsizes'}
        sw = w;
        sh = h;

    case {'pixels'}
        sw = 1;
        sh = 1;

    otherwise
        error('Invalid value ''%s'' for parameter offsetUnit specified.', offsetUnit);
end

% Query full specification of processing slot for left eye view shader:
% 'slot' is position in processing chain, others are parameters for the
% operation:
[slot shaderid blittercfg voidptr glsl] = Screen('HookFunction', win, 'Query', 'StereoCompositingBlit', 'StereoCompositingShaderDualViewLeft'); %#ok<ASGLU>
if slot == -1
    error('Could not find processing slot for left-eye view!');
end

% Delete old processing slot from pipeline:
Screen('HookFunction', win, 'Remove', 'StereoCompositingBlit' , slot);

if ~isempty(shaders)
    glDeleteProgram(glsl);
    glsl = shaders(1);
end

% Define new blitter configuration for changed parameters:
leftOffset(1) = leftOffset(1) * sw;
leftOffset(2) = leftOffset(2) * sh;
blittercfg = sprintf('Builtin:IdentityBlit:Offset:%i:%i:Scaling:%f:%f', floor(leftOffset(1)), floor(leftOffset(2)), leftScale(1), leftScale(2));

% Insert modified processing function at old position (slot) in the
% pipeline, effectively replacing the slot:
posstring = sprintf('InsertAt%iShader', slot);
Screen('Hookfunction', win, posstring, 'StereoCompositingBlit', shaderid, glsl, blittercfg);

% Query full specification of processing slot for right eye view shader:
% 'slot' is position in processing chain, others are parameters for the
% operation:
[slot shaderid blittercfg voidptr glsl] = Screen('HookFunction', win, 'Query', 'StereoCompositingBlit', 'StereoCompositingShaderDualViewRight'); %#ok<ASGLU>
if slot == -1
    error('Could not find processing slot for right-eye view!');
end

% Delete old processing slot from pipeline:
Screen('HookFunction', win, 'Remove', 'StereoCompositingBlit' , slot);

if ~isempty(shaders)
    glDeleteProgram(glsl);
    glsl = shaders(2);
end

% Define new blitter configuration for changed parameters:
rightOffset(1) = rightOffset(1) * sw;
rightOffset(2) = rightOffset(2) * sh;
blittercfg = sprintf('Builtin:IdentityBlit:Offset:%i:%i:Scaling:%f:%f', floor(rightOffset(1)), floor(rightOffset(2)), rightScale(1), rightScale(2));

% Insert modified processing function at old position (slot) in the
% pipeline, effectively replacing the slot:
posstring = sprintf('InsertAt%iShader', slot);
Screen('Hookfunction', win, posstring, 'StereoCompositingBlit', shaderid, glsl, blittercfg);

% Now we need to define suitable inverse mappings for RemapMouse():

% If we don't have a backup copy of the initial ptb_geometry_inverseWarpMap for
% this win'dow - after PsychImaging('OpenWindow') and before our first invocation,
% then create one as a baseline, so successive invocations of this function always
% start with the same baseline, instead of compounding results from multiple calls:
if isempty(initial_inverseWarpMap) || isempty(initial_inverseWarpMap{win})
    % Initial call, make internal backup for successive calls:
    initial_inverseWarpMap{win} = ptb_geometry_inverseWarpMap{win};
end

% Get true framebuffer size of window and build identity mapping:
[wr, hr] = Screen('WindowSize', win, 1);
[xg, yg] = meshgrid(0:wr-1, 0:hr-1);

% Reset window global modulo parameter:
ptb_geometry_inverseWarpMap{win}.mx = wr;

% Assign left view inverse mapping of mouse position:
curmap(:,:,1) = (xg - leftOffset(1)) * 1 / leftScale(1);
curmap(:,:,2) = (yg - leftOffset(2)) * 1 / leftScale(2);
if ~isfield(initial_inverseWarpMap{win}, 'LeftView')
    ptb_geometry_inverseWarpMap{win}.('LeftView') = int16(curmap);
else
    prevmap = initial_inverseWarpMap{win}.('LeftView');
    ptb_geometry_inverseWarpMap{win}.('LeftView') = int16(remap(prevmap, curmap));
end

% Assign right view inverse mapping of mouse position:
curmap(:,:,1) = (xg - rightOffset(1)) * 1 / rightScale(1);
curmap(:,:,2) = (yg - rightOffset(2)) * 1 / rightScale(2);
if ~isfield(initial_inverseWarpMap{win}, 'RightView')
    ptb_geometry_inverseWarpMap{win}.('RightView') = int16(curmap);
else
    prevmap = initial_inverseWarpMap{win}.('RightView');
    ptb_geometry_inverseWarpMap{win}.('RightView') = int16(remap(prevmap, curmap));
end

end

function newmap = remap(prevmap, curmap)
    newmap = nan(size(curmap));
    curmap = round(curmap + 1);
    curmap = max(curmap, 1);
    curmap(:,:,1) = min(curmap(:,:,1), size(prevmap, 2));
    curmap(:,:,2) = min(curmap(:,:,2), size(prevmap, 1));
    ym = size(curmap, 1);
    xm = size(curmap, 2);
    for y = 1:ym
        for x = 1:xm
            xl = curmap(y, x, 1);
            yl = curmap(y, x, 2);
            newmap(y, x, 1) = prevmap(yl, xl, 1);
            newmap(y, x, 2) = prevmap(yl, xl, 2);
        end
    end
end