digital-vcd-render/src/hook/wave-view/gen-render-waves-gl.js

598 lines
20 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use strict';
// 控制颜色
const gl_Colors = new Float32Array([
0, 0, 0, 0, // 0: 空
0, 0, 1, 1, // 1: 高阻态 Z
0.2, 0.847, 0.1, 1, // 2: value = 0 用于 width = 1 的信号
0.2, 0.847, 0.1, 1, // 3: value = 1 用于 width = 1 的信号
0.9, 0.2, 0.2, 1, // 4: 未知态 X
.5, 1, 1, 1, // 5: vec 用于 width > 1 的信号
1, 1, 0, 1, // 6: yellow
1, 0, 1, 1, // 7: strange purple
0, 1, 0, .5, // 8: (l L) weak 0
0, 1, 1, .5, // 9: (h H) weak 1
1, 0, 0, .5, // 10: (w W) weak unknown
0, 0, 1, 0.1, // 11: 高阻态 Z 遮罩
0.2, 0.847, 0.1, 0.1, // 12: value = 0 遮罩
0.2, 0.847, 0.1, 0.1, // 13: value = 1 遮罩
0.9, 0.2, 0.2, 0.1, // 14: 未知态 X 遮罩
.5, 1, 1, 0.1 // 15: vec 遮罩
]);
// 控制方向
const gl_Shifts = new Float32Array([ // 14
0, 0, // 0
1, -1, // 1
1, 0, // 2
1, 1, // 3
-1, -1, // 4
-1, 0, // 5
-1, 1 // 6
]);
const gl_Shifts_map = new Map();
gl_Shifts_map.set(-1, 4);
gl_Shifts_map.set(0, 0);
gl_Shifts_map.set(1, 1);
const lineWidth = 0.004; // 不能写为 0.0045 这样会因为插值造成不同线段的宽度不一致的问题
const widthShift = lineWidth / 2;
const gl_WidthShifts = new Float32Array([
0, widthShift, // 0
- widthShift, widthShift, // 1
- widthShift, 0, // 2
- widthShift, - widthShift, // 3
0, - widthShift, // 4
widthShift, - widthShift, // 5
widthShift, 0, // 6
widthShift, widthShift // 7
]);
class ShaderMaker {
/**
*
* @param {'VERTEX_SHADER' | 'FRAGMENT_SHADER'} type
* @param {string} source
*/
constructor(type, source) {
this.type = type;
this.source = source;
}
/**
* @param {WebGL2RenderingContext} gl
* @return {WebGLShader}
*/
make(gl) {
const shader = gl.createShader(gl[this.type]);
gl.shaderSource(shader, this.source);
gl.compileShader(shader);
const ok = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if (!ok) {
console.log('创建类型为 ' + type + ' 的着色器失败!');
}
return shader;
}
}
const vertexShaderScalar = new ShaderMaker('VERTEX_SHADER', `#version 300 es
in uvec4 pos;
out vec4 v_color;
uniform vec2 scale;
uniform vec2 offset;
uniform vec4 colors[16];
uniform vec2 shifts[7]; // 基础八位图偏移量为了性能pos 只传入整数,需要的坐标负数由该值提供
uniform vec2 widthShifts[8]; // 用于构造线宽的偏移
void main() {
v_color = colors[pos.z];
vec2 shift = shifts[pos.y];
vec2 widthShift = widthShifts[pos.w];
gl_Position = vec4(
float(pos.x) * scale.x + offset.x + float(widthShift.x),
float(shift.x) * scale.y + offset.y + float(widthShift.y),
1, 1
);
}`);
const fragmentShader = new ShaderMaker('FRAGMENT_SHADER', `#version 300 es
precision mediump float;
in vec4 v_color;
out vec4 outColor;
void main() {
outColor = v_color;
}`);
class WebGL2WaveRender {
/**
*
* @param {{
* grid: HTMLDivElement,
* view: HTMLDivElement,
* values: HTMLDivElement
* }} elements
* @param {{
* chango: Record<string, {
* kind: string,
* wave: Array<number | string>,
* lineVao: WebGLVertexArrayObject
* maskVao: WebGLVertexArrayObject
* }>
* view: Array<{ ref: string }>,
* currentWires: Set<{
* kind: string,
* link: string,
* name: string,
* size: number,
* parent: object,
* type: string
* }>,
* time: number
* }} globalLookup
* @param {{
* width: number,
* height: number,
* xScale: number,
* xOffset: number,
* yOffset: number,
* yStep: number,
* yDuty: number
* }} pstate
* @param { Array } plugins
*/
constructor(elements, globalLookup, pstate, plugins) {
const canvas = document.createElement('canvas');
elements.view.replaceChildren(canvas);
this.elements = elements;
this.globalLookup = globalLookup;
this.canvas = canvas;
this.pstate = pstate;
this.plugins = plugins;
const gl = canvas.getContext('webgl2', {
premultipliedAlpha: false,
alpha: true,
antialias: false,
depth: false
});
this.webglLocation = this.initProgram(gl);
const { lineVerticesMap, maskVerticesMap } = this.makeVertex();
this.lineVerticesMap = lineVerticesMap;
this.maskVerticesMap = maskVerticesMap;
this.initData();
this.animationHandler = undefined;
}
/**
*
* @param {WebGL2RenderingContext} gl
* @returns {{
* colors: WebGLUniformLocation,
* shifts: WebGLUniformLocation,
* scale: WebGLUniformLocation,
* offset: WebGLUniformLocation,
* pos: number,
* widthShifts: WebGLUniformLocation,
* gl: WebGL2RenderingContext
* }}
*/
initProgram(gl) {
const program = gl.createProgram();
gl.attachShader(program, vertexShaderScalar.make(gl));
gl.attachShader(program, fragmentShader.make(gl));
gl.linkProgram(program);
gl.useProgram(program);
const webglLocation = {
colors: gl.getUniformLocation(program, 'colors'),
shifts: gl.getUniformLocation(program, 'shifts'),
scale: gl.getUniformLocation(program, 'scale'),
offset: gl.getUniformLocation(program, 'offset'),
pos: gl.getAttribLocation(program, 'pos'),
widthShifts: gl.getUniformLocation(program, 'widthShifts'),
gl
};
return webglLocation;
}
/**
*
* @returns {{
* lineVerticesMap: Map<string, Uint32Array>,
* maskVerticesMap: Map<string, Uint32Array>
* }}
*/
makeVertex() {
const globalLookup = this.globalLookup;
const time = globalLookup.time;
const lineVerticesMap = new Map();
const maskVerticesMap = new Map();
for (const id of Reflect.ownKeys(globalLookup.chango)) {
const signalItem = globalLookup.chango[id];
const { kind, wave } = signalItem;
if (kind === 'bit') {
const { lineVertices, maskVertices } = this.makeBitVertex(wave, time);
lineVerticesMap.set(id, lineVertices);
maskVerticesMap.set(id, maskVertices);
} else if (kind === 'vec') {
// const vertices = this.makeVecVertex(wave, time);
// lineVerticesMap.set(id, vertices);
}
}
return { lineVerticesMap, maskVerticesMap };
}
/**
* @description 将wave 的值转化为渲染需要用到的值
* @param {*} value
* @return {{
* y: number // 裁剪空间的纵坐标
* color: number // 颜色的索引 颜色rgb = gl_Colors[color]
* }}
*/
translateValue2RenderParameter(value) {
switch (value) {
case 0: return { y: -1, color: 2 }; // 0 value
case 1: return { y: 1, color: 3 }; // 1 value
case 2: case 3: return { y: -1, color: 4 }; // 不定态 x
case 4: case 5: return { y: 0, color: 2 }; // 高阻态 z
default: return { y: -1, color: 7 }; // 其他,我也不知道还有啥
}
}
/**
*
* @param {{x: number, y: number, color: number} | undefined} p0 前一个点
* @param {{x: number, y: number, color: number} | undefined} p1 当前的点
* @param {{x: number, y: number, color: number} | undefined} p2 后一个点
* @returns {number} 这是 widthshift 的索引,只需要 + 4 再 % 8 就能得到另一个
*/
makeWidthShiftIndexByPoints(p0, p1, p2) {
if (p0 === undefined) {
if (p1.y === p2.y) {
return 0;
} else if (p1.x === p2.x) {
return 6;
}
} else if (p2 === undefined) {
if (p1.y === p0.y) {
return 0;
} else if (p1.x === p0.x) {
return 6;
}
} else {
if (p0.x !== p1.x && p0.y === p1.y && p1.x === p2.x && p1.y !== p2.y) {
if (p2.y > p1.y) {
return 1;
} else {
return 7;
}
} else if (p0.x === p1.x && p0.y !== p1.y && p1.x !== p2.x && p1.y === p2.y) {
if (p1.y > p0.y) {
return 1;
} else {
return 7;
}
}
}
}
/**
*
* @param {Array<string | number>} wave
* @param { number } time
* @returns {{
* lineVertices: Uint32Array
* maskVertices: Uint32Array
* }}
*/
makeBitVertex(wave, time, debug = false) {
const length = wave.length;
// 先将节点数据转化为裁剪空间的坐标
const perspectivePoints = [];
for (let i = 0; i < length; ++ i) {
// const currentWave = wave[(i === 0) ? 0 : (i - 1)];
const currentWave = wave[i];
const nextWave = (i === (length - 1)) ? wave[i] : wave[i + 1];
const t1 = currentWave[0];
const t2 = (i === (length - 1)) ? time : wave[i + 1][0];
const value1 = currentWave[1];
const value2 = nextWave[1];
const renderParam1 = this.translateValue2RenderParameter(value1);
const renderParam2 = this.translateValue2RenderParameter(value2);
if (i === 0) {
perspectivePoints.push({ x: t1, y: renderParam1.y, color: renderParam1.color });
perspectivePoints.push({ x: t2, y: renderParam1.y, color: renderParam1.color });
} else {
const lastPoint = perspectivePoints.at(-1);
if ((lastPoint.y !== renderParam1.y) || (lastPoint.color !== renderParam1.color)) {
perspectivePoints.push({ x: t1, y: renderParam1.y, color: renderParam1.color });
}
if ((renderParam1.y !== renderParam2.y) || (renderParam1.color !== renderParam2.color)) {
perspectivePoints.push({ x: t2, y: renderParam1.y, color: renderParam1.color });
}
}
}
// 确保最后一个点延申到了 time
const lastPoint = perspectivePoints.at(-1);
if (lastPoint.x < time) {
perspectivePoints.push({ x: time, y: lastPoint.y, color: lastPoint.color });
}
// 计算出传入 shader 四元组数组
// 四元组: (x, yshift_index, color_index, width_shift_index)
const pointNum = perspectivePoints.length;
const lineVertices = [];
const maskVertices = [];
// const lineVertices = [
// 0, 0, 2, 0,
// 10, 0, 2, 0,
// 10, 4, 2, 0,
// 20, 4, 2, 0,
// 20, 0, 2, 0,
// 30, 0, 2, 0,
// 30, 4, 2, 0
// ];
// return new Uint32Array(lineVertices);
// 制作 lineVertices
for (let i = 0; i < pointNum; ++ i) {
// p0: 上一个点
// p1: 当前的点
// p2: 下一个点
const p0 = perspectivePoints[i - 1];
const p1 = perspectivePoints[i];
const p2 = perspectivePoints[i + 1];
// p1.x, y1Index, p1.color, 0,
const y1Index = gl_Shifts_map.get(p1.y);
const wsIndex = this.makeWidthShiftIndexByPoints(p0, p1, p2);
lineVertices.push(
p1.x, y1Index, p1.color, wsIndex,
p1.x, y1Index, p1.color, (wsIndex + 4) % 8
);
// 防止颜色不同导致单个图元内出现两个颜色这会引发shader的渐变
if (p2 !== undefined && p1.color !== p2.color) {
lineVertices.push(
p1.x, y1Index, p2.color, wsIndex,
p1.x, y1Index, p2.color, (wsIndex + 4) % 8
);
}
}
// 制作 maskVertices
for (let i = 0; i < pointNum; ++ i) {
const p1 = perspectivePoints[i];
const p2 = perspectivePoints[i + 1];
const p3 = perspectivePoints[i + 2];
if (p1 === undefined || p2 === undefined || p3 === undefined) {
continue;
}
if (p2.y > p1.y) {
// 矩形的四个点
// 四元组: (x, yshift_index, color_index, width_shift_index)
const r1 = [p1.x, gl_Shifts_map.get(p1.y), p2.color + 10, 4];
const r2 = [p2.x, gl_Shifts_map.get(p2.y), p2.color + 10, 4];
const r3 = [p3.x, gl_Shifts_map.get(p3.y), p2.color + 10, 4];
const r4 = [p3.x, gl_Shifts_map.get(p1.y), p2.color + 10, 4];
// 三角图元画矩形
maskVertices.push(
...r1, ...r2, ...r3,
...r1, ...r3, ...r4,
);
}
}
if (debug) {
console.log(perspectivePoints);
console.log(pointNum);
console.log(lineVertices);
}
const Uint32lineVertices = new Uint32Array(lineVertices);
const Uint32maskVertices = new Uint32Array(maskVertices);
return {
lineVertices : Uint32lineVertices,
maskVertices : Uint32maskVertices
};
}
/**
*
* @param {Array<string | number>} wave
* @param { number } time
* @returns {Uint32Array}
*/
makeVecVertex(wave, time) {
const vertices = [];
const length = wave.length;
for (let i = 0; i < length; ++ i) {
const [t1, val, msk] = wave[i];
const t2 = (i === (length - 1)) ? time : wave[i + 1][0];
// t1(val) --- t2(val)
if (msk) {
vertices.push(
t1, 0, 0,
t2, 0, 0,
t2, 0, 4,
t2, 4, 4,
t1, 6, 4,
t1, 0, 4,
t1, 3, 4,
t2, 1, 4,
t2, 0, 4
);
} else {
vertices.push(
t1, 0, 0,
t2, 0, 0,
t2, 0, 5,
t2, 4, 5,
t1, 6, 5,
t1, 0, 5,
t1, 3, 5,
t2, 1, 5,
t2, 0, 5
);
}
}
return new Uint32Array(vertices);
}
initData() {
const webglLocation = this.webglLocation;
const gl = webglLocation.gl;
const globalLookup = this.globalLookup;
const lineVerticesMap = this.lineVerticesMap;
const maskVerticesMap = this.maskVerticesMap;
for (const id of Reflect.ownKeys(globalLookup.chango)) {
const signalItem = globalLookup.chango[id];
const lineVertices = lineVerticesMap.get(id);
if (lineVertices === undefined) {
// console.warn(`无法找到 link 为 ${id} 的顶点数据`);
continue;
}
// 创建并设置 绘制wave轮廓 主体轮廓的 缓冲区、vao、顶点设置
const lineVertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, lineVertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, lineVertices, gl.STATIC_DRAW);
signalItem.lineVao = gl.createVertexArray();
gl.bindVertexArray(signalItem.lineVao);
gl.vertexAttribIPointer(webglLocation.pos, 4, gl.UNSIGNED_INT, 0, 0);
gl.enableVertexAttribArray(webglLocation.pos);
const maskVertices = maskVerticesMap.get(id);
if (maskVertices === undefined) {
continue;
}
// 创建并设置 绘制wave半透明遮罩层 主体轮廓的 缓冲区、vao、顶点设置
const maskVertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, maskVertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, maskVertices, gl.STATIC_DRAW);
signalItem.maskVao = gl.createVertexArray();
gl.bindVertexArray(signalItem.maskVao);
gl.vertexAttribIPointer(webglLocation.pos, 4, gl.UNSIGNED_INT, 0, 0);
gl.enableVertexAttribArray(webglLocation.pos);
}
gl.uniform4fv(webglLocation.colors, gl_Colors);
gl.uniform2fv(webglLocation.widthShifts, gl_WidthShifts);
gl.uniform2fv(webglLocation.shifts, gl_Shifts);
}
render() {
if (this.animationHandler !== undefined) {
cancelAnimationFrame(this.animationHandler);
}
const canvas = this.canvas;
const webglLocation = this.webglLocation;
const gl = webglLocation.gl;
const globalLookup = this.globalLookup;
const lineVerticesMap = this.lineVerticesMap;
const maskVerticesMap = this.maskVerticesMap;
const elements = this.elements;
const timeScaleHeight = parseInt(document.body.style.getPropertyValue('--time-scale-height').match(/\d+/)[0]);
const sidebarPadding = parseInt(document.body.style.getPropertyValue('--sidebar-padding').match(/\d+/)[0]);
const vcdRenderPadding = parseInt(document.body.style.getPropertyValue('--vcd-render-padding').match(/\d+/)[0]);
const canvasPaddingTop = timeScaleHeight + sidebarPadding - vcdRenderPadding;
this.animationHandler = window.requestAnimationFrame(() => {
const { width, height, xScale, xOffset, yOffset, yStep, yDuty } = this.pstate;
const canvasHeight = height - canvasPaddingTop;
const canvasWidth = width;
// 默认 1594
canvas.width = canvasWidth;
// 默认 1260
canvas.height = canvasHeight;
// 设置 glsl 变量
gl.uniform2f(webglLocation.scale,
2 * xScale / canvasWidth,
yStep * yDuty / canvasHeight
);
console.log(yStep, yDuty, canvasHeight);
// 设置 webgl 和 canvas 大小位置一致
gl.viewport(0, 0, canvasWidth, canvasHeight);
// 清楚颜色缓冲区,也就是删除上一次的渲染结果
gl.clear(gl.COLOR_BUFFER_BIT);
// 根据 globalLookup 当前激活的需要渲染的信号进行渲染
let index = 0;
for (const signal of globalLookup.currentWires) {
const wave = globalLookup.chango[signal.link].wave;
// this.makeBitVertex(wave, globalLookup.time, true);
const signalItem = globalLookup.chango[signal.link];
if (!signalItem) {
return;
}
// TODO: 将此处的 offset 计算中的参数和 globalSetting 的数字形成关联
gl.uniform2f(webglLocation.offset,
(2 * xOffset / width) - 1,
(2 * yOffset - 2 * yStep * (index + .7)) / canvasHeight + 1
);
console.log('offset y', (2 * yOffset - 2 * yStep * (index + .7)) / canvasHeight + 1);
// 根据 lineVao 进行绘制
const lineVertices = lineVerticesMap.get(signal.link);
const maskVertices = maskVerticesMap.get(signal.link);
if (signal.size === 1) {
// 如果是 width 为 1 的
gl.bindVertexArray(signalItem.lineVao);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, lineVertices.length / 4);
``
gl.bindVertexArray(signalItem.maskVao);
gl.drawArrays(gl.TRIANGLES, 0, maskVertices.length / 4);
} else {
// 如果是 width 大于 1 的
gl.drawArrays(gl.LINE_STRIP, 0, lineVertices.length / 4);
}
index ++;
}
this.plugins.map(fn => fn(globalLookup, this.pstate, elements));
this.animationHandler = undefined;
});
}
}
module.exports = WebGL2WaveRender;