optimization
This commit is contained in:
+346
-66
@@ -20,6 +20,12 @@ let frameCounter = 0;
|
|||||||
let eyeScale = 1.5;
|
let eyeScale = 1.5;
|
||||||
let interactionTimer = 0;
|
let interactionTimer = 0;
|
||||||
|
|
||||||
|
let fpsHistory = [];
|
||||||
|
let lastFrameTime = 0;
|
||||||
|
let scaleCheckTimer = 0;
|
||||||
|
let currentPixelScale = 1.0;
|
||||||
|
let maxPixelScale = 1.0;
|
||||||
|
|
||||||
let postProgram;
|
let postProgram;
|
||||||
let postFramebuffer, postTexture;
|
let postFramebuffer, postTexture;
|
||||||
|
|
||||||
@@ -103,6 +109,88 @@ void main() {
|
|||||||
}
|
}
|
||||||
`.trim();
|
`.trim();
|
||||||
|
|
||||||
|
const vsGlitch = `#version 300 es
|
||||||
|
in vec2 a_quad; // 0..1
|
||||||
|
in vec4 a_glitchData; // x, y, w, h
|
||||||
|
in float a_colorType; // 0..4
|
||||||
|
|
||||||
|
uniform vec2 u_resolution;
|
||||||
|
out float v_colorType;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
// a_glitchData: x = pos.x, y = pos.y, z = width, w = height
|
||||||
|
vec2 pixelPos = vec2(a_glitchData.x, a_glitchData.y) + a_quad * vec2(a_glitchData.z, a_glitchData.w);
|
||||||
|
|
||||||
|
// Переводим пиксели (0..ширина) в пространство 0..1
|
||||||
|
vec2 zeroToOne = pixelPos / u_resolution;
|
||||||
|
|
||||||
|
// Переводим 0..1 в пространство -1..1 (Clip Space)
|
||||||
|
vec2 clipSpace = (zeroToOne * 2.0) - 1.0;
|
||||||
|
|
||||||
|
// Переворачиваем Y, так как в WebGL Y идет вверх
|
||||||
|
gl_Position = vec4(clipSpace * vec2(1.0, -1.0), 0.0, 1.0);
|
||||||
|
|
||||||
|
v_colorType = a_colorType;
|
||||||
|
}
|
||||||
|
`.trim();
|
||||||
|
|
||||||
|
const fsGlitch = `#version 300 es
|
||||||
|
precision mediump float;
|
||||||
|
in float v_colorType;
|
||||||
|
|
||||||
|
uniform vec3 u_themeMain;
|
||||||
|
uniform vec3 u_themeBg;
|
||||||
|
|
||||||
|
out vec4 outColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
int type = int(v_colorType + 0.1); // Добавили epsilon для точности
|
||||||
|
|
||||||
|
if (type == 0) {
|
||||||
|
outColor = vec4(u_themeBg, 1.0);
|
||||||
|
} else if (type == 1) {
|
||||||
|
outColor = vec4(u_themeMain, 0.9);
|
||||||
|
} else if (type == 2) {
|
||||||
|
outColor = vec4(vec3(1.0) - u_themeMain, 0.7);
|
||||||
|
} else if (type == 3) {
|
||||||
|
outColor = vec4(1.0, 1.0, 1.0, 0.95);
|
||||||
|
} else if (type == 4) {
|
||||||
|
outColor = vec4(1.0, 0.08, 0.31, 0.95); // Тот самый красный
|
||||||
|
} else {
|
||||||
|
discard; // Если тип странный — просто не рисуем
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`.trim();
|
||||||
|
|
||||||
|
const vsEye = `#version 300 es
|
||||||
|
in vec2 a_quadCentered; // -0.5 to 0.5
|
||||||
|
uniform vec2 u_resolution;
|
||||||
|
uniform vec2 u_eyePos; // Центр глаза
|
||||||
|
uniform float u_eyeSize;
|
||||||
|
|
||||||
|
out vec2 v_uv;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 pos = u_eyePos + a_quadCentered * u_eyeSize;
|
||||||
|
vec2 zeroToOne = pos / u_resolution;
|
||||||
|
vec2 clipSpace = (zeroToOne * 2.0) - 1.0;
|
||||||
|
gl_Position = vec4(clipSpace * vec2(1.0, -1.0), 0.0, 1.0);
|
||||||
|
v_uv = a_quadCentered + 0.5; // Переводим -0.5..0.5 в 0..1 для текстуры
|
||||||
|
}
|
||||||
|
`.trim();
|
||||||
|
|
||||||
|
const fsEye = `#version 300 es
|
||||||
|
precision mediump float;
|
||||||
|
in vec2 v_uv;
|
||||||
|
uniform sampler2D u_texture;
|
||||||
|
out vec4 outColor;
|
||||||
|
void main() {
|
||||||
|
vec4 color = texture(u_texture, v_uv);
|
||||||
|
if (color.a < 0.01) discard;
|
||||||
|
outColor = color;
|
||||||
|
}
|
||||||
|
`.trim();
|
||||||
|
|
||||||
const postProcessSource = `#version 300 es
|
const postProcessSource = `#version 300 es
|
||||||
precision highp float;
|
precision highp float;
|
||||||
|
|
||||||
@@ -183,7 +271,6 @@ void main() {
|
|||||||
}
|
}
|
||||||
`.trim();
|
`.trim();
|
||||||
|
|
||||||
// --- WEBGL STATE ---
|
|
||||||
let matrixProgram, overlayProgram;
|
let matrixProgram, overlayProgram;
|
||||||
let instanceBuffer, quadBuffer, overlayQuadBuffer;
|
let instanceBuffer, quadBuffer, overlayQuadBuffer;
|
||||||
let vaoMatrix, vaoOverlay;
|
let vaoMatrix, vaoOverlay;
|
||||||
@@ -191,6 +278,11 @@ let uResLoc, uFontLoc, uAtlasSizeLoc;
|
|||||||
let overlayTexture;
|
let overlayTexture;
|
||||||
let instanceBufferCapacity = 0;
|
let instanceBufferCapacity = 0;
|
||||||
|
|
||||||
|
// ДОБАВЛЕННЫЕ ПЕРЕМЕННЫЕ ДЛЯ ГЛИТЧЕЙ И ГЛАЗ
|
||||||
|
let glitchProgram, vaoGlitch, glitchInstanceBuffer;
|
||||||
|
let eyeProgram, vaoEye, quadBufferCentered;
|
||||||
|
let eyeCanvas, eyeCtx, eyeTexture;
|
||||||
|
|
||||||
// --- ИНИЦИАЛИЗАЦИЯ WEBGL ---
|
// --- ИНИЦИАЛИЗАЦИЯ WEBGL ---
|
||||||
function compileShader(type, source) {
|
function compileShader(type, source) {
|
||||||
const shader = gl.createShader(type);
|
const shader = gl.createShader(type);
|
||||||
@@ -216,12 +308,11 @@ function createProgram(vsSource, fsSource) {
|
|||||||
|
|
||||||
function initWebGL() {
|
function initWebGL() {
|
||||||
matrixProgram = createProgram(vsMatrix, fsMatrix);
|
matrixProgram = createProgram(vsMatrix, fsMatrix);
|
||||||
overlayProgram = createProgram(vsOverlay, fsOverlay);
|
postProgram = createProgram(vsOverlay, postProcessSource);
|
||||||
|
glitchProgram = createProgram(vsGlitch, fsGlitch);
|
||||||
// --- VAO для Матрицы ---
|
eyeProgram = createProgram(vsEye, fsEye);
|
||||||
vaoMatrix = gl.createVertexArray();
|
|
||||||
gl.bindVertexArray(vaoMatrix);
|
|
||||||
|
|
||||||
|
// --- БАЗОВЫЕ КВАДРАТЫ ---
|
||||||
quadBuffer = gl.createBuffer();
|
quadBuffer = gl.createBuffer();
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, quadBuffer);
|
gl.bindBuffer(gl.ARRAY_BUFFER, quadBuffer);
|
||||||
gl.bufferData(
|
gl.bufferData(
|
||||||
@@ -230,16 +321,28 @@ function initWebGL() {
|
|||||||
gl.STATIC_DRAW,
|
gl.STATIC_DRAW,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
quadBufferCentered = gl.createBuffer();
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, quadBufferCentered);
|
||||||
|
gl.bufferData(
|
||||||
|
gl.ARRAY_BUFFER,
|
||||||
|
new Float32Array([
|
||||||
|
-0.5, -0.5, 0.5, -0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5,
|
||||||
|
]),
|
||||||
|
gl.STATIC_DRAW,
|
||||||
|
);
|
||||||
|
|
||||||
|
// --- VAO: МАТРИЦА ---
|
||||||
|
vaoMatrix = gl.createVertexArray();
|
||||||
|
gl.bindVertexArray(vaoMatrix);
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, quadBuffer);
|
||||||
const aQuadLoc = gl.getAttribLocation(matrixProgram, "a_quad");
|
const aQuadLoc = gl.getAttribLocation(matrixProgram, "a_quad");
|
||||||
gl.enableVertexAttribArray(aQuadLoc);
|
gl.enableVertexAttribArray(aQuadLoc);
|
||||||
gl.vertexAttribPointer(aQuadLoc, 2, gl.FLOAT, false, 0, 0);
|
gl.vertexAttribPointer(aQuadLoc, 2, gl.FLOAT, false, 0, 0);
|
||||||
gl.vertexAttribDivisor(aQuadLoc, 0);
|
|
||||||
|
|
||||||
instanceBuffer = gl.createBuffer();
|
instanceBuffer = gl.createBuffer();
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, instanceBuffer);
|
gl.bindBuffer(gl.ARRAY_BUFFER, instanceBuffer);
|
||||||
instanceBufferCapacity = 20000;
|
instanceBufferCapacity = 20000;
|
||||||
gl.bufferData(gl.ARRAY_BUFFER, instanceBufferCapacity * 4, gl.DYNAMIC_DRAW);
|
gl.bufferData(gl.ARRAY_BUFFER, instanceBufferCapacity * 4, gl.DYNAMIC_DRAW);
|
||||||
|
|
||||||
const strideBytes = 5 * 4;
|
const strideBytes = 5 * 4;
|
||||||
const aPosLoc = gl.getAttribLocation(matrixProgram, "a_position");
|
const aPosLoc = gl.getAttribLocation(matrixProgram, "a_position");
|
||||||
gl.enableVertexAttribArray(aPosLoc);
|
gl.enableVertexAttribArray(aPosLoc);
|
||||||
@@ -261,12 +364,38 @@ function initWebGL() {
|
|||||||
gl.vertexAttribPointer(aScaleLoc, 1, gl.FLOAT, false, strideBytes, 4 * 4);
|
gl.vertexAttribPointer(aScaleLoc, 1, gl.FLOAT, false, strideBytes, 4 * 4);
|
||||||
gl.vertexAttribDivisor(aScaleLoc, 1);
|
gl.vertexAttribDivisor(aScaleLoc, 1);
|
||||||
|
|
||||||
gl.bindVertexArray(null);
|
// --- VAO: ГЛИТЧИ ---
|
||||||
|
vaoGlitch = gl.createVertexArray();
|
||||||
|
gl.bindVertexArray(vaoGlitch);
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, quadBuffer);
|
||||||
|
const aQuadGlitch = gl.getAttribLocation(glitchProgram, "a_quad");
|
||||||
|
gl.enableVertexAttribArray(aQuadGlitch);
|
||||||
|
gl.vertexAttribPointer(aQuadGlitch, 2, gl.FLOAT, false, 0, 0);
|
||||||
|
|
||||||
// --- VAO для Overlay ---
|
glitchInstanceBuffer = gl.createBuffer();
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, glitchInstanceBuffer);
|
||||||
|
const strideG = 5 * 4; // БЫЛО 6, СТАЛО 5 (согласно Rust структуре)
|
||||||
|
const aDataLoc = gl.getAttribLocation(glitchProgram, "a_glitchData");
|
||||||
|
gl.enableVertexAttribArray(aDataLoc);
|
||||||
|
gl.vertexAttribPointer(aDataLoc, 4, gl.FLOAT, false, strideG, 0);
|
||||||
|
gl.vertexAttribDivisor(aDataLoc, 1);
|
||||||
|
|
||||||
|
const aColorLoc = gl.getAttribLocation(glitchProgram, "a_colorType");
|
||||||
|
gl.enableVertexAttribArray(aColorLoc);
|
||||||
|
gl.vertexAttribPointer(aColorLoc, 1, gl.FLOAT, false, strideG, 4 * 4);
|
||||||
|
gl.vertexAttribDivisor(aColorLoc, 1);
|
||||||
|
|
||||||
|
// --- VAO: ГЛАЗА (Sprite) ---
|
||||||
|
vaoEye = gl.createVertexArray();
|
||||||
|
gl.bindVertexArray(vaoEye);
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, quadBufferCentered);
|
||||||
|
const aQuadEye = gl.getAttribLocation(eyeProgram, "a_quadCentered");
|
||||||
|
gl.enableVertexAttribArray(aQuadEye);
|
||||||
|
gl.vertexAttribPointer(aQuadEye, 2, gl.FLOAT, false, 0, 0);
|
||||||
|
|
||||||
|
// --- VAO: POST PROCESS (Overlay Quad) ---
|
||||||
vaoOverlay = gl.createVertexArray();
|
vaoOverlay = gl.createVertexArray();
|
||||||
gl.bindVertexArray(vaoOverlay);
|
gl.bindVertexArray(vaoOverlay);
|
||||||
|
|
||||||
overlayQuadBuffer = gl.createBuffer();
|
overlayQuadBuffer = gl.createBuffer();
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, overlayQuadBuffer);
|
gl.bindBuffer(gl.ARRAY_BUFFER, overlayQuadBuffer);
|
||||||
gl.bufferData(
|
gl.bufferData(
|
||||||
@@ -274,40 +403,115 @@ function initWebGL() {
|
|||||||
new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]),
|
new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]),
|
||||||
gl.STATIC_DRAW,
|
gl.STATIC_DRAW,
|
||||||
);
|
);
|
||||||
|
const aPosLocOver = gl.getAttribLocation(postProgram, "a_position");
|
||||||
const aPosLocOver = gl.getAttribLocation(overlayProgram, "a_position");
|
|
||||||
gl.enableVertexAttribArray(aPosLocOver);
|
gl.enableVertexAttribArray(aPosLocOver);
|
||||||
gl.vertexAttribPointer(aPosLocOver, 2, gl.FLOAT, false, 0, 0);
|
gl.vertexAttribPointer(aPosLocOver, 2, gl.FLOAT, false, 0, 0);
|
||||||
gl.vertexAttribDivisor(aPosLocOver, 0);
|
|
||||||
|
|
||||||
gl.bindVertexArray(null);
|
gl.bindVertexArray(null);
|
||||||
|
|
||||||
// --- Uniforms & Textures ---
|
// --- ТЕКСТУРЫ И БУФЕРЫ ---
|
||||||
uResLoc = gl.getUniformLocation(matrixProgram, "u_resolution");
|
uResLoc = gl.getUniformLocation(matrixProgram, "u_resolution");
|
||||||
uFontLoc = gl.getUniformLocation(matrixProgram, "u_fontSize");
|
uFontLoc = gl.getUniformLocation(matrixProgram, "u_fontSize");
|
||||||
uAtlasSizeLoc = gl.getUniformLocation(matrixProgram, "u_atlasSize");
|
uAtlasSizeLoc = gl.getUniformLocation(matrixProgram, "u_atlasSize");
|
||||||
|
|
||||||
atlasTexture = gl.createTexture();
|
atlasTexture = gl.createTexture();
|
||||||
overlayTexture = gl.createTexture();
|
|
||||||
|
|
||||||
// ИНИЦИАЛИЗАЦИЯ ПОСТОБРАБОТКИ
|
// Инициализация маленького канваса для глаз (300x300 пикселей)
|
||||||
postProgram = createProgram(vsOverlay, postProcessSource);
|
eyeCanvas = new OffscreenCanvas(300, 300);
|
||||||
|
eyeCtx = eyeCanvas.getContext("2d", { alpha: true });
|
||||||
|
eyeTexture = gl.createTexture();
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, eyeTexture);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||||
|
|
||||||
postTexture = gl.createTexture();
|
postTexture = gl.createTexture();
|
||||||
postFramebuffer = gl.createFramebuffer();
|
postFramebuffer = gl.createFramebuffer();
|
||||||
|
|
||||||
// Настройка текстуры постобработки (размер установим в resize)
|
|
||||||
gl.bindTexture(gl.TEXTURE_2D, postTexture);
|
gl.bindTexture(gl.TEXTURE_2D, postTexture);
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||||
|
|
||||||
// Смена настроек WebGL
|
|
||||||
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
|
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
|
||||||
gl.enable(gl.BLEND);
|
gl.enable(gl.BLEND);
|
||||||
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateDynamicScaling(now) {
|
||||||
|
if (lastFrameTime === 0) lastFrameTime = now;
|
||||||
|
const dt = now - lastFrameTime;
|
||||||
|
lastFrameTime = now;
|
||||||
|
|
||||||
|
// Собираем историю последних 60 кадров
|
||||||
|
fpsHistory.push(dt);
|
||||||
|
if (fpsHistory.length > 60) fpsHistory.shift();
|
||||||
|
|
||||||
|
scaleCheckTimer++;
|
||||||
|
// Проверяем производительность каждую секунду (60 кадров)
|
||||||
|
if (scaleCheckTimer >= 60) {
|
||||||
|
const avgDt = fpsHistory.reduce((a, b) => a + b, 0) / fpsHistory.length;
|
||||||
|
let needsResize = false;
|
||||||
|
|
||||||
|
// Если FPS падает ниже ~45-50 (кадр дольше 22 мс)
|
||||||
|
if (avgDt > 22.0 && currentPixelScale > 0.5) {
|
||||||
|
currentPixelScale = Math.max(0.5, currentPixelScale - 0.25);
|
||||||
|
needsResize = true;
|
||||||
|
}
|
||||||
|
// Если летит стабильно 60+ FPS (кадр меньше 15 мс) и мы не на максимуме
|
||||||
|
else if (avgDt < 15.0 && currentPixelScale < maxPixelScale) {
|
||||||
|
currentPixelScale = Math.min(maxPixelScale, currentPixelScale + 0.25);
|
||||||
|
needsResize = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needsResize) {
|
||||||
|
console.log(`[AutoScaler] Adjusting pixelScale to: ${currentPixelScale}`);
|
||||||
|
// Применяем новый скейл к буферам WebGL
|
||||||
|
gl.canvas.width = width * currentPixelScale;
|
||||||
|
gl.canvas.height = height * currentPixelScale;
|
||||||
|
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, postTexture);
|
||||||
|
gl.texImage2D(
|
||||||
|
gl.TEXTURE_2D,
|
||||||
|
0,
|
||||||
|
gl.RGBA,
|
||||||
|
width * currentPixelScale,
|
||||||
|
height * currentPixelScale,
|
||||||
|
0,
|
||||||
|
gl.RGBA,
|
||||||
|
gl.UNSIGNED_BYTE,
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
// Если используешь overlayCanvas для глаз (см. ниже), его тоже ресайзим
|
||||||
|
}
|
||||||
|
scaleCheckTimer = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSingleEyeToTexture(gazeX, gazeY, dir, isClosed) {
|
||||||
|
// Очищаем маленький канвас
|
||||||
|
eyeCtx.clearRect(0, 0, 300, 300);
|
||||||
|
|
||||||
|
// Передаем cx = 150, cy = 150 (центр 300x300)
|
||||||
|
drawEye(eyeCtx, 150, 150, gazeX, gazeY, dir, isClosed);
|
||||||
|
|
||||||
|
// Загружаем результат в текстуру
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, eyeTexture);
|
||||||
|
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
|
||||||
|
|
||||||
|
// ИСПРАВЛЕНИЕ: Ставим false, чтобы текстура не переворачивалась дважды
|
||||||
|
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
|
||||||
|
|
||||||
|
gl.texImage2D(
|
||||||
|
gl.TEXTURE_2D,
|
||||||
|
0,
|
||||||
|
gl.RGBA,
|
||||||
|
gl.RGBA,
|
||||||
|
gl.UNSIGNED_BYTE,
|
||||||
|
eyeCanvas,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function buildAtlas() {
|
function buildAtlas() {
|
||||||
atlasCanvas = new OffscreenCanvas(chars.length * fontSize, fontSize * 11);
|
atlasCanvas = new OffscreenCanvas(chars.length * fontSize, fontSize * 11);
|
||||||
atlasCtx = atlasCanvas.getContext("2d", { alpha: true });
|
atlasCtx = atlasCanvas.getContext("2d", { alpha: true });
|
||||||
@@ -627,26 +831,30 @@ function render(currentTime) {
|
|||||||
if (now - lastDrawTime < 16) return;
|
if (now - lastDrawTime < 16) return;
|
||||||
lastDrawTime = now;
|
lastDrawTime = now;
|
||||||
|
|
||||||
// 1. Физика капель (WASM)
|
// 1. Авто-Скейлер
|
||||||
// Для слабых устройств можно включить троттлинг обратно, если будет греться
|
updateDynamicScaling(now);
|
||||||
|
|
||||||
|
// 2. Физика
|
||||||
engine.tick();
|
engine.tick();
|
||||||
|
|
||||||
|
// 3. Подготовка буферов
|
||||||
const targetBuffer = isLowQuality ? null : postFramebuffer;
|
const targetBuffer = isLowQuality ? null : postFramebuffer;
|
||||||
gl.bindFramebuffer(gl.FRAMEBUFFER, targetBuffer);
|
gl.bindFramebuffer(gl.FRAMEBUFFER, targetBuffer);
|
||||||
gl.viewport(0, 0, width * pixelScale, height * pixelScale);
|
gl.viewport(0, 0, width * currentPixelScale, height * currentPixelScale);
|
||||||
|
|
||||||
const bgR = isDarkMode ? 10 / 255 : 250 / 255;
|
const bgR = isDarkMode ? 10 / 255 : 250 / 255;
|
||||||
const bgG = isDarkMode ? 10 / 255 : 250 / 255;
|
const bgG = isDarkMode ? 10 / 255 : 250 / 255;
|
||||||
const bgB = isDarkMode ? 12 / 255 : 252 / 255;
|
const bgB = isDarkMode ? 12 / 255 : 252 / 255;
|
||||||
|
|
||||||
// Всегда очищаем реальным цветом фона, а не прозрачностью!
|
|
||||||
// Это гарантирует, что даже если шейдер заглючит, под ним будет верный цвет.
|
|
||||||
gl.clearColor(bgR, bgG, bgB, 1.0);
|
gl.clearColor(bgR, bgG, bgB, 1.0);
|
||||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||||
// 1.1. РЕНДЕР МАТРИЦЫ (Instanced Rendering)
|
|
||||||
|
// ==========================================
|
||||||
|
// ЭТАП 1: МАТРИЦА (Zero-Copy Instancing)
|
||||||
|
// ==========================================
|
||||||
const renderLen = engine.render_len();
|
const renderLen = engine.render_len();
|
||||||
|
if (renderLen > 0) {
|
||||||
const instanceCount = renderLen / 5;
|
const instanceCount = renderLen / 5;
|
||||||
if (instanceCount > 0) {
|
|
||||||
gl.useProgram(matrixProgram);
|
gl.useProgram(matrixProgram);
|
||||||
gl.bindVertexArray(vaoMatrix);
|
gl.bindVertexArray(vaoMatrix);
|
||||||
|
|
||||||
@@ -675,45 +883,122 @@ function render(currentTime) {
|
|||||||
gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, instanceCount);
|
gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, instanceCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1.2. РЕНДЕР ОВЕРЛЕЯ (Глаза и эффекты)
|
// ==========================================
|
||||||
const needsOverlayUpdate = drawOverlayElements();
|
// ЭТАП 2: ГЛИТЧИ (Pure WebGL Instancing)
|
||||||
|
// ==========================================
|
||||||
|
const glLen = engine.glitch_len();
|
||||||
|
if (glLen > 0) {
|
||||||
|
const glitchInstanceCount = glLen / 5;
|
||||||
|
gl.useProgram(glitchProgram);
|
||||||
|
gl.bindVertexArray(vaoGlitch);
|
||||||
|
|
||||||
// КРИТИЧЕСКАЯ ОПТИМИЗАЦИЯ ДЛЯ СЛАБЫХ ПК:
|
// Определяем цвета темы
|
||||||
// Не вызываем рендер оверлея, если он статичен (interactionTimer вышел)
|
let tr = 2 / 255,
|
||||||
const skipOverlay =
|
tg = 132 / 255,
|
||||||
isLowQuality && interactionTimer <= 0 && engine.glitch_len() === 0;
|
tb = 199 / 255; // Blue
|
||||||
|
if (isVpnOn && isDarkMode) {
|
||||||
|
tr = 139 / 255;
|
||||||
|
tg = 61 / 255;
|
||||||
|
tb = 255 / 255;
|
||||||
|
} else if (!isVpnOn && isDarkMode) {
|
||||||
|
tr = 0;
|
||||||
|
tg = 229 / 255;
|
||||||
|
tb = 242 / 255;
|
||||||
|
} else if (isVpnOn && !isDarkMode) {
|
||||||
|
tr = 109 / 255;
|
||||||
|
tg = 40 / 255;
|
||||||
|
tb = 217 / 255;
|
||||||
|
}
|
||||||
|
|
||||||
if (!skipOverlay) {
|
gl.uniform3f(
|
||||||
gl.useProgram(overlayProgram);
|
gl.getUniformLocation(glitchProgram, "u_themeMain"),
|
||||||
gl.bindVertexArray(vaoOverlay);
|
tr,
|
||||||
gl.activeTexture(gl.TEXTURE1);
|
tg,
|
||||||
gl.bindTexture(gl.TEXTURE_2D, overlayTexture);
|
tb,
|
||||||
|
|
||||||
if (needsOverlayUpdate) {
|
|
||||||
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
|
|
||||||
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
|
|
||||||
gl.texImage2D(
|
|
||||||
gl.TEXTURE_2D,
|
|
||||||
0,
|
|
||||||
gl.RGBA,
|
|
||||||
gl.RGBA,
|
|
||||||
gl.UNSIGNED_BYTE,
|
|
||||||
overlayCanvas,
|
|
||||||
);
|
);
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
gl.uniform3f(
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
gl.getUniformLocation(glitchProgram, "u_themeBg"),
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
bgR,
|
||||||
|
bgG,
|
||||||
|
bgB,
|
||||||
|
);
|
||||||
|
gl.uniform2f(
|
||||||
|
gl.getUniformLocation(glitchProgram, "u_resolution"),
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
);
|
||||||
|
|
||||||
|
const glDataView = new Float32Array(
|
||||||
|
wasm.memory.buffer,
|
||||||
|
engine.glitch_ptr(),
|
||||||
|
glLen,
|
||||||
|
);
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, glitchInstanceBuffer);
|
||||||
|
gl.bufferData(gl.ARRAY_BUFFER, glDataView, gl.DYNAMIC_DRAW);
|
||||||
|
|
||||||
|
gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, glitchInstanceCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
gl.uniform1i(gl.getUniformLocation(overlayProgram, "u_texture"), 1);
|
// ==========================================
|
||||||
|
// ЭТАП 3: ГЛАЗА (Sprite Batching)
|
||||||
|
// ==========================================
|
||||||
|
const eyeLen = engine.eye_len();
|
||||||
|
if (eyeLen >= 6) {
|
||||||
|
interactionTimer = 120;
|
||||||
|
|
||||||
|
const eyeData = new Float32Array(
|
||||||
|
wasm.memory.buffer,
|
||||||
|
engine.eye_ptr(),
|
||||||
|
eyeLen,
|
||||||
|
);
|
||||||
|
const eyeBaseX = eyeData[0];
|
||||||
|
const eyeBaseY = eyeData[1];
|
||||||
|
|
||||||
|
// Обновляем текстуру глаз ТОЛЬКО если мы не пропускаем кадр из-за lowQuality
|
||||||
|
if (!isLowQuality || frameCounter % 2 === 0) {
|
||||||
|
renderSingleEyeToTexture(eyeData[2], eyeData[3], eyeData[4], eyeData[5]);
|
||||||
|
}
|
||||||
|
|
||||||
|
gl.useProgram(eyeProgram);
|
||||||
|
gl.bindVertexArray(vaoEye);
|
||||||
|
gl.activeTexture(gl.TEXTURE0);
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, eyeTexture);
|
||||||
|
gl.uniform1i(gl.getUniformLocation(eyeProgram, "u_texture"), 0);
|
||||||
|
gl.uniform2f(
|
||||||
|
gl.getUniformLocation(eyeProgram, "u_resolution"),
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
);
|
||||||
|
gl.uniform1f(gl.getUniformLocation(eyeProgram, "u_eyeSize"), 300.0);
|
||||||
|
|
||||||
|
const gap = (isMobile ? 75.0 : 80.0) * eyeScale;
|
||||||
|
|
||||||
|
// Рисуем ЛЕВЫЙ глаз
|
||||||
|
gl.uniform2f(
|
||||||
|
gl.getUniformLocation(eyeProgram, "u_eyePos"),
|
||||||
|
eyeBaseX - gap,
|
||||||
|
eyeBaseY,
|
||||||
|
);
|
||||||
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
||||||
|
|
||||||
|
// Рисуем ПРАВЫЙ глаз
|
||||||
|
gl.uniform2f(
|
||||||
|
gl.getUniformLocation(eyeProgram, "u_eyePos"),
|
||||||
|
eyeBaseX + gap,
|
||||||
|
eyeBaseY,
|
||||||
|
);
|
||||||
|
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
||||||
|
} else if (interactionTimer > 0) {
|
||||||
|
interactionTimer--;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. ФИНАЛЬНЫЙ ЭТАП (ПОСТОБРАБОТКА)
|
frameCounter++;
|
||||||
// Если мы на слабом устройстве — работа закончена, выходим!
|
|
||||||
|
// ==========================================
|
||||||
|
// ЭТАП 4: ПОСТ-ПРОЦЕССИНГ (Для мощных ПК)
|
||||||
|
// ==========================================
|
||||||
if (isLowQuality) return;
|
if (isLowQuality) return;
|
||||||
|
|
||||||
// Для мощных ПК переносим результат из скрытого буфера на экран с эффектами
|
|
||||||
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
||||||
gl.useProgram(postProgram);
|
gl.useProgram(postProgram);
|
||||||
gl.bindVertexArray(vaoOverlay);
|
gl.bindVertexArray(vaoOverlay);
|
||||||
@@ -725,11 +1010,9 @@ function render(currentTime) {
|
|||||||
gl.uniform1f(gl.getUniformLocation(postProgram, "u_time"), now / 1000);
|
gl.uniform1f(gl.getUniformLocation(postProgram, "u_time"), now / 1000);
|
||||||
gl.uniform2f(
|
gl.uniform2f(
|
||||||
gl.getUniformLocation(postProgram, "u_resolution"),
|
gl.getUniformLocation(postProgram, "u_resolution"),
|
||||||
width * pixelScale,
|
width * currentPixelScale,
|
||||||
height * pixelScale,
|
height * currentPixelScale,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Параметры "Киберпанк 1984"
|
|
||||||
gl.uniform1f(gl.getUniformLocation(postProgram, "u_distortion"), 18.0);
|
gl.uniform1f(gl.getUniformLocation(postProgram, "u_distortion"), 18.0);
|
||||||
gl.uniform1f(gl.getUniformLocation(postProgram, "u_aberration"), 0.008);
|
gl.uniform1f(gl.getUniformLocation(postProgram, "u_aberration"), 0.008);
|
||||||
gl.uniform1f(gl.getUniformLocation(postProgram, "u_scanlines"), 0.15);
|
gl.uniform1f(gl.getUniformLocation(postProgram, "u_scanlines"), 0.15);
|
||||||
@@ -745,7 +1028,6 @@ function render(currentTime) {
|
|||||||
|
|
||||||
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- ОБРАБОТЧИК СООБЩЕНИЙ ОТ REACT ---
|
// --- ОБРАБОТЧИК СООБЩЕНИЙ ОТ REACT ---
|
||||||
self.onmessage = async (e) => {
|
self.onmessage = async (e) => {
|
||||||
const { type, payload } = e.data;
|
const { type, payload } = e.data;
|
||||||
@@ -877,13 +1159,11 @@ self.onmessage = async (e) => {
|
|||||||
|
|
||||||
// А вот в движок передаем только если он уже готов
|
// А вот в движок передаем только если он уже готов
|
||||||
if (engine) {
|
if (engine) {
|
||||||
if (typeof engine.set_vpn_status === "function")
|
if (typeof engine.set_vpn_status === "function") {
|
||||||
engine.set_vpn_status(isVpnOn);
|
engine.set_vpn_status(isVpnOn);
|
||||||
|
}
|
||||||
buildAtlas();
|
buildAtlas();
|
||||||
}
|
}
|
||||||
if (typeof engine.set_vpn_status === "function")
|
|
||||||
engine.set_vpn_status(isVpnOn);
|
|
||||||
buildAtlas();
|
|
||||||
} else if (type === "MOUSE_MOVE") {
|
} else if (type === "MOUSE_MOVE") {
|
||||||
if (engine) engine.update_mouse(payload.x, payload.y);
|
if (engine) engine.update_mouse(payload.x, payload.y);
|
||||||
} else if (type === "DRAW_START") {
|
} else if (type === "DRAW_START") {
|
||||||
|
|||||||
Reference in New Issue
Block a user