From ad9584865b2fec05c6dea6fcf318f20a0fbf9c97 Mon Sep 17 00:00:00 2001 From: Sharad Mayank Sinha Date: Sun, 10 May 2026 15:20:36 +0530 Subject: [PATCH 1/3] feat: Add AMD FSR 1.0 upscaling and fix alert bug --- js/config/config-manager.js | 36 ++++- js/effect/fsr.js | 169 ++++++++++++++++++++++ js/gui/gui-layout.js | 272 ++++++++++++++++++++++++++++-------- 3 files changed, 417 insertions(+), 60 deletions(-) create mode 100644 js/effect/fsr.js diff --git a/js/config/config-manager.js b/js/config/config-manager.js index fb4b659..8f01aab 100644 --- a/js/config/config-manager.js +++ b/js/config/config-manager.js @@ -116,7 +116,9 @@ let defaultConfig = { 'TEST_SAFARI_ENTRY': false, 'UI_THEME': "OpenLive3D", 'RENDER_RESOLUTION': 1.0, - 'AA_MODE': 'None (Fastest)' + 'AA_MODE': 'None (Fastest)', + 'UPSCALING_PRESET': 'Ultra Quality (0.77x)', + 'FSR_SHARPNESS': 0.2, }; function getDefaultCMV(key) { @@ -335,7 +337,7 @@ function getBinaryCM() { } function getSelectCM() { - return ['LANGUAGE', 'TRACKING_MODE', 'UI_THEME', 'AA_MODE']; + return ['LANGUAGE', 'TRACKING_MODE', 'UI_THEME', 'AA_MODE', 'UPSCALING_PRESET']; } function getSideBoxes() { @@ -407,12 +409,36 @@ function getConfigModifiers() { 'RENDERING': [{ 'key': 'AA_MODE', 'title': 'Anti-Aliasing', - 'describe': 'Select anti-aliasing mode. FXAA is a fast shader-based filter. MSAA uses hardware multi-sampling via render targets. Combine both for best quality.', - 'valid': ['None (Fastest)', 'FXAA (Fast)', 'MSAA 4x (Balanced)', 'MSAA 4x + FXAA (Quality)'] + 'describe': 'Select anti-aliasing and FSR upscaling.', + 'valid': [ + 'None (Fastest)', + 'FXAA (Fast)', + 'MSAA 4x (Balanced)', + 'MSAA 4x + FXAA (Quality)', + 'FSR 1.0', + 'FXAA + FSR 1.0', + 'MSAA 4x + FSR 1.0' + ] + }, { + 'key': 'UPSCALING_PRESET', + 'title': 'FSR Quality', + 'describe': 'FSR Resolution Presets. Custom allows manual scaling.', + 'valid': [ + 'Ultra Quality (0.77x)', + 'Quality (0.67x)', + 'Balanced (0.59x)', + 'Performance (0.50x)', + 'Custom' + ] + }, { + 'key': 'FSR_SHARPNESS', + 'title': 'FSR Sharpness', + 'describe': 'FSR RCAS Sharpness. 0 = max, 2 = none.', + 'range': [0, 2] }, { 'key': 'RENDER_RESOLUTION', 'title': 'Render Resolution', - 'describe': 'Adjust the 3D render resolution ratio. Default is 1.0. Lower for performance, higher for quality.', + 'describe': 'Adjust the 3D render resolution ratio.', 'range': [0.1, 2.0] }], 'SMOOTH': [{ diff --git a/js/effect/fsr.js b/js/effect/fsr.js new file mode 100644 index 0000000..87b4ee2 --- /dev/null +++ b/js/effect/fsr.js @@ -0,0 +1,169 @@ +// ol3dc/js/effect/fsr.js + +const fsrVertexShader = `#version 300 es +in vec3 position; +in vec2 uv; +out vec2 vUv; +void main() { + vUv = uv; + gl_Position = vec4(position, 1.0); +}`; + +let fsrEASUMaterial = null; +let fsrRCASMaterial = null; + +const fsrEASUFragmentShader = `#version 300 es +precision highp float; +uniform sampler2D tDiffuse; +uniform vec2 resolution; // Final display resolution +uniform vec2 renderResolution;// Internal 3D render resolution +in vec2 vUv; +out vec4 fragColor; + +// EASU logic heavily optimized for WebGL2 +void main() { + vec2 inputSize = renderResolution; + vec2 outputSize = resolution; + + vec2 fragCoord = vUv * outputSize; + vec2 pp = fragCoord * (inputSize / outputSize) - vec2(0.5); + vec2 fp = floor(pp); + vec2 f = pp - fp; + + vec2 p0 = (fp + vec2(0.5)) / inputSize; + vec2 d = vec2(1.0) / inputSize; + + // Sample 12-tap window + vec3 bC = texture(tDiffuse, p0 + vec2(0.0, -1.0) * d).rgb; + vec3 cC = texture(tDiffuse, p0 + vec2(1.0, -1.0) * d).rgb; + vec3 dC = texture(tDiffuse, p0 + vec2(-1.0, 0.0) * d).rgb; + vec3 eC = texture(tDiffuse, p0 + vec2(0.0, 0.0) * d).rgb; + vec3 fC = texture(tDiffuse, p0 + vec2(1.0, 0.0) * d).rgb; + vec3 gC = texture(tDiffuse, p0 + vec2(2.0, 0.0) * d).rgb; + vec3 hC = texture(tDiffuse, p0 + vec2(-1.0, 1.0) * d).rgb; + vec3 iC = texture(tDiffuse, p0 + vec2(0.0, 1.0) * d).rgb; + vec3 jC = texture(tDiffuse, p0 + vec2(1.0, 1.0) * d).rgb; + vec3 kC = texture(tDiffuse, p0 + vec2(2.0, 1.0) * d).rgb; + vec3 lC = texture(tDiffuse, p0 + vec2(0.0, 2.0) * d).rgb; + vec3 mC = texture(tDiffuse, p0 + vec2(1.0, 2.0) * d).rgb; + + // Luma approximation + float bL = bC.g + 0.5 * (bC.r + bC.b); + float cL = cC.g + 0.5 * (cC.r + cC.b); + float dL = dC.g + 0.5 * (dC.r + dC.b); + float eL = eC.g + 0.5 * (eC.r + eC.b); + float fL = fC.g + 0.5 * (fC.r + fC.b); + float gL = gC.g + 0.5 * (gC.r + gC.b); + float hL = hC.g + 0.5 * (hC.r + hC.b); + float iL = iC.g + 0.5 * (iC.r + iC.b); + float jL = jC.g + 0.5 * (jC.r + jC.b); + float kL = kC.g + 0.5 * (kC.r + kC.b); + float lL = lC.g + 0.5 * (lC.r + lC.b); + float mL = mC.g + 0.5 * (mC.r + mC.b); + + // Gradients + float dirX = -bL + cL - eL + fL - iL + jL; + float dirY = -dL - eL - hL + fL + gL + jL; + vec2 dir = vec2(dirX, dirY); + + float len = length(dir); + if (len > 0.0) dir /= len; + + float weight = clamp(len / max(max(eL, fL), max(iL, jL)), 0.0, 1.0); + weight *= weight; + + // Bilinear fallback for non-edge + vec3 color = eC * (1.0 - f.x) * (1.0 - f.y) + fC * f.x * (1.0 - f.y) + iC * (1.0 - f.x) * f.y + jC * f.x * f.y; + + // Edge reconstruction (simplified to save GPU bandwidth) + color = mix(color, (eC + fC + iC + jC) * 0.25, weight * 0.5); + + float a = texture(tDiffuse, vUv).a; + fragColor = vec4(color, a); +} `; + +const fsrRCASFragmentShader = `#version 300 es +precision highp float; +uniform sampler2D tDiffuse; +uniform vec2 resolution; +uniform float sharpness; +in vec2 vUv; +out vec4 fragColor; + +vec3 linearToSRGB(vec3 color) { + return mix(color * 12.92, 1.055 * pow(color, vec3(1.0 / 2.4)) - 0.055, step(0.0031308, color)); +} + +// RCAS logic heavily optimized for WebGL2 +void main() { + vec2 d = vec2(1.0) / resolution; + + vec3 b = texture(tDiffuse, vUv + vec2( 0.0, -1.0) * d).rgb; + vec3 dC = texture(tDiffuse, vUv + vec2(-1.0, 0.0) * d).rgb; + vec3 e = texture(tDiffuse, vUv).rgb; + vec3 f = texture(tDiffuse, vUv + vec2( 1.0, 0.0) * d).rgb; + vec3 h = texture(tDiffuse, vUv + vec2( 0.0, 1.0) * d).rgb; + + float bL = b.g + 0.5 * (b.r + b.b); + float dL = dC.g + 0.5 * (dC.r + dC.b); + float eL = e.g + 0.5 * (e.r + e.b); + float fL = f.g + 0.5 * (f.r + f.b); + float hL = h.g + 0.5 * (h.r + h.b); + + float minL = min(eL, min(min(bL, dL), min(fL, hL))); + float maxL = max(eL, max(max(bL, dL), max(fL, hL))); + float limit = min(eL - minL, maxL - eL); + + float w = limit / (maxL + 1e-5); + w = clamp(w, 0.0, 1.0); + + float s = exp2(-sharpness); + w = w * s * 0.5; + + vec3 color = (b + dC + f + h) * (-w) + e; + color /= (1.0 - 4.0 * w); + + // Convert to sRGB to fix washed-out colors + color = linearToSRGB(color); + + float a = texture(tDiffuse, vUv).a; + fragColor = vec4(clamp(color, 0.0, 1.0), a); +}`; + +function setupFSR(displayW, displayH, renderW, renderH) { + if (!fsrEASUMaterial) { + // Use RawShaderMaterial because we are using raw #version 300 es GLSL3 + fsrEASUMaterial = new THREE.RawShaderMaterial({ + uniforms: { + tDiffuse: { value: null }, + resolution: { value: new THREE.Vector2(displayW, displayH) }, + renderResolution: { value: new THREE.Vector2(renderW, renderH) } + }, + vertexShader: fsrVertexShader, + fragmentShader: fsrEASUFragmentShader, + depthTest: false, + depthWrite: false, + blending: THREE.NoBlending + }); + } else { + fsrEASUMaterial.uniforms.resolution.value.set(displayW, displayH); + fsrEASUMaterial.uniforms.renderResolution.value.set(renderW, renderH); + } + + if (!fsrRCASMaterial) { + fsrRCASMaterial = new THREE.RawShaderMaterial({ + uniforms: { + tDiffuse: { value: null }, + resolution: { value: new THREE.Vector2(displayW, displayH) }, + sharpness: { value: 0.2 } + }, + vertexShader: fsrVertexShader, + fragmentShader: fsrRCASFragmentShader, + depthTest: false, + depthWrite: false, + blending: THREE.NoBlending + }); + } else { + fsrRCASMaterial.uniforms.resolution.value.set(displayW, displayH); + } +} \ No newline at end of file diff --git a/js/gui/gui-layout.js b/js/gui/gui-layout.js index 74278ca..399eb4a 100644 --- a/js/gui/gui-layout.js +++ b/js/gui/gui-layout.js @@ -120,12 +120,69 @@ function onWindowResize() { backgroundeffect.height = window.innerHeight; } +function getRenderResRatio() { + let resRatio = getCMV("RENDER_RESOLUTION") || 1.0; + let aaMode = getCMV("AA_MODE") || ""; + if (aaMode.includes("FSR")) { + let preset = getCMV("UPSCALING_PRESET") || "Ultra Quality (0.77x)"; + if (preset === "Ultra Quality (0.77x)") resRatio = 0.77; + else if (preset === "Quality (0.67x)") resRatio = 0.67; + else if (preset === "Balanced (0.59x)") resRatio = 0.59; + else if (preset === "Performance (0.50x)") resRatio = 0.50; + } + return resRatio; +} + +function updateRenderingUI() { + let aaMode = getCMV("AA_MODE") || ""; + let useFSR = aaMode.includes("FSR"); + let preset = getCMV("UPSCALING_PRESET") || "Custom"; + + let toggleVisibility = (key, show) => { + let box = document.getElementById(key + "_box"); + if (!box) return; + + let isSelect = getSelectCM().includes(key); + let nextEl = box.nextElementSibling; + + if (isSelect) { + box.style.display = "none"; // Keep dummy input hidden + if (nextEl && nextEl.tagName === 'SELECT') { + nextEl.style.display = show ? "" : "none"; + let finalBr = nextEl.nextElementSibling; + if (finalBr && finalBr.tagName === 'BR') finalBr.style.display = show ? "" : "none"; + } + } else { + box.style.display = show ? "" : "none"; // Range slider + if (nextEl && nextEl.tagName === 'INPUT') { + nextEl.style.display = show ? "inline-block" : "none"; + let finalBr = nextEl.nextElementSibling; + if (finalBr && finalBr.tagName === 'BR') finalBr.style.display = show ? "" : "none"; + } + } + + let br1 = box.previousElementSibling; + if (br1 && br1.tagName === 'BR') br1.style.display = show ? "" : "none"; + let name = br1 ? br1.previousElementSibling : null; + if (name) name.style.display = show ? "" : "none"; + let info = name ? name.previousElementSibling : null; + if (info) info.style.display = show ? "" : "none"; + }; + + toggleVisibility("UPSCALING_PRESET", useFSR); + toggleVisibility("FSR_SHARPNESS", useFSR); + toggleVisibility("RENDER_RESOLUTION", !useFSR || preset === "Custom"); +} + function updateRenderResolution() { - let resRatio = 1.0; - if (typeof getCMV === "function" && getCMV("RENDER_RESOLUTION") !== undefined) { - resRatio = getCMV("RENDER_RESOLUTION"); + let aaMode = typeof getCMV === "function" ? getCMV("AA_MODE") || "" : ""; + let resRatio = getRenderResRatio(); + + if (aaMode.includes("FSR")) { + renderer.setPixelRatio(window.devicePixelRatio); // FSR handle scale + } else { + renderer.setPixelRatio(window.devicePixelRatio * resRatio); } - renderer.setPixelRatio(window.devicePixelRatio * resRatio); } // --- Anti-Aliasing Post-Processing Pipeline --- @@ -133,6 +190,10 @@ let ppRenderTarget = null; let ppScene = null; let ppCamera = null; let ppQuad = null; +let fxaaRenderTarget = null; +let easuRenderTarget = null; +let easuQuad = null; +let rcasQuad = null; const _fxaaVertexShader = [ 'varying vec2 vUv;', @@ -145,6 +206,7 @@ const _fxaaVertexShader = [ const _fxaaFragmentShader = [ 'uniform sampler2D tDiffuse;', 'uniform vec2 resolution;', + 'uniform bool convertToSRGB;', 'varying vec2 vUv;', '', '#define FXAA_REDUCE_MIN (1.0 / 128.0)', @@ -196,7 +258,11 @@ const _fxaaFragmentShader = [ ' } else {', ' result = rgbB.xyz;', ' }', - ' gl_FragColor = vec4(linearToSRGB(result), texColor.a);', + ' if (convertToSRGB) {', + ' gl_FragColor = vec4(linearToSRGB(result), texColor.a);', + ' } else {', + ' gl_FragColor = vec4(result, texColor.a);', + ' }', '}' ].join('\n'); @@ -212,59 +278,123 @@ const _passthroughFragmentShader = [ '}' ].join('\n'); -function setupPostProcessing(useFXAA, useMSAA) { - // Dispose old resources +function setupPostProcessing(useFXAA, useMSAA, useFSR) { if (ppRenderTarget) { ppRenderTarget.dispose(); ppRenderTarget = null; } - if (ppQuad) { - ppQuad.geometry.dispose(); - ppQuad.material.dispose(); - ppQuad = null; - } + if (fxaaRenderTarget) { fxaaRenderTarget.dispose(); fxaaRenderTarget = null; } + if (easuRenderTarget) { easuRenderTarget.dispose(); easuRenderTarget = null; } + if (ppQuad) { ppQuad.geometry.dispose(); ppQuad.material.dispose(); ppQuad = null; } + if (easuQuad) { easuQuad.geometry.dispose(); easuQuad = null; } + if (rcasQuad) { rcasQuad.geometry.dispose(); rcasQuad = null; } ppScene = null; ppCamera = null; - if (!useFXAA && !useMSAA) return; + let resRatio = getRenderResRatio(); + if (!useFXAA && !useMSAA && !useFSR && resRatio === 1.0) return; let size = renderer.getSize(new THREE.Vector2()); - let pixelRatio = renderer.getPixelRatio(); - let w = Math.floor(size.x * pixelRatio); - let h = Math.floor(size.y * pixelRatio); + let displayW = Math.floor(size.x * window.devicePixelRatio); + let displayH = Math.floor(size.y * window.devicePixelRatio); + let renderW = displayW; + let renderH = displayH; + + if (useFSR) { + renderW = Math.floor(displayW * resRatio); + renderH = Math.floor(displayH * resRatio); + } else { + let pixelRatio = renderer.getPixelRatio(); + renderW = Math.floor(size.x * pixelRatio); + renderH = Math.floor(size.y * pixelRatio); + } let targetOptions = {}; if (useMSAA) targetOptions.samples = 4; - ppRenderTarget = new THREE.WebGLRenderTarget(w, h, targetOptions); - - let fragShader = useFXAA ? _fxaaFragmentShader : _passthroughFragmentShader; - let uniforms = { tDiffuse: { value: ppRenderTarget.texture } }; - if (useFXAA) { - uniforms.resolution = { value: new THREE.Vector2(w, h) }; - } - - let material = new THREE.ShaderMaterial({ - uniforms: uniforms, - vertexShader: _fxaaVertexShader, - fragmentShader: fragShader, - depthTest: false, - depthWrite: false, - blending: THREE.NoBlending, - toneMapped: false - }); - + ppRenderTarget = new THREE.WebGLRenderTarget(renderW, renderH, targetOptions); ppCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1); ppScene = new THREE.Scene(); - ppQuad = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), material); - ppScene.add(ppQuad); + + if (useFSR) { + easuRenderTarget = new THREE.WebGLRenderTarget(displayW, displayH); + if (useFXAA) { + fxaaRenderTarget = new THREE.WebGLRenderTarget(renderW, renderH); + let fxaaMat = new THREE.ShaderMaterial({ + uniforms: { + tDiffuse: { value: ppRenderTarget.texture }, + resolution: { value: new THREE.Vector2(renderW, renderH) }, + convertToSRGB: { value: false } // Output linear for FSR + }, + vertexShader: _fxaaVertexShader, + fragmentShader: _fxaaFragmentShader, + depthTest: false, depthWrite: false, blending: THREE.NoBlending, toneMapped: false + }); + ppQuad = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), fxaaMat); + } + setupFSR(displayW, displayH, renderW, renderH); + fsrEASUMaterial.uniforms.tDiffuse.value = useFXAA ? fxaaRenderTarget.texture : ppRenderTarget.texture; + fsrRCASMaterial.uniforms.tDiffuse.value = easuRenderTarget.texture; + easuQuad = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), fsrEASUMaterial); + rcasQuad = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), fsrRCASMaterial); + } else { + let fragShader = useFXAA ? _fxaaFragmentShader : _passthroughFragmentShader; + let uniforms = { tDiffuse: { value: ppRenderTarget.texture } }; + if (useFXAA) { + uniforms.resolution = { value: new THREE.Vector2(renderW, renderH) }; + uniforms.convertToSRGB = { value: true }; // Final pass, output sRGB + } + let material = new THREE.ShaderMaterial({ + uniforms: uniforms, vertexShader: _fxaaVertexShader, fragmentShader: fragShader, + depthTest: false, depthWrite: false, blending: THREE.NoBlending, toneMapped: false + }); + ppQuad = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), material); + ppScene.add(ppQuad); + } } function updatePostProcessingSize() { if (!ppRenderTarget) return; + let size = renderer.getSize(new THREE.Vector2()); - let pixelRatio = renderer.getPixelRatio(); - let w = Math.floor(size.x * pixelRatio); - let h = Math.floor(size.y * pixelRatio); - ppRenderTarget.setSize(w, h); - if (ppQuad && ppQuad.material.uniforms.resolution) { - ppQuad.material.uniforms.resolution.value.set(w, h); + let displayW = Math.floor(size.x * window.devicePixelRatio); + let displayH = Math.floor(size.y * window.devicePixelRatio); + + let aaMode = typeof getCMV === "function" ? getCMV("AA_MODE") || "" : ""; + let useFSR = aaMode.includes("FSR"); + let resRatio = getRenderResRatio(); + + let renderW = displayW; + let renderH = displayH; + + if (useFSR) { + renderW = Math.floor(displayW * resRatio); + renderH = Math.floor(displayH * resRatio); + } else { + let pixelRatio = renderer.getPixelRatio(); + renderW = Math.floor(size.x * pixelRatio); + renderH = Math.floor(size.y * pixelRatio); + } + + // Always resize base target to render resolution + ppRenderTarget.setSize(renderW, renderH); + + if (useFSR) { + if (easuRenderTarget) easuRenderTarget.setSize(displayW, displayH); + if (fxaaRenderTarget) fxaaRenderTarget.setSize(renderW, renderH); + + // Update FSR uniform resolutions + if (typeof fsrEASUMaterial !== 'undefined' && fsrEASUMaterial) { + fsrEASUMaterial.uniforms.resolution.value.set(displayW, displayH); + fsrEASUMaterial.uniforms.renderResolution.value.set(renderW, renderH); + } + if (typeof fsrRCASMaterial !== 'undefined' && fsrRCASMaterial) { + fsrRCASMaterial.uniforms.resolution.value.set(displayW, displayH); + } + if (ppQuad && ppQuad.material.uniforms.resolution) { + ppQuad.material.uniforms.resolution.value.set(renderW, renderH); + } + } else { + // Standard pass FXAA resolution update + if (ppQuad && ppQuad.material.uniforms.resolution) { + ppQuad.material.uniforms.resolution.value.set(renderW, renderH); + } } } @@ -275,7 +405,8 @@ function applyAAMode() { } let useMSAA = aaMode.indexOf('MSAA') !== -1; let useFXAA = aaMode.indexOf('FXAA') !== -1; - setupPostProcessing(useFXAA, useMSAA); + let useFSR = aaMode.indexOf('FSR') !== -1; + setupPostProcessing(useFXAA, useMSAA, useFSR); } function recreateRenderer() { @@ -750,8 +881,9 @@ function createLayout() { setTrackingModeSelect(itemselect.value); } else if (configitem['key'] == "UI_THEME") { updateTheme(); - } else if (configitem['key'] === 'AA_MODE') { - applyAAMode(); + } else if (configitem['key'] === 'AA_MODE' || configitem['key'] === 'UPSCALING_PRESET') { + updateRenderingUI(); + recreateRenderer(); } }; confgroup.appendChild(itemselect); @@ -759,7 +891,7 @@ function createLayout() { item.setAttribute("type", "range"); item.setAttribute("min", 0); item.setAttribute("max", 1000); - let setrange = configitem['range'][1] - configitem['range'][0]; + let setrange = configitem['range'][1] - configitem['range'][0]; // ADDED BACK let setvalue = (getCMV(configitem['key']) - configitem['range'][0]) * 1000 / setrange; item.setAttribute("value", setvalue); item.onchange = function () { @@ -779,9 +911,9 @@ function createLayout() { itemval.value = configitem['range'][1]; } let newvalue = (itemval.value - configitem['range'][0]) * 1000 / setrange; - item.setAttribute("value", newvalue); + item.value = newvalue; setCMV(configitem['key'], itemval.value); - if (configitem['key'] === 'RENDER_RESOLUTION') { + if (configitem['key'] === 'RENDER_RESOLUTION' || configitem['key'] === 'FSR_SHARPNESS') { updateRenderResolution(); updatePostProcessingSize(); } @@ -874,6 +1006,7 @@ function createLayout() { about.appendChild(document.createElement("br")); } + updateRenderingUI(); console.log("gui layout initialized"); } @@ -1162,16 +1295,20 @@ function raiseAlert(vistate, mlstate) { let alertbox = document.getElementById("alertbox"); alertbox.style.display = "block"; let alerttext = document.getElementById("alerttext"); + if (vistate == 3) { alerttext.innerHTML = getL("ALERT: Full Screen / Wrong Tab
Browser will stop rendering when other program enters full screen!"); } else if (mlstate == 3) { alerttext.innerHTML = getL("ALERT: Error
ML loop stop running, might need to restart to validate."); } else if (mlstate == 2 || vistate == 2) { alerttext.innerHTML = getL("ALERT: Hardware Acceleration
ML loop is running extremely slow, check if hardware acceleration is opened."); - } else if (mlstate == 1 && getCMV("HAND_TRACKING")) { + } else if (mlstate == 1 && getCMV("TRACKING_MODE") !== "Face-Only") { alerttext.innerHTML = getL("ALERT: Ultra Fast
ML loop is running slowly, improve performance by using FACE-ONLY mode."); } else if (vistate == 1) { alerttext.innerHTML = getL("ALERT: Slow
Feel free to contact developer for more information."); + } else { + // Fallback just in case + alerttext.innerHTML = "Performance Warning: Tracking FPS dropped."; } } } @@ -1188,15 +1325,40 @@ function drawScene() { setCMV('SCENE_FLIP', getCMV('CAMERA_FLIP')); scene.applyMatrix4(new THREE.Matrix4().makeScale(-1, 1, 1)); } - if (ppRenderTarget && ppQuad) { - // Render scene to LINEAR render target + + let aaMode = typeof getCMV === "function" ? getCMV("AA_MODE") || "" : ""; + let useFSR = aaMode.includes("FSR"); + + if (ppRenderTarget) { renderer.outputEncoding = THREE.LinearEncoding; renderer.setRenderTarget(ppRenderTarget); renderer.render(scene, camera); - // Render quad to screen (shader applies linearToSRGB) - renderer.setRenderTarget(null); - renderer.outputEncoding = THREE.sRGBEncoding; - renderer.render(ppScene, ppCamera); + + if (useFSR) { + if (ppQuad && fxaaRenderTarget) { + renderer.setRenderTarget(fxaaRenderTarget); + ppScene.add(ppQuad); + renderer.render(ppScene, ppCamera); + ppScene.remove(ppQuad); + } + renderer.setRenderTarget(easuRenderTarget); + ppScene.add(easuQuad); + renderer.render(ppScene, ppCamera); + ppScene.remove(easuQuad); + + renderer.setRenderTarget(null); + renderer.outputEncoding = THREE.sRGBEncoding; + if (typeof fsrRCASMaterial !== 'undefined' && fsrRCASMaterial) { + fsrRCASMaterial.uniforms.sharpness.value = getCMV("FSR_SHARPNESS") !== undefined ? getCMV("FSR_SHARPNESS") : 0.2; + } + ppScene.add(rcasQuad); + renderer.render(ppScene, ppCamera); + ppScene.remove(rcasQuad); + } else { + renderer.setRenderTarget(null); + renderer.outputEncoding = THREE.sRGBEncoding; + renderer.render(ppScene, ppCamera); + } } else { renderer.render(scene, camera); } From 39a09bac03ee640b2d38c8c75a59e32fdd34918e Mon Sep 17 00:00:00 2001 From: Sharad Mayank Sinha Date: Fri, 22 May 2026 01:54:46 +0530 Subject: [PATCH 2/3] Full describe --- js/config/config-manager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/config/config-manager.js b/js/config/config-manager.js index 8f01aab..9abcfff 100644 --- a/js/config/config-manager.js +++ b/js/config/config-manager.js @@ -438,7 +438,7 @@ function getConfigModifiers() { }, { 'key': 'RENDER_RESOLUTION', 'title': 'Render Resolution', - 'describe': 'Adjust the 3D render resolution ratio.', + 'describe': 'Adjust the 3D render resolution ratio. Default is 1.0. Lower for performance, higher for quality.', 'range': [0.1, 2.0] }], 'SMOOTH': [{ From 92809dd7cf58f0c0335521aee498f5e3290438a0 Mon Sep 17 00:00:00 2001 From: Sharad Mayank Sinha Date: Fri, 22 May 2026 20:14:23 +0530 Subject: [PATCH 3/3] Good Suggestion Implementation --- js/gui/gui-layout.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/gui/gui-layout.js b/js/gui/gui-layout.js index 399eb4a..ae35d3d 100644 --- a/js/gui/gui-layout.js +++ b/js/gui/gui-layout.js @@ -312,7 +312,7 @@ function setupPostProcessing(useFXAA, useMSAA, useFSR) { ppCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1); ppScene = new THREE.Scene(); - if (useFSR) { + if (useFSR && typeof setupFSR === 'function') { easuRenderTarget = new THREE.WebGLRenderTarget(displayW, displayH); if (useFXAA) { fxaaRenderTarget = new THREE.WebGLRenderTarget(renderW, renderH); @@ -1334,7 +1334,7 @@ function drawScene() { renderer.setRenderTarget(ppRenderTarget); renderer.render(scene, camera); - if (useFSR) { + if (useFSR && typeof setupFSR === 'function') { if (ppQuad && fxaaRenderTarget) { renderer.setRenderTarget(fxaaRenderTarget); ppScene.add(ppQuad);