File: _sdf_gpu.py

package info (click to toggle)
python-vispy 0.14.3-1
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 8,840 kB
  • sloc: python: 59,436; javascript: 6,800; makefile: 69; sh: 6
file content (316 lines) | stat: -rw-r--r-- 10,480 bytes parent folder | download | duplicates (3)
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
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
# -*- coding: utf-8 -*-
"""
Jump flooding algoritm for EDT using GLSL code:
Author: Stefan Gustavson (stefan.gustavson@gmail.com)
2010-08-24. This code is in the public domain.

Adapted to `vispy` by Eric Larson <larson.eric.d@gmail.com>.
"""

import numpy as np

from ...gloo import (Program, FrameBuffer, VertexBuffer, Texture2D,
                     set_viewport, set_state)

vert_seed = """
attribute vec2 a_position;
attribute vec2 a_texcoord;
varying vec2 v_uv;

void main( void )
{
  v_uv = a_texcoord.xy;
  gl_Position = vec4(a_position.xy, 0., 1.);
}
"""

vert = """
uniform float u_texw;
uniform float u_texh;
uniform float u_step;
attribute vec2 a_position;
attribute vec2 a_texcoord;
varying float v_stepu;
varying float v_stepv;
varying vec2 v_uv;

void main( void )
{
  v_uv = a_texcoord.xy;
  v_stepu = u_step / u_texw; // Saves a division in the fragment shader
  v_stepv = u_step / u_texh;
  gl_Position = vec4(a_position.xy, 0., 1.);
}
"""

frag_seed = """
uniform sampler2D u_texture;
varying vec2 v_uv;

void main( void )
{
  float pixel = texture2D(u_texture, v_uv).r;
  vec4 myzero = vec4(128. / 255., 128. / 255., 0., 0.);  // Zero
  vec4 myinfinity = vec4(0., 0., 0., 0.);                // Infinity
  // Pixels >= 0.5 are objects, others are background
  gl_FragColor = pixel >= 0.5 ? myzero : myinfinity;
}
"""

frag_flood = """
uniform sampler2D u_texture;
varying float v_stepu;
varying float v_stepv;
varying vec2 v_uv;

vec2 remap(vec4 floatdata) {
    vec2 scaleddata = vec2(floatdata.x * 65280. + floatdata.z * 255.,
                           floatdata.y * 65280. + floatdata.w * 255.);
    return scaleddata / 32768. - 1.0;
}

vec4 remap_inv(vec2 floatvec) {
    vec2 data = (floatvec + 1.0) * 32768.;
    float x = floor(data.x / 256.);
    float y = floor(data.y / 256.);
    return vec4(x, y, data.x - x * 256., data.y - y * 256.) / 255.;
}

void main( void )
{
  // Search for better distance vectors among 8 candidates
  vec2 stepvec; // Relative offset to candidate being tested
  vec2 newvec;  // Absolute position of that candidate
  vec3 newseed; // Closest point from that candidate (.xy) and its dist (.z)
  vec3 bestseed; // Closest seed so far
  bestseed.xy = remap(texture2D(u_texture, v_uv).rgba);
  bestseed.z = length(bestseed.xy);

  stepvec = vec2(-v_stepu, -v_stepv);
  newvec = v_uv + stepvec;
  if (all(bvec4(lessThan(newvec, vec2(1.0)), greaterThan(newvec, vec2(0.0))))){
    newseed.xy = remap(texture2D(u_texture, newvec).rgba);
    if(newseed.x > -0.99999) { // if the new seed is not "indeterminate dist"
      newseed.xy = newseed.xy + stepvec;
      newseed.z = length(newseed.xy);
      if(newseed.z < bestseed.z) {
        bestseed = newseed;
      }
    }
  }

  stepvec = vec2(-v_stepu, 0.0);
  newvec = v_uv + stepvec;
  if (all(bvec4(lessThan(newvec, vec2(1.0)), greaterThan(newvec, vec2(0.0))))){
    newseed.xy = remap(texture2D(u_texture, newvec).rgba);
    if(newseed.x > -0.99999) { // if the new seed is not "indeterminate dist"
      newseed.xy = newseed.xy + stepvec;
      newseed.z = length(newseed.xy);
      if(newseed.z < bestseed.z) {
        bestseed = newseed;
      }
    }
  }

  stepvec = vec2(-v_stepu, v_stepv);
  newvec = v_uv + stepvec;
  if (all(bvec4(lessThan(newvec, vec2(1.0)), greaterThan(newvec, vec2(0.0))))){
    newseed.xy = remap(texture2D(u_texture, newvec).rgba);
    if(newseed.x > -0.99999) { // if the new seed is not "indeterminate dist"
      newseed.xy = newseed.xy + stepvec;
      newseed.z = length(newseed.xy);
      if(newseed.z < bestseed.z) {
        bestseed = newseed;
      }
    }
  }

  stepvec = vec2(0.0, -v_stepv);
  newvec = v_uv + stepvec;
  if (all(bvec4(lessThan(newvec, vec2(1.0)), greaterThan(newvec, vec2(0.0))))){
    newseed.xy = remap(texture2D(u_texture, newvec).rgba);
    if(newseed.x > -0.99999) { // if the new seed is not "indeterminate dist"
      newseed.xy = newseed.xy + stepvec;
      newseed.z = length(newseed.xy);
      if(newseed.z < bestseed.z) {
        bestseed = newseed;
      }
    }
  }

  stepvec = vec2(0.0, v_stepv);
  newvec = v_uv + stepvec;
  if (all(bvec4(lessThan(newvec, vec2(1.0)), greaterThan(newvec, vec2(0.0))))){
    newseed.xy = remap(texture2D(u_texture, newvec).rgba);
    if(newseed.x > -0.99999) { // if the new seed is not "indeterminate dist"
      newseed.xy = newseed.xy + stepvec;
      newseed.z = length(newseed.xy);
      if(newseed.z < bestseed.z) {
        bestseed = newseed;
      }
    }
  }

  stepvec = vec2(v_stepu, -v_stepv);
  newvec = v_uv + stepvec;
  if (all(bvec4(lessThan(newvec, vec2(1.0)), greaterThan(newvec, vec2(0.0))))){
    newseed.xy = remap(texture2D(u_texture, newvec).rgba);
    if(newseed.x > -0.99999) { // if the new seed is not "indeterminate dist"
      newseed.xy = newseed.xy + stepvec;
      newseed.z = length(newseed.xy);
      if(newseed.z < bestseed.z) {
        bestseed = newseed;
      }
    }
  }

  stepvec = vec2(v_stepu, 0.0);
  newvec = v_uv + stepvec;
  if (all(bvec4(lessThan(newvec, vec2(1.0)), greaterThan(newvec, vec2(0.0))))){
    newseed.xy = remap(texture2D(u_texture, newvec).rgba);
    if(newseed.x > -0.99999) { // if the new seed is not "indeterminate dist"
      newseed.xy = newseed.xy + stepvec;
      newseed.z = length(newseed.xy);
      if(newseed.z < bestseed.z) {
        bestseed = newseed;
      }
    }
  }

  stepvec = vec2(v_stepu, v_stepv);
  newvec = v_uv + stepvec;
  if (all(bvec4(lessThan(newvec, vec2(1.0)), greaterThan(newvec, vec2(0.0))))){
    newseed.xy = remap(texture2D(u_texture, newvec).rgba);
    if(newseed.x > -0.99999) { // if the new seed is not "indeterminate dist"
      newseed.xy = newseed.xy + stepvec;
      newseed.z = length(newseed.xy);
      if(newseed.z < bestseed.z) {
        bestseed = newseed;
      }
    }
  }

  gl_FragColor = remap_inv(bestseed.xy);
}
"""

frag_insert = """

uniform sampler2D u_texture;
uniform sampler2D u_pos_texture;
uniform sampler2D u_neg_texture;
varying float v_stepu;
varying float v_stepv;
varying vec2 v_uv;

vec2 remap(vec4 floatdata) {
    vec2 scaled_data = vec2(floatdata.x * 65280. + floatdata.z * 255.,
                            floatdata.y * 65280. + floatdata.w * 255.);
    return scaled_data / 32768. - 1.0;
}

void main( void )
{
    float pixel = texture2D(u_texture, v_uv).r;
    // convert distance from normalized units -> pixels
    vec2 rescale = vec2(v_stepu, v_stepv);
    float shrink = 8.;
    rescale = rescale * 256. / shrink;
    // Without the division, 1 RGB increment = 1 px distance
    vec2 pos_distvec = remap(texture2D(u_pos_texture, v_uv).rgba) / rescale;
    vec2 neg_distvec = remap(texture2D(u_neg_texture, v_uv).rgba) / rescale;
    if (pixel <= 0.5)
        gl_FragColor = vec4(0.5 - length(pos_distvec));
    else
        gl_FragColor = vec4(0.5 - (shrink - 1.) / 256. + length(neg_distvec));
}
"""


class SDFRendererGPU(object):
    def __init__(self):
        self.program_seed = Program(vert_seed, frag_seed)
        self.program_flood = Program(vert, frag_flood)
        self.program_insert = Program(vert, frag_insert)
        self.programs = [self.program_seed, self.program_flood,
                         self.program_insert]

        # Initialize variables
        self.fbo_to = [FrameBuffer(), FrameBuffer(), FrameBuffer()]
        vtype = np.dtype([('a_position', np.float32, 2),
                          ('a_texcoord', np.float32, 2)])
        vertices = np.zeros(4, dtype=vtype)
        vertices['a_position'] = [[-1., -1.], [-1., 1.], [1., -1.], [1., 1.]]
        vertices['a_texcoord'] = [[0., 0.], [0., 1.], [1., 0.], [1., 1.]]
        vertices = VertexBuffer(vertices)
        self.program_insert['u_step'] = 1.
        for program in self.programs:
            program.bind(vertices)

    def render_to_texture(self, data, texture, offset, size):
        """Render a SDF to a texture at a given offset and size

        Parameters
        ----------
        data : array
            Must be 2D with type np.ubyte.
        texture : instance of Texture2D
            The texture to render to.
        offset : tuple of int
            Offset (x, y) to render to inside the texture.
        size : tuple of int
            Size (w, h) to render inside the texture.
        """
        assert isinstance(texture, Texture2D)
        set_state(blend=False, depth_test=False)

        # calculate the negative half (within object)
        orig_tex = Texture2D(255 - data, format='luminance',
                             wrapping='clamp_to_edge', interpolation='nearest')
        edf_neg_tex = self._render_edf(orig_tex)

        # calculate positive half (outside object)
        orig_tex[:, :, 0] = data

        edf_pos_tex = self._render_edf(orig_tex)

        # render final product to output texture
        self.program_insert['u_texture'] = orig_tex
        self.program_insert['u_pos_texture'] = edf_pos_tex
        self.program_insert['u_neg_texture'] = edf_neg_tex
        self.fbo_to[-1].color_buffer = texture
        with self.fbo_to[-1]:
            set_viewport(tuple(offset) + tuple(size))
            self.program_insert.draw('triangle_strip')

    def _render_edf(self, orig_tex):
        """Render an EDF to a texture"""
        # Set up the necessary textures
        sdf_size = orig_tex.shape[:2]

        comp_texs = []
        for _ in range(2):
            tex = Texture2D(sdf_size + (4,), format='rgba',
                            interpolation='nearest', wrapping='clamp_to_edge')
            comp_texs.append(tex)
        self.fbo_to[0].color_buffer = comp_texs[0]
        self.fbo_to[1].color_buffer = comp_texs[1]
        for program in self.programs[1:]:  # program_seed does not need this
            program['u_texh'], program['u_texw'] = sdf_size

        # Do the rendering
        last_rend = 0
        with self.fbo_to[last_rend]:
            set_viewport(0, 0, sdf_size[1], sdf_size[0])
            self.program_seed['u_texture'] = orig_tex
            self.program_seed.draw('triangle_strip')
        stepsize = (np.array(sdf_size) // 2).max()
        while stepsize > 0:
            self.program_flood['u_step'] = stepsize
            self.program_flood['u_texture'] = comp_texs[last_rend]
            last_rend = 1 if last_rend == 0 else 0
            with self.fbo_to[last_rend]:
                set_viewport(0, 0, sdf_size[1], sdf_size[0])
                self.program_flood.draw('triangle_strip')
            stepsize //= 2
        return comp_texs[last_rend]