| |
| |
| |
| |
| |
|
|
| const QUALITY_PRESETS = { |
| LOW: { |
| resScale: 0.5, |
| shadowRes: 512, |
| bloom: false, |
| fps: 30, |
| description: 'Performance mode' |
| }, |
| MED: { |
| resScale: 0.75, |
| shadowRes: 1024, |
| bloom: true, |
| fps: 60, |
| description: 'Balanced' |
| }, |
| HIGH: { |
| resScale: 1.0, |
| shadowRes: 2048, |
| bloom: true, |
| fps: 60, |
| description: 'High Quality' |
| }, |
| ULTRA: { |
| resScale: 1.5, |
| shadowRes: 4096, |
| bloom: true, |
| fps: 144, |
| description: 'Ultra Cinematic' |
| } |
| }; |
|
|
| class RenderCore { |
| constructor() { |
| this.renderer = null; |
| this.currentPreset = 'MED'; |
| this.renderTarget = null; |
| this.blitScene = null; |
| this.blitCamera = null; |
| this.blitMesh = null; |
| } |
|
|
| |
| |
| |
| |
| init(canvas) { |
| this.canvas = canvas; |
| this.renderer = new THREE.WebGLRenderer({ |
| canvas: canvas, |
| antialias: true, |
| powerPreference: 'high-performance' |
| }); |
|
|
| |
| this.renderer.outputEncoding = THREE.sRGBEncoding; |
| this.renderer.toneMapping = THREE.ACESFilmicToneMapping; |
| this.renderer.toneMappingExposure = 1.0; |
|
|
| |
| this.autoDetectQuality(); |
|
|
| return this.renderer; |
| } |
|
|
| |
| |
| |
| |
| setQuality(presetKey) { |
| if (!QUALITY_PRESETS[presetKey]) { |
| console.error(`Invalid quality preset: ${presetKey}`); |
| return; |
| } |
|
|
| this.currentPreset = presetKey; |
| this._applyQualitySettings(QUALITY_PRESETS[presetKey]); |
| } |
|
|
| |
| |
| |
| |
| autoDetectQuality() { |
| console.log("RenderCore: Auto-detecting hardware capabilities..."); |
| let score = 2; |
|
|
| |
| const gl = this.renderer.getContext(); |
| const debugInfo = gl.getExtension('WEBGL_debug_renderer_info'); |
| if (debugInfo) { |
| const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL); |
| console.log(`RenderCore: Detected GPU: ${renderer}`); |
|
|
| const highEnd = /NVIDIA|RTX|Radeon|AMD/i; |
| const lowEnd = /Adreno|Mali|Intel|Apple GPU/i; |
|
|
| if (highEnd.test(renderer)) { |
| score += 1; |
| if (/RTX|GTX 30|GTX 40/i.test(renderer)) score += 1; |
| } else if (lowEnd.test(renderer)) { |
| score -= 1; |
| } |
| } |
|
|
| |
| if (navigator.deviceMemory) { |
| console.log(`RenderCore: Device Memory: ${navigator.deviceMemory}GB`); |
| if (navigator.deviceMemory < 4) { |
| score -= 1; |
| } else if (navigator.deviceMemory >= 16) { |
| score += 1; |
| } |
| } |
|
|
| |
| const msPerFrame = this._perfTest(); |
| console.log(`RenderCore: Burn-in test: ${msPerFrame.toFixed(2)}ms/frame`); |
| if (msPerFrame > 16.67) { |
| score -= 2; |
| } |
|
|
| |
| let finalPreset = 'MED'; |
| if (score <= 1) finalPreset = 'LOW'; |
| else if (score === 2) finalPreset = 'MED'; |
| else if (score === 3) finalPreset = 'HIGH'; |
| else if (score >= 4) finalPreset = 'ULTRA'; |
|
|
| |
| if (msPerFrame > 33) finalPreset = 'LOW'; |
|
|
| console.log(`RenderCore: Auto-detected quality: ${finalPreset} (score: ${score})`); |
| this.setQuality(finalPreset); |
| } |
|
|
| |
| |
| |
| |
| |
| _perfTest() { |
| const scene = new THREE.Scene(); |
| const camera = new THREE.PerspectiveCamera(); |
| const start = performance.now(); |
| for (let i = 0; i < 10; i++) { |
| this.renderer.render(scene, camera); |
| } |
| return (performance.now() - start) / 10; |
| } |
|
|
| |
| |
| |
| |
| _setupRenderBypass() { |
| if (!this.renderer || !this.canvas) return; |
|
|
| const preset = this.getPreset(); |
| const scale = preset.resScale; |
|
|
| const width = Math.floor(this.canvas.width * scale); |
| const height = Math.floor(this.canvas.height * scale); |
|
|
| this.renderTarget = new THREE.WebGLRenderTarget(width, height, { |
| minFilter: THREE.LinearFilter, |
| magFilter: THREE.LinearFilter, |
| format: THREE.RGBAFormat |
| }); |
|
|
| |
| if (!this.blitScene) { |
| this.blitScene = new THREE.Scene(); |
| this.blitCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1); |
|
|
| const geometry = new THREE.PlaneGeometry(2, 2); |
| this.blitMesh = new THREE.Mesh( |
| geometry, |
| new THREE.MeshBasicMaterial({ transparent: true }) |
| ); |
| this.blitScene.add(this.blitMesh); |
| } |
|
|
| |
| this.blitMesh.material.map = this.renderTarget.texture; |
| this.blitMesh.material.needsUpdate = true; |
| } |
|
|
| |
| |
| |
| |
| _applyQualitySettings(preset) { |
| if (!this.renderer) return; |
|
|
| console.log(`RenderCore: Applying quality settings - ${preset.description}`); |
|
|
| if (preset.resScale !== 1.0) { |
| this._setupRenderBypass(); |
| } else { |
| this.renderTarget = null; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| render(scene, camera) { |
| if (!this.renderer) return; |
|
|
| const preset = this.getPreset(); |
| const scale = preset.resScale; |
|
|
| if (scale === 1.0) { |
| |
| this.renderer.render(scene, camera); |
| } else { |
| |
| if (!this.renderTarget) { |
| this._setupRenderBypass(); |
| } |
|
|
| this.renderer.setRenderTarget(this.renderTarget); |
| this.renderer.render(scene, camera); |
|
|
| this.renderer.setRenderTarget(null); |
| this.renderer.render(this.blitScene, this.blitCamera); |
| } |
| } |
|
|
| getPreset() { |
| return QUALITY_PRESETS[this.currentPreset]; |
| } |
| } |
|
|
| |
| window.RenderCore = RenderCore; |
| window.QUALITY_PRESETS = QUALITY_PRESETS; |
|
|