@bokeh/bokehjs
Version:
Interactive, novel data visualization
518 lines • 17.6 kB
JavaScript
import createRegl from "regl";
import { DashCache } from "./dash_cache";
import accumulate_vertex_shader from "./accumulate.vert";
import accumulate_fragment_shader from "./accumulate.frag";
import image_vertex_shader from "./image.vert";
import image_fragment_shader from "./image.frag";
import line_vertex_shader from "./regl_line.vert";
import line_fragment_shader from "./regl_line.frag";
import marker_vertex_shader from "./marker.vert";
import marker_fragment_shader from "./marker.frag";
// All access to regl is performed via the get_regl() function that returns a
// ReglWrapper object. This ensures that regl is correctly initialised before
// it is used, and is only initialised once.
let regl_wrapper = null;
export function get_regl(gl) {
if (regl_wrapper == null) {
regl_wrapper = new ReglWrapper(gl);
}
return regl_wrapper;
}
export class ReglWrapper {
static __name__ = "ReglWrapper";
_regl;
_regl_available;
_dash_cache;
// Drawing functions.
_accumulate;
_image;
_solid_line;
_dashed_line;
_marker_no_hatch_map = new Map();
_marker_hatch_map = new Map();
// Static Buffers/Elements
_line_geometry;
_line_triangles;
_rect_geometry;
_rect_triangles;
// WebGL state variables.
_scissor;
_viewport;
// WebGL framebuffer used to accumulate glyph rendering before single blit to Canvas.
_framebuffer;
_framebuffer_texture;
constructor(gl) {
try {
this._regl = createRegl({
gl,
extensions: [
"ANGLE_instanced_arrays",
"EXT_blend_minmax",
],
});
this._regl_available = true;
// Initialise static Buffers/Elements.
this._line_geometry = this._regl.buffer({
usage: "static",
type: "float",
data: [[-2, 0], [-1, -1], [1, -1], [1, 1], [-1, 1]],
});
this._line_triangles = this._regl.elements({
usage: "static",
primitive: "triangle fan",
data: [0, 1, 2, 3, 4],
});
this._rect_geometry = this._regl.buffer({
usage: "static",
type: "float",
data: [[-1, -1], [1, -1], [1, 1], [-1, 1]],
});
this._rect_triangles = this._regl.elements({
usage: "static",
primitive: "triangle fan",
data: [0, 1, 2, 3],
});
}
catch (err) {
this._regl_available = false;
}
}
// Create and return ReGL Buffer.
buffer(options) {
return this._regl.buffer(options);
}
clear(width, height) {
this._viewport = { x: 0, y: 0, width, height };
this._regl.clear({ color: [0, 0, 0, 0] });
}
clear_framebuffer(framebuffer) {
this._regl.clear({ color: [0, 0, 0, 0], framebuffer });
}
get framebuffer_and_texture() {
const { _regl } = this;
const { _gl } = _regl;
const size = {
height: _gl.drawingBufferHeight,
width: _gl.drawingBufferWidth,
};
if (this._framebuffer_texture == null) {
this._framebuffer_texture = _regl.texture(size);
}
else {
// Resize texture, no-op if no change.
this._framebuffer_texture(size);
}
if (this._framebuffer == null) {
this._framebuffer = _regl.framebuffer({
// Auto-sizes to size of texture.
color: this._framebuffer_texture,
depth: false,
stencil: false,
});
}
return [this._framebuffer, this._framebuffer_texture];
}
get has_webgl() {
return this._regl_available;
}
get scissor() {
return this._scissor;
}
set_scissor(x, y, width, height) {
this._scissor = { x, y, width, height };
}
texture(options) {
return this._regl.texture(options);
}
get viewport() {
return this._viewport;
}
accumulate() {
if (this._accumulate == null) {
this._accumulate = regl_accumulate(this._regl, this._rect_geometry, this._rect_triangles);
}
return this._accumulate;
}
dashed_line() {
if (this._dashed_line == null) {
this._dashed_line = regl_dashed_line(this._regl, this._line_geometry, this._line_triangles);
}
return this._dashed_line;
}
get_dash(line_dash) {
if (this._dash_cache == null) {
this._dash_cache = new DashCache(this._regl);
}
return this._dash_cache.get(line_dash);
}
image() {
if (this._image == null) {
this._image = regl_image(this._regl, this._rect_geometry, this._rect_triangles);
}
return this._image;
}
marker_no_hatch(marker_type) {
let func = this._marker_no_hatch_map.get(marker_type);
if (func == null) {
func = regl_marker(this._regl, marker_type);
this._marker_no_hatch_map.set(marker_type, func);
}
return func;
}
marker_hatch(marker_type) {
let func = this._marker_hatch_map.get(marker_type);
if (func == null) {
func = regl_marker_hatch(this._regl, marker_type);
this._marker_hatch_map.set(marker_type, func);
}
return func;
}
solid_line() {
if (this._solid_line == null) {
this._solid_line = regl_solid_line(this._regl, this._line_geometry, this._line_triangles);
}
return this._solid_line;
}
}
function regl_accumulate(regl, geometry, triangles) {
const config = {
vert: accumulate_vertex_shader,
frag: accumulate_fragment_shader,
attributes: {
a_position: {
buffer: geometry,
divisor: 0,
},
},
uniforms: {
u_framebuffer_tex: regl.prop("framebuffer_tex"),
},
elements: triangles,
blend: {
enable: true,
func: {
srcRGB: "one",
srcAlpha: "one",
dstRGB: "one minus src alpha",
dstAlpha: "one minus src alpha",
},
},
depth: { enable: false },
scissor: {
enable: true,
box: regl.prop("scissor"),
},
viewport: regl.prop("viewport"),
};
return regl(config);
}
// Regl rendering functions are here as some will be reused, e.g. lines may also
// be used around polygons or for bezier curves.
function regl_image(regl, geometry, triangles) {
const config = {
vert: image_vertex_shader,
frag: image_fragment_shader,
attributes: {
a_position: {
buffer: geometry,
divisor: 0,
},
a_bounds(_, props) {
return props.bounds.to_attribute_config();
},
},
uniforms: {
u_canvas_size: regl.prop("canvas_size"),
u_tex: regl.prop("tex"),
u_global_alpha: regl.prop("global_alpha"),
},
elements: triangles,
blend: {
enable: true,
func: {
srcRGB: "one",
srcAlpha: "one",
dstRGB: "one minus src alpha",
dstAlpha: "one minus src alpha",
},
},
depth: { enable: false },
scissor: {
enable: true,
box: regl.prop("scissor"),
},
viewport: regl.prop("viewport"),
};
return regl(config);
}
// Mesh for line rendering (solid and dashed).
//
// 1 4-----3
// / |
// / |
// y 0 0 |
// \ |
// \ |
// -1 1-----2
//
// -2 -1 1
// x
function regl_solid_line(regl, line_geometry, line_triangles) {
const config = {
vert: line_vertex_shader,
frag: line_fragment_shader,
attributes: {
a_position: {
buffer: line_geometry,
divisor: 0,
},
a_point_prev(_, props) {
return props.points.to_attribute_config(props.point_offset);
},
a_point_start(_, props) {
return props.points.to_attribute_config(props.point_offset + 2);
},
a_point_end(_, props) {
return props.points.to_attribute_config(props.point_offset + 4);
},
a_point_next(_, props) {
return props.points.to_attribute_config(props.point_offset + 6);
},
a_show_prev(_, props) {
return props.show.to_attribute_config(props.point_offset / 2 - props.line_offset);
},
a_show_curr(_, props) {
return props.show.to_attribute_config(props.point_offset / 2 - props.line_offset + 1);
},
a_show_next(_, props) {
return props.show.to_attribute_config(props.point_offset / 2 - props.line_offset + 2);
},
a_linewidth(_, props) {
return props.linewidth.to_attribute_config_nested(props.line_offset, props.nsegments + 3);
},
a_line_color(_, props) {
return props.line_color.to_attribute_config_nested(props.line_offset, props.nsegments + 3);
},
a_line_cap(_, props) {
return props.line_cap.to_attribute_config_nested(props.line_offset, props.nsegments + 3);
},
a_line_join(_, props) {
return props.line_join.to_attribute_config_nested(props.line_offset, props.nsegments + 3);
},
},
uniforms: {
u_canvas_size: regl.prop("canvas_size"),
u_antialias: regl.prop("antialias"),
u_miter_limit: regl.prop("miter_limit"),
},
elements: line_triangles,
instances: regl.prop("nsegments"),
blend: {
enable: true,
equation: "max",
func: {
srcRGB: 1,
srcAlpha: 1,
dstRGB: 1,
dstAlpha: 1,
},
},
depth: { enable: false },
framebuffer: regl.prop("framebuffer"),
scissor: {
enable: true,
box: regl.prop("scissor"),
},
viewport: regl.prop("viewport"),
};
return regl(config);
}
function regl_dashed_line(regl, line_geometry, line_triangles) {
const config = {
vert: `\
#define DASHED
${line_vertex_shader}
`,
frag: `\
#define DASHED
${line_fragment_shader}
`,
attributes: {
a_position: {
buffer: line_geometry,
divisor: 0,
},
a_point_prev(_, props) {
return props.points.to_attribute_config(props.point_offset);
},
a_point_start(_, props) {
return props.points.to_attribute_config(props.point_offset + 2);
},
a_point_end(_, props) {
return props.points.to_attribute_config(props.point_offset + 4);
},
a_point_next(_, props) {
return props.points.to_attribute_config(props.point_offset + 6);
},
a_show_prev(_, props) {
return props.show.to_attribute_config(props.point_offset / 2 - props.line_offset);
},
a_show_curr(_, props) {
return props.show.to_attribute_config(props.point_offset / 2 - props.line_offset + 1);
},
a_show_next(_, props) {
return props.show.to_attribute_config(props.point_offset / 2 - props.line_offset + 2);
},
a_linewidth(_, props) {
return props.linewidth.to_attribute_config_nested(props.line_offset, props.nsegments + 3);
},
a_line_color(_, props) {
return props.line_color.to_attribute_config_nested(props.line_offset, props.nsegments + 3);
},
a_line_cap(_, props) {
return props.line_cap.to_attribute_config_nested(props.line_offset, props.nsegments + 3);
},
a_line_join(_, props) {
return props.line_join.to_attribute_config_nested(props.line_offset, props.nsegments + 3);
},
a_length_so_far(_, props) {
return props.length_so_far.to_attribute_config(props.point_offset / 2 - 3 * props.line_offset);
},
a_dash_tex_info(_, props) {
return props.dash_tex_info.to_attribute_config_nested(props.line_offset, props.nsegments + 3);
},
a_dash_scale(_, props) {
return props.dash_scale.to_attribute_config_nested(props.line_offset, props.nsegments + 3);
},
a_dash_offset(_, props) {
return props.dash_offset.to_attribute_config_nested(props.line_offset, props.nsegments + 3);
},
},
uniforms: {
u_canvas_size: regl.prop("canvas_size"),
u_antialias: regl.prop("antialias"),
u_miter_limit: regl.prop("miter_limit"),
u_dash_tex: regl.prop("dash_tex"),
},
elements: line_triangles,
instances: regl.prop("nsegments"),
blend: {
enable: true,
equation: "max",
func: {
srcRGB: 1,
srcAlpha: 1,
dstRGB: 1,
dstAlpha: 1,
},
},
depth: { enable: false },
framebuffer: regl.prop("framebuffer"),
scissor: {
enable: true,
box: regl.prop("scissor"),
},
viewport: regl.prop("viewport"),
};
return regl(config);
}
function regl_marker(regl, marker_type, vert_defs = [], frag_defs = [], attributes) {
const vert_prefix = vert_defs.map((def) => `#define ${def}`).join("\n");
const frag_prefix = frag_defs.map((def) => `#define ${def}`).join("\n");
const config = {
vert: `\
${vert_prefix}
#define MULTI_MARKER
#define USE_${marker_type.toUpperCase()}
${marker_vertex_shader}
`,
frag: `\
${frag_prefix}
#define USE_${marker_type.toUpperCase()}
${marker_fragment_shader}
`,
attributes: {
a_position: {
buffer: regl.buffer([[-0.5, -0.5], [-0.5, 0.5], [0.5, 0.5], [0.5, -0.5]]),
divisor: 0,
},
a_center(_, props) {
return props.center.to_attribute_config(0, props.nmarkers);
},
a_width(_, props) {
return props.width.to_attribute_config(0, props.nmarkers);
},
a_height(_, props) {
return props.height.to_attribute_config(0, props.nmarkers);
},
a_angle(_, props) {
return props.angle.to_attribute_config(0, props.nmarkers);
},
a_aux(_, props) {
return props.aux.to_attribute_config(0, props.nmarkers);
},
a_linewidth(_, props) {
return props.linewidth.to_attribute_config(0, props.nmarkers);
},
a_line_color(_, props) {
return props.line_color.to_attribute_config(0, props.nmarkers);
},
a_fill_color(_, props) {
return props.fill_color.to_attribute_config(0, props.nmarkers);
},
a_line_cap(_, props) {
return props.line_cap.to_attribute_config(0, props.nmarkers);
},
a_line_join(_, props) {
return props.line_join.to_attribute_config(0, props.nmarkers);
},
a_show(_, props) {
return props.show.to_attribute_config(0, props.nmarkers);
},
...attributes,
},
uniforms: {
u_canvas_size: regl.prop("canvas_size"),
u_antialias: regl.prop("antialias"),
u_size_hint: regl.prop("size_hint"),
u_border_radius: regl.prop("border_radius"),
},
count: 4,
primitive: "triangle fan",
instances: regl.prop("nmarkers"),
blend: {
enable: true,
func: {
srcRGB: "one",
srcAlpha: "one",
dstRGB: "one minus src alpha",
dstAlpha: "one minus src alpha",
},
},
depth: { enable: false },
scissor: {
enable: true,
box: regl.prop("scissor"),
},
viewport: regl.prop("viewport"),
};
return regl(config);
}
function regl_marker_hatch(regl, marker_type) {
const hatch_attributes = {
a_hatch_pattern(_, props) {
return props.hatch_pattern.to_attribute_config(0, props.nmarkers);
},
a_hatch_scale(_, props) {
return props.hatch_scale.to_attribute_config(0, props.nmarkers);
},
a_hatch_weight(_, props) {
return props.hatch_weight.to_attribute_config(0, props.nmarkers);
},
a_hatch_color(_, props) {
return props.hatch_color.to_attribute_config(0, props.nmarkers);
},
};
return regl_marker(regl, marker_type, ["HATCH"], ["HATCH"], hatch_attributes);
}
//# sourceMappingURL=regl_wrap.js.map