'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, * 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, * maskVerticesMap: Map * }} */ 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} 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} 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;