2024-08-28 23:04:05 +08:00

564 lines
21 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.

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<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;
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;