Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 36 additions & 10 deletions client/src/core/Renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ import type { NetworkManagerEventPayload } from '../network/NetworkManager';
import { type ClientSettingsEventPayload, ClientSettingsEventType } from '../settings/SettingsManager';

const MISSING_SKYBOX_TEXTURE_PATH = '/textures/missing-skybox';
// Cap internal render target pixel count to avoid severe fullscreen slowdowns on
// high-DPI displays (e.g. Retina). Windowed mode remains sharper because viewport
// area is smaller and usually falls below this budget.
const MAX_RENDER_TARGET_PIXELS = 2560 * 1440;
const MIN_RENDER_PIXEL_RATIO = 0.5;

// Working variables
const color = new Color();
Expand Down Expand Up @@ -203,6 +208,34 @@ export default class Renderer {
public get viewDistance(): number { return Math.min(this._game.settingsManager.qualityPerfTradeoff.viewDistance.distance, this._fogFar); }
public get webGLRenderer(): WebGLRenderer { return this._renderer; }

private _getViewportSize(): { width: number; height: number } {
return {
width: Math.max(1, document.documentElement.clientWidth),
height: Math.max(1, document.documentElement.clientHeight),
};
}

private _calculateEffectivePixelRatio(): number {
const resolutionMultiplier = this._game.settingsManager.qualityPerfTradeoff.resolution.multiplier;
const requestedPixelRatio = window.devicePixelRatio * resolutionMultiplier;
const { width, height } = this._getViewportSize();
const viewportPixelCount = width * height;
const maxPixelRatioForBudget = Math.sqrt(MAX_RENDER_TARGET_PIXELS / viewportPixelCount);

return Math.max(
MIN_RENDER_PIXEL_RATIO,
Math.min(requestedPixelRatio, maxPixelRatioForBudget),
);
}

private _applyRenderResolution(): void {
const { width, height } = this._getViewportSize();
this._renderer.setPixelRatio(this._calculateEffectivePixelRatio());
this._renderer.setSize(width, height);
this._sceneUiRenderer.setSize(width, height);
this._resizePostProcessing();
}

private _setupPostProcessing(): void {
this._effectComposer.addPass(this._renderPass);
this._effectComposer.addPass(this._outlinePass);
Expand Down Expand Up @@ -400,9 +433,7 @@ export default class Renderer {
// document.documentElement.clientHeight are used instead. However, it needs to be
// verified whether this solution works correctly on other platforms as well.
this._game.camera.onWindowResize();
this._renderer.setSize(document.documentElement.clientWidth, document.documentElement.clientHeight);
this._sceneUiRenderer.setSize(document.documentElement.clientWidth, document.documentElement.clientHeight);
this._resizePostProcessing();
this._applyRenderResolution();
}

private _onWorldPacket = (payload: NetworkManagerEventPayload.IWorldPacket): void => {
Expand Down Expand Up @@ -490,13 +521,9 @@ export default class Renderer {
}

private _onClientSettingsUpdate = (_payload: ClientSettingsEventPayload.IUpdate): void => {
const { resolution } = this._game.settingsManager.qualityPerfTradeoff;

this._renderer.setPixelRatio(window.devicePixelRatio * resolution.multiplier);

this._applyRenderResolution();
this._clampTargetFogNearAndFar();
this._setupFog();
this._resizePostProcessing();
};

private _setupEventListeners(): void {
Expand Down Expand Up @@ -589,8 +616,7 @@ export default class Renderer {
}

private _setupRenderer(): void {
this._renderer.setSize(document.documentElement.clientWidth, document.documentElement.clientHeight);
this._renderer.setPixelRatio(window.devicePixelRatio * this._game.settingsManager.qualityPerfTradeoff.resolution.multiplier);
this._applyRenderResolution();
this._renderer.info.autoReset = false;
this._renderer.localClippingEnabled = false;
// Be explicit about output space; this is cheap and avoids surprises across Three.js versions.
Expand Down