mirror of
https://github.com/Boof2015/prism.git
synced 2026-08-16 16:21:11 +02:00
fps controls
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
import { useEffect, useLayoutEffect, useRef, type CSSProperties, type JSX } from 'react'
|
||||
import { useAudioStore } from '../stores/audioStore'
|
||||
import { usePerformanceStore } from '../stores/performanceStore'
|
||||
import { useSettingsStore } from '../stores/settingsStore'
|
||||
import { useThemeStore, PRESETS, PRESET_IDS } from '../stores/themeStore'
|
||||
import type { ScopeKind } from '../../types/scope'
|
||||
import { VISUALIZER_FRAME_TARGETS, type VisualizerFrameTarget } from '../../types/performance'
|
||||
import { SCOPE_KINDS } from '../../types/scope'
|
||||
|
||||
const SCOPE_LABELS: Record<ScopeKind, string> = {
|
||||
@@ -20,11 +22,23 @@ interface BottomBarProps {
|
||||
onHeightChange?: (height: number) => void
|
||||
}
|
||||
|
||||
const FRAME_TARGET_LABELS: Record<VisualizerFrameTarget, string> = {
|
||||
10: '10',
|
||||
30: '30',
|
||||
60: '60',
|
||||
120: '120',
|
||||
144: '144',
|
||||
'display-sync': 'Sync',
|
||||
}
|
||||
|
||||
export default function BottomBar({ onClose, onHeightChange }: BottomBarProps): JSX.Element {
|
||||
const rootRef = useRef<HTMLDivElement | null>(null)
|
||||
|
||||
const hiddenScopes = useSettingsStore((s) => s.hiddenScopes)
|
||||
const toggleScope = useSettingsStore((s) => s.toggleScope)
|
||||
const frameTarget = usePerformanceStore((s) => s.frameTarget)
|
||||
const dockedRenderFps = usePerformanceStore((s) => s.dockedRenderFps)
|
||||
const setFrameTarget = usePerformanceStore((s) => s.setFrameTarget)
|
||||
const { presetId, accent, setPreset, setCustomAccent, customAccent } = useThemeStore()
|
||||
|
||||
const {
|
||||
@@ -113,6 +127,7 @@ export default function BottomBar({ onClose, onHeightChange }: BottomBarProps):
|
||||
: 'Idle'
|
||||
|
||||
const trimPercent = Math.min(100, Math.max(0, ((inputGainDb + 12) / 24) * 100))
|
||||
const roundedDockedRenderFps = Math.max(0, Math.round(dockedRenderFps))
|
||||
|
||||
return (
|
||||
<div className="bottom-bar" ref={rootRef}>
|
||||
@@ -226,6 +241,33 @@ export default function BottomBar({ onClose, onHeightChange }: BottomBarProps):
|
||||
|
||||
<div className="bottom-bar__divider" />
|
||||
|
||||
<section className="bottom-bar__section bottom-bar__section--performance">
|
||||
<div className="bottom-bar__section-title">Performance</div>
|
||||
<div className="bottom-bar__section-body">
|
||||
<div className="bottom-bar__inline bottom-bar__inline--performance">
|
||||
<div className="bottom-bar__inline bottom-bar__inline--chips">
|
||||
{VISUALIZER_FRAME_TARGETS.map((target) => (
|
||||
<button
|
||||
key={String(target)}
|
||||
type="button"
|
||||
className={`settings-chip ${frameTarget === target ? 'is-active' : ''}`.trim()}
|
||||
onClick={() => setFrameTarget(target)}
|
||||
title={target === 'display-sync' ? 'Display Sync' : `Cap visualizers at ${target} FPS`}
|
||||
>
|
||||
{FRAME_TARGET_LABELS[target]}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="settings-status-pill bottom-bar__fps-pill" title="Docked visualizer render FPS">
|
||||
<span>{roundedDockedRenderFps} FPS</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="bottom-bar__divider" />
|
||||
|
||||
<section className="bottom-bar__section bottom-bar__section--trim">
|
||||
<div className="bottom-bar__section-title">Trim</div>
|
||||
<div className="bottom-bar__section-body">
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { useEffect, useMemo, useRef } from 'react'
|
||||
import { audioRouter } from '../audio/AudioRouter'
|
||||
import { usePerformanceStore } from '../stores/performanceStore'
|
||||
import { useSettingsStore } from '../stores/settingsStore'
|
||||
import { useThemeStore } from '../stores/themeStore'
|
||||
import { FrameScheduler } from '../visualizers/frameScheduler'
|
||||
import type {
|
||||
ScopePopoutAudioBatch,
|
||||
ScopePopoutSessionState,
|
||||
@@ -59,14 +61,20 @@ export default function ScopePopoutBridge(): null {
|
||||
const updatePopoutBounds = useSettingsStore((s) => s.updatePopoutBounds)
|
||||
const updateScopeSettings = useSettingsStore((s) => s.updateScopeSettings)
|
||||
const accent = useThemeStore((s) => s.accent)
|
||||
const frameTarget = usePerformanceStore((s) => s.frameTarget)
|
||||
|
||||
const activePopoutKinds = useMemo(
|
||||
() => SCOPE_KINDS.filter((kind) => scopePopouts[kind]?.poppedOut && !hiddenScopes.has(kind)),
|
||||
[hiddenScopes, scopePopouts],
|
||||
)
|
||||
const flushScheduler = useMemo(() => new FrameScheduler({ frameTarget }), [])
|
||||
const activePopoutKindsRef = useRef<ScopeKind[]>(activePopoutKinds)
|
||||
const sessionStateRef = useRef(audioRouter.getSessionState())
|
||||
|
||||
useEffect(() => {
|
||||
flushScheduler.setFrameTarget(frameTarget)
|
||||
}, [flushScheduler, frameTarget])
|
||||
|
||||
useEffect(() => {
|
||||
activePopoutKindsRef.current = activePopoutKinds
|
||||
}, [activePopoutKinds])
|
||||
@@ -153,10 +161,9 @@ export default function ScopePopoutBridge(): null {
|
||||
}, [activePopoutKinds])
|
||||
|
||||
useEffect(() => {
|
||||
let frameId = 0
|
||||
let unsubscribeFlush: (() => void) | null = null
|
||||
|
||||
const flushFrame = (): void => {
|
||||
frameId = 0
|
||||
if (!sessionStateRef.current.capturing || activePopoutKindsRef.current.length === 0) {
|
||||
return
|
||||
}
|
||||
@@ -167,22 +174,20 @@ export default function ScopePopoutBridge(): null {
|
||||
window.electronAPI.sendScopePopoutAudio(kind, batch)
|
||||
}
|
||||
}
|
||||
|
||||
frameId = window.requestAnimationFrame(flushFrame)
|
||||
}
|
||||
|
||||
const syncFlushLoop = (): void => {
|
||||
const shouldRun = sessionStateRef.current.capturing && activePopoutKindsRef.current.length > 0
|
||||
if (!shouldRun) {
|
||||
if (frameId) {
|
||||
window.cancelAnimationFrame(frameId)
|
||||
frameId = 0
|
||||
if (unsubscribeFlush) {
|
||||
unsubscribeFlush()
|
||||
unsubscribeFlush = null
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (!frameId) {
|
||||
frameId = window.requestAnimationFrame(flushFrame)
|
||||
if (!unsubscribeFlush) {
|
||||
unsubscribeFlush = flushScheduler.subscribe(flushFrame)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,12 +199,12 @@ export default function ScopePopoutBridge(): null {
|
||||
syncFlushLoop()
|
||||
|
||||
return () => {
|
||||
if (frameId) {
|
||||
window.cancelAnimationFrame(frameId)
|
||||
if (unsubscribeFlush) {
|
||||
unsubscribeFlush()
|
||||
}
|
||||
unsubscribeSession()
|
||||
}
|
||||
}, [activePopoutKinds])
|
||||
}, [activePopoutKinds, flushScheduler])
|
||||
|
||||
useEffect(() => {
|
||||
return audioRouter.subscribeToSessionChanges((state) => {
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { WindowBounds } from '../../types/popout'
|
||||
import ScopeModule from './ScopeModule'
|
||||
import { buildAnalyzerGridTemplateColumns } from '../analyzerLayout'
|
||||
import { audioRouter } from '../audio/AudioRouter'
|
||||
import { usePerformanceStore } from '../stores/performanceStore'
|
||||
import { FrameScheduler } from '../visualizers/frameScheduler'
|
||||
|
||||
export default function Strip(): JSX.Element {
|
||||
@@ -17,7 +18,9 @@ export default function Strip(): JSX.Element {
|
||||
const setScopeWidthWeight = useSettingsStore((s) => s.setScopeWidthWeight)
|
||||
const popOutScope = useSettingsStore((s) => s.popOutScope)
|
||||
const accent = useThemeStore((s) => s.accent)
|
||||
const frameScheduler = useMemo(() => new FrameScheduler(), [])
|
||||
const frameTarget = usePerformanceStore((s) => s.frameTarget)
|
||||
const setDockedRenderFps = usePerformanceStore((s) => s.setDockedRenderFps)
|
||||
const frameScheduler = useMemo(() => new FrameScheduler({ frameTarget }), [])
|
||||
const stripRef = useRef<HTMLDivElement>(null)
|
||||
const gridRef = useRef<HTMLDivElement>(null)
|
||||
const scopeRefs = useRef<Partial<Record<ScopeKind, HTMLDivElement | null>>>({})
|
||||
@@ -35,6 +38,21 @@ export default function Strip(): JSX.Element {
|
||||
return { gridTemplateColumns } as CSSProperties
|
||||
}, [gridTemplateColumns])
|
||||
|
||||
useEffect(() => {
|
||||
frameScheduler.setFrameTarget(frameTarget)
|
||||
}, [frameScheduler, frameTarget])
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = frameScheduler.subscribeToActualFps((fps) => {
|
||||
setDockedRenderFps(fps)
|
||||
})
|
||||
|
||||
return () => {
|
||||
unsubscribe()
|
||||
setDockedRenderFps(0)
|
||||
}
|
||||
}, [frameScheduler, setDockedRenderFps])
|
||||
|
||||
const updateHandleOffsets = useCallback((): void => {
|
||||
if (dockedScopes.length < 2) {
|
||||
setHandleOffsets([])
|
||||
|
||||
Reference in New Issue
Block a user