554 lines
21 KiB
JavaScript
554 lines
21 KiB
JavaScript
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';
|
||
|
||
/**
|
||
* @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<string, Int32Array>,
|
||
* maskVerticesMap: Map<string, Int32Array>
|
||
* }}
|
||
*/
|
||
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<string | number>} 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<string | number>} 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<string | number>} 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<Updater>} 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;
|
||
|
||
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;
|
||
|
||
if (needHiddenAnimation) {
|
||
_this.elements.grid.classList.remove('vcd-hidden');
|
||
_this.elements.values.classList.remove('vcd-hidden');
|
||
}
|
||
return;
|
||
}
|
||
}
|
||
// 进入下一帧
|
||
requestAnimationFrame(drawWaves);
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
export default WebGL2WaveRender; |