import { globalSetting, globalStyle } from '../global'; import { gl_Colors, gl_Shifts, gl_Shifts_for_bar, gl_WidthShifts, barShift, getRatio, screenHeightPixel, maskColorIndexOffset, posYFactor, glslInputLength, prettyPrint, ladderAnalog_GL_WidthShifts, lineAnlog_GL_WidthShifts } from './render-utils.js'; import { vertexShader, fragmentShader } from './render-shader.js'; import { renderAsBit, renderAsCommonDigital, renderAsLadderAnalog, renderAsLineAnalog } from './toolbar/renderModal'; import { StaticCursor } from '@/components/render/cursor'; /** * @description * @typedef {Object} VecRenderNumberVertices * @property {number[]} lineVertices * @property {number[]} maskVertices */ // const { ChangoItem } = require('./types.d.ts'); class WebGL2WaveRender { /** * * @param {WaveElements} elements * @param {GlobalLookup} globalLookup * @param {Pstate} 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, * control: number, * widthShifts: WebGLUniformLocation, * gl: WebGL2RenderingContext * }} */ initProgram(gl) { const program = gl.createProgram(); gl.attachShader(program, vertexShader.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'), posYFactor: gl.getUniformLocation(program, 'posYFactor'), scale: gl.getUniformLocation(program, 'scale'), offset: gl.getUniformLocation(program, 'offset'), pos: gl.getAttribLocation(program, 'pos'), control: gl.getAttribLocation(program, 'control'), 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(); if (globalSetting.prerender) { for (const id of Reflect.ownKeys(globalLookup.chango)) { const { lineVertices, maskVertices } = this.makeVertexByID(id); lineVerticesMap.set(id, lineVertices); maskVerticesMap.set(id, maskVertices); } } return { lineVerticesMap, maskVerticesMap }; } /** * * @param {string} id 波形的 link * @returns {{ * lineVertices: Int32Array * maskVertices: Int32Array * }} */ makeVertexByID(id) { const globalLookup = this.globalLookup; const time = globalLookup.time; const signalItem = globalLookup.chango[id]; const { kind, wave } = signalItem; if (kind === 'bit') { const { lineVertices, maskVertices } = this.makeBitVertex(id, wave, time); return { lineVertices, maskVertices }; } else if (kind === 'vec') { const { lineVertices, maskVertices } = this.makeVecVertex(id, wave, time); return { lineVertices, maskVertices }; } return { lineVertices: undefined, maskVertices: undefined } } /** * * @param {string} link * @param {Array} wave * @param { number } time * @returns {{ * lineVertices: Int32Array * maskVertices: Int32Array * }} */ makeBitVertex(link, wave, time, debug = false) { const { lineVertices, maskVertices } = renderAsBit(link, wave, time); if (debug) { console.log(lineVertices); } return { lineVertices : new Int32Array(lineVertices), maskVertices : new Int32Array(maskVertices) }; } /** * @param {GlobalLookup} lookup * @param {string} link * @returns {number} */ getVecRenderModal(lookup, link) { const renderOptions = lookup.currentSignalRenderOptions; if (renderOptions.has(link)) { const option = renderOptions.get(link); if (typeof option.renderModal === 'number') { return option.renderModal; } } return 0; } /** * @param {GlobalLookup} lookup * @param {string} link * @param {Array} wave * @param { number } time * @returns {(lookup: GlobalLookup, link: string, wave: number[], time: number) => VecRenderNumberVertices} */ selectVecRenderFn(lookup, link, wave, time) { const modal = this.getVecRenderModal(lookup, link); switch (modal) { case 0: return renderAsCommonDigital; case 1: return renderAsLadderAnalog; case 2: return renderAsLineAnalog; default: return renderAsCommonDigital; } } /** * @param {string} link * @param {Array} wave * @param { number } time * @returns {{ * lineVertices: Int32Array * maskVertices: Int32Array * }} */ makeVecVertex(link, wave, time, debug = false) { const lookup = this.globalLookup; const vecRenderFn = this.selectVecRenderFn(lookup, link, wave, time); const { lineVertices, maskVertices } = vecRenderFn(lookup, link, wave, time); if (debug) { console.log(lineVertices); } return { lineVertices: new Int32Array(lineVertices), maskVertices: new Int32Array(maskVertices) }; } setShaderInput() { const UIntSize = Int32Array.BYTES_PER_ELEMENT; // 4 const webglLocation = this.webglLocation; const gl = webglLocation.gl; gl.vertexAttribIPointer(webglLocation.pos, 2, gl.INT, 5 * UIntSize, 0); gl.vertexAttribIPointer(webglLocation.control, 3, gl.INT, 5 * UIntSize, 2 * UIntSize); gl.enableVertexAttribArray(webglLocation.pos); gl.enableVertexAttribArray(webglLocation.control); } /** * @description 根据 id (link) 将对应信号的 value 数据转换成对应视图的 VAO 以供 webgl 渲染 * @param {string} id * @returns */ initVertice(id) { const webglLocation = this.webglLocation; const gl = webglLocation.gl; const signalItem = this.globalLookup.chango[id]; if (this.lineVerticesMap.get(id) === undefined) { const { lineVertices, maskVertices } = this.makeVertexByID(id); this.lineVerticesMap.set(id, lineVertices); this.maskVerticesMap.set(id, maskVertices); } const lineVertices = this.lineVerticesMap.get(id); const maskVertices = this.maskVerticesMap.get(id); // 创建并设置 绘制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); this.setShaderInput(); // 创建并设置 绘制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); this.setShaderInput(); } /** * @description 用于重置 VAO 的函数 * @param {string} id 波形的 link */ resetVertice(id) { const webglLocation = this.webglLocation; const gl = webglLocation.gl; const signalItem = this.globalLookup.chango[id]; const { lineVertices, maskVertices } = this.makeVertexByID(id); this.lineVerticesMap.set(id, lineVertices); this.maskVerticesMap.set(id, maskVertices); // 创建并设置 绘制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); this.setShaderInput(); // 创建并设置 绘制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); this.setShaderInput(); } initData() { if (globalSetting.prerender) { for (const id of Reflect.ownKeys(this.globalLookup.chango)) { this.initVertice(id); } } const webglLocation = this.webglLocation; const gl = webglLocation.gl; gl.uniform4fv(webglLocation.colors, gl_Colors); gl.uniform2fv(webglLocation.shifts, gl_Shifts); gl.uniform1f(webglLocation.posYFactor, posYFactor); } /** * @description 更新默认波形的颜色 * @param {Array} updaters * @param {{ * updateMask: boolean * }} config */ updateGLColor(updaters, config) { const webglLocation = this.webglLocation; const gl = webglLocation.gl; for (const updater of updaters) { const startIndex = updater.index * 4; const rgba = updater.rgba; gl_Colors[startIndex] = rgba.red; gl_Colors[startIndex + 1] = rgba.green; gl_Colors[startIndex + 2] = rgba.blue; gl_Colors[startIndex + 3] = rgba.alpha; if (config.updateMask) { const maskIndex = (updater.index + maskColorIndexOffset) * 4; gl_Colors[maskIndex] = rgba.red; gl_Colors[maskIndex + 1] = rgba.green; gl_Colors[maskIndex + 2] = rgba.blue; } } gl.uniform4fv(webglLocation.colors, gl_Colors); this.render(); } /** * * @param {RenderConfig} renderConfig */ render(renderConfig) { renderConfig = renderConfig || { type: 'common' }; const needHiddenAnimation = globalSetting.renderAnimation && renderConfig.type === 'action'; 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; // 更新 yDuty & yStep // yStep: scale 之后的单个 wave 的容器高度需要和 displaySignalHeight + sideBarItemMargin 一样 // yDuty: scale 之后的单个 wave 的 padding 必须等于 sideBarItemMargin,也就是 (1 - yDuty) * yStep = 2 * margin const sidebarItemMargin = globalStyle.sideBarItemMargin; const pstate = this.pstate; const plugins = this.plugins; pstate.yStep = globalSetting.displaySignalHeight + globalStyle.sideBarItemMargin; pstate.yDuty = (1 - 2 * globalStyle.sideBarItemMargin / pstate.yStep) * 0.95; document.body.style.setProperty('--vcd-render-padding', pstate.topBarHeight + 'px'); const canvasHeight = pstate.height - pstate.topBarHeight - pstate.botBarHeight; const canvasWidth = pstate.width; // 默认 1594 canvas.width = canvasWidth; // 默认 1260 canvas.height = canvasHeight; let startTime = undefined; // 默认执行一个 300 ms 的动画 const animationDuration = 300; if (needHiddenAnimation) { this.elements.grid.classList.add('vcd-hidden'); this.elements.values.classList.add('vcd-hidden'); } let animationHandler = this.animationHandler = requestAnimationFrame(drawWaves); const _this = this; if (renderConfig.type === 'value') { const renderValue = plugins.filter(plugin => plugin.name === 'pluginRenderValues')[0]; if (renderValue) { renderValue(globalLookup, pstate, elements); } return; } // 更新静态 cursor StaticCursor.updateLeft(); function linearAnimation(delta, oldVal, newVal) { return (1 - delta) * oldVal + delta * newVal; } function cubicBezierAnimation(delta, oldVal, newVal) { delta = 3 * (1 - delta) * (1 - delta) * delta + 3 * (1 - delta) * delta * delta + delta * delta * delta; return (1 - delta) * oldVal + delta * newVal; } function renderOneFrame(animationDelta) { // 考虑设置线性动画的变量 xScale, xOffset, yOffset, yStep, yDuty const xScale = cubicBezierAnimation(animationDelta, pstate.oldXScale, pstate.xScale); const xOffset = cubicBezierAnimation(animationDelta, pstate.oldXOffset, pstate.xOffset); const yOffset = cubicBezierAnimation(animationDelta, pstate.oldYOffset, pstate.yOffset); const yStep = cubicBezierAnimation(animationDelta, pstate.oldYStep, pstate.yStep); const yDuty = cubicBezierAnimation(animationDelta, pstate.oldYDuty, pstate.yDuty); // 设置 glsl 变量 let scaleX = 2 * xScale / canvasWidth; let scaleY = yStep * yDuty / canvasHeight; // console.log(scaleX); // // scaleX 保留6位有效数字 // scaleX = parseInt(scaleX * 10000) / 10000; // console.log(scaleX); gl.uniform2f(webglLocation.scale, scaleX, scaleY); // 设置 webgl 和 canvas 大小位置一致 gl.viewport(0, 0, canvasWidth, canvasHeight); // 清除颜色缓冲区,也就是删除上一次的渲染结果 gl.clear(gl.COLOR_BUFFER_BIT); // 根据 currentWiresRenderView 视图渲染 const renderSignals = []; const link2CurrentWires = globalLookup.link2CurrentWires; for (const view of globalLookup.currentWiresRenderView) { if (view.renderType === 0) { const realSignal = link2CurrentWires.get(view.signalInfo.link); renderSignals.push(realSignal); } else { // 如果是组,需要渲染空白的一行 renderSignals.push(0); // 如果没有关闭,把所有子节点加入其中 if (view.groupInfo.collapse === false) { for (const child of view.children) { const realSignal = link2CurrentWires.get(child.signalInfo.link); renderSignals.push(realSignal); } } } } for (let index = 0; index < renderSignals.length; ++ index) { const signal = renderSignals[index]; if (signal === 0) { continue; } const id = signal.link; const wave = globalLookup.chango[id].wave; const signalItem = globalLookup.chango[id]; if (!signalItem) { return; } // 区间映射公式,将 [a, b] 内的数字映射到 [c, d] 中 // $f(x) = \frac{d - c}{b - a} (x - a) + c$ const offsetX = 2 / canvasWidth * xOffset - 1; const lineHeightExtra = (globalSetting.displaySignalHeight - 30) / 2; const offsetY = 2 / canvasHeight * (yOffset - yStep * index - lineHeightExtra) + 1; gl.uniform2f(webglLocation.offset, offsetX, offsetY); // 根据 lineVao 进行绘制 if (lineVerticesMap.get(id) === undefined) { _this.initVertice(id); } const lineVertices = lineVerticesMap.get(id); const maskVertices = maskVerticesMap.get(id); if (signal.size === 1) { // 如果是 bit gl.uniform2fv(webglLocation.widthShifts, gl_WidthShifts); gl.uniform2fv(webglLocation.shifts, gl_Shifts); gl.bindVertexArray(signalItem.lineVao); gl.drawArrays(gl.TRIANGLE_STRIP, 0, lineVertices.length / glslInputLength); gl.bindVertexArray(signalItem.maskVao); gl.drawArrays(gl.TRIANGLES, 0, maskVertices.length / glslInputLength); } else { // 如果是 vec,根据设定的渲染模式和进行设置 const vecRenderModal = _this.getVecRenderModal(globalLookup, signal.link); if (vecRenderModal === 0) { // 普通数字渲染模式、 gl.uniform2fv(webglLocation.widthShifts, gl_WidthShifts); gl.uniform2fv(webglLocation.shifts, gl_Shifts_for_bar); gl.bindVertexArray(signalItem.lineVao); gl.drawArrays(gl.TRIANGLES, 0, lineVertices.length / glslInputLength); gl.bindVertexArray(signalItem.maskVao); gl.drawArrays(gl.TRIANGLES, 0, maskVertices.length / glslInputLength); } else if (vecRenderModal === 1) { // 梯形渲染模式 gl.uniform2fv(webglLocation.widthShifts, ladderAnalog_GL_WidthShifts); gl.uniform2fv(webglLocation.shifts, gl_Shifts_for_bar); gl.bindVertexArray(signalItem.lineVao); gl.drawArrays(gl.TRIANGLE_STRIP, 0, lineVertices.length / glslInputLength); gl.bindVertexArray(signalItem.maskVao); gl.drawArrays(gl.TRIANGLES, 0, maskVertices.length / glslInputLength); } else { // 折线渲染模式 gl.uniform2fv(webglLocation.widthShifts, lineAnlog_GL_WidthShifts); gl.uniform2fv(webglLocation.shifts, gl_Shifts_for_bar); gl.bindVertexArray(signalItem.lineVao); gl.drawArrays(gl.TRIANGLES, 0, lineVertices.length / glslInputLength); gl.bindVertexArray(signalItem.maskVao); gl.drawArrays(gl.TRIANGLES, 0, maskVertices.length / glslInputLength); } } } plugins.map(fn => fn(globalLookup, pstate, elements)); } function drawWaves(now) { if (startTime === undefined) { startTime = now; } else { const pastTime = now - startTime; const animationDelta = globalSetting.renderAnimation ? Math.min(pastTime / animationDuration, 1) : 1; renderOneFrame(animationDelta); // 达到 1 时说明线性动画时间到了,退出即可 if (animationDelta >= 1) { cancelAnimationFrame(animationHandler); // 更新状态变量 pstate.oldXOffset = pstate.xOffset; pstate.oldYOffset = pstate.yOffset; pstate.oldXScale = pstate.xScale; pstate.oldYStep = pstate.yStep; pstate.oldYDuty = pstate.yDuty; _this.elements.grid.classList.remove('vcd-hidden'); _this.elements.values.classList.remove('vcd-hidden'); return; } } // 进入下一帧 requestAnimationFrame(drawWaves); } } } export default WebGL2WaveRender;