// ==UserScript==
// @name WebGL (Shaders优化工具)
// @namespace http://tampermonkey.net/
// @version 3.0.1
// @description 全栈WebGL性能优化(含WASM加速、智能LOD、动态批处理)
// @author KiwiFruit
// @match *://*/*
// @license MIT
// @grant GM.addStyle
// @grant GM.getValue
// @grant GM.setValue
// @grant GM.getResourceUrl
// @grant GM.xmlHttpRequest
// @connect wasm-optimizer.example.com
// @resource APIKEY https://yourdomain.com/apikey.txt
// @resource optimizer.wasm https://wasm-optimizer.example.com/optimizer.wasm
// ==/UserScript==
(function() {
'use strict';
/* global GLTFLoader,CryptoJS */
// 核心依赖类(本地实现)
class OctreeNode {
constructor(nodes, bounds) {
this.nodes = nodes || [];
this.bounds = bounds || { x: 0, y: 0, z: 0, size: 1 };
this.children = new Array(8).fill(null);
}
insert(item) {
if (!this.bounds.contains(item.position)) return false;
if (this.nodes.length < 8) {
this.nodes.push(item);
return true;
}
if (!this.children[0]) this.subdivide();
return this.children.some(child => child.insert(item));
}
subdivide() {
const half = this.bounds.size / 2;
const corners = [
{ x: -1, y: -1, z: -1 },
{ x: 1, y: -1, z: -1 },
{ x: -1, y: 1, z: -1 },
{ x: 1, y: 1, z: -1 },
{ x: -1, y: -1, z: 1 },
{ x: 1, y: -1, z: 1 },
{ x: -1, y: 1, z: 1 },
{ x: 1, y: 1, z: 1 }
];
corners.forEach((corner, index) => {
const bounds = {
x: this.bounds.x + corner.x * half,
y: this.bounds.y + corner.y * half,
z: this.bounds.z + corner.z * half,
size: half
};
this.children[index] = new OctreeNode([], bounds);
});
}
}
class DynamicBatcher {
constructor(config) {
this.config = {
maxBatches: config.maxBatches || 32,
maxVertices: config.maxVertices || 65536,
...config
};
this.batches = new Map();
this.currentBatch = null;
}
createBatch(materialId) {
const batch = {
materialId,
vertices: [],
indices: [],
buffer: null,
timestamp: Date.now()
};
this.batches.set(materialId, batch);
return batch;
}
addMesh(mesh) {
const batch = this.batches.get(mesh.material.id) || this.createBatch(mesh.material.id);
batch.vertices.push(...mesh.vertices);
batch.indices.push(...mesh.indices);
if (batch.vertices.length > this.config.maxVertices) {
this.flushBatch(batch);
}
return batch;
}
flushBatch(batch) {
if (!batch.vertices.length) return;
// WebGL缓冲区创建逻辑(省略具体实现)
batch.buffer = this.createWebGLBuffer(batch.vertices, batch.indices);
batch.vertices = [];
batch.indices = [];
}
}
// 主优化器类
class WebGLOptimizer {
constructor() {
this.glContext = null;
this.wasmModule = null;
this.resourceLoader = null;
this.performanceMonitor = null;
this.resourceCache = new Map();
this.errorHandlers = [];
this.recoveryTasks = [];
this.initState = 'pending';
}
// 资源加载器
async initResourceLoader() {
this.resourceLoader = {
loadTexture: async (url) => {
try {
const response = await GM.xmlHttpRequest({
method: 'GET',
url,
responseType: 'arraybuffer'
});
return new Uint8Array(response.response);
} catch (error) {
this.handleError('resourceLoader', error);
return null;
}
},
loadModel: async (url) => {
const gltfLoader = new GLTFLoader();
const model = await gltfLoader.load(url);
return this.optimizeModel(model);
}
};
}
// 性能监控器
initPerformanceMonitor() {
this.performanceMonitor = {
fps: 0,
memoryUsage: 0,
frameTimes: [],
start: performance.now(),
update: () => {
const now = performance.now();
const delta = now - this.start;
this.fps = Math.round(1000 / delta);
this.start = now;
this.frameTimes.push(delta);
if (this.frameTimes.length > 60) this.frameTimes.shift();
this.memoryUsage = performance.memory.usedJSHeapSize;
},
getStats: () => ({
fps: this.fps,
avgFrameTime: this.frameTimes.reduce((a, b) => a + b, 0) / this.frameTimes.length,
memoryUsage: this.memoryUsage
})
};
setInterval(() => this.performanceMonitor.update(), 16);
}
// 核心优化方法
async optimizeScene(scene) {
try {
// 1. 资源预处理
const optimizedResources = await Promise.all(
scene.resources.map(async res => ({
...res,
data: await this.resourceLoader.loadTexture(res.url)
}))
);
// 2. 空间分割
const octree = new OctreeNode();
scene.meshes.forEach(mesh => octree.insert(mesh));
// 3. 动态批处理
const batcher = new DynamicBatcher({ maxBatches: 64 });
const batches = scene.meshes.map(mesh => batcher.addMesh(mesh));
// 4. 着色器优化
const optimizedShaders = await this.wasmModule.optimizeShaders({
vertex: scene.shader.vertex,
fragment: scene.shader.fragment
});
// 5. 渲染管线配置
const program = this.glContext.createProgram();
optimizedShaders.uniforms.forEach(uniform => {
this.glContext.uniformBlockBinding(
program,
this.glContext.getUniformBlockIndex(program, uniform.name),
uniform.binding
);
});
return {
optimizedResources,
octree,
batches,
program,
stats: this.performanceMonitor.getStats()
};
} catch (error) {
this.handleError('optimizeScene', error);
return null;
}
}
// 错误处理系统
handleError(stage, error) {
const severity = this.getErrorSeverity(error);
const context = {
stage,
error: {
message: error.message,
stack: error.stack,
code: error.code || 'UNKNOWN'
},
timestamp: Date.now(),
context: {
glContext: this.glContext,
wasmModule: this.wasmModule,
performance: this.performanceMonitor.getStats()
}
};
// 触发错误处理链
this.errorHandlers.forEach(handler => handler(context));
// 根据严重程度采取行动
switch(severity) {
case 'critical':
this.recoverFromCriticalError(context);
break;
case 'severe':
this.scheduleRecoveryTask(context);
break;
case 'warning':
this.logWarning(context);
break;
}
}
// 资源管理
async loadWASM() {
try {
const wasmBinary = await GM.getResourceText('optimizer.wasm');
const module = await WebAssembly.instantiate(
new Uint8Array(wasmBinary),
{
env: {
memory: new WebAssembly.Memory({ initial: 256 }),
abort: (msg, file) => this.handleError('wasm', new Error(`WASM Abort: ${msg}`))
}
}
);
this.wasmModule = module.instance.exports;
return true;
} catch (error) {
this.handleError('wasmLoader', error);
return false;
}
}
// 初始化流程
async init() {
this.initState = 'initializing';
try {
// 阶段1: 环境准备
const context = await this.createWebGLContext();
this.glContext = context;
// 阶段2: 资源加载
await this.loadWASM();
await this.initResourceLoader();
// 阶段3: 系统初始化
this.initPerformanceMonitor();
this.setupEventHandlers();
this.initSecurity();
// 阶段4: 首次渲染
const sampleScene = await this.loadSampleScene();
this.optimizeScene(sampleScene);
this.initState = 'ready';
return true;
} catch (error) {
this.handleError('init', error);
return false;
} finally {
this.initState = 'initialized';
}
}
// 安全机制
initSecurity() {
// CSP策略
GM.addStyle(`
#webgl-optimizer-container {
display: none !important;
visibility: hidden !important;
}
`);
// 防注入检测
const allowedDomains = ['yourdomain.com', 'wasm-optimizer.example.com'];
if (!allowedDomains.some(domain => window.location.hostname.includes(domain))) {
throw new Error('Domain not authorized');
}
// 定期安全检查
setInterval(() => {
if (!this.wasmModule || !this.glContext) {
this.handleError('security', new Error('Core components missing'));
}
}, 60000);
}
// 资源释放
releaseResources() {
if (this.glContext) {
this.glContext.deleteProgram(this.glContext.currentProgram);
this.glContext.currentProgram = null;
}
if (this.wasmModule) {
this.wasmModule = null;
// 手动释放WASM内存
const memory = this.wasmModule.env.memory;
new Uint8Array(memory.buffer, 0, memory.buffer.byteLength).fill(0);
}
this.resourceCache.clear();
this.recoveryTasks = [];
this.errorHandlers = [];
}
// 自动恢复
async recoverFromCriticalError(context) {
try {
await this.releaseResources();
await this.init();
return true;
} catch (recoveryError) {
this.handleError('recovery', recoveryError);
return false;
}
}
// 事件处理
setupEventHandlers() {
window.addEventListener('resize', () => {
if (this.glContext) {
const canvas = this.glContext.canvas;
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
this.glContext.viewport(0, 0, canvas.width, canvas.height);
}
});
window.addEventListener('beforeunload', () => {
this.releaseResources();
});
}
// 工具方法
getErrorSeverity(error) {
if (error.message.includes('out of memory')) return 'critical';
if (error.code === 'MODULE_NOT_FOUND') return 'severe';
return 'warning';
}
// 核心WebGL上下文创建(透明化渲染核心修改)
async createWebGLContext() {
const canvas = document.createElement('canvas');
// 设置为1x1像素并隐藏
canvas.width = 1;
canvas.height = 1;
canvas.style.position = 'absolute';
canvas.style.left = '-9999px';
canvas.style.top = '-9999px';
canvas.style.display = 'block';
document.body.appendChild(canvas);
const contextAttributes = {
antialias: true,
alpha: true, // 启用透明通道
depth: true,
stencil: true,
premultipliedAlpha: true,
preserveDrawingBuffer: false,
powerPreference: 'high-performance',
failIfMajorPerformanceCaveat: true,
version: 2,
extensions: ['EXT_color_buffer_float']
};
const gl = canvas.getContext('webgl2', contextAttributes);
if (!gl) throw new Error('WebGL2 context creation failed');
// 设置透明清除颜色
gl.clearColor(0.0, 0.0, 0.0, 0.0);
gl.clearDepth(1.0);
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
// 移除显隐控制逻辑(原toggle按钮)
return { gl, canvas };
}
// 示例场景加载
async loadSampleScene() {
try {
const response = await GM.xmlHttpRequest({
method: 'GET',
url: '/samplescene.json',
headers: {
'Authorization': `Bearer ${this.getSecureAPIKey()}`
}
});
return JSON.parse(response.responseText);
} catch (error) {
this.handleError('sampleScene', error);
return null;
}
}
// 安全API密钥获取
getSecureAPIKey() {
const encryptedKey = GM.getValue('APIKEY', '');
if (!encryptedKey) throw new Error('API key not initialized');
return CryptoJS.AES.decrypt(encryptedKey, this.generateKeySalt()).toString(CryptoJS.enc.Utf8);
}
generateKeySalt() {
return CryptoJS.lib.WordArray.random(16).toString();
}
}
// 全局初始化
const optimizer = new WebGLOptimizer();
optimizer.errorHandlers.push({
handle: (context) => {
console.error(`[${context.stage}] Error: ${context.error.message}`);
if (context.stage === 'init' && context.error.code === 'MODULE_NOT_FOUND') {
window.location.reload();
}
}
});
// 异步初始化
(async () => {
try {
await optimizer.init();
console.log('WebGLOptimizer initialized successfully');
optimizer.startPerformanceReporting();
} catch (error) {
console.error('Critical initialization failure:', error);
optimizer.handleError('global', error);
}
})();
// 性能报告
WebGLOptimizer.prototype.startPerformanceReporting = function() {
setInterval(() => {
const stats = this.performanceMonitor.getStats();
GM.setValue('performanceStats', stats);
console.log('Performance stats:', stats);
}, 5000);
};
})();