@bokeh/bokehjs
Version:
Interactive, novel data visualization
184 lines • 7.34 kB
JavaScript
import { cap_lookup, hatch_pattern_to_index, join_lookup } from "./webgl_utils";
import { assert } from "../../../core/util/assert";
import { color2rgba, byte } from "../../../core/util/color";
// Arrays are sent to GPU using ReGL Buffer objects. CPU-side arrays used to
// update the Buffers are also kept for reuse to avoid unnecessary reallocation.
class WrappedBuffer {
static __name__ = "WrappedBuffer";
regl_wrapper;
buffer;
array;
is_scalar;
// Number of buffer elements per rendered primitive, e.g. for RGBA buffers this is 4
// as a single color is 4 x uint8 = 32-bit in total.
elements_per_primitive;
constructor(regl_wrapper, elements_per_primitive = 1) {
this.regl_wrapper = regl_wrapper;
this.is_scalar = true;
this.elements_per_primitive = elements_per_primitive;
}
// Return array if already know it exists and is the correct length.
get_array() {
assert(this.array != null, "WrappedBuffer not yet initialised");
return this.array;
}
// Return array of correct size, creating it if necessary.
// Must call update() when finished setting the array values.
get_sized_array(length) {
if (this.array == null || this.array.length != length) {
this.array = this.new_array(length);
}
return this.array;
}
is_normalized() {
return false;
}
get length() {
return this.array != null ? this.array.length : 0;
}
set_from_array(numbers) {
const len = numbers.length;
const array = this.get_sized_array(len);
for (let i = 0; i < len; i++) {
array[i] = numbers[i];
}
this.update();
}
set_from_prop(prop) {
const len = prop.is_Scalar() ? 1 : prop.length;
const array = this.get_sized_array(len);
for (let i = 0; i < len; i++) {
array[i] = prop.get(i);
}
this.update(prop.is_Scalar());
}
set_from_scalar(scalar) {
this.get_sized_array(1).fill(scalar);
this.update(true);
}
// Return a ReGL AttributeConfig that corresponds to one value for each glyph
// or the same value for a number of glyphs. A buffer passed to ReGL for
// instanced rendering can be used for multiple rendering calls and the
// important attributes for this are the offset (in bytes) into the buffer
// and the divisor, which is the number of instances rendered before the
// offset is advanced to the next buffer element.
// to_attribute_config() is used for the common case of a single render call
// per buffer with visual properties that are either scalar or vector.
// Visual properties of scatter markers are an good example, and scalar_divisor
// would be the number of markers rendered.
to_attribute_config(offset = 0, scalar_divisor = 1) {
return {
buffer: this.buffer,
divisor: this.is_scalar ? scalar_divisor : 1,
normalized: this.is_normalized(),
offset: offset * this.bytes_per_element(),
};
}
// to_attribute_config_nested() is used for the more complicated case in
// which the vectorisation is nested, such as rendering multi_lines where
// each visual property has a single buffer that is used multiple times, once
// for each of the constituent lines. Vector properties are therefore
// constant for each constituent line (composed of multiple rendered
// instances) but change between lines.
to_attribute_config_nested(offset_vector = 0, divisor = 1) {
return {
buffer: this.buffer,
divisor: divisor * this.elements_per_primitive,
normalized: this.is_normalized(),
offset: this.is_scalar ? 0 : offset_vector * this.bytes_per_element() * this.elements_per_primitive,
};
}
// Update ReGL buffer with data contained in array in preparation for passing
// it to the GPU. This function must be called after get_sized_array().
update(is_scalar = false) {
// Update buffer with data contained in array.
if (this.buffer == null) {
// Create new buffer.
this.buffer = this.regl_wrapper.buffer({
usage: "dynamic",
data: this.array,
});
}
else {
// Reuse existing buffer.
this.buffer({ data: this.array });
}
this.is_scalar = is_scalar;
}
}
export class Float32Buffer extends WrappedBuffer {
static __name__ = "Float32Buffer";
bytes_per_element() {
return Float32Array.BYTES_PER_ELEMENT;
}
new_array(len) {
return new Float32Array(len);
}
}
export class Uint8Buffer extends WrappedBuffer {
static __name__ = "Uint8Buffer";
bytes_per_element() {
return Uint8Array.BYTES_PER_ELEMENT;
}
new_array(len) {
return new Uint8Array(len);
}
set_from_color(color_prop, alpha_prop) {
const is_scalar_colors = color_prop.is_Scalar();
const is_scalar = is_scalar_colors && alpha_prop.is_Scalar();
const ncolors = is_scalar ? 1 : color_prop.length;
if (!is_scalar_colors) {
const color_v = color_prop;
const array = new Uint8Array(color_v.copy_buffer());
for (let i = 0; i < ncolors; i++) {
const alpha = alpha_prop.get(i);
array[4 * i + 3] = byte(alpha * array[4 * i + 3]);
}
this.array = array;
this.update(is_scalar);
return;
}
const array = this.get_sized_array(4 * ncolors);
for (let i = 0; i < ncolors; i++) {
const [r, g, b, a] = color2rgba(color_prop.get(i), alpha_prop.get(i));
array[4 * i] = r;
array[4 * i + 1] = g;
array[4 * i + 2] = b;
array[4 * i + 3] = a;
}
this.update(is_scalar);
}
set_from_hatch_pattern(hatch_pattern_prop) {
const len = hatch_pattern_prop.is_Scalar() ? 1 : hatch_pattern_prop.length;
const array = this.get_sized_array(len);
for (let i = 0; i < len; i++) {
array[i] = hatch_pattern_to_index(hatch_pattern_prop.get(i));
}
this.update(hatch_pattern_prop.is_Scalar());
}
set_from_line_cap(line_cap_prop) {
const len = line_cap_prop.is_Scalar() ? 1 : line_cap_prop.length;
const array = this.get_sized_array(len);
for (let i = 0; i < len; i++) {
array[i] = cap_lookup[line_cap_prop.get(i)];
}
this.update(line_cap_prop.is_Scalar());
}
set_from_line_join(line_join_prop) {
const len = line_join_prop.is_Scalar() ? 1 : line_join_prop.length;
const array = this.get_sized_array(len);
for (let i = 0; i < len; i++) {
array[i] = join_lookup[line_join_prop.get(i)];
}
this.update(line_join_prop.is_Scalar());
}
}
// Normalized refers to optional WebGL behaviour of automatically converting
// Uint8 values that are passed to shaders into floats in the range 0 to 1.
export class NormalizedUint8Buffer extends Uint8Buffer {
static __name__ = "NormalizedUint8Buffer";
is_normalized() {
return true;
}
}
//# sourceMappingURL=buffer.js.map