// ==UserScript== // @name MCJS 性能助推器 · MC 全版本 // @namespace workbuddy.mcjs.booster // @version 1.0.0 // @description 网页版 MC(Eaglercraft 全版本 / MCJS 全站)性能增强:动态分辨率缩放、强制硬件渲染、抗锯齿关闭、FPS 监控、硬件诊断、自动适配帧率 // @author WorkBuddy // @match *://mcjs.link/* // @match *://*.mcjs.link/* // @match *://mc.js.cool/* // @match *://*.mc.js.cool/* // @match *://localhost/* // @match *://127.0.0.1/* // @run-at document-start // @grant none // @noframes // @license MIT // ==/UserScript== (function () { 'use strict'; if (window.__MCJS_BOOSTER__) return; window.__MCJS_BOOSTER__ = true; var VERSION = '1.0.0'; var LOG = function () {}; /* ============================================================ * 1. 配置与持久化 * ============================================================ */ var CFG_KEY = 'mcjs_booster_cfg_v1'; var DEFAULTS = { scale: 0.70, // 渲染缩放 0.3 ~ 1.0 auto: true, // 自动适配帧率 target: 55, // 目标帧率 aa: false, // 抗锯齿(需刷新才生效) hud: true, // 显示面板 open: true, collapsed: false, x: 14, y: 14 }; var cfg = {}; try { var raw = localStorage.getItem(CFG_KEY); cfg = Object.assign({}, DEFAULTS, raw ? JSON.parse(raw) : {}); } catch (e) { cfg = Object.assign({}, DEFAULTS); } function saveCfg() { try { localStorage.setItem(CFG_KEY, JSON.stringify(cfg)); } catch (e) {} } /* ============================================================ * 2. WebGL 层拦截:注入参数 + 分辨率缩放 * ============================================================ */ var CANVAS_PROTO = HTMLCanvasElement.prototype; var W_DESC = Object.getOwnPropertyDescriptor(CANVAS_PROTO, 'width'); var H_DESC = Object.getOwnPropertyDescriptor(CANVAS_PROTO, 'height'); var controllers = []; var mainCtl = null; var diag = { type: '未检测到', renderer: '未知', vendor: '未知', maxTex: 0, software: false }; /** * 解析显卡信息,识别软件渲染(SwiftShader / llvmpipe / Basic Render) */ function inspectGL(gl, type) { try { diag.type = (type === 'webgl2') ? 'WebGL 2' : 'WebGL 1'; var ext = gl.getExtension('WEBGL_debug_renderer_info'); if (ext) { diag.renderer = String(gl.getParameter(ext.UNMASKED_RENDERER_WEBGL) || ''); diag.vendor = String(gl.getParameter(ext.UNMASKED_VENDOR_WEBGL) || ''); } else { diag.renderer = String(gl.getParameter(gl.RENDERER) || ''); diag.vendor = String(gl.getParameter(gl.VENDOR) || ''); } diag.maxTex = gl.getParameter(gl.MAX_TEXTURE_SIZE) || 0; diag.software = /swiftshader|software|llvmpipe|basic render|microsoft basic/i.test(diag.renderer); } catch (e) {} LOG('GL', diag); } /** * 单个 WebGL 画布的分辨率控制器 */ function ResCtl(canvas, gl, type) { this.canvas = canvas; this.gl = gl; this.s = 1; this.installed = false; this.lw = 1; this.lh = 1; this._lv = 1; this._lh2 = 1; } ResCtl.prototype.install = function () { if (this.installed) return; var c = this.canvas; var gl = this.gl; var self = this; // 记录当前真实缓冲尺寸作为"逻辑尺寸" this.lw = W_DESC.get.call(c) || c.clientWidth || 300; this.lh = H_DESC.get.call(c) || c.clientHeight || 150; // 影子属性:应用读写 canvas.width 时看到的是逻辑尺寸 try { Object.defineProperty(c, 'width', { configurable: true, enumerable: true, get: function () { return self.lw; }, set: function (v) { var n = v | 0; if (n < 1) n = 1; self.lw = n; // 强制写回,保留 canvas.width = canvas.width 清屏语义 self.applyBacking(true); } }); Object.defineProperty(c, 'height', { configurable: true, enumerable: true, get: function () { return self.lh; }, set: function (v) { var n = v | 0; if (n < 1) n = 1; self.lh = n; self.applyBacking(true); } }); } catch (e) { LOG('shadow fail', e); return; } // 视口与裁剪区跟随缩放 try { this._origViewport = gl.viewport; gl.viewport = function (x, y, w, h) { var s = self.s; return self._origViewport.call(gl, x * s, y * s, w * s, h * s); }; this._origScissor = gl.scissor; gl.scissor = function (x, y, w, h) { var s = self.s; return self._origScissor.call(gl, x * s, y * s, w * s, h * s); }; } catch (e) { LOG('viewport hook fail', e); } // 应用读取 drawingBufferWidth 时也应拿到逻辑尺寸,保证内部数学一致 try { Object.defineProperty(gl, 'drawingBufferWidth', { configurable: true, get: function () { return self.lw; } }); Object.defineProperty(gl, 'drawingBufferHeight', { configurable: true, get: function () { return self.lh; } }); } catch (e) {} this.installed = true; }; ResCtl.prototype.applyBacking = function (force) { var c = this.canvas; var s = this.s; var bw = Math.max(1, Math.round(this.lw * s)); var bh = Math.max(1, Math.round(this.lh * s)); if (force || W_DESC.get.call(c) !== bw) W_DESC.set.call(c, bw); if (force || H_DESC.get.call(c) !== bh) H_DESC.set.call(c, bh); if (s < 0.995) { // 缩小缓冲后由浏览器放大回到原显示尺寸 var dpr = window.devicePixelRatio || 1; c.style.width = (this.lw / dpr) + 'px'; c.style.height = (this.lh / dpr) + 'px'; } else { // 恢复 100% 时还原本来的 CSS 尺寸控制 c.style.width = ''; c.style.height = ''; } }; ResCtl.prototype.setScale = function (s) { if (s < 0.30) s = 0.30; if (s > 1) s = 1; if (Math.abs(s - this.s) < 0.002 && this.installed) return; this.s = s; this.install(); this.applyBacking(); }; ResCtl.prototype.restore = function () { this.s = 1; if (this.installed) { this.applyBacking(); this.canvas.style.width = ''; this.canvas.style.height = ''; } }; /** * 拦截 getContext,让所有 WebGL 上下文都强制走硬件 + 关抗锯齿 */ var origGetContext = CANVAS_PROTO.getContext; CANVAS_PROTO.getContext = function (type, attrs) { if (type === 'webgl' || type === 'experimental-webgl' || type === 'webgl2') { var a = {}; if (attrs) { for (var k in attrs) { if (Object.prototype.hasOwnProperty.call(attrs, k)) a[k] = attrs[k]; } } if (cfg.aa !== true) a.antialias = false; a.powerPreference = 'high-performance'; a.desynchronized = true; a.preserveDrawingBuffer = false; var ctx; try { ctx = origGetContext.call(this, type, a); } catch (e) { ctx = origGetContext.call(this, type, attrs); } if (ctx) { for (var i = 0; i < controllers.length; i++) { if (controllers[i].canvas === this) return ctx; } controllers.push(new ResCtl(this, ctx, type)); inspectGL(ctx, type); setTimeout(pickMain, 0); setTimeout(pickMain, 800); } return ctx; } return origGetContext.call(this, type, attrs); }; /** * 选出面积最大的 WebGL 画布作为游戏主画布,其余恢复原状 */ function pickMain() { var best = null, bestArea = 0; for (var i = 0; i < controllers.length; i++) { var c = controllers[i].canvas; var w = c.clientWidth || c.width || 0; var h = c.clientHeight || c.height || 0; var area = w * h; if (area > bestArea) { bestArea = area; best = controllers[i]; } } if (!best) return; mainCtl = best; for (var j = 0; j < controllers.length; j++) { var ctl = controllers[j]; if (ctl === best) ctl.setScale(cfg.scale); else if (ctl.s !== 1) ctl.restore(); } } /* ============================================================ * 3. 帧率监控 * ============================================================ */ var perf = { frames: 0, last: performance.now(), fps: 0, ms: 0, hist: [], // 每次采样的 fps frameTimes: [], low1: 0 }; function frameTick(now) { perf.frames++; var dt = now - perf.last; perf.frameTimes.push(dt); if (perf.frameTimes.length > 240) perf.frameTimes.shift(); if (dt >= 500) { perf.fps = Math.round((perf.frames * 1000) / dt); perf.ms = +(dt / perf.frames).toFixed(1); perf.frames = 0; perf.last = now; perf.hist.push(perf.fps); if (perf.hist.length > 60) perf.hist.shift(); var sorted = perf.frameTimes.slice().sort(function (a, b) { return a - b; }); var idx = Math.floor(sorted.length * 0.99); perf.low1 = sorted.length ? Math.round(1000 / sorted[Math.min(idx, sorted.length - 1)]) : 0; autoTune(now); if (ui.fps) ui.render(); } requestAnimationFrame(frameTick); } /* ============================================================ * 4. 自动适配 * ============================================================ */ var lastAdjust = 0; var cpuHintShown = false; function autoTune(now) { if (!cfg.auto || !mainCtl) return; if (now - lastAdjust < 1200) return; var f = perf.fps; var s = cfg.scale; if (f < cfg.target - 6 && s > 0.36) { cfg.scale = Math.max(0.30, +(s - 0.06).toFixed(2)); saveCfg(); lastAdjust = now; mainCtl.setScale(cfg.scale); ui.syncControls(); ui.render(); } else if (f > cfg.target + 10 && s < 0.995) { cfg.scale = Math.min(1, +(s + 0.04).toFixed(2)); saveCfg(); lastAdjust = now; mainCtl.setScale(cfg.scale); ui.syncControls(); ui.render(); } else if (f < cfg.target - 15 && s <= 0.36 && !cpuHintShown) { cpuHintShown = true; ui.warn('分辨率已到最低仍不达标 → CPU 瓶颈。建议换站内的 WASM 版(体积只有 JS 版一半,执行效率高很多),并调低渲染距离。', 'warn'); } } /* ============================================================ * 5. 界面 * ============================================================ */ var ui = { host: null, fps: null, ms: null, spark: null, info: null, warnEl: null, scaleInput: null, scaleOut: null, autoInput: null, targetSel: null, collapsed: false, // 占位,build 成功后会被真实实现覆盖,避免界面不存在时抛错 render: function () {}, syncControls: function () {}, warn: function () {}, hideWarn: function () {}, place: function () {}, applyVisible: function () {}, applyCollapsed: function () {}, drawSpark: function () {} }; var CSS = [ ':host{all:initial}', '.host{position:fixed;z-index:2147483000;pointer-events:none;font:12px/1.45 -apple-system,"Segoe UI","Microsoft YaHei",sans-serif;color:#d7dde5}', '.panel{pointer-events:auto;width:262px;background:#101318;border:1px solid #2a3038;border-radius:10px;box-shadow:0 8px 28px rgba(0,0,0,.55);overflow:hidden}', '.hd{display:flex;align-items:center;gap:6px;padding:8px 10px;background:#151a21;border-bottom:1px solid #232a33;cursor:move;user-select:none}', '.hd .dot{width:7px;height:7px;border-radius:50%;background:#4dd4c4;box-shadow:0 0 6px #4dd4c4}', '.hd .t{font-weight:600;font-size:12px;color:#e6edf5;letter-spacing:.3px}', '.hd .v{font-size:10px;color:#5b6672;margin-left:auto}', '.hd button{pointer-events:auto;background:none;border:0;color:#7b8794;font-size:14px;line-height:1;cursor:pointer;padding:0 2px}', '.hd button:hover{color:#e6edf5}', '.body{padding:10px}', '.fpsrow{display:flex;align-items:baseline;gap:5px;margin-bottom:6px}', '.fps{font-size:30px;font-weight:700;line-height:1;color:#4ade80;font-variant-numeric:tabular-nums}', '.unit{font-size:11px;color:#5b6672}', '.ms{margin-left:auto;font-size:11px;color:#7b8794;font-variant-numeric:tabular-nums}', '.spark{display:block;width:100%;height:34px;background:#0b0e12;border-radius:5px;margin-bottom:8px}', '.warn{display:none;font-size:11px;line-height:1.5;padding:6px 8px;border-radius:6px;margin-bottom:8px}', '.warn.show{display:block}', '.warn.warn{background:rgba(251,191,36,.12);border:1px solid rgba(251,191,36,.35);color:#fbbf24}', '.warn.bad{background:rgba(248,113,113,.12);border:1px solid rgba(248,113,113,.35);color:#f87171}', '.row{display:flex;align-items:center;gap:8px;margin:7px 0;font-size:11px;color:#9aa6b3}', '.row>span{flex:0 0 52px}', '.row b{flex:0 0 40px;text-align:right;color:#e6edf5;font-weight:600;font-variant-numeric:tabular-nums}', '.row input[type=range]{flex:1;-webkit-appearance:none;height:3px;border-radius:2px;background:#2a3038;outline:0}', '.row input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:12px;height:12px;border-radius:50%;background:#4dd4c4;cursor:pointer}', '.row.chk input{flex:0 0 auto;accent-color:#4dd4c4}', '.row.chk>span{flex:1}', '.row select{flex:1;background:#0b0e12;color:#d7dde5;border:1px solid #2a3038;border-radius:5px;padding:2px 4px;font-size:11px}', '.info{margin-top:8px;padding-top:8px;border-top:1px solid #232a33;font-size:10px;line-height:1.6;color:#6b7784;word-break:break-all;font-family:Consolas,Menlo,monospace}', '.info i{color:#4dd4c4;font-style:normal}', '.btns{display:flex;gap:6px;margin-top:9px}', '.btns button{flex:1;padding:5px 0;font-size:11px;background:#1a2029;color:#c3ccd6;border:1px solid #2a3038;border-radius:6px;cursor:pointer}', '.btns button:hover{background:#222a35;color:#fff}', '.btns button.pri{background:#123b38;border-color:#1d5d57;color:#7ee8da}' ].join(''); ui.build = function () { if (ui.host) return; var host = document.createElement('div'); host.className = 'host'; var root; try { root = host.attachShadow({ mode: 'open' }); } catch (e) { return; } var style = document.createElement('style'); style.textContent = CSS; root.appendChild(style); var panel = document.createElement('div'); panel.className = 'panel'; panel.innerHTML = '