-
Notifications
You must be signed in to change notification settings - Fork 5
/
wave-plots.html
1075 lines (898 loc) · 44.3 KB
/
wave-plots.html
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
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!--
See main readme for extensive introductorary notes.
Note that although we don't have a worker, we still split the logic into two sections: a polymer
component, which communicates with the outer world, and a WavesRenderer that is very
self-contained, this behaves vaguely like it was a worker on another thread, as in rm-plots and tac-plots.
In particular, note that the WavesRenderer and polymer component maintain separate okey_to_canvas
and okey_to_rendered_options maps, and in fact they contain slightly different sorts of data.
There are two rendering modes:
"flag" - lookup the colour of each line segment from a palette "texture", pass it from
the vertex shader to the fragment shader.
"density" - here there are two render phases, firstly we render counts in each pixel
then we "copy" the counts image, recolouring it using a computed color-scale, i.e.
we compute the colour arithmetically from the raw count value rather than looking
it up in a palette.
As far as I can tell, OES_texture_float gives 32bit float for each of r, g, and b. This gives
precise integer representation from 0 to 16million, which is plenty if we are trying to count
occurences.
TODO: it would be fun to add a drift rendering mode, that uses the g channel to store accumulate
time, and then divide total time by number of counts (stored in the red channel), so as to get
the mean time, as done in other places where drift rendering is used.
If you do this, you probably want to express time in seconds, so as to get the most out of the
range of 32bit floats...although I think you would have to come up with some fairly contrived
data if you wanted to observe the limitations of the 32bit precision.
TODO: it probably wouldn't be that hard to implement chanel-specific toggling. Keep the indices
of the wave buffers fixed as though all were in use, but only upload those that are needed and
be careful when building render metrics and using them.
Notes on show-toggling....
This is a bit messy because some of the logic is within WavesRenderer and some is in the polymer element.
And the WavesRenderer doesn't actually fully toggle off everything when show is off (="n"). And we use
"yyyy"/"nnnn" in the polymer element but "y"/"n" in the WavesRenderer. And there's a disctinction between
want_data_for_gl and whether or not the data is actually availble.
The WavesRenderer has a ._show property. When this is "n" no rendering will occurr, but canvases
are still created. If voltage data is avilable for the current tetrode it will not be removed from the gpu until
the tetrode is switched. The polymer element only provides voltage data to the WavesRenderer if show is
on in the element ("yyyy")....TODO: clean this all up.p
-->
<link rel="import" href="bower_components/polymer/polymer.html">
<link rel="import" href="palettes.html">
<link rel="import" href="managed-canvas.html">
<link rel="import" href="utils.html">
<link rel="import" href="webgl-debug.html">
<dom-module id="wave-plots">
<template></template>
<script>
"use strict";
// WavesRender is a class that does all the hard work for the wave-plots
// (hopefully) you can safely have multiple WavesRendere instances, i.e.
// one per wave-plots, if there are multiple wave-plot instances.
var WavesRenderer = (function(){
var palette_flag_register_ind = 2;
var float_texture_register_ind = 0; //this may need to be fixed at 0, not sure
var n_c = 4; // number of channels
var n_w = 50; // number of t points per channel
// Define metrics for the "off_canv"
// The following are all in units of actual pixels
var off_canv_w = 512; // full width
var off_canv_h = 800; // full height
var off_canv_wave_h = 128; // height of a wave
var off_canv_dt = 2; //distance from t to t+1 on the wave
var off_canv_wave_gap = 4; //horizontal and vertical gap between waves
// These two are applied in css..the actual width used matches the pixels on the off_canv
var on_canv_w = n_c*n_w*2;
var on_canv_h = 256; // this is absolutely critical, if it's smaller then drawimage is about 50x slower.
var off_canv_count_increment = 0.00001;
var shader_strs = (function(){
// multiline, template strings are nice, but it's wierd that they use the enclosing scope
// rather than accepting a .format(dict) like in python, anyway....
var delta_t_x_offset = (off_canv_dt/off_canv_w*2).toPrecision(3);
var y_factor = (1/(off_canv_h/off_canv_wave_h) * (1/128)).toPrecision(3);
return {
vertex_flag: `
attribute lowp float is_t_plus_one; // 0 1 0 1 0 1 0 1 ... 1
attribute float voltage; // v_1(t) v_1(t+1) v_2(t) v_2(t+1) v_3(t) ... v_n(t+1) values are on the interval [0 1]
attribute vec2 wave_xy_offset; // x_1 y_1 # x_1 y_1 # x_2 y_2 # x_2 y_2 # ... x_n y_n # x_n y_n #
attribute float wave_color_tex; // # # c_1 # # c_1 # # c_2 # # c_2 ... # # c_n # # c_n
uniform mediump float t_x_offset; // canavas x-coordiantes from the leftmost point of the wave to point t
varying lowp vec4 v_color;
uniform sampler2D palette;
const mediump float delta_t_x_offset = ${delta_t_x_offset}; // canvas x-coordinates from point t to point t+1
const mediump float y_factor = ${y_factor}; //scales voltage values, initially expressed in [0 1], to lie withn +- wave_h/2, in canvas coords [-1 +1]
void main(void) {
//v_color = vec4(1.,0.,0.,1.);
v_color = texture2D(palette, vec2(wave_color_tex, 0.));
//calculate the x coordiante in canvas coordinates
gl_Position.x = wave_xy_offset.x*(1./128.) -1. + t_x_offset + delta_t_x_offset*is_t_plus_one;
//calculate the y coordiante in canvas coordinates
gl_Position.y = wave_xy_offset.y*(1./128.) -1. + voltage*y_factor;
// best to set the fourth element to 1
gl_Position[3] = 1.;
}
`,
fragment_flag: `
varying lowp vec4 v_color;
void main(void) {
gl_FragColor = v_color;
}
`,
vertex_density_1: `
attribute lowp float is_t_plus_one; // 0 1 0 1 0 1 0 1 ... 1
attribute float voltage; // v_1(t) v_1(t+1) v_2(t) v_2(t+1) v_3(t) ... v_n(t+1) values are on the interval [0 1]
attribute vec2 wave_xy_offset; // x_1 y_1 x_1 y_1 x_2 y_2 x_2 y_2 ... x_n y_n x_n y_n
uniform mediump float t_x_offset; // canavas x-coordiantes from the leftmost point of the wave to point t
const mediump float delta_t_x_offset = ${delta_t_x_offset}; // canvas x-coordinates from point t to point t+1
const mediump float y_factor = ${y_factor}; //scales voltage values, initially expressed in [0 1], to lie with 128 pixels expressed in canvas coords [-1 +1]
void main(void) {
//calculate the x coordiante in canvas coordinates
gl_Position.x = wave_xy_offset.x*(1./128.) -1. + t_x_offset + delta_t_x_offset * is_t_plus_one;
//calculate the y coordiante in canvas coordinates
gl_Position.y = wave_xy_offset.y*(1./128.) -1. + voltage*y_factor;
// best to set the fourth element to 1
gl_Position[3] = 1.;
}
`,
fragment_density_1: `
void main(void) {
gl_FragColor = vec4(${off_canv_count_increment}, 0., 0., 1.);
}
`,
vertex_density_2: `
attribute vec2 a_tex_coord;
varying vec2 v_tex_coord;
attribute vec2 a_position;
const vec2 u_resolution = vec2(${off_canv_w}.0, ${off_canv_h}.0);
void main() {
vec2 zero_to_one = a_position / u_resolution;
vec2 zero_to_two = zero_to_one * 2.0;
vec2 clip_space = zero_to_two - 1.0;
gl_Position = vec4(clip_space, 0, 1);
v_tex_coord = a_tex_coord;
}
`,
fragment_density_2: `
precision mediump float;
uniform sampler2D u_src;
varying vec2 v_tex_coord;
uniform highp float normalising_factor;
void main() {
highp vec4 src = texture2D(u_src, v_tex_coord);
highp float counts = sqrt(src.r*normalising_factor*10.);
gl_FragColor = vec4(counts > 0.5 ?
counts > 0.75 ?
4. - 4.*counts
: 4.*counts-2.
: counts > 0.25 ?
2. - 4.*counts
: counts*4.
,
counts < 0.5 ?
2.*counts
: 2.-2.*counts
,
counts
,
src.a);
}
`
};
})()
var add_line_numbers = function(str){
str = str.split("\n");
var new_str = [];
var L = str.length;
for(var i=0;i<L;i++){
new_str.push((i+1) + ".\t");
new_str.push(str.shift());
new_str.push("\n");
}
return new_str.join('');
}
var get_shader_from_string = function(gl, str, type){
var shader = gl.createShader(type);
gl.shaderSource(shader, str);
gl.compileShader(shader);
if (gl.getShaderParameter(shader, gl.COMPILE_STATUS) == 0){
console.log("Shader failed to compile:\n" + gl.getShaderInfoLog(shader) + add_line_numbers(str));
throw "shader did not compile, see above,";
}
return shader;
}
var validate_program = function(gl, test_prog, throw_on_error){
gl.validateProgram(test_prog);
if (!gl.getProgramParameter(test_prog, gl.VALIDATE_STATUS)){
console.log("Error during program validation:\n" + gl.getProgramInfoLog(test_prog));
if(throw_on_error){
throw "program did not compile, see above.";
} else {
return false;
}
}
return true;
}
var upload_palette = function(gl, register_ind, data){
gl.activeTexture(gl.TEXTURE0 + register_ind);
gl.bindTexture(gl.TEXTURE_2D, gl.createTexture());
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 256, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
}
var WavesRenderer = function(callback, debug){
this._callback = callback; // we send it a map from okey to canvas every time we finish a bunch of rendering
// create the off_canv
this.el_off_canv = document.createElement('canvas');
this.el_off_canv.width = off_canv_w;
this.el_off_canv.height = off_canv_h;
this.el_off_canv.style.imageRendering = 'pixelated';
// initialise gl context for the off_canv
var gl = this.gl = this.el_off_canv.getContext("webgl") || this.el_off_canv.getContext("experimental-webgl");
if(!this.gl){
throw "could not get gl context.";
}
if(debug){
//gl = this.gl = WebGLDebugUtils.makeDebugContext(gl, throwOnGLError, validateNoneOfTheArgsAreUndefined);
this.debug_show_off_canv();
}
// TODO: we could possibly do the following lazily, i.e. when switching programs for the first time
if (!gl.getExtension('OES_texture_float')) {
console.log("No support for OES_texture_float");
// TODO: this is needed for density mode, so should disable that mode
}
// compile all the shaders and link into a library of programs
this._prog_flag = gl.createProgram();
gl.attachShader(this._prog_flag, get_shader_from_string(gl, shader_strs.vertex_flag, gl.VERTEX_SHADER));
gl.attachShader(this._prog_flag, get_shader_from_string(gl, shader_strs.fragment_flag, gl.FRAGMENT_SHADER));
gl.linkProgram(this._prog_flag);
validate_program(gl, this._prog_flag, true);
this._prog_density_1 = gl.createProgram();
gl.attachShader(this._prog_density_1, get_shader_from_string(gl, shader_strs.vertex_density_1, gl.VERTEX_SHADER));
gl.attachShader(this._prog_density_1, get_shader_from_string(gl, shader_strs.fragment_density_1, gl.FRAGMENT_SHADER));
gl.linkProgram(this._prog_density_1);
validate_program(gl, this._prog_density_1, true);
this._prog_density_2 = gl.createProgram();
gl.attachShader(this._prog_density_2, get_shader_from_string(gl, shader_strs.vertex_density_2, gl.VERTEX_SHADER));
gl.attachShader(this._prog_density_2, get_shader_from_string(gl, shader_strs.fragment_density_2, gl.FRAGMENT_SHADER));
gl.linkProgram(this._prog_density_2);
validate_program(gl, this._prog_density_2, true);
this._mode = undefined;
this._active_sub_prog = undefined;
// get all the uniforms/attribute locations for all programs
this._locs_flag = {};
this._locs_flag.wave_xy_offset = gl.getAttribLocation(this._prog_flag, "wave_xy_offset");
this._locs_flag.is_t_plus_one = gl.getAttribLocation(this._prog_flag, "is_t_plus_one");
this._locs_flag.wave_color_tex = gl.getAttribLocation(this._prog_flag, "wave_color_tex");
this._locs_flag.voltage = gl.getAttribLocation(this._prog_flag, "voltage");
this._locs_flag.t_x_offset = gl.getUniformLocation(this._prog_flag, "t_x_offset");
this._locs_flag.palette = gl.getUniformLocation(this._prog_flag, "palette");
this._locs_density_1 = {};
this._locs_density_1.wave_xy_offset = gl.getAttribLocation(this._prog_density_1, "wave_xy_offset");
this._locs_density_1.is_t_plus_one = gl.getAttribLocation(this._prog_density_1, "is_t_plus_one");
this._locs_density_1.voltage = gl.getAttribLocation(this._prog_density_1, "voltage");
this._locs_density_1.t_x_offset = gl.getUniformLocation(this._prog_density_1, "t_x_offset");
this._locs_density_2 = {};
this._locs_density_2.a_position = gl.getAttribLocation(this._prog_density_2, "a_position");
this._locs_density_2.a_tex_coord = gl.getAttribLocation(this._prog_density_2, "a_tex_coord");
this._locs_density_2.u_src = gl.getUniformLocation(this._prog_density_2, "u_src");
this._locs_density_2.normalising_factor = gl.getUniformLocation(this._prog_density_2, "normalising_factor");
// create all the neccessarry buffers (no space is actually allocated at this stage for data)
this._buf_wave = gl.createBuffer();
this._buf_voltage = gl.createBuffer();
this._buf_is_t_plus_one = gl.createBuffer();
// well, these two buffers are small and simple... (used only in the density_2 program)
this._buf_a_position = gl.createBuffer();
this._buf_a_tex_coord = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this._buf_a_position);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0, 0, off_canv_w, 0, 0, off_canv_h, 0, off_canv_h, off_canv_w, 0, off_canv_w, off_canv_h]), gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, this._buf_a_tex_coord);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0]), gl.STATIC_DRAW);
// upload flag palette texture
upload_palette(gl, palette_flag_register_ind, Palettes.flag);
// turn off depth testing since we want to just render in order (negative z is still invisible)
gl.disable(gl.DEPTH_TEST);
// prepare intermediate texture/buffer for density mode rendering
this._off_texture = gl.createTexture();
this._off_fbo = gl.createFramebuffer();
this._okey_to_part_rendered = new Map(); // this holds {mode, group_num, n_t, and canvas}. items here are pending
this._n_t_to_okeys = []; // a list of sets. Items in _okey_to_part_rendered, appear in the set at index item.n_t
for(let ii=0; ii<n_c*(n_w-1); ii++){
this._n_t_to_okeys.push(new Set()); // slightly wasteful, but probably not terrible in the grand scheme of things
}
this._okey_to_rendered = new Map(); // when a render completes, we send the canvas back to the polymer element, and store the settings used here
this._show = 'n'; // currently you cannot toggle individual channels.
this._has_voltages = false;
this._timer = 0;
}
WavesRenderer.prototype._switch_to_flag_prog = function(){
var gl = this.gl;
this._active_sub_prog = 'flag';
this.gl.useProgram(this._prog_flag);
gl.disable(gl.BLEND);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
this.gl.uniform1i(this._locs_flag.palette, palette_flag_register_ind);
gl.enableVertexAttribArray(this._locs_flag.is_t_plus_one);
gl.enableVertexAttribArray(this._locs_flag.wave_xy_offset);
gl.enableVertexAttribArray(this._locs_flag.wave_color_tex);
gl.enableVertexAttribArray(this._locs_flag.voltage);
gl.bindBuffer(gl.ARRAY_BUFFER, this._buf_is_t_plus_one);
gl.vertexAttribPointer(this._locs_flag.is_t_plus_one, 1, gl.UNSIGNED_BYTE, true, 1, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._buf_wave);
gl.vertexAttribPointer(this._locs_flag.wave_xy_offset, 2, gl.UNSIGNED_BYTE, false, 3, 0);
gl.vertexAttribPointer(this._locs_flag.wave_color_tex, 1, gl.UNSIGNED_BYTE, true, 3, 2);
}
WavesRenderer.prototype._switch_to_density_1_prog = function(){
var gl = this.gl;
this._active_sub_prog = 'density_1';
gl.useProgram(this._prog_density_1);
gl.enable(gl.BLEND);
gl.blendEquation(gl.FUNC_ADD);
gl.blendFunc(gl.ONE, gl.ONE);
gl.activeTexture(gl.TEXTURE0 + float_texture_register_ind);
gl.bindTexture(gl.TEXTURE_2D, this._off_texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, off_canv_w, off_canv_h, 0, gl.RGBA, gl.FLOAT, null);
gl.bindFramebuffer(gl.FRAMEBUFFER, this._off_fbo);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + float_texture_register_ind, gl.TEXTURE_2D, this._off_texture, 0);
gl.enableVertexAttribArray(this._locs_density_1.is_t_plus_one);
gl.enableVertexAttribArray(this._locs_density_1.wave_xy_offset);
gl.enableVertexAttribArray(this._locs_density_1.voltage);
gl.bindBuffer(gl.ARRAY_BUFFER, this._buf_is_t_plus_one);
gl.vertexAttribPointer(this._locs_density_1.is_t_plus_one, 1, gl.UNSIGNED_BYTE, true, 1, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._buf_wave);
gl.vertexAttribPointer(this._locs_density_1.wave_xy_offset, 2, gl.UNSIGNED_BYTE, false, 2, 0);
}
WavesRenderer.prototype._switch_to_density_2_prog = function(){
var gl = this.gl;
this._active_sub_prog = 'density_2';
gl.useProgram(this._prog_density_2);
gl.disable(gl.BLEND);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.enableVertexAttribArray(this._locs_density_2.a_position);
gl.enableVertexAttribArray(this._locs_density_2.a_tex_coord);
gl.bindBuffer(gl.ARRAY_BUFFER, this._buf_a_position);
gl.vertexAttribPointer(this._locs_density_2.a_position, 2, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._buf_a_tex_coord);
gl.vertexAttribPointer(this._locs_density_2.a_tex_coord, 2, gl.FLOAT, false, 0, 0);
gl.uniform1i(this._locs_density_2.u_src, float_texture_register_ind);
gl.activeTexture(gl.TEXTURE0 + float_texture_register_ind);
gl.bindTexture(gl.TEXTURE_2D, this._off_texture);
gl.uniform1f(this._locs_density_2.normalising_factor, 1/this._n/off_canv_count_increment);
}
WavesRenderer.prototype._run_density_2_prog = function(){
var gl = this.gl;
gl.drawArrays(gl.TRIANGLES, 0, 6);
}
WavesRenderer.prototype.set_n = function(n){
var gl = this.gl;
this._n = n;
if(this._active_sub_prog === 'density_2'){
gl.uniform1f(this._locs_density_2.normalising_factor, 1/this._n/off_canv_count_increment);
}
// uploads an 2n-length vector to the gpu, the vector is of the form 0 1 0 1 ... 0 1
var data = new Uint8Array(n*2);
for(var i=1;i<data.length;i+=2){
data[i] = 255;
}
// upload to the gpu
gl.bindBuffer(gl.ARRAY_BUFFER, this._buf_is_t_plus_one);
gl.bufferData(gl.ARRAY_BUFFER, data, gl.DYNAMIC_DRAW);
}
WavesRenderer.prototype.set_mode = function(mode){
if(this._mode === mode){
return;
}
var gl = this.gl;
this._mode = mode;
if(this._mode === "flag"){
this._switch_to_flag_prog();
}else if(this._mode === "density"){
this._switch_to_density_1_prog();
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
throw "gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE";
}
} else {
console.log("unrecognised mode");
}
// update all the pending logic...
this._deal_with_modified_okeys(Array.from(this._okey_to_part_rendered.keys())); //need to copy this because the map may be modified inside the function
this._deal_with_modified_okeys(this._okey_to_rendered.keys());
this._render_metrics = undefined;
this._touch_timer();
}
WavesRenderer.prototype.set_voltage_data = function(prepared_voltage_data){
var gl = this.gl;
this._voltage_data = prepared_voltage_data;
// the voltage data is pre-prepared exactly the way we wanted by the parse-data module.
// The prepared_voltage data is really a concatenated list of many separate arrays,
// where each array contains the data for all spikes at a specific channel-time, and is of
// the form: v_1(t) v_1(t+1) v_2(t) v_2(t+1) v_3(t) ... v_n(t+1)
// note there are 1 fewer such arrays then time points on the wave, n_w.
if(prepared_voltage_data){
this._has_voltages = true;
gl.bindBuffer(gl.ARRAY_BUFFER, this._buf_voltage);
gl.bufferData(gl.ARRAY_BUFFER, prepared_voltage_data.buffer, gl.DYNAMIC_DRAW);
} else {
this._has_voltages = false;
gl.bindBuffer(gl.ARRAY_BUFFER, this._buf_voltage);
gl.bufferData(gl.ARRAY_BUFFER, gl.NONE, gl.DYNAMIC_DRAW);
}
// note we don't set vertexAttribPointer here, because we do it multiple times
// during the render
this._touch_timer();
}
WavesRenderer.prototype._prepare_render_metrics = function(okeys, from_t, to_t){
// okeys is an array of objs, each with .akey property that gives a akey.
okeys = [null].concat(Array.from(okeys)); // if it was a set, then make it an array with well-defined order
// also, make the 0th okey null, because we render the rubbish in that
// bit of the canvas.
// we want to draw all okeys to the off_canv in one go,
// but in general we cant fit the full width of each okey on at the same time,
// instead we render small slices from t to t+something.
// the question is, how big is that something?
let n_okeys = okeys.length;
let n_rows = Math.floor(off_canv_h/(off_canv_wave_h+off_canv_wave_gap)); // TODO: we should really have a vertical gap between waves
let success = false;
let n_cols, batch_t;
for(batch_t=to_t-from_t; batch_t>3; batch_t = Math.ceil(batch_t/2)){
n_cols = Math.floor(off_canv_w/(batch_t*off_canv_dt + off_canv_wave_gap));
if(n_okeys <= n_cols * n_rows){
success = true;
break;
}
}
if(!success){
throw "too many okeys to render using current render logic"; // TODO: we could page through the okeys
}
// Now we compute that offsets for each okey, in both pixel coordinates
let x_offset = new Uint16Array(n_rows*n_cols);
let y_offset = new Uint16Array(n_rows*n_cols);
for(let row=0, p=0; row<n_rows; row++){
for(let col=0; col<n_cols; col++, p++){
x_offset[p] = col*(batch_t*off_canv_dt + off_canv_wave_gap) + off_canv_wave_gap; // dunno why the +gap at the end, but it helps the first column
y_offset[p] = row*(off_canv_wave_h + off_canv_wave_gap);
}
}
// We could have done the following in the above loop, but hey, we use a second loop...
// convert x and y offsets to 8-bit, which are the values to be passed to the vertex-shader.
// Then convert the rounded values back to pixels, thus correcting the pixel values to match the
// rounding errors involved.
let xy_offset_gl_i8 = new Uint8Array(x_offset.length*2);
for(let p=0; p<x_offset.length; p++){
let x_gl = 0 | (x_offset[p]/off_canv_w * 256);
let y_gl = 0 | ((y_offset[p] + off_canv_wave_h/2)/off_canv_h * 256);
xy_offset_gl_i8[p*2] = x_gl;
xy_offset_gl_i8[p*2+1] = y_gl;
let y_gl_11 = (y_gl*(1/128) -1); // value on interval [-1 to 1]
y_offset[p] = Math.max(0, (y_gl_11+1)*off_canv_h/2 - off_canv_wave_h/2);
}
return {
from_t: from_t,
to_t: to_t,
batch_t: batch_t,
x_offset: x_offset,
y_offset: y_offset,
xy_offset_gl_i8: xy_offset_gl_i8,
okeys: okeys
}
}
WavesRenderer.prototype._upload_okey_data = function(){
var gl = this.gl;
// okeys is an array of objs, each with .akey property that gives a akey.
// if render mode is flag it must also provide a .group_num property.
// Convert easy pixel coords to the weird [-128, 127] x [-128, 127] coordinates.
// we will be assigning two elements at a time, so lets use 16bit view
var render_metrics = this._render_metrics;
var okeys = render_metrics.okeys; // 0th entry is null, because offsets are reserved for rubbish
var xy_offset_gl_i16 = new Uint16Array(render_metrics.xy_offset_gl_i8.buffer);
if(this._mode === 'density'){
// just need to provide x and y
var data_i8 = new Uint8Array(this._n*4); // TODO: it might be worth holding a reference to this permanentyl to avoid GC and realoc
var data_i16 = new Uint16Array(data_i8.buffer);
for(let ii=1; ii<okeys.length; ii++){
let inds = okeys[ii].akey.array;
let xy = xy_offset_gl_i16[ii];
for(let jj=0; jj<inds.length; jj++){
data_i16[(inds[jj]<<1) +0] = xy;
data_i16[(inds[jj]<<1) +1] = xy;
}
}
}else{
// need to give x, y, and palette index
// TODO: test whether it's faster to provide the full rgb color rather than a palette index. I suspect it might be.
var data_i8 = new Uint8Array(this._n*6);
var data_i16 = new Uint16Array(data_i8.buffer);
for(let ii=1; ii<okeys.length; ii++){
let inds = okeys[ii].akey.array;
let xy = xy_offset_gl_i16[ii];
let gx = okeys[ii].group_num | (xy << 8);
let yg = ((0xff00 & xy) >>> 8) | (okeys[ii].group_num << 8);
for(let jj=0; jj<inds.length; jj++){
data_i16[inds[jj]*3 +0] = xy;
data_i16[inds[jj]*3 +1] = gx;
data_i16[inds[jj]*3 +2] = yg;
}
}
}
// upload it to the wave buffer on the gpu
gl.bindBuffer(gl.ARRAY_BUFFER, this._buf_wave);
gl.bufferData(gl.ARRAY_BUFFER, data_i8.buffer, gl.DYNAMIC_DRAW);
// tell the gpu where to find the wave data
if(this._mode === 'density'){
gl.vertexAttribPointer(this._locs_density_1.wave_xy_offset, 2, gl.UNSIGNED_BYTE, false, 2, 0);
}else{
gl.vertexAttribPointer(this._locs_flag.wave_xy_offset, 2, gl.UNSIGNED_BYTE, false, 3, 0);
gl.vertexAttribPointer(this._locs_flag.wave_color_tex, 1, gl.UNSIGNED_BYTE, true, 3, 2);
}
}
WavesRenderer.prototype._options_match = function(a, group_num){
if(!a || a.mode !== this._mode){
return false;
}
if(a.mode === 'density'){
return true;
} else if(this._mode === 'flag' && a.group_num == group_num){
return true;
}
return false;
}
WavesRenderer.prototype._deal_with_modified_okeys = function(okeys_modified){
let results = new Map(); // we may be able to provide some resutls already
for(let okey of okeys_modified){
if(this._options_match(this._okey_to_rendered.get(okey), okey.group_num)){
// this matches the last canvas we sent, so abandon any partial render (if there was one)
// and send the results back to main again
if(this._okey_to_part_rendered.has(okey)){
this._n_t_to_okeys[this._okey_to_part_rendered.get(okey).n_t].delete(okey);
this._okey_to_part_rendered.delete(okey);
}
results.set(okey, this._okey_to_rendered.get(okey).el_canv);
} else if (!this._okey_to_part_rendered.has(okey)){
// this is different to the last thing we renderd, and we weren't
// previously planning on rendering it. So need to render from scratch
let part = {
n_t: 0,
el_canv: document.createElement('canvas'),
main_has_canv: false
};
part.el_canv.width = off_canv_dt*n_c*(n_w-1);
part.el_canv.height = on_canv_h;
part.el_canv.style.width = part.el_canv.width + "px"; // if managed-canvas sets the height then this will hold the width fixed
part.ctx = part.el_canv.getContext('2d');
this._okey_to_part_rendered.set(okey, part);
this._n_t_to_okeys[0].add(okey);
} else if (this._options_match(this._okey_to_part_rendered.get(okey), okey.group_num)){
// this matches what we are already in the middle of trying to render,
// so don't change anything
} else {
// we were trying to render this, but we now need to start again with new options
let parts = this._okey_to_part_rendered.get(okey);
if(parts.n_t > 0){ // had we actually rendered anything yet?
this._n_t_to_okeys[parts.n_t].delete(okey);
parts.ctx.clearRect(0, 0, parts.n_t*off_canv_dt, off_canv_wave_h);
parts.n_t = 0;
this._n_t_to_okeys[0].add(okey);
}
}
}
if(results.size > 0){
this._callback(results);
}
}
WavesRenderer.prototype.update_okeys = function(okeys_to_remove, okeys_to_add, okeys_modified){
// okeys_modified is used when group_num changes for an existing okey
// note how _okey_to_rendered is a map defining both the pending okeys,
// and their current state of render. When we are done rendering the canvas
// is sent to the polymer-element, and the settings are stored in _okey_to_rendered.
for(let okey of okeys_to_remove){
this._okey_to_rendered.delete(okey);
let part = this._okey_to_part_rendered.get(okey);
if(part){
this._n_t_to_okeys[part.n_t].delete(okey);
this._okey_to_part_rendered.delete(okey);
}
}
// originally the idea was to wait till we've finished rendering before providing
// results, but actually the first time we render an okey we can actually make the
// canvas we are going to draw to, and send it to main immediately.
let results = new Map();
for(let okey of okeys_to_add){
let part = {
n_t: 0,
el_canv: document.createElement('canvas'), // TODO: might be good to avoid creating canvas entierly when show="n"
main_has_canv: this._show === "y"
}
this._okey_to_part_rendered.set(okey, part);
part.el_canv.width = off_canv_dt*n_c*(n_w-1);
part.el_canv.height = on_canv_h;
part.el_canv.style.width = part.el_canv.width + "px"; // if managed-canvas sets the height then this will hold the width fixed
part.ctx = part.el_canv.getContext('2d');
this._n_t_to_okeys[0].add(okey);
results.set(okey, part.el_canv);
}
if(this._show === "y"){
this._callback(results);
}
// dealing with modifeid okey is more complicated (and we
// also do it when render mode changes), hence it has a function...
this._deal_with_modified_okeys(okeys_modified);
this._render_metrics = undefined;
this._touch_timer();
}
WavesRenderer.prototype.set_show = function(v){
this._show = v;
if(this._show === "n"){
for(let opts of this._okey_to_part_rendered.values()){
opts.main_has_canv = false;
}
} else {
this._deal_with_modified_okeys(this._okey_to_rendered.keys());
}
this._touch_timer();
}
WavesRenderer.prototype._touch_timer = function(){
// turns timer on if there's work to be done, otherwise stops it (if it was running)
// note it's not actually a timer, because the interval is zero!
// the point of using this is that it enables messages from elsewhere to interupt
// between ticks, we can thus cancel work as and when needed.
if(this._has_voltages && this._okey_to_part_rendered.size && this._show === 'y'){
this._timer = this._timer || setImmediate(this._timer_tick.bind(this));
} else{
clearImmediate(this._timer);
this._timer = 0;
}
}
WavesRenderer.prototype._timer_tick = function(){
// This is the where the main render takes place.
this._timer = 0;
if(this._show === "n"){
return;
}
// TODO: we really don't need this mode-switching stuff here,
// though it's not a big deal I suppose.
if(this._mode === 'density'){
if(this._active_sub_prog !== "density_1"){
this._switch_to_density_1_prog();
}
} else {
if(this._active_sub_prog !== "flag"){
this._switch_to_flag_prog();
}
}
if(!this._render_metrics){
let from_t, to_t;
// okeys were modified since the last tick, so find the
// first set of okeys to render, and prepare their render metrics...
for(from_t=0; from_t<n_c*(n_w-1); from_t++){
if(this._n_t_to_okeys[from_t].size){
break;
}
}
for(to_t=from_t+1; to_t<n_c*(n_w-1); to_t++){
if(this._n_t_to_okeys[to_t].size){
break;
}
}
this._render_metrics = this._prepare_render_metrics(this._n_t_to_okeys[from_t], from_t, to_t);
this._upload_okey_data();
} // else the last tick has already prepared the _render_metrics for us
var from_t = this._render_metrics.from_t;
var end_t = Math.min(from_t + this._render_metrics.batch_t, this._render_metrics.to_t);
var okeys = this._render_metrics.okeys; // an array not a set
if(this._mode === 'density'){
this._render_batch_density(from_t, end_t);
}else if(this._mode == 'flag'){
this._render_batch_flag(from_t, end_t);
}
this.gl.finish();
// copy fragments from off_canv into individual canvases
var width = (end_t - from_t) * off_canv_dt;
var x_offsets = this._render_metrics.x_offset;
var y_offsets = this._render_metrics.y_offset;
for(let ii=1; ii<okeys.length; ii++){
let okey = okeys[ii];
let part = this._okey_to_part_rendered.get(okey);
part.ctx.drawImage(this.el_off_canv, x_offsets[ii], off_canv_h - 1 - y_offsets[ii] - off_canv_wave_h,
width, off_canv_wave_h, part.n_t*off_canv_dt, 0, width, on_canv_h);
}
// record that the okeys have now increaed their n_t
if(end_t < n_c*(n_w-1)){
// more work to do still...
for(let ii=1; ii<okeys.length; ii++){
let okey = okeys[ii];
this._okey_to_part_rendered.get(okey).n_t = end_t;
this._n_t_to_okeys[end_t].add(okey);
}
} else {
// finished rendering (everything). Note that the results should be absolutely up to date
// because we're on the same thread as main and get notifications about changes to groups etc..
let results = new Map();
for(let ii=1; ii<okeys.length; ii++){
let okey = okeys[ii];
let part = this._okey_to_part_rendered.get(okey);
let item = {
mode: this._mode,
group_num: okey.group_num,
el_canv: part.el_canv
};
this._okey_to_rendered.set(okey, item);
if(!part.main_has_canv){
results.set(okey, item.el_canv);
}
this._okey_to_part_rendered.delete(okey);
}
this._callback(results)
}
this._n_t_to_okeys[this._render_metrics.from_t].clear();
// update render_metrics for the next tick
if (end_t === n_c*(n_w-1)){
this._render_metrics = undefined; // nothing left to render
}else if (end_t === this._render_metrics.to_t){
// the current set of okeys has caught up with the next
// non-empty set, so we need to change the render metrics...
// note that if things changed during the render we would have
// already recalculated the render_metrics.to_t at that point.
var okeys = this._n_t_to_okeys[end_t]; // includes the stuff we just rendered and the new stuff
for(var to_t=end_t+1; to_t<n_c*(n_w-1); to_t++){
if(this._n_t_to_okeys[to_t].size){
break;
}
}
this._prepare_render_metrics(okeys, end_t, to_t);
this._upload_okey_data(); // it's a bit awkward to upload here...
// as we could poentially end up changing the
// render metrics before use, but whatever, right?
} else {
// more batches to do before we finsih or the set of okeys grows
this._render_metrics.from_t += this._render_metrics.batch_t;
}
//return; //debug
this._touch_timer(); // and so it continues...
}
WavesRenderer.prototype._render_batch_flag = function(from_t, end_t){
var gl = this.gl;
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.bindBuffer(gl.ARRAY_BUFFER, this._buf_voltage);
for(let t=from_t; t<end_t; t++){
gl.uniform1f(this._locs_flag.t_x_offset, off_canv_dt*(t-from_t)/off_canv_w*2);
// bind the voltage buffer with data for (t,t+1) and render it
gl.vertexAttribPointer(this._locs_flag.voltage, 1, gl.BYTE, false, 1, t*this._n*2);
gl.drawArrays(gl.LINES, 0, 2*this._n);
}
}
WavesRenderer.prototype._render_batch_density = function(from_t, end_t){
this._switch_to_density_1_prog();
var gl = this.gl;
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.bindBuffer(gl.ARRAY_BUFFER, this._buf_voltage);
for(let t=from_t; t<end_t; t++){
gl.uniform1f(this._locs_density_1.t_x_offset, off_canv_dt*(t-from_t)/off_canv_w*2);
// bind the voltage buffer with data for (t,t+1) and render it
gl.vertexAttribPointer(this._locs_density_1.voltage, 1, gl.BYTE, false, 1, t*this._n*2);
gl.drawArrays(gl.LINES, 0, 2*this._n);
}
this._switch_to_density_2_prog();
this._run_density_2_prog();
}
WavesRenderer.prototype.mask_at_vt = function(inds, point){
let t = 0 | (point[1] / off_canv_dt);
let v = 0 | (127 - (point[0] /on_canv_h)*255);
let m = new Uint8Array(inds.length);
let vdata = new Int8Array(this._voltage_data.buffer);
let n = this._n;
for(let ii=0; ii<inds.length; ii++){
m[ii] = vdata[n*t*2 + inds[ii]*2] > v ? 1 : 0;
}
return m;
}
WavesRenderer.prototype.debug_show_off_canv = function(){
this.el_off_canv.style.position = 'fixed';
this.el_off_canv.style.right = "0px";
this.el_off_canv.style.bottom = "0px";
this.el_off_canv.style.background = "#fff";
this.el_off_canv.style.border = "1px solid #f00";
//this.el_off_canv.style.transform = 'scale(0.5)';
//this.el_off_canv.style.transformOrigin = 'bottom right';
document.body.appendChild(this.el_off_canv);
}
return WavesRenderer;
})();
Polymer({
is:'wave-plots',
behaviors: [
Polymer.ShortcutNotifyerBehavior
],
properties: {
palette_mode: {
type: String,
value: "density",
notify: true
},
show_chans: {
type: String,
value: "yyyy",
notify: true
},
groups: {
type: Array,
value: function(){return [];},
notify: true,
observer: '_groups_set'
},
data_for_gl: {
type: Object,
value: function(){return {};}, // has n value and typed-array-manager id for gl voltages
notify: true
},
want_data_for_gl: {
type: Array,
notify: true,
observer: 'want_data_for_gl_set'
}
},
observers: [
'_groups_spliced(groups.splices)',
'_palette_mode_changed(palette_mode)',
'_show_chans_changed(show_chans)',
'_data_for_gl_changed(data_for_gl)'
], created: function(){
try{
this._renderer = new WavesRenderer(this._okey_rendered.bind(this), false);
} catch (e){
console.log("failed to create renderer....\n" + e);
}
this._has_want_data_for_gl_ref = false;
}, want_data_for_gl_set: function(){
this._show_chans_changed(this.show_chans);
}, _data_for_gl_changed: function(){
// note that if gl data was avaialble and then we turned off show we will
// get a null-ing event the next time the tet file changes...which is good.
let is_null = !(this.data_for_gl && this.data_for_gl.voltages);