diff --git a/packages/frontend/src/pages/room.vue b/packages/frontend/src/pages/room.vue
index 007665e224..ad58aa7189 100644
--- a/packages/frontend/src/pages/room.vue
+++ b/packages/frontend/src/pages/room.vue
@@ -154,14 +154,26 @@ SPDX-License-Identifier: AGPL-3.0-only
保存
graphicsQuality = v"
+ ]"
>
Graphics quality
+
+ Framerate
+
reload
@@ -187,6 +199,7 @@ import { deviceKind } from '@/utility/device-kind.js';
import MkProgressBar from '@/components/MkProgressBar.vue';
import { Joystick } from '@/world/joystick.js';
import { isTouchUsing } from '@/utility/touch.js';
+import { prefer } from '@/preferences.js';
const canvas = useTemplateRef('canvas');
@@ -204,7 +217,20 @@ function resize() {
const isZenMode = ref(false);
const isRoomSettingsOpen = ref(false);
const isChanged = ref(false);
-const graphicsQuality = ref(deviceKind === 'smartphone' ? GRAPHICS_QUALITY_LOW : GRAPHICS_QUALITY_MEDIUM);
+
+const graphicsQualityRaw = prefer.model('world.graphicsQuality');
+const graphicsQualityAutoValue = computed(() => deviceKind === 'smartphone' ? GRAPHICS_QUALITY_LOW : GRAPHICS_QUALITY_MEDIUM);
+const graphicsQuality = computed(() => graphicsQualityRaw.value ?? graphicsQualityAutoValue.value);
+
+const fpsRaw = prefer.model('world.fps');
+const fpsAutoValue = computed(() => graphicsQuality.value >= GRAPHICS_QUALITY_HIGH ? null : 30);
+const fps = computed(() =>
+ fpsRaw.value == null ? fpsAutoValue.value :
+ fpsRaw.value === 'max' ? null :
+ fpsRaw.value === '120' ? 120 :
+ fpsRaw.value === '60' ? 60 :
+ 30);
+
const useVirtualJoystick = isTouchUsing && (deviceKind === 'smartphone' || deviceKind === 'tablet');
const joyStickRadiusPx = 100;
@@ -254,6 +280,7 @@ let latestData = deepClone(data);
const controller = new RoomController(data, {
graphicsQuality: graphicsQuality.value,
+ fps: fps.value,
useVirtualJoystick,
});
@@ -413,6 +440,7 @@ async function revert() {
async function reload() {
await controller.reset(null, {
graphicsQuality: graphicsQuality.value,
+ fps: fps.value,
useVirtualJoystick,
});
}
diff --git a/packages/frontend/src/preferences/def.ts b/packages/frontend/src/preferences/def.ts
index d6a4c63011..3d0c49898b 100644
--- a/packages/frontend/src/preferences/def.ts
+++ b/packages/frontend/src/preferences/def.ts
@@ -533,6 +533,13 @@ export const PREF_DEF = definePreferences({
},
},
+ 'world.graphicsQuality': {
+ default: null as number | null,
+ },
+ 'world.fps': {
+ default: null as 'max' | '120' | '60' | '30' | null,
+ },
+
'experimental.stackingRouterView': {
default: false,
},
diff --git a/packages/frontend/src/world/room/controller.ts b/packages/frontend/src/world/room/controller.ts
index 312d706a7b..4dc289d055 100644
--- a/packages/frontend/src/world/room/controller.ts
+++ b/packages/frontend/src/world/room/controller.ts
@@ -16,6 +16,7 @@ import * as sound from '@/utility/sound.js';
type Options = {
workerMode?: boolean;
graphicsQuality: number;
+ fps: number | null;
useVirtualJoystick?: boolean;
};
@@ -24,7 +25,7 @@ export class RoomController {
private worker: Worker | null = null;
private engine: RoomEngine | null = null;
private canvas: HTMLCanvasElement | null = null;
- private options: Options = {};
+ private options: Options;
private isCanvasDragging = false;
public isReady = ref(false);
public isSitting = ref(false);
@@ -60,7 +61,7 @@ export class RoomController {
if (this.options.workerMode) {
const offscreen = canvas.transferControlToOffscreen();
this.worker = new RoomWorker();
- this.worker.postMessage({ type: 'init', canvas: offscreen, roomState: this.roomState.value, graphicsQuality: this.options.graphicsQuality, useVirtualJoystick: this.options.useVirtualJoystick }, [offscreen]);
+ this.worker.postMessage({ type: 'init', canvas: offscreen, roomState: this.roomState.value, ...this.options }, [offscreen]);
this.isReady.value = true;
} else {
const babylonEngine = new BABYLON.WebGPUEngine(canvas, { doNotHandleContextLost: true });
@@ -68,7 +69,7 @@ export class RoomController {
babylonEngine.enableOfflineSupport = false;
await babylonEngine.initAsync();
- this.engine = new RoomEngine(this.roomState.value, { canvas, engine: babylonEngine, graphicsQuality: this.options.graphicsQuality, useVirtualJoystick: this.options.useVirtualJoystick });
+ this.engine = new RoomEngine(this.roomState.value, { canvas, engine: babylonEngine, ...this.options });
this.engine.on('loadingProgress', ({ progress }) => {
this.initializeProgress.value = progress;
});
diff --git a/packages/frontend/src/world/room/engine.ts b/packages/frontend/src/world/room/engine.ts
index e9f009dab9..8d5f9cf0c1 100644
--- a/packages/frontend/src/world/room/engine.ts
+++ b/packages/frontend/src/world/room/engine.ts
@@ -224,6 +224,7 @@ export class RoomEngine extends EventEmitter {
canvas: HTMLCanvasElement;
engine: BABYLON.WebGPUEngine;
graphicsQuality: number;
+ fps: number | null;
useVirtualJoystick?: boolean;
}) {
super();
@@ -237,7 +238,7 @@ export class RoomEngine extends EventEmitter {
};
this.canvas = options.canvas;
- this.fps = options.graphicsQuality >= GRAPHICS_QUALITY_HIGH ? null : 30;
+ this.fps = options.fps;
this.useGlow = options.graphicsQuality >= GRAPHICS_QUALITY_MEDIUM;
registerBuiltInLoaders();