manually handle frameskipping for better audio on non multiple of 60hz screens

This commit is contained in:
Corwin 2024-04-06 19:42:44 +01:00
parent b47dc28f6f
commit 7f0de5f87b
No known key found for this signature in database
3 changed files with 58 additions and 63 deletions

View file

@ -5,10 +5,10 @@ import {
useRef, useRef,
useState, useState,
} from "react"; } from "react";
import mGBA from "./vendor/mgba"; import mGBA, { mGBAEmulator } from "./vendor/mgba";
import { GbaKey, KeyBindings } from "./bindings"; import { GbaKey, KeyBindings } from "./bindings";
import { styled } from "styled-components"; import { styled } from "styled-components";
import { useSmoothedFramerate } from "./useSmoothedFramerate.hook"; import { useFrameSkip } from "./useFrameSkip.hook";
type Module = any; type Module = any;
@ -41,18 +41,10 @@ export interface MgbaHandle {
buttonRelease: (key: GbaKey) => void; buttonRelease: (key: GbaKey) => void;
} }
const whichFrameSkip = (frameRate: number): number | undefined => {
if ((frameRate + 5) % 60 <= 10) {
// framerate close to multiple of 60
// use frameskip
return Math.round(frameRate / 60);
}
};
export const Mgba = forwardRef<MgbaHandle, MgbaProps>( export const Mgba = forwardRef<MgbaHandle, MgbaProps>(
({ gameUrl, volume, controls, paused }, ref) => { ({ gameUrl, volume, controls, paused }, ref) => {
const canvas = useRef(null); const canvas = useRef(null);
const mgbaModule = useRef<Module>({}); const mgbaModule = useRef<Module>({} as mGBAEmulator);
const [state, setState] = useState(MgbaState.Uninitialised); const [state, setState] = useState(MgbaState.Uninitialised);
const [gameLoaded, setGameLoaded] = useState(false); const [gameLoaded, setGameLoaded] = useState(false);
@ -101,23 +93,7 @@ export const Mgba = forwardRef<MgbaHandle, MgbaProps>(
}; };
}, [state]); }, [state]);
const frameRate = useSmoothedFramerate(); useFrameSkip(mgbaModule);
const frameSkipToUse = whichFrameSkip(frameRate);
useEffect(() => {
if (!gameLoaded) return;
if (frameSkipToUse) {
// framerate close to multiple of 60
// use frameskip
console.log("Using frameskip");
mgbaModule.current.setMainLoopTiming(1, frameSkipToUse);
} else {
// frame rate not close to multiple of 60, use timeout
console.log("Using timeout");
mgbaModule.current.setMainLoopTiming(0, 1000 / 59.727500569606);
}
}, [frameSkipToUse, gameLoaded]);
useEffect(() => { useEffect(() => {
if (!gameLoaded) return; if (!gameLoaded) return;

View file

@ -0,0 +1,54 @@
import { MutableRefObject, useEffect } from "react";
import { mGBAEmulator } from "./vendor/mgba";
export const useFrameSkip = (mgbaModule: MutableRefObject<mGBAEmulator>) => {
useEffect(() => {
let previous: number | undefined = undefined;
let stopped = false;
let smoothedFrameTime = 60;
let totalTime = 0;
let paused = false;
const raf = (time: DOMHighResTimeStamp) => {
if (previous) {
const delta = time - previous;
console.log(delta);
smoothedFrameTime = (smoothedFrameTime * 3 + delta) / 4;
const smoothedFrameRate = Math.round(1 / (smoothedFrameTime / 1000));
totalTime += 1 / smoothedFrameRate;
if (totalTime >= 1 / 60) {
totalTime -= 1 / 60;
if (paused) {
mgbaModule.current.resumeGame();
paused = false;
}
} else {
if (!paused) {
mgbaModule.current.pauseGame();
paused = true;
}
}
}
previous = time;
if (!stopped) {
window.requestAnimationFrame(raf);
}
}
window.requestAnimationFrame(raf);
return () => { stopped = true; }
}, [mgbaModule]);
}

View file

@ -1,35 +0,0 @@
import { useEffect, useState } from "react";
export const useSmoothedFramerate = (): number => {
const [smoothedFrameTime, setSmoothedFrameTime] = useState(60);
useEffect(() => {
let previous: number | undefined = undefined;
let stopped = false;
const raf = (time: DOMHighResTimeStamp) => {
if (previous) {
let delta = time - previous;
setSmoothedFrameTime((time) => (time * 3 + delta) / 4);
}
previous = time;
if (!stopped) {
window.requestAnimationFrame(raf);
}
}
window.requestAnimationFrame(raf);
return () => { stopped = true; }
}, []);
return Math.round(1 / (smoothedFrameTime / 1000));
}