init() { const { gl } = this; const frag = this.FRAG.replace(/MARKERCODE/, this.MARKERCODE); this.last_trans = {}; // Keep track of transform // The program this.prog = new Program(gl); this.prog.set_shaders(this.VERT, frag); // Real attributes this.vbo_x = new VertexBuffer(gl); this.prog.set_attribute('a_x', 'float', this.vbo_x); this.vbo_y = new VertexBuffer(gl); this.prog.set_attribute('a_y', 'float', this.vbo_y); this.vbo_s = new VertexBuffer(gl); this.prog.set_attribute('a_size', 'float', this.vbo_s); this.vbo_a = new VertexBuffer(gl); this.prog.set_attribute('a_angle', 'float', this.vbo_a); // VBO's for attributes (they may not be used if value is singleton) this.vbo_linewidth = new VertexBuffer(gl); this.vbo_fg_color = new VertexBuffer(gl); this.vbo_bg_color = new VertexBuffer(gl); return this.index_buffer = new IndexBuffer(gl); }
protected _set_visuals(nvertices: number): void { attach_float(this.prog, this.vbo_linewidth, 'a_linewidth', nvertices, this.glyph.visuals.line, 'line_width') attach_color(this.prog, this.vbo_fg_color, 'a_fg_color', nvertices, this.glyph.visuals.line, 'line') attach_color(this.prog, this.vbo_bg_color, 'a_bg_color', nvertices, this.glyph.visuals.fill, 'fill') // Static value for antialias. Smaller aa-region to obtain crisper images this.prog.set_uniform('u_antialias', 'float', [0.8]) }
protected init(): void { const {gl} = this const vert = vertex_shader const frag = fragment_shader(this._marker_code) // The program this.prog = new Program(gl) this.prog.set_shaders(vert, frag) // Real attributes this.vbo_x = new VertexBuffer(gl) this.prog.set_attribute('a_x', 'float', this.vbo_x) this.vbo_y = new VertexBuffer(gl) this.prog.set_attribute('a_y', 'float', this.vbo_y) this.vbo_s = new VertexBuffer(gl) this.prog.set_attribute('a_size', 'float', this.vbo_s) this.vbo_a = new VertexBuffer(gl) this.prog.set_attribute('a_angle', 'float', this.vbo_a) // VBO's for attributes (they may not be used if value is singleton) this.vbo_linewidth = new VertexBuffer(gl) this.vbo_fg_color = new VertexBuffer(gl) this.vbo_bg_color = new VertexBuffer(gl) this.index_buffer = new IndexBuffer(gl) }
init(): void { const {gl} = this; this._scale_aspect = 0; // keep track, so we know when we need to update segment data // The program this.prog = new Program(gl); this.prog.set_shaders(this.VERT, this.FRAG); this.index_buffer = new IndexBuffer(gl); // Buffers this.vbo_position = new VertexBuffer(gl); this.vbo_tangents = new VertexBuffer(gl); this.vbo_segment = new VertexBuffer(gl); this.vbo_angles = new VertexBuffer(gl); this.vbo_texcoord = new VertexBuffer(gl); // Dash atlas this.dash_atlas = new DashAtlas(gl); }
draw(indices: number[], mainGlyph: MarkerView | CircleView, trans: Transform): void { // The main glyph has the data, *this* glyph has the visuals. const mainGlGlyph = mainGlyph.glglyph const {nvertices} = mainGlGlyph // Upload data if we must. Only happens for main glyph. if (mainGlGlyph.data_changed) { if (!(isFinite(trans.dx) && isFinite(trans.dy))) { return; // not sure why, but it happens on init sometimes (#4367) } mainGlGlyph._baked_offset = [trans.dx, trans.dy]; // float32 precision workaround; used in _set_data() and below mainGlGlyph._set_data(nvertices) mainGlGlyph.data_changed = false } else if (this.glyph instanceof CircleView && this.glyph._radius != null && (this.last_trans == null || trans.sx != this.last_trans.sx || trans.sy != this.last_trans.sy)) { // Keep screen radius up-to-date for circle glyph. Only happens when a radius is given this.last_trans = trans this.vbo_s.set_data(0, new Float32Array(map(this.glyph.sradius, (s) => s*2))) } // Update visuals if we must. Can happen for all glyphs. if (this.visuals_changed) { this._set_visuals(nvertices) this.visuals_changed = false } // Handle transformation to device coordinates // Note the baked-in offset to avoid float32 precision problems const baked_offset = mainGlGlyph._baked_offset this.prog.set_uniform('u_pixel_ratio', 'float', [trans.pixel_ratio]) this.prog.set_uniform('u_canvas_size', 'vec2', [trans.width, trans.height]) this.prog.set_uniform('u_offset', 'vec2', [trans.dx - baked_offset[0], trans.dy - baked_offset[1]]) this.prog.set_uniform('u_scale', 'vec2', [trans.sx, trans.sy]) // Select buffers from main glyph // (which may be this glyph but maybe not if this is a (non)selection glyph) this.prog.set_attribute('a_x', 'float', mainGlGlyph.vbo_x) this.prog.set_attribute('a_y', 'float', mainGlGlyph.vbo_y) this.prog.set_attribute('a_size', 'float', mainGlGlyph.vbo_s) this.prog.set_attribute('a_angle', 'float', mainGlGlyph.vbo_a) // Draw directly or using indices. Do not handle indices if they do not // fit in a uint16; WebGL 1.0 does not support uint32. if (indices.length == 0) return else if (indices.length === nvertices) this.prog.draw(this.gl.POINTS, [0, nvertices]) else if (nvertices < 65535) { // On IE the marker size is reduced to 1 px when using an index buffer // A MS Edge dev on Twitter said on 24-04-2014: "gl_PointSize > 1.0 works // in DrawArrays; gl_PointSize > 1.0 in DrawElements is coming soon in the // next renderer update. const ua = window.navigator.userAgent if ((ua.indexOf("MSIE ") + ua.indexOf("Trident/") + ua.indexOf("Edge/")) > 0) { logger.warn('WebGL warning: IE is known to produce 1px sprites whith selections.') } this.index_buffer.set_size(indices.length*2) this.index_buffer.set_data(0, new Uint16Array(indices)) this.prog.draw(this.gl.POINTS, this.index_buffer) } else { // Work around the limit that the indexbuffer must be uint16. We draw in chunks. // First collect indices in chunks const chunksize = 64000; // 65536 const chunks: number[][] = [] for (let i = 0, end = Math.ceil(nvertices/chunksize); i < end; i++) { chunks.push([]) } for (let i = 0, end = indices.length; i < end; i++) { const uint16_index = indices[i] % chunksize const chunk = Math.floor(indices[i] / chunksize) chunks[chunk].push(uint16_index) } // Then draw each chunk for (let chunk = 0, end = chunks.length; chunk < end; chunk++) { const these_indices = new Uint16Array(chunks[chunk]) const offset = chunk * chunksize * 4 if (these_indices.length === 0) { continue } this.prog.set_attribute('a_x', 'float', mainGlGlyph.vbo_x, 0, offset) this.prog.set_attribute('a_y', 'float', mainGlGlyph.vbo_y, 0, offset) this.prog.set_attribute('a_size', 'float', mainGlGlyph.vbo_s, 0, offset) this.prog.set_attribute('a_angle', 'float', mainGlGlyph.vbo_a, 0, offset) if (this.vbo_linewidth.used) { this.prog.set_attribute('a_linewidth', 'float', this.vbo_linewidth, 0, offset) } if (this.vbo_fg_color.used) { this.prog.set_attribute('a_fg_color', 'vec4', this.vbo_fg_color, 0, offset * 4) } if (this.vbo_bg_color.used) { this.prog.set_attribute('a_bg_color', 'vec4', this.vbo_bg_color, 0, offset * 4) } // The actual drawing this.index_buffer.set_size(these_indices.length*2) this.index_buffer.set_data(0, these_indices) this.prog.draw(this.gl.POINTS, this.index_buffer) } } }
_set_visuals() { const color = color2rgba(this.glyph.visuals.line.line_color.value(), this.glyph.visuals.line.line_alpha.value()); const cap = this.CAPS[this.glyph.visuals.line.line_cap.value()]; const join = this.JOINS[this.glyph.visuals.line.line_join.value()]; this.prog.set_uniform('u_color', 'vec4', color); this.prog.set_uniform('u_linewidth', 'float', [this.glyph.visuals.line.line_width.value()]); this.prog.set_uniform('u_antialias', 'float', [0.9]); // Smaller aa-region to obtain crisper images this.prog.set_uniform('u_linecaps', 'vec2', [cap, cap]); this.prog.set_uniform('u_linejoin', 'float', [join]); this.prog.set_uniform('u_miter_limit', 'float', [10.0]); // 10 should be a good value // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-miterlimit const dash_pattern = this.glyph.visuals.line.line_dash.value(); let dash_index = 0; let dash_period = 1; if (dash_pattern.length) { [dash_index, dash_period] = this.dash_atlas.get_atlas_data(dash_pattern); } this.prog.set_uniform('u_dash_index', 'float', [dash_index]); // 0 means solid line this.prog.set_uniform('u_dash_phase', 'float', [this.glyph.visuals.line.line_dash_offset.value()]); this.prog.set_uniform('u_dash_period', 'float', [dash_period]); this.prog.set_uniform('u_dash_caps', 'vec2', [cap, cap]); return this.prog.set_uniform('u_closed', 'float', [0]); // We dont do closed lines }
draw(indices, mainGlyph, trans) { const mainGlGlyph = mainGlyph.glglyph; if (mainGlGlyph.data_changed) { if (!(isFinite(trans.dx) && isFinite(trans.dy))) { return; // not sure why, but it happens on init sometimes (#4367) } mainGlGlyph._baked_offset = [trans.dx, trans.dy]; // float32 precision workaround; used in _bake() and below mainGlGlyph._set_data(); mainGlGlyph.data_changed = false; } if (this.visuals_changed) { this._set_visuals(); this.visuals_changed = false; } // Decompose x-y scale into scalar scale and aspect-vector. let { sx } = trans; let { sy } = trans; const scale_length = Math.sqrt((sx * sx) + (sy * sy)); sx /= scale_length; sy /= scale_length; // Do we need to re-calculate segment data and cumsum? if (Math.abs(this._scale_aspect - (sy / sx)) > Math.abs(1e-3 * this._scale_aspect)) { mainGlGlyph._update_scale(sx, sy); this._scale_aspect = sy / sx; } // Select buffers from main glyph // (which may be this glyph but maybe not if this is a (non)selection glyph) this.prog.set_attribute('a_position', 'vec2', mainGlGlyph.vbo_position); this.prog.set_attribute('a_tangents', 'vec4', mainGlGlyph.vbo_tangents); this.prog.set_attribute('a_segment', 'vec2', mainGlGlyph.vbo_segment); this.prog.set_attribute('a_angles', 'vec2', mainGlGlyph.vbo_angles); this.prog.set_attribute('a_texcoord', 'vec2', mainGlGlyph.vbo_texcoord); // this.prog.set_uniform('u_length', 'float', [mainGlGlyph.cumsum]); this.prog.set_texture('u_dash_atlas', this.dash_atlas.tex); // Handle transformation to device coordinates const baked_offset = mainGlGlyph._baked_offset; this.prog.set_uniform('u_pixel_ratio', 'float', [trans.pixel_ratio]); this.prog.set_uniform('u_canvas_size', 'vec2', [trans.width, trans.height]); this.prog.set_uniform('u_offset', 'vec2', [trans.dx - baked_offset[0], trans.dy - baked_offset[1]]); this.prog.set_uniform('u_scale_aspect', 'vec2', [sx, sy]); this.prog.set_uniform('u_scale_length', 'float', [scale_length]); this.I_triangles = mainGlGlyph.I_triangles; if (this.I_triangles.length < 65535) { // Data is small enough to draw in one pass this.index_buffer.set_size(this.I_triangles.length*2); this.index_buffer.set_data(0, new Uint16Array(this.I_triangles)); return this.prog.draw(this.gl.TRIANGLES, this.index_buffer); // @prog.draw(@gl.LINE_STRIP, @index_buffer) # Use this to draw the line skeleton } else { // Work around the limit that the indexbuffer must be uint16. We draw in chunks. // First collect indices in chunks indices = this.I_triangles; const nvertices = this.I_triangles.length; const chunksize = 64008; // 65536 max. 64008 is divisible by 12 const chunks = []; for (let i = 0, end = Math.ceil(nvertices/chunksize); i < end; i++) { chunks.push([]); } for (let i = 0, end = indices.length; i < end; i++) { const uint16_index = indices[i] % chunksize; const chunk = Math.floor(indices[i] / chunksize); chunks[chunk].push(uint16_index); } // Then draw each chunk for (let chunk = 0, end = chunks.length; chunk < end; chunk++) { const these_indices = new Uint16Array(chunks[chunk]); const offset = chunk * chunksize * 4; if (these_indices.length === 0) { continue; } this.prog.set_attribute('a_position', 'vec2', mainGlGlyph.vbo_position, 0, offset * 2); this.prog.set_attribute('a_tangents', 'vec4', mainGlGlyph.vbo_tangents, 0, offset * 4); this.prog.set_attribute('a_segment', 'vec2', mainGlGlyph.vbo_segment, 0, offset * 2); this.prog.set_attribute('a_angles', 'vec2', mainGlGlyph.vbo_angles, 0, offset * 2); this.prog.set_attribute('a_texcoord', 'vec2', mainGlGlyph.vbo_texcoord, 0, offset * 2); // The actual drawing this.index_buffer.set_size(these_indices.length*2); this.index_buffer.set_data(0, these_indices); this.prog.draw(this.gl.TRIANGLES, this.index_buffer); } } }