1
function CreateResolutionPyramid(tex, glsl, usebilinear)
2
% Build a mip-map image resolution pyramid for given texture.
4
% CreateResolutionPyramid(tex [, glsl=0][, usebilinear=0])
6
% 'tex' must be the Screen() texture or offscreen window handle of a
7
% texture or offscreen window of type GL_TEXTURE_2D, ie., created with
8
% 'specialFlags' setting 1. The function will create a OpenGL mip-map
9
% resolution pyramid by successive downsampling of 'tex' image content down
10
% to a image of 1x1 pixels. If 'glsl' is provided as handle to a shader, it
11
% will use that shader to do the downsampling, otherwise it will use
12
% bilinear filtering for successive pyramid levels. If 'usebilinear' is set
13
% to 1 it will feed the shader with linear interpolated samples, otherwise
14
% with nearest neighbour samples.
16
% You will likely also want to set 'specialFlags' flag 8 or 16 to prevent
17
% Screen('DrawTexture') from overwriting the image pyramid with its own
20
% This needs a modern GLSL shading capable graphics card to work.
24
% 27.08.2012 mk Written.
27
% Needed for GL constant definitions:
30
% Make sure MOGL is initialized:
32
InitializeMatlabOpenGL([],[],1);
35
if nargin < 1 || isempty(tex)
36
error('Must provide a valid Screen() texture or offscreen window handle for building of resolution pyramid!');
39
if nargin < 2 || isempty(glsl)
43
if nargin < 3 || isempty(usebilinear)
47
% Only supported for offscreen targets, not onscreen windows, proxies etc.:
48
if Screen('WindowKind', tex) ~= -1
49
error('Tried to built a resolution image pyramid for something else than a texture or offscreen window! Unsupported.');
52
% This, as a well defined side effect, will bind the tex'tures associated
53
% FBO, or create one if it doesn't have one yet, as well as normalize the
54
% textures format and orientation, just as we need it:
55
Screen('GetWindowInfo', tex);
57
% Get width and height:
58
[w, h] = Screen('WindowSize', tex);
60
% Get low-level texture handle and type:
61
[gltexid, gltextarget] = Screen('GetOpenGLTexture', tex, tex);
63
% Make sure it is a mip-map capable type:
64
if gltextarget ~= GL.TEXTURE_2D
65
error('Tried to built a resolution image pyramid for something else than a GL_TEXTURE_2D texture! Unsupported.');
68
% Time for a state backup:
69
glPushAttrib(GL.ALL_ATTRIB_BITS);
71
% Ok, the currently bound FBO is the FBO to use for drawing into this
72
% texture. And we got the gltexid of the corresponding texture.
73
% We also know that all texture parameters are set in a way to make it
74
% available as a rendertarget, e.g., the texture minification/magnification
75
% filters, wrap modes etc. are set to safe values, e.g., nearest neighbour
76
% sampling, clamp to edge etc.
79
glBindTexture(gltextarget, gltexid);
82
glDisable(GL.TEXTURE_RECTANGLE_EXT);
83
glEnable(gltextarget);
85
% Compute to what miplevel we need to downsample to cover all bases:
86
maxlod = floor(log2(max(w, h)));
88
% Query size of mip-level 'maxlod':
89
tw = glGetTexLevelParameteriv(gltextarget, maxlod, GL.TEXTURE_WIDTH);
90
th = glGetTexLevelParameteriv(gltextarget, maxlod, GL.TEXTURE_HEIGHT);
92
% If no such mip-level exists?
94
% No such mip-level: Generate initial mip-chain for this texture. We
95
% allocate mip-levels of proper format and size, but empty, ie.,
96
% without actually computing any content here:
97
internalFormat = glGetTexLevelParameteriv(gltextarget, 0, GL.TEXTURE_INTERNAL_FORMAT);
99
for miplevel = 1:maxlod
100
% Need to case tw, th to double(), use max() operators and whatnot
101
% to get proper size specs -- Matlab arithmetic on the returned
102
% int32's from query behaves rather strange otherwise:
103
tw = double(glGetTexLevelParameteriv(gltextarget, miplevel - 1, GL.TEXTURE_WIDTH));
104
th = double(glGetTexLevelParameteriv(gltextarget, miplevel - 1, GL.TEXTURE_HEIGHT));
105
glTexImage2D(gltextarget, miplevel, internalFormat, max(floor(tw/2), 1), max(floor(th/2), 1), 0, GL.RGBA, GL.UNSIGNED_BYTE, 0);
109
% Bind downsampling shader and set it up to read from texture unit 0:
112
% If we've actually bound the fixed-function pipeline, we skip this step:
114
glUniform1i(glGetUniformLocation(glsl, 'Image'), 0);
116
% Query locations of uniforms for passing src / dst dimensions:
117
srcSize = glGetUniformLocation(glsl, 'srcSize');
118
dstSize = glGetUniformLocation(glsl, 'dstSize');
121
% Just to be extra safe:
122
glTexParameteri(gltextarget, GL.TEXTURE_WRAP_S, GL.CLAMP_TO_EDGE);
123
glTexParameteri(gltextarget, GL.TEXTURE_WRAP_T, GL.CLAMP_TO_EDGE);
125
if glsl ~= 0 && ~usebilinear
126
% Use nearest neighbour sampling for real downsampling shaders, so they
127
% have full control over actual sampling:
128
glTexParameteri(gltextarget, GL.TEXTURE_MIN_FILTER, GL.NEAREST);
129
glTexParameteri(gltextarget, GL.TEXTURE_MAG_FILTER, GL.NEAREST);
131
% For fixed function, use bilinear filtering. This should pretty much
132
% emulate a 2x2 texels box-filter:
133
glTexParameteri(gltextarget, GL.TEXTURE_MIN_FILTER, GL.LINEAR);
134
glTexParameteri(gltextarget, GL.TEXTURE_MAG_FILTER, GL.LINEAR);
137
% Base level 0 is always enabled, as we never write to it:
138
glTexParameteri(gltextarget, GL.TEXTURE_BASE_LEVEL, 0);
140
% Iterate over all mip-levels 'miplevel' from 1 to maxlod, and source each
141
% 'miplevel-1' to compute 'miplevel':
142
for miplevel = 1:maxlod
143
% Query size of target mip level:
144
tw = glGetTexLevelParameteriv(gltextarget, miplevel, GL.TEXTURE_WIDTH);
145
th = glGetTexLevelParameteriv(gltextarget, miplevel, GL.TEXTURE_HEIGHT);
147
% If size is smaller than 1 texel in any dimension, exit the downsampling loop:
152
% Set mip-level upper range limit for sampling from texture from to "miplevel-1":
153
glTexParameteri(gltextarget, GL.TEXTURE_MAX_LEVEL, miplevel-1);
155
% Select the target mip-level for drawing into (== render-to-texture
156
% via FBO color attachment) to 'miplevel':
157
glFramebufferTexture2DEXT(GL.FRAMEBUFFER_EXT, GL.COLOR_ATTACHMENT0_EXT, gltextarget, gltexid, miplevel);
159
% Setup drawing transforms, projection and viewport:
160
glMatrixMode(GL.PROJECTION);
162
gluOrtho2D(0, tw, 0 , th);
163
glMatrixMode(GL.MODELVIEW);
165
glViewport(0, 0, tw, th);
167
% Ok, we should be setup properly now. Any shader sampling from the
168
% texture can only do nearest neighbour sampling from level
169
% 'miplevel-1', and all writes go to 'miplevel'.
171
% Communicate sizes of source and destination surface to possible
174
glUniform2f(srcSize, glGetTexLevelParameteriv(gltextarget, miplevel-1, GL.TEXTURE_WIDTH), glGetTexLevelParameteriv(gltextarget, miplevel-1, GL.TEXTURE_HEIGHT));
175
glUniform2f(dstSize, tw, th);
178
% Blit a "fullscreen quad" - or more specifically a full-texture quad
179
% to drive the downsampling shader:
191
% Rinse, wash, repeat at next miplevel...
194
% Reset mip-level range to normal:
195
glTexParameteri(gltextarget, GL.TEXTURE_BASE_LEVEL, 0);
196
glTexParameteri(gltextarget, GL.TEXTURE_MAX_LEVEL, 1000);
198
% Disable and unbind texture:
199
glDisable(gltextarget);
200
glBindTexture(gltextarget, 0);
202
% Restore framebuffer attachment for texture to render into miplevel zero:
203
glFramebufferTexture2DEXT(GL.FRAMEBUFFER_EXT, GL.COLOR_ATTACHMENT0_EXT, gltextarget, gltexid, 0);
208
% Final state: Texture unbound from sampler, but bound as current
209
% framebuffer/rendertarget. Regular PTB code will take care of this.
211
% Restore pre-op state: