@dailies-studio/shot-editor 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.vite/manifest.json +382 -0
- package/dist/assets/AgentPanel-CQejLbiX.js +2 -0
- package/dist/assets/AgentPanel-DJEphah0.css +1 -0
- package/dist/assets/AgentQuestionDialog-BqkD0kXF.js +1 -0
- package/dist/assets/CodeStudio-Dv4ihHpz.js +23 -0
- package/dist/assets/CommentsPanel-B4yhhT1T.js +1 -0
- package/dist/assets/ComponentLibraryRail-Ch_dsAru.js +1 -0
- package/dist/assets/ComponentStage-BRhRotyH.js +1 -0
- package/dist/assets/CurvePane-BjFVabKX.css +1 -0
- package/dist/assets/CurvePane-R22WzllJ.js +1 -0
- package/dist/assets/ExportDialog-CWLQWTmG.js +2 -0
- package/dist/assets/FontPicker-DWKaYL7F.js +1 -0
- package/dist/assets/FontPicker-v0IOIArM.css +1 -0
- package/dist/assets/GenerateDrawer-Bh3SqDKt.js +2 -0
- package/dist/assets/InsertDrawer-Ce1fod1L.js +1 -0
- package/dist/assets/LibraryBrowser-D9sEGz5w.js +2 -0
- package/dist/assets/MakeItYoursSection-CMO4319_.js +2 -0
- package/dist/assets/MaterialGraphCanvas-BFzl4wWR.js +23 -0
- package/dist/assets/MaterialGraphCanvas-BIUD59ik.css +1 -0
- package/dist/assets/MaterialWorkspace-CqTWqaRA.js +14 -0
- package/dist/assets/PreferencesDialog-CBf9_OoD.js +1 -0
- package/dist/assets/PresetsSection-CUEaoCVU.js +1 -0
- package/dist/assets/SoundHistory-BeS8EW1h.js +1 -0
- package/dist/assets/SoundMode-BmcZru3i.js +3 -0
- package/dist/assets/StockSounds-DdAUGRYK.js +1 -0
- package/dist/assets/TemplateFieldSection-CaeXpLFo.js +1 -0
- package/dist/assets/VersionHistoryPanel-CxG6XhNP.js +1 -0
- package/dist/assets/board-B88EPuLO.css +1 -0
- package/dist/assets/board-CL-he7SL.js +3 -0
- package/dist/assets/components-0oOSPN0l.js +1 -0
- package/dist/assets/googleFontRanges-gp32wJMo.js +1 -0
- package/dist/assets/googleFonts-BNon6M7X.js +1 -0
- package/dist/assets/iconsAndAliases-DL7gXP5j.js +1863 -0
- package/dist/assets/index-4F5NQmI8.css +1 -0
- package/dist/assets/index-BGHQGV5z.js +117 -0
- package/dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- package/dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- package/dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- package/dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- package/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- package/dist/assets/libraryModel-CpIwfFha.js +1 -0
- package/dist/assets/lucide-6iK64tTB.js +9319 -0
- package/dist/assets/motionStamp-CH-Hew4E.js +1 -0
- package/dist/assets/takeMeasure-Tdg0TxEi.js +1 -0
- package/dist/assets/webmcp-BsB9gxWC.js +2 -0
- package/dist/index.html +34 -3
- package/package.json +12 -9
- package/src/addFamilies.ts +66 -0
- package/src/api.ts +1631 -55
- package/src/app/App.tsx +977 -198
- package/src/app/lazyPanel.tsx +137 -0
- package/src/app/lazySurfaces.tsx +203 -0
- package/src/audio/index.tsx +41 -10
- package/src/audio/meter.ts +485 -0
- package/src/audio/meterMath.ts +273 -0
- package/src/audio/meterStore.ts +155 -0
- package/src/audio/offlineMix.ts +289 -0
- package/src/audio/preview.ts +928 -118
- package/src/bridge.ts +15 -5
- package/src/bridgeTypes.ts +1611 -36
- package/src/channels.ts +17 -32
- package/src/fonts/googleFonts.ts +4 -0
- package/src/hostApi.ts +174 -0
- package/src/icons.tsx +770 -0
- package/src/layerWords.ts +61 -0
- package/src/main.tsx +56 -4
- package/src/panels/ResizeHandle.tsx +33 -43
- package/src/panels/agent/AgentDots.tsx +36 -0
- package/src/panels/agent/AgentPanel.tsx +119 -43
- package/src/panels/agent/AgentQuestionDialog.tsx +202 -0
- package/src/panels/agent/agent.css +113 -97
- package/src/panels/agent/agentGate.ts +35 -0
- package/src/panels/code/CodeDefaultsPanel.tsx +345 -0
- package/src/panels/code/CodeStudio.tsx +373 -0
- package/src/panels/code/FileTree.tsx +265 -0
- package/src/panels/code/codeBufferGuard.ts +52 -0
- package/src/panels/code/codeParts.tsx +321 -0
- package/src/panels/comments/CommentComposer.tsx +239 -0
- package/src/panels/comments/CommentThread.tsx +346 -0
- package/src/panels/comments/CommentsPanel.tsx +336 -0
- package/src/panels/comments/InboxButton.tsx +131 -0
- package/src/panels/comments/MentionMenu.tsx +98 -0
- package/src/panels/comments/parts.tsx +171 -0
- package/src/panels/export/ExportDialog.tsx +919 -96
- package/src/panels/export/ExportScrim.tsx +57 -0
- package/src/panels/files/AssetsTab.tsx +656 -0
- package/src/panels/files/FilePanel.tsx +1466 -216
- package/src/panels/files/LayerMarks.tsx +40 -0
- package/src/panels/files/LeftColumn.tsx +37 -0
- package/src/panels/files/McpSection.tsx +690 -47
- package/src/panels/files/MissingAssetsNotice.tsx +60 -0
- package/src/panels/files/assetDrag.ts +79 -0
- package/src/panels/files/resourceParts.tsx +255 -0
- package/src/panels/fonts/FontPicker.tsx +762 -0
- package/src/panels/fonts/faceCache.ts +207 -0
- package/src/panels/fonts/fontPicker.css +271 -0
- package/src/panels/fonts/pickerModel.ts +265 -0
- package/src/panels/fonts/pickerRequest.ts +46 -0
- package/src/panels/header/AccountAvatar.tsx +76 -0
- package/src/panels/header/AgentChip.tsx +77 -0
- package/src/panels/header/AppMenu.tsx +213 -0
- package/src/panels/header/Header.tsx +564 -84
- package/src/panels/header/HistoryControls.tsx +31 -5
- package/src/panels/header/PresenceStack.tsx +329 -0
- package/src/panels/header/ProjectTitle.tsx +232 -0
- package/src/panels/header/ShareButton.tsx +59 -0
- package/src/panels/header/StatusBadges.tsx +358 -27
- package/src/panels/header/SyncDeletedDialog.tsx +88 -0
- package/src/panels/header/ZoomControls.tsx +46 -15
- package/src/panels/header/agentHandoff.ts +52 -0
- package/src/panels/header/leave.ts +26 -0
- package/src/panels/help/HelpSection.tsx +189 -163
- package/src/panels/insert/InsertDrawer.tsx +723 -0
- package/src/panels/insert/iconSet.ts +109 -0
- package/src/panels/inspector/ExposeDot.tsx +117 -0
- package/src/panels/inspector/FramingRows.tsx +314 -44
- package/src/panels/inspector/InspectorFooter.tsx +91 -0
- package/src/panels/inspector/InspectorHead.tsx +425 -0
- package/src/panels/inspector/InspectorPanel.tsx +408 -124
- package/src/panels/inspector/KeyframeDot.tsx +373 -0
- package/src/panels/inspector/KeyframeSection.tsx +257 -0
- package/src/panels/inspector/MarkerSection.tsx +55 -11
- package/src/panels/inspector/ParamRow.tsx +84 -53
- package/src/panels/inspector/RightColumn.tsx +152 -0
- package/src/panels/inspector/TimeSection.tsx +866 -0
- package/src/panels/inspector/animate/EaseControl.tsx +244 -0
- package/src/panels/inspector/animate/PresetsSection.tsx +135 -0
- package/src/panels/inspector/animate/ShotSection.tsx +562 -48
- package/src/panels/inspector/design/AlignRow.tsx +95 -31
- package/src/panels/inspector/design/AppearanceSection.tsx +118 -9
- package/src/panels/inspector/design/ClonerSection.tsx +323 -62
- package/src/panels/inspector/design/ComponentSection.tsx +50 -43
- package/src/panels/inspector/design/CornerSection.tsx +45 -14
- package/src/panels/inspector/design/DefinitionProps.tsx +315 -0
- package/src/panels/inspector/design/DefinitionSection.tsx +136 -0
- package/src/panels/inspector/design/DeviceSection.tsx +210 -0
- package/src/panels/inspector/design/FillSection.tsx +83 -51
- package/src/panels/inspector/design/FontField.tsx +196 -0
- package/src/panels/inspector/design/HtmlSection.tsx +23 -12
- package/src/panels/inspector/design/InstanceSection.tsx +373 -154
- package/src/panels/inspector/design/MaskSection.tsx +84 -0
- package/src/panels/inspector/design/MaterialSection.tsx +177 -0
- package/src/panels/inspector/design/MediaSection.tsx +273 -120
- package/src/panels/inspector/design/MeshSection.tsx +910 -82
- package/src/panels/inspector/design/PaintRows.tsx +550 -0
- package/src/panels/inspector/design/PlateStyleSection.tsx +177 -0
- package/src/panels/inspector/design/ShadowSection.tsx +221 -48
- package/src/panels/inspector/design/SplineFollowSection.tsx +235 -0
- package/src/panels/inspector/design/StrokeSection.tsx +228 -43
- package/src/panels/inspector/design/TemplateFieldSection.tsx +57 -0
- package/src/panels/inspector/design/TransformSection.tsx +244 -50
- package/src/panels/inspector/design/TypographySection.tsx +679 -110
- package/src/panels/inspector/layout.ts +295 -0
- package/src/panels/inspector/propValue.ts +199 -0
- package/src/panels/inspector/scene/CameraSection.tsx +232 -22
- package/src/panels/inspector/scene/EffectsSection.tsx +722 -71
- package/src/panels/inspector/scene/MakeItYoursSection.tsx +180 -0
- package/src/panels/inspector/scene/SceneDocSection.tsx +191 -0
- package/src/panels/inspector/scene/SceneSection.tsx +219 -44
- package/src/panels/inspector/scene/TransitionSection.tsx +523 -72
- package/src/panels/inspector/storyboard/StoryboardReferenceSection.tsx +593 -0
- package/src/panels/inspector/storyboard/VideoStill.tsx +120 -0
- package/src/panels/inspector/timeModel.ts +190 -0
- package/src/panels/inspector/useDocWrite.ts +438 -146
- package/src/panels/library/LibraryBrowser.tsx +435 -0
- package/src/panels/library/LibraryGrid.tsx +190 -0
- package/src/panels/library/LibraryPreviewPanel.tsx +349 -0
- package/src/panels/library/LibraryRail.tsx +84 -0
- package/src/panels/library/LibrarySoundPanel.tsx +293 -0
- package/src/panels/library/SoundHistory.tsx +245 -0
- package/src/panels/library/StockSounds.tsx +390 -0
- package/src/panels/library/libraryModel.ts +147 -0
- package/src/panels/library/stockSearch.ts +77 -0
- package/src/panels/maskMarks.tsx +117 -0
- package/src/panels/material/MaterialCommentNode.tsx +54 -0
- package/src/panels/material/MaterialGraphCanvas.tsx +861 -0
- package/src/panels/material/MaterialGraphMenu.tsx +45 -0
- package/src/panels/material/MaterialGroupNode.tsx +56 -0
- package/src/panels/material/MaterialLibrary.tsx +253 -0
- package/src/panels/material/MaterialNode.tsx +453 -0
- package/src/panels/material/MaterialNodePalette.tsx +349 -0
- package/src/panels/material/MaterialParameterControls.tsx +525 -0
- package/src/panels/material/MaterialRerouteNode.tsx +42 -0
- package/src/panels/material/MaterialWorkspace.tsx +501 -0
- package/src/panels/material/NodePreview.tsx +70 -0
- package/src/panels/material/flowModel.ts +441 -0
- package/src/panels/material/parameterValues.ts +231 -0
- package/src/panels/material/previewScheduler.ts +235 -0
- package/src/panels/material/reactFlow.css +6 -0
- package/src/panels/material/socketGlyph.ts +85 -0
- package/src/panels/material/viewportMemory.ts +58 -0
- package/src/panels/preferences/PreferencesDialog.tsx +215 -0
- package/src/panels/share/ShareComponentDialog.tsx +247 -0
- package/src/panels/stage/StageMenu.tsx +35 -5
- package/src/panels/stage/StagePanel.tsx +338 -23
- package/src/panels/stage/cellSlots.tsx +70 -0
- package/src/panels/storyboard/BoardCanvas.tsx +739 -0
- package/src/panels/storyboard/BoardCursors.tsx +158 -0
- package/src/panels/storyboard/BoardDropDialog.tsx +161 -0
- package/src/panels/storyboard/BoardPlan.tsx +42 -0
- package/src/panels/storyboard/ComponentSheet.tsx +187 -0
- package/src/panels/storyboard/ComponentStage.tsx +235 -0
- package/src/panels/storyboard/MiniSketch.tsx +173 -0
- package/src/panels/storyboard/PartPicker.tsx +64 -0
- package/src/panels/storyboard/PlanPanel.tsx +617 -0
- package/src/panels/storyboard/ReferencePreview.tsx +217 -0
- package/src/panels/storyboard/ShotBrief.tsx +360 -0
- package/src/panels/storyboard/ShotCard.tsx +251 -0
- package/src/panels/storyboard/StagePreview.tsx +348 -0
- package/src/panels/storyboard/StillThumbs.tsx +162 -0
- package/src/panels/storyboard/StoryboardView.tsx +79 -0
- package/src/panels/storyboard/board.css +1065 -0
- package/src/panels/storyboard/board.ts +12 -0
- package/src/panels/storyboard/boardClipboard.ts +111 -0
- package/src/panels/storyboard/boardOrder.ts +68 -0
- package/src/panels/storyboard/boardRows.tsx +425 -0
- package/src/panels/storyboard/liveCap.ts +90 -0
- package/src/panels/storyboard/plan.css +295 -0
- package/src/panels/storyboard/posterCache.ts +133 -0
- package/src/panels/storyboard/shotboard/ComponentPicker.tsx +373 -0
- package/src/panels/storyboard/shotboard/ImagePicker.tsx +145 -0
- package/src/panels/storyboard/shotboard/ShotBoard.tsx +292 -0
- package/src/panels/storyboard/shotboard/SketchCanvas.tsx +443 -0
- package/src/panels/storyboard/shotboard/SketchInspector.tsx +471 -0
- package/src/panels/storyboard/shotboard/SketchItem.tsx +240 -0
- package/src/panels/storyboard/shotboard/SketchLayers.tsx +130 -0
- package/src/panels/storyboard/shotboard/SketchPlay.tsx +108 -0
- package/src/panels/storyboard/shotboard/SketchStrip.tsx +178 -0
- package/src/panels/storyboard/shotboard/SketchToolFooter.tsx +99 -0
- package/src/panels/storyboard/shotboard/sketch.css +1392 -0
- package/src/panels/storyboard/shotboard/sketchModel.ts +219 -0
- package/src/panels/storyboard/storyboardDrop.ts +164 -0
- package/src/panels/studio/ComponentLibraryRail.tsx +346 -0
- package/src/panels/studio/ComponentSidePanel.tsx +355 -0
- package/src/panels/studio/ComponentStudio.tsx +243 -0
- package/src/panels/studio/ComponentsHome.tsx +318 -0
- package/src/panels/studio/ComponentsPreview.tsx +179 -0
- package/src/panels/studio/EveryUse.tsx +244 -0
- package/src/panels/studio/ThisUsePanel.tsx +679 -0
- package/src/panels/studio/componentParts.tsx +341 -0
- package/src/panels/studio/components.ts +6 -0
- package/src/panels/studio/newCodeComponent.ts +66 -0
- package/src/panels/timeline/AudioFxButton.tsx +290 -0
- package/src/panels/timeline/AudioLane.tsx +674 -85
- package/src/panels/timeline/BlockRow.tsx +154 -85
- package/src/panels/timeline/ChannelRow.tsx +378 -129
- package/src/panels/timeline/CurvePane.tsx +508 -0
- package/src/panels/timeline/Filmstrip.tsx +361 -0
- package/src/panels/timeline/GenerateDrawer.tsx +407 -0
- package/src/panels/timeline/KeyStretch.tsx +115 -0
- package/src/panels/timeline/LifespanRow.tsx +730 -47
- package/src/panels/timeline/MarkerLane.tsx +46 -8
- package/src/panels/timeline/PlayButton.tsx +91 -0
- package/src/panels/timeline/PropertyPicker.tsx +254 -0
- package/src/panels/timeline/ShotRow.tsx +400 -0
- package/src/panels/timeline/SoundMode.tsx +1406 -0
- package/src/panels/timeline/TimelineDock.tsx +2338 -272
- package/src/panels/timeline/TransportVolume.tsx +163 -0
- package/src/panels/timeline/beatRuler.ts +74 -0
- package/src/panels/timeline/curveModel.ts +500 -0
- package/src/panels/timeline/duckUnderVoice.ts +68 -0
- package/src/panels/timeline/keyframeMenu.ts +138 -0
- package/src/panels/timeline/labelWidth.ts +49 -0
- package/src/panels/timeline/marquee.ts +155 -0
- package/src/panels/timeline/model.ts +1860 -199
- package/src/panels/timeline/snap.ts +125 -43
- package/src/panels/timeline/staggerPreview.tsx +92 -0
- package/src/panels/timeline/takeMeasure.ts +72 -0
- package/src/panels/timeline/useLive.ts +48 -0
- package/src/panels/timeline/viewport.ts +234 -0
- package/src/panels/toolbar/BackChip.tsx +116 -0
- package/src/panels/toolbar/CameraViewBar.tsx +118 -49
- package/src/panels/toolbar/CanvasBar.tsx +117 -0
- package/src/panels/toolbar/InsertButton.tsx +38 -0
- package/src/panels/toolbar/IsolationTrail.tsx +156 -0
- package/src/panels/toolbar/PanelToggles.tsx +86 -0
- package/src/panels/toolbar/ShadingBar.tsx +119 -26
- package/src/panels/toolbar/ToolDockBar.tsx +329 -80
- package/src/panels/toolbar/ToolFooter.tsx +102 -0
- package/src/panels/toolbar/ViewModeBar.tsx +247 -38
- package/src/panels/toolbar/WorkPlaneBar.tsx +94 -0
- package/src/panels/toolbar/useAddRows.tsx +326 -0
- package/src/panels/toolbar/useDismiss.ts +122 -0
- package/src/panels/versions/VersionHistoryPanel.tsx +274 -0
- package/src/panels/versions/versionModel.ts +103 -0
- package/src/panels/virtualWindow.ts +271 -0
- package/src/shotTokens.css +151 -60
- package/src/stage/FrustumGizmo.tsx +504 -84
- package/src/stage/GridPlane.tsx +225 -16
- package/src/stage/NodePreviews.tsx +628 -0
- package/src/stage/PerfHud.tsx +437 -47
- package/src/stage/SplitRenderer.tsx +816 -169
- package/src/stage/StageApp.tsx +4274 -486
- package/src/stage/StageRootBoundary.tsx +114 -0
- package/src/stage/StageTools.tsx +651 -151
- package/src/stage/TransformGizmo.tsx +696 -0
- package/src/stage/adaptivePresentation.ts +145 -0
- package/src/stage/bridgeClient.ts +2522 -41
- package/src/stage/exportWorker.ts +9 -0
- package/src/stage/feed.ts +305 -14
- package/src/stage/fontPreview.ts +176 -0
- package/src/stage/frustumGeometry.ts +319 -1
- package/src/stage/furnitureTokens.ts +33 -0
- package/src/stage/gizmoMath.ts +508 -16
- package/src/stage/gpuTimer.ts +276 -0
- package/src/stage/instanceModules.ts +194 -33
- package/src/stage/interactions/move.ts +24 -5
- package/src/stage/isolation.ts +291 -33
- package/src/stage/jsSelfProfiler.ts +325 -0
- package/src/stage/main.tsx +116 -16
- package/src/stage/modalState.ts +222 -23
- package/src/stage/moduleEpoch.ts +60 -0
- package/src/stage/motionPathStage.ts +279 -0
- package/src/stage/motionPreview.ts +74 -0
- package/src/stage/overlays/Overlays.tsx +1112 -159
- package/src/stage/overlays/Rulers.tsx +495 -0
- package/src/stage/panes.ts +159 -10
- package/src/stage/project.ts +68 -3
- package/src/stage/renderScaleController.ts +730 -0
- package/src/stage/rulers.ts +129 -0
- package/src/stage/sceneExtent.ts +75 -0
- package/src/stage/shading.ts +20 -0
- package/src/stage/splineStage.ts +632 -0
- package/src/stage/stagePerformanceTelemetry.ts +717 -0
- package/src/stage/stageTheme.ts +36 -0
- package/src/stage/stageWarmup.ts +11 -0
- package/src/stage/textAutoSize.ts +124 -0
- package/src/stage/tier1Export.ts +641 -0
- package/src/stage/tokenColor.ts +14 -9
- package/src/stage/view.ts +56 -9
- package/src/stage/workingView.ts +277 -33
- package/src/state/accountPrefs.ts +261 -0
- package/src/state/agentActions.ts +173 -15
- package/src/state/agentActivity.ts +198 -0
- package/src/state/agentQuestion.ts +239 -0
- package/src/state/bootOpen.ts +42 -0
- package/src/state/cameraVerbs.ts +139 -0
- package/src/state/clientId.ts +6 -1
- package/src/state/comments.ts +1099 -0
- package/src/state/context.tsx +19 -1
- package/src/state/editorPrefs.ts +220 -0
- package/src/state/errorReports.ts +121 -0
- package/src/state/fieldWrite.ts +161 -0
- package/src/state/fontRecents.ts +70 -0
- package/src/state/fonts.ts +656 -0
- package/src/state/graphClipboard.ts +414 -0
- package/src/state/imageThumbs.ts +216 -0
- package/src/state/insert.ts +444 -67
- package/src/state/isolationTrail.ts +92 -0
- package/src/state/keyMoves.ts +134 -0
- package/src/state/keyframeClipboard.ts +177 -0
- package/src/state/keymap.ts +1153 -117
- package/src/state/layoutPrefs.ts +272 -0
- package/src/state/layouts.ts +205 -0
- package/src/state/measureMedia.ts +167 -0
- package/src/state/menu.ts +2341 -28
- package/src/state/motionStamp.ts +7 -0
- package/src/state/panelSize.ts +375 -5
- package/src/state/pendingChanges.ts +119 -0
- package/src/state/playHold.ts +78 -0
- package/src/state/playbackPreparation.ts +562 -0
- package/src/state/posterCapture.ts +242 -0
- package/src/state/presence.ts +254 -0
- package/src/state/projectComponents.ts +160 -0
- package/src/state/renderEvents.ts +298 -18
- package/src/state/runtimeErrors.ts +46 -0
- package/src/state/selection.ts +12 -0
- package/src/state/shortcuts.ts +1606 -0
- package/src/state/sketchGuide.ts +51 -0
- package/src/state/splineRefs.ts +87 -0
- package/src/state/stageHealth.ts +259 -0
- package/src/state/stageWatchdog.ts +47 -0
- package/src/state/stillStage.ts +330 -0
- package/src/state/stillThumbs.ts +474 -0
- package/src/state/store.ts +23591 -2982
- package/src/state/storyboardModel.ts +528 -0
- package/src/state/sync.ts +1257 -0
- package/src/state/syncCache.ts +159 -0
- package/src/state/triangles.ts +13 -0
- package/src/state/types.ts +1698 -55
- package/src/state/unsavedGuard.ts +49 -0
- package/src/state/viewMode.ts +200 -0
- package/src/state/webmcp.ts +1128 -0
- package/src/state/workspace.ts +1147 -0
- package/src/styles/assets-tab.css +304 -0
- package/src/styles/canvas.css +545 -0
- package/src/styles/comments.css +1435 -0
- package/src/styles/controls.css +162 -0
- package/src/styles/dialogs.css +390 -0
- package/src/styles/insert-drawer.css +586 -0
- package/src/styles/inspector-board.css +207 -0
- package/src/styles/inspector-object.css +486 -0
- package/src/styles/inspector-shot.css +252 -0
- package/src/styles/inspector.css +360 -0
- package/src/styles/left-column.css +1628 -0
- package/src/styles/library-preview.css +575 -0
- package/src/styles/library.css +512 -0
- package/src/styles/modes/code.css +569 -0
- package/src/styles/modes/component.css +1561 -0
- package/src/styles/modes/material.css +1201 -0
- package/src/styles/modes/storyboard.css +518 -0
- package/src/styles/share-component.css +49 -0
- package/src/styles/shell.css +653 -0
- package/src/styles/shot-brief.css +360 -0
- package/src/styles/states.css +76 -0
- package/src/styles/sync.css +380 -0
- package/src/styles/timeline-graph.css +384 -0
- package/src/styles/timeline-meters.css +89 -0
- package/src/styles/timeline-rows.css +1959 -0
- package/src/styles/timeline-sound.css +1003 -0
- package/src/styles/timeline.css +754 -0
- package/src/styles/tool-footer.css +525 -0
- package/src/styles/topbar.css +886 -0
- package/src/styles/versions.css +262 -0
- package/src/styles.css +72 -2921
- package/src/tools.ts +108 -23
- package/dist/assets/index-B7notge7.css +0 -1
- package/dist/assets/index-BBE6Ta9L.js +0 -52
- package/src/panels/files/icons.tsx +0 -118
- package/src/panels/header/DocSwitcher.tsx +0 -85
- package/src/panels/header/TransportBar.tsx +0 -86
- package/src/panels/inspector/animate/BlockTiming.tsx +0 -341
- package/src/panels/inspector/animate/DuringBlocks.tsx +0 -85
- package/src/panels/inspector/animate/InOutPresets.tsx +0 -57
- package/src/panels/inspector/animate/ResolvedChannels.tsx +0 -150
- package/src/panels/inspector/animate/useBlocks.ts +0 -43
- package/src/panels/material/MaterialPicker.tsx +0 -172
- package/src/panels/timeline/SceneStrip.tsx +0 -145
- package/src/panels/toolbar/AddMenu.tsx +0 -230
- package/src/stage/MoveGizmo.tsx +0 -411
|
@@ -0,0 +1,730 @@
|
|
|
1
|
+
// The playback render scale's controller (the playback plan's Phase 5): the
|
|
2
|
+
// share of the display's density a played frame renders at, lowered only while
|
|
3
|
+
// played frames miss their deadline and the GPU's measured time says the
|
|
4
|
+
// drawing is why. The stage hands it to the runtime as
|
|
5
|
+
// `ShotRuntimeProps.presentationScale`, whose child asks it once per played
|
|
6
|
+
// frame and draws the frame at that scale into an offscreen target, never
|
|
7
|
+
// resizing the canvas (packages/shot/src/presentationScale.ts).
|
|
8
|
+
//
|
|
9
|
+
// ── A REAL PROBLEM, AND THE GPU'S ──────────────────────────────────────────
|
|
10
|
+
//
|
|
11
|
+
// Two inputs, and a step down needs both:
|
|
12
|
+
//
|
|
13
|
+
// - THE DEADLINES. Every frame of the transport that reaches the canvas, as
|
|
14
|
+
// the stage's telemetry matches the runtime's echo to its publication
|
|
15
|
+
// (`presented`), timed by the clock read its request took before it was
|
|
16
|
+
// drawn: a gap between two frames is the first one's draw and the wait for
|
|
17
|
+
// the next refresh, so a frame drawn slower than the one before, in time
|
|
18
|
+
// for its refresh, is not late. A frame is late when it follows the one
|
|
19
|
+
// before by more than its cadence holds a frame on a 60 Hz display, plus
|
|
20
|
+
// half a refresh (`deadlineMs`), or when a refresh went by without a frame
|
|
21
|
+
// and it skipped a document frame (`MISSED_REFRESH_MS`): the transport runs
|
|
22
|
+
// on wall time, so a stage that cannot keep up skips frames rather than
|
|
23
|
+
// showing them late, and at 24, 25 and 50 fps a stage held to 20, 20 and 30
|
|
24
|
+
// fps shows only gaps the cadence shows too. A late frame counts once,
|
|
25
|
+
// however late, and a window has a problem only when `LATE_SHARE` of its
|
|
26
|
+
// frames came late: one long stall is one late frame, where an fps average
|
|
27
|
+
// reads it as many.
|
|
28
|
+
// - THE GPU'S TIME. three's timestamp queries time the render passes and
|
|
29
|
+
// nothing outside them (gpuTimer.ts). A frame missed because a texture
|
|
30
|
+
// uploaded, a pipeline compiled, a raster was cut or the main thread was
|
|
31
|
+
// busy costs the same at any resolution, and none of that is in the GPU's
|
|
32
|
+
// time, so such a film misses deadlines with its GPU time under the budget
|
|
33
|
+
// and never steps. A GPU whose clocks fell with such a film's load can read
|
|
34
|
+
// over it (cero's on the integrated GPU did): then the step's own verdict
|
|
35
|
+
// undoes it ("A STEP DOWN MUST PAY").
|
|
36
|
+
//
|
|
37
|
+
// Neither alone. GPU timestamps follow the GPU's clocks, and a lightly loaded
|
|
38
|
+
// GPU lowers its clocks, so its frame's milliseconds rise toward the period
|
|
39
|
+
// while it still has headroom (the RTX 3060 Laptop's graphics clock ranged
|
|
40
|
+
// 210 to 1965 MHz within single runs). Stepping on GPU time alone, the
|
|
41
|
+
// controller stepped down with every deadline met, and judged a step's relief
|
|
42
|
+
// by clocks coming back.
|
|
43
|
+
//
|
|
44
|
+
// ── THE RULES ──────────────────────────────────────────────────────────────
|
|
45
|
+
//
|
|
46
|
+
// - The budget is the document's frame period, and the steps are 1, 0.85, 0.7
|
|
47
|
+
// and 0.5 of the display's density (`RENDER_SCALE_STEPS`).
|
|
48
|
+
// - A window is the document's frame rate's worth of GPU samples, or a second
|
|
49
|
+
// of them once it holds `MIN_WINDOW_SAMPLES`, whichever closes it first:
|
|
50
|
+
// frames dropped to a GPU that is behind would otherwise stretch a window
|
|
51
|
+
// over several seconds, and three's WebGL timer loses the queries of the
|
|
52
|
+
// frames drawn while a resolve is out, so there it sees one frame in two or
|
|
53
|
+
// three. Its deadlines are the frames presented while it gathered them. With
|
|
54
|
+
// fewer than `MIN_WINDOW_SAMPLES` of those it says nothing of deadlines: it
|
|
55
|
+
// steps nothing down and judges no step.
|
|
56
|
+
// - A WINDOW IS ONE SHOT'S (`PresentationFrame.shot`). A frame of another
|
|
57
|
+
// Shot ends the window in progress, judged if it holds `MIN_WINDOW_SAMPLES`
|
|
58
|
+
// and dropped if not, and the `SAMPLE_LAG_FRAMES` samples after it are
|
|
59
|
+
// dropped too, the old Shot's last frames' that land after its ask. No gap
|
|
60
|
+
// ends or starts at a cut's frame (`PresentationFrame.cut`): the one ending
|
|
61
|
+
// there follows another picture, or a jump of the transport (a loop's wrap,
|
|
62
|
+
// a seek), and the one starting there is the cut's own draw, its compiles
|
|
63
|
+
// and uploads, which no resolution buys back. Two Shots draw two pictures,
|
|
64
|
+
// so a p75 of both describes neither, and a step is judged by its own
|
|
65
|
+
// Shot's windows ("A STEP DOWN MUST PAY").
|
|
66
|
+
// - A window whose frames miss their deadline while its GPU p75 is over
|
|
67
|
+
// `DOWN_SHARE` of the period steps DOWN, predictively: to the largest step
|
|
68
|
+
// s' predicted to fit under it. A p75 over the whole period is a severe miss
|
|
69
|
+
// and lands at the next frame; every other step lands at a cut, because
|
|
70
|
+
// pixel-sized post effects the chain cannot keep at the canvas's size (the
|
|
71
|
+
// tilt-shift spread, the bloom chain) change their look with the scale. A
|
|
72
|
+
// later window that steps nothing down, its deadlines met or its GPU inside
|
|
73
|
+
// the budget, withdraws a step still waiting for its cut.
|
|
74
|
+
// - `UP_WINDOWS` windows in a row whose prediction puts the step above under
|
|
75
|
+
// `UP_SHARE` of the period step UP one, at a cut, late frames or not: a film
|
|
76
|
+
// late for its main thread is made no later by pixels its GPU has room for.
|
|
77
|
+
// The gap between the two shares is the hysteresis. Under DVFS the rule can
|
|
78
|
+
// only err toward staying: below a step, a GPU with headroom lowers its
|
|
79
|
+
// clocks, so its time there overstates what the step above costs once its
|
|
80
|
+
// clocks follow the load up. And a step up that puts the GPU's time over the
|
|
81
|
+
// budget with every deadline met stays, where on GPU time alone it stepped
|
|
82
|
+
// back down, and climbed again four windows later.
|
|
83
|
+
// - THE PREDICTION. A frame below 1 is its own passes, which shrink with the
|
|
84
|
+
// area, and one pass up to the canvas, which covers the canvas whatever the
|
|
85
|
+
// scale (gpuTimer.ts times it apart): the present, three's output pass drawn
|
|
86
|
+
// at the canvas's density, for a Shot without post, or the copy of P for one
|
|
87
|
+
// with. So a step s' costs the window's content p75 times (s'/s)^2, plus the
|
|
88
|
+
// present's p75 at every scale, plus the copy's p75 when s' is below 1.
|
|
89
|
+
// Folded into the per-pixel term instead, the copy priced every step up too
|
|
90
|
+
// dear, and a film that fits at full density could stay below it for good.
|
|
91
|
+
// Priced as a copy, the present left a step up to 1 a canvas pass too cheap:
|
|
92
|
+
// a film whose output pass costs more than the hysteresis stepped up into a
|
|
93
|
+
// window over the budget, and down again, every few windows. A frame at 1
|
|
94
|
+
// draws the present's pass as three's own output pass, untagged, and a Shot
|
|
95
|
+
// with post draws none, so the two are each Shot's own, from its last window
|
|
96
|
+
// below 1 (`CanvasPasses`), and a window at 1 takes its Shot's present out of
|
|
97
|
+
// its time before the time scales. Counted in the per-pixel term and again
|
|
98
|
+
// whole, the output pass priced every step down from 1 too dear and chose a
|
|
99
|
+
// step lower than the film needed. A Shot that has not drawn below 1 is
|
|
100
|
+
// priced with the last window's passes below 1, another Shot's, which may be
|
|
101
|
+
// the other kind; before any, with none, which errs toward the higher step,
|
|
102
|
+
// and the next window takes another. The s^2 model is conservative upward
|
|
103
|
+
// besides: a pass's fixed cost does not shrink with the area, so a
|
|
104
|
+
// measurement below a step overstates it.
|
|
105
|
+
// - The window after every step is not judged: the timer's samples land one
|
|
106
|
+
// to three frames late, and the first frames at a new size allocate.
|
|
107
|
+
// - A STEP DOWN MUST PAY, in deadlines. Each Shot's step down is on trial
|
|
108
|
+
// until the first window of that Shot at the step's scale or below judges
|
|
109
|
+
// it (`#trials`). A window of another Shot says nothing of the step, and a
|
|
110
|
+
// step that waits for a cut is followed by the next Shot's windows: compared
|
|
111
|
+
// with them, a heavier Shot read as no relief, and a lighter one as relief.
|
|
112
|
+
// Another Shot's step down meanwhile leaves it on trial, judged below its
|
|
113
|
+
// scale. With one trial for the film the later step took its place, and the
|
|
114
|
+
// first stayed in force with no verdict: two Shots of a film late for its
|
|
115
|
+
// main thread, each stepping in turn, settled at 0.7 with a floor in each,
|
|
116
|
+
// though no step had paid. A scale above a step's ends its trial, the step
|
|
117
|
+
// no longer in force. The step paid when that window met its deadlines,
|
|
118
|
+
// whatever its GPU time: a GPU whose load fell lowers its clocks, so its
|
|
119
|
+
// time no longer falls with its work. Still late, it paid when its frames
|
|
120
|
+
// came faster (`typicalGap`) and its GPU p75 fell, each by `RELIEF_SHARE`:
|
|
121
|
+
// both, because frames that came faster while the GPU's time held came
|
|
122
|
+
// faster for something else, and GPU time that fell while the frames came
|
|
123
|
+
// no faster was not what held them (a film bound by its main thread: on GPU
|
|
124
|
+
// time alone, the integrated GPU kept cero at 0.85 though it played 24 to 26
|
|
125
|
+
// fps of 60 at every scale). Otherwise the step bought nothing, as it does
|
|
126
|
+
// for a film whose GPU time is draw calls and fixed passes, not pixels,
|
|
127
|
+
// which draws as long at 0.5 as at 1 and pays the copy on top. Such a step
|
|
128
|
+
// is undone at the next cut, back to the scale it was taken from with every
|
|
129
|
+
// step taken after it, whatever the windows before that cut say, and no
|
|
130
|
+
// window of that Shot steps below the scale the step was taken from until
|
|
131
|
+
// conditions change: one of its windows at that
|
|
132
|
+
// scale over `RETRY_LOAD` times the GPU load that prompted the step, or
|
|
133
|
+
// another Project, canvas size, mode or timer. Another Shot is not held back
|
|
134
|
+
// by it. Before this rule such a film stepped down window after window, each
|
|
135
|
+
// step predicted to fit, to the lowest step, and stayed there, softer and
|
|
136
|
+
// slower than at full density.
|
|
137
|
+
// - A transition window keeps the scale its first frame got
|
|
138
|
+
// (`PresentationSchedule`), and its frames are no samples and no deadlines:
|
|
139
|
+
// a dissolve draws both Shots, and a step it decided could land only at its
|
|
140
|
+
// own closing cut, softening the Shot after it to pay for frames already
|
|
141
|
+
// gone.
|
|
142
|
+
// - Pause draws at 1, because the child asks nothing while paused, and a
|
|
143
|
+
// stopped controller answers 1 whoever asks. What it settled on is kept for
|
|
144
|
+
// the next Play of the same Project at the same canvas size (`resting`, for
|
|
145
|
+
// which the stage holds its scaled targets while paused): another Project,
|
|
146
|
+
// or a canvas of another size, starts again from the top.
|
|
147
|
+
// - With no timer, Auto stays at 1: nothing measured, nothing lowered.
|
|
148
|
+
//
|
|
149
|
+
// `full` pins 1. `fast` is the steps from 0.7 down: it starts there, steps to
|
|
150
|
+
// 0.5 when the GPU needs it, and never goes above 0.7.
|
|
151
|
+
//
|
|
152
|
+
// ── THE THRESHOLDS ─────────────────────────────────────────────────────────
|
|
153
|
+
//
|
|
154
|
+
// Gate G1 (2026-10-24: an RTX 3060 Laptop on WebGPU, six films at device scale
|
|
155
|
+
// 1.5 and 2) measured GPU p95 at 0.5 to 3.7 ms against frame periods of 16.7
|
|
156
|
+
// to 41.7 ms, at most 0.15 of a period. That is far under `DOWN_SHARE`, so
|
|
157
|
+
// Auto never leaves 1 on such a GPU and full density alone is safe there. On
|
|
158
|
+
// the Linux test box's integrated GPU (AMD RDNA 2 through Vulkan, headless,
|
|
159
|
+
// device scale 2), tabs-a-launch-film played 19.2 fps of 60 at full density
|
|
160
|
+
// with a GPU p50 of 22 ms, and 26.9 to 27.8 at Auto, stepped to 0.5, with
|
|
161
|
+
// 11 ms. `LATE_SHARE` and `RELIEF_SHARE` are this rule's own, and no run has
|
|
162
|
+
// tuned them yet. With skipped frames counted, a fifth is the same bar at every
|
|
163
|
+
// rate: a stage that shows under five sixths of a film's frames, each of its
|
|
164
|
+
// frames skipping at most one. A 24 fps stage held to 20 fps, every frame
|
|
165
|
+
// three refreshes, skips one frame in five, the bar exactly, so whether its
|
|
166
|
+
// window reads late turns on where the window's edges fall.
|
|
167
|
+
import { DEFAULT_SHOT_FPS } from "@dailies-studio/shot/doc";
|
|
168
|
+
import type { PresentationFrame, PresentationScaleSource } from "@dailies-studio/shot";
|
|
169
|
+
import type { PlaybackResolution } from "../bridgeTypes.js";
|
|
170
|
+
import { percentile } from "./stagePerformanceTelemetry.js";
|
|
171
|
+
|
|
172
|
+
/** The scales a played frame may render at, as shares of the display's
|
|
173
|
+
* density, highest first. */
|
|
174
|
+
export const RENDER_SCALE_STEPS: readonly number[] = [1, 0.85, 0.7, 0.5];
|
|
175
|
+
/** `fast`: the steps from this one down. */
|
|
176
|
+
export const FAST_RENDER_SCALE = 0.7;
|
|
177
|
+
/** A window whose frames miss their deadline steps down while its GPU p75 is
|
|
178
|
+
* over this share of the frame period. */
|
|
179
|
+
export const DOWN_SHARE = 0.8;
|
|
180
|
+
/** One over the whole period is severe: it lands at the next frame. */
|
|
181
|
+
export const SEVERE_SHARE = 1;
|
|
182
|
+
/** A window is clean when the step above is predicted under this share. */
|
|
183
|
+
export const UP_SHARE = 0.7;
|
|
184
|
+
/** Clean windows in a row before a step up. */
|
|
185
|
+
export const UP_WINDOWS = 4;
|
|
186
|
+
/** A window misses its deadlines when this share of its presented frames, or
|
|
187
|
+
* more, came late (`deadlineMs`). */
|
|
188
|
+
export const LATE_SHARE = 0.2;
|
|
189
|
+
/** A window closes after this long once it holds `MIN_WINDOW_SAMPLES`: the
|
|
190
|
+
* second the stage's telemetry windows span (`ADAPTIVE_WINDOW_MS`,
|
|
191
|
+
* adaptivePresentation.ts), restated so this pure class imports no React. */
|
|
192
|
+
export const WINDOW_MS = 1_000;
|
|
193
|
+
/** The fewest samples a window closed by its second holds, and the fewest
|
|
194
|
+
* presented frames that say anything of its deadlines. */
|
|
195
|
+
export const MIN_WINDOW_SAMPLES = 8;
|
|
196
|
+
/** A step down whose Shot's next window is still late paid when that window's
|
|
197
|
+
* typical gap and its GPU p75 each fell by at least this share. */
|
|
198
|
+
export const RELIEF_SHARE = 0.1;
|
|
199
|
+
/** A step that did not pay is tried again once the load at the scale it was
|
|
200
|
+
* taken from is this many times the one that prompted it. */
|
|
201
|
+
export const RETRY_LOAD = 1.25;
|
|
202
|
+
/** The frames a timer sample lands after its draw, at most (gpuTimer.ts): the
|
|
203
|
+
* samples dropped after a Shot change, the old Shot's. */
|
|
204
|
+
export const SAMPLE_LAG_FRAMES = 3;
|
|
205
|
+
/** The most gaps a window keeps, its latest: ten seconds at 60 fps. Only a
|
|
206
|
+
* window no GPU sample closes for that long (no timer, or one that resolves
|
|
207
|
+
* nothing) gets that far. */
|
|
208
|
+
const GAPS_KEPT = 600;
|
|
209
|
+
|
|
210
|
+
/** A 60 Hz display's refresh, the one the playback benchmark judges its floor
|
|
211
|
+
* by (scripts/performance/floor.ts). */
|
|
212
|
+
const REFRESH_MS = 1_000 / 60;
|
|
213
|
+
|
|
214
|
+
// ponytail: a 60 Hz display assumed, so a faster one reads a frame one of its
|
|
215
|
+
// own refreshes late as on time; measure the refresh if that must count.
|
|
216
|
+
/** A gap past this went by a refresh without a frame. A frame of such a gap
|
|
217
|
+
* that skipped a document frame is late, whatever the cadence: the transport
|
|
218
|
+
* runs on wall time. Within one refresh a frame may skip by design, at a
|
|
219
|
+
* shuttle speed or a rate over the display's, which the stage is never told. */
|
|
220
|
+
export const MISSED_REFRESH_MS = 1.5 * REFRESH_MS;
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* The longest a presented frame may follow the one before and still have met
|
|
224
|
+
* its deadline: the most refreshes a 60 Hz display holds a frame of this
|
|
225
|
+
* cadence for, plus half a refresh. 24 fps alternates 33.3 and 50 ms there, so
|
|
226
|
+
* 58.3 ms; 30 and 60 fps hold every frame alike, so 41.7 and 25 ms. A rational
|
|
227
|
+
* rate a hair over a whole number of refreshes (29.97, 59.94) is held to that
|
|
228
|
+
* number, and its rare extra refresh reads as late. A stage that holds every
|
|
229
|
+
* frame that long and skips the ones it cannot show is caught by the frames
|
|
230
|
+
* it skips (`MISSED_REFRESH_MS`).
|
|
231
|
+
*/
|
|
232
|
+
export function deadlineMs(periodMs: number): number {
|
|
233
|
+
return (Math.ceil(periodMs / REFRESH_MS - 0.05) + 0.5) * REFRESH_MS;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** The gap a window's frames typically followed one another by: the mean of
|
|
237
|
+
* those at or under their p75. A stall is past it, and a mean is not held to
|
|
238
|
+
* the display's refreshes as a median is, so a film whose gaps straddle two
|
|
239
|
+
* refreshes does not read a refresh faster or slower from window to window. */
|
|
240
|
+
export function typicalGap(gaps: readonly number[]): number {
|
|
241
|
+
const p75 = percentile(gaps, 0.75)!;
|
|
242
|
+
const kept = gaps.filter((gap) => gap <= p75);
|
|
243
|
+
return kept.reduce((sum, gap) => sum + gap, 0) / kept.length;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** `?renderScale=` on the stage URL, forwarded by the chrome's `stageSrc`:
|
|
247
|
+
* the heavy proofs' arm. `off` is no scale source at all, the stage before
|
|
248
|
+
* the playback render scale existed; a step answers that step on every
|
|
249
|
+
* played frame, whatever the GPU or the Preferences row says. Anything else,
|
|
250
|
+
* absent included, is null: the controller decides. */
|
|
251
|
+
export function stageRenderScaleArm(value: string | null): "off" | number | null {
|
|
252
|
+
if (value === "off") return "off";
|
|
253
|
+
return RENDER_SCALE_STEPS.find((step) => String(step) === value) ?? null;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** The arm, read once, like `?rasterDensity` (adaptivePresentation.ts): the
|
|
257
|
+
* stage URL is set at iframe mount and never rewritten. */
|
|
258
|
+
export const STAGE_RENDER_SCALE: "off" | number | null =
|
|
259
|
+
typeof window === "undefined" ? null : stageRenderScaleArm(new URLSearchParams(window.location.search).get("renderScale"));
|
|
260
|
+
|
|
261
|
+
const ARM_SOURCE: PresentationScaleSource | null =
|
|
262
|
+
typeof STAGE_RENDER_SCALE === "number" ? fixedScale(STAGE_RENDER_SCALE) : null;
|
|
263
|
+
|
|
264
|
+
function fixedScale(scale: number): PresentationScaleSource {
|
|
265
|
+
return { scale: () => scale, resting: () => scale };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/** The source the stage hands the runtime and the split view: the
|
|
269
|
+
* controller, or the arm's (`STAGE_RENDER_SCALE`), null for `off`. The same
|
|
270
|
+
* object every call. */
|
|
271
|
+
export function stageScaleSource(controller: RenderScaleController): PresentationScaleSource | null {
|
|
272
|
+
if (STAGE_RENDER_SCALE === null) return controller;
|
|
273
|
+
return STAGE_RENDER_SCALE === "off" ? null : ARM_SOURCE;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** The controller as the HUD prints it. */
|
|
277
|
+
export interface RenderScaleReading {
|
|
278
|
+
mode: PlaybackResolution;
|
|
279
|
+
/** What a played frame renders at now (`inForce`). */
|
|
280
|
+
scale: number;
|
|
281
|
+
/** A step decided and waiting for its frame, or null. */
|
|
282
|
+
next: number | null;
|
|
283
|
+
/** The budget: the document's frame period, ms. */
|
|
284
|
+
periodMs: number;
|
|
285
|
+
/** The last judged window's GPU p75, ms, at the scale it drew at. */
|
|
286
|
+
gpuP75Ms: number | null;
|
|
287
|
+
/** The last judged window's frames that missed their deadline, of those it
|
|
288
|
+
* presented, or null when it presented too few to say. */
|
|
289
|
+
late: { frames: number; of: number } | null;
|
|
290
|
+
/** The renderer has a GPU timer. */
|
|
291
|
+
timer: boolean;
|
|
292
|
+
/** Why the scale last moved, or null before it did. */
|
|
293
|
+
reason: string | null;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** One frame's GPU time, and the shares of it its passes up to the canvas
|
|
297
|
+
* took: the copy of P, and the present (the header's prediction). */
|
|
298
|
+
interface Sample {
|
|
299
|
+
gpuMs: number;
|
|
300
|
+
copyMs: number;
|
|
301
|
+
presentMs: number;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** A window's deadlines: how many of its presented frames came late, of how
|
|
305
|
+
* many, and the gap they typically followed one another by. */
|
|
306
|
+
interface Deadlines {
|
|
307
|
+
late: number;
|
|
308
|
+
presented: number;
|
|
309
|
+
typicalMs: number;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/** How long a presented frame followed the one before, and how many document
|
|
313
|
+
* frames it moved on. */
|
|
314
|
+
interface Gap {
|
|
315
|
+
ms: number;
|
|
316
|
+
frames: number;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/** A Shot's passes up to the canvas, p75 ms, from its last window below 1:
|
|
320
|
+
* what a frame of it adds at every scale (the present, which a frame at 1
|
|
321
|
+
* draws untagged as three's output pass, and a Shot with post never draws)
|
|
322
|
+
* and below 1 (the copy of P). */
|
|
323
|
+
interface CanvasPasses {
|
|
324
|
+
copyMs: number;
|
|
325
|
+
presentMs: number;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const NO_CANVAS_PASSES: CanvasPasses = { copyMs: 0, presentMs: 0 };
|
|
329
|
+
|
|
330
|
+
/** A step down and what decided it: the p75 it was taken at, what the
|
|
331
|
+
* prediction said it would cost, the Shot of the window that did, and that
|
|
332
|
+
* window's typical gap. */
|
|
333
|
+
interface Step {
|
|
334
|
+
from: number;
|
|
335
|
+
to: number;
|
|
336
|
+
p75: number;
|
|
337
|
+
predicted: number;
|
|
338
|
+
shot: string | null;
|
|
339
|
+
typicalMs: number;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/** In one Shot, a step below `scale` did not pay at `load` there. */
|
|
343
|
+
interface Floor {
|
|
344
|
+
scale: number;
|
|
345
|
+
load: number;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const ms = (value: number): string => `${value.toFixed(1)}ms`;
|
|
349
|
+
const lateText = (deadlines: Deadlines): string => `${deadlines.late} of ${deadlines.presented} frames late`;
|
|
350
|
+
|
|
351
|
+
export class RenderScaleController implements PresentationScaleSource {
|
|
352
|
+
#mode: PlaybackResolution = "auto";
|
|
353
|
+
#timer = false;
|
|
354
|
+
#playing = false;
|
|
355
|
+
#periodMs = 0;
|
|
356
|
+
#windowSize = 1;
|
|
357
|
+
/** The canvas's backing pixels the kept scale was settled for. */
|
|
358
|
+
#pixels = 0;
|
|
359
|
+
/** The scale the frames render at: applied only as a frame asks, or by a
|
|
360
|
+
* mode or timer change. Kept across Pause, so the next Play starts here. */
|
|
361
|
+
#scale = 1;
|
|
362
|
+
/** A frame asked this run, so `#scale` is in force. Nothing asks in a
|
|
363
|
+
* layout the child does not draw, and no window is judged there. */
|
|
364
|
+
#asked = false;
|
|
365
|
+
/** The frame asking last was inside a transition window. */
|
|
366
|
+
#blending = false;
|
|
367
|
+
/** The controller's own step, waiting for a cut, or for the next frame
|
|
368
|
+
* when `#now`. */
|
|
369
|
+
#pending: number | null = null;
|
|
370
|
+
#now = false;
|
|
371
|
+
/** `#pending` undoes a step that did not pay: no window moves it before
|
|
372
|
+
* the cut it waits for. */
|
|
373
|
+
#undoing = false;
|
|
374
|
+
#samples: Sample[] = [];
|
|
375
|
+
/** The gaps between the frames presented while the window in progress
|
|
376
|
+
* gathered its samples. */
|
|
377
|
+
#gaps: Gap[] = [];
|
|
378
|
+
/** The last frame presented that a gap may start at: when its request was
|
|
379
|
+
* timed and its document frame. Null at the start of a run, at a cut's
|
|
380
|
+
* frame and inside a transition window (the header's "A WINDOW IS ONE
|
|
381
|
+
* SHOT'S"). */
|
|
382
|
+
#shown: { atMs: number; frame: number } | null = null;
|
|
383
|
+
/** The frame asking last is a cut's, until it is presented. */
|
|
384
|
+
#cut = false;
|
|
385
|
+
/** The Shot the window in progress draws: undefined until a frame asks. */
|
|
386
|
+
#windowShot: string | null | undefined = undefined;
|
|
387
|
+
/** Samples still to drop after a Shot change. */
|
|
388
|
+
#skip = 0;
|
|
389
|
+
/** When the window in progress took its first sample. */
|
|
390
|
+
#windowStart = 0;
|
|
391
|
+
/** The next whole window is not judged: the one after a step. */
|
|
392
|
+
#settle = false;
|
|
393
|
+
#clean = 0;
|
|
394
|
+
#lastP75: number | null = null;
|
|
395
|
+
#lastDeadlines: Deadlines | null = null;
|
|
396
|
+
/** By Shot, its passes up to the canvas, from its last window judged below
|
|
397
|
+
* 1 (the header's prediction). */
|
|
398
|
+
#canvas = new Map<string | null, CanvasPasses>();
|
|
399
|
+
/** The last window's below 1, any Shot's: what a Shot that has not drawn
|
|
400
|
+
* below 1 is priced with. */
|
|
401
|
+
#lastCanvas: CanvasPasses = NO_CANVAS_PASSES;
|
|
402
|
+
#reason: string | null = null;
|
|
403
|
+
/** The step down waiting in `#pending`, with what decided it. */
|
|
404
|
+
#decided: Step | null = null;
|
|
405
|
+
/** By Shot, its step down in force, until the first window of that Shot at
|
|
406
|
+
* the step's scale or below says whether it paid. */
|
|
407
|
+
#trials = new Map<string, Step>();
|
|
408
|
+
/** By Shot: a step below `scale` did not pay at `load` there, so no window
|
|
409
|
+
* of the Shot tries one while they stay under `RETRY_LOAD` times it. */
|
|
410
|
+
#floors = new Map<string, Floor>();
|
|
411
|
+
|
|
412
|
+
constructor() {
|
|
413
|
+
this.setFrameRate(DEFAULT_SHOT_FPS);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/** The Preferences row (bridge v52 `EditorChrome.playbackResolution`). */
|
|
417
|
+
setMode(mode: PlaybackResolution): void {
|
|
418
|
+
if (mode === this.#mode) return;
|
|
419
|
+
this.#mode = mode;
|
|
420
|
+
this.#retarget(mode);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/** Whether the renderer can time a pass (`GpuTimer`'s answer). */
|
|
424
|
+
setTimer(available: boolean): void {
|
|
425
|
+
if (available === this.#timer) return;
|
|
426
|
+
this.#timer = available;
|
|
427
|
+
this.#retarget(available ? "gpu timer" : "no gpu timer");
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/** The document's rate: the budget, and how many frames make a window. */
|
|
431
|
+
setFrameRate(fps: { num: number; den: number }): void {
|
|
432
|
+
if (!(fps.num > 0 && fps.den > 0 && Number.isFinite(fps.num / fps.den))) return;
|
|
433
|
+
this.#periodMs = (1000 * fps.den) / fps.num;
|
|
434
|
+
this.#windowSize = Math.max(1, Math.round(fps.num / fps.den));
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/** The canvas's backing pixels (CSS size times density). A scale settled
|
|
438
|
+
* for another size says nothing about this one: a step that fit a smaller
|
|
439
|
+
* canvas misses on a larger one, and one a larger canvas needed is too soft
|
|
440
|
+
* for a smaller, so it starts again from the top. */
|
|
441
|
+
setCanvasPixels(pixels: number): void {
|
|
442
|
+
if (!(pixels > 0) || pixels === this.#pixels) return;
|
|
443
|
+
const first = this.#pixels === 0;
|
|
444
|
+
this.#pixels = pixels;
|
|
445
|
+
if (!first) this.reset();
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/** The transport runs. Only its frames are samples and deadlines: a
|
|
449
|
+
* preparing pass's and a paused stage's draw at full density and decide
|
|
450
|
+
* nothing. */
|
|
451
|
+
setPlaying(playing: boolean): void {
|
|
452
|
+
if (playing === this.#playing) return;
|
|
453
|
+
this.#playing = playing;
|
|
454
|
+
this.#samples = [];
|
|
455
|
+
this.#gaps = [];
|
|
456
|
+
this.#shown = null;
|
|
457
|
+
this.#cut = false;
|
|
458
|
+
this.#windowShot = undefined;
|
|
459
|
+
this.#skip = 0;
|
|
460
|
+
this.#asked = false;
|
|
461
|
+
this.#blending = false;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/** Another Project: what this one settled on says nothing about it. */
|
|
465
|
+
reset(): void {
|
|
466
|
+
this.#scale = this.#steps()[0]!;
|
|
467
|
+
this.#pending = null;
|
|
468
|
+
this.#now = false;
|
|
469
|
+
this.#undoing = false;
|
|
470
|
+
this.#samples = [];
|
|
471
|
+
this.#gaps = [];
|
|
472
|
+
this.#shown = null;
|
|
473
|
+
this.#cut = false;
|
|
474
|
+
this.#settle = false;
|
|
475
|
+
this.#clean = 0;
|
|
476
|
+
this.#lastP75 = null;
|
|
477
|
+
this.#lastDeadlines = null;
|
|
478
|
+
this.#canvas.clear();
|
|
479
|
+
this.#lastCanvas = NO_CANVAS_PASSES;
|
|
480
|
+
this.#reason = null;
|
|
481
|
+
this.#decided = null;
|
|
482
|
+
this.#trials.clear();
|
|
483
|
+
this.#floors.clear();
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/** The scale the next Play's first frame gets (`PresentationScaleSource
|
|
487
|
+
* .resting`): the one kept across Pause, or a step waiting for that frame,
|
|
488
|
+
* which is a cut. The stage's idle warm-up holds its scaled targets for it. */
|
|
489
|
+
resting(): number {
|
|
490
|
+
return this.#pending ?? this.#scale;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/** One frame's GPU time, ms, as the timer resolved it at `atMs`, and how
|
|
494
|
+
* much of it was the copy of P and the present (gpuTimer.ts). A frame
|
|
495
|
+
* inside a transition window is no sample. */
|
|
496
|
+
observe(gpuMs: number, atMs: number, copyMs = 0, presentMs = 0): void {
|
|
497
|
+
if (!this.#playing || this.#blending || !(gpuMs >= 0 && Number.isFinite(gpuMs))) return;
|
|
498
|
+
if (this.#skip > 0) {
|
|
499
|
+
this.#skip -= 1;
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
if (this.#samples.length === 0) this.#windowStart = atMs;
|
|
503
|
+
const copy = copyMs > 0 && Number.isFinite(copyMs) ? Math.min(copyMs, gpuMs) : 0;
|
|
504
|
+
const present = presentMs > 0 && Number.isFinite(presentMs) ? Math.min(presentMs, gpuMs - copy) : 0;
|
|
505
|
+
this.#samples.push({ gpuMs, copyMs: copy, presentMs: present });
|
|
506
|
+
const full = this.#samples.length >= this.#windowSize;
|
|
507
|
+
const timed = this.#samples.length >= MIN_WINDOW_SAMPLES && atMs - this.#windowStart >= WINDOW_MS;
|
|
508
|
+
if (full || timed) this.#closeWindow();
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/** Document frame `frame` of the transport reached the canvas, its request
|
|
512
|
+
* timed at `atMs` before it was drawn (bridgeClient.ts, the telemetry's
|
|
513
|
+
* match of the runtime's echo): the deadline half of a window. No gap
|
|
514
|
+
* starts at a cut's frame or a transition window's, nor ends at one (the
|
|
515
|
+
* header's "A WINDOW IS ONE SHOT'S"). */
|
|
516
|
+
presented(atMs: number, frame: number): void {
|
|
517
|
+
if (!this.#playing || !Number.isFinite(atMs) || !Number.isFinite(frame)) return;
|
|
518
|
+
const last = this.#shown;
|
|
519
|
+
this.#shown = this.#blending || this.#cut ? null : { atMs, frame };
|
|
520
|
+
this.#cut = false;
|
|
521
|
+
if (last === null || this.#blending || atMs < last.atMs) return;
|
|
522
|
+
if (this.#gaps.length >= GAPS_KEPT) this.#gaps.shift();
|
|
523
|
+
this.#gaps.push({ ms: atMs - last.atMs, frames: Math.abs(frame - last.frame) });
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/** The window in progress is over: judged, unless it is the one after a
|
|
527
|
+
* step. */
|
|
528
|
+
#closeWindow(): void {
|
|
529
|
+
const window = this.#samples;
|
|
530
|
+
const gaps = this.#gaps;
|
|
531
|
+
this.#samples = [];
|
|
532
|
+
this.#gaps = [];
|
|
533
|
+
if (this.#settle) {
|
|
534
|
+
this.#settle = false;
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
if (this.#asked) this.#judge(window, this.#deadlines(gaps), this.#windowShot ?? null);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/** What a window's presented frames say of its deadlines, or null when
|
|
541
|
+
* there were too few of them to say anything. */
|
|
542
|
+
#deadlines(gaps: readonly Gap[]): Deadlines | null {
|
|
543
|
+
if (gaps.length < MIN_WINDOW_SAMPLES) return null;
|
|
544
|
+
const allowed = deadlineMs(this.#periodMs);
|
|
545
|
+
const late = gaps.filter((gap) => gap.ms > allowed || (gap.frames > 1 && gap.ms > MISSED_REFRESH_MS)).length;
|
|
546
|
+
return { late, presented: gaps.length, typicalMs: typicalGap(gaps.map((gap) => gap.ms)) };
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/** The child's question, once per played frame (`PresentationScaleSource`).
|
|
550
|
+
* Only a transition window's first frame asks, and the window keeps that
|
|
551
|
+
* answer; its frames are no samples until the next ask, its closing cut. A
|
|
552
|
+
* stopped controller answers 1: a preparing pass draws at full density
|
|
553
|
+
* whoever asks. */
|
|
554
|
+
scale(frame: PresentationFrame): number {
|
|
555
|
+
if (!this.#playing) return 1;
|
|
556
|
+
this.#asked = true;
|
|
557
|
+
this.#blending = frame.blending;
|
|
558
|
+
// The header's "A WINDOW IS ONE SHOT'S": no gap ends at a cut's frame,
|
|
559
|
+
// and none starts there once it is presented.
|
|
560
|
+
this.#cut = frame.cut;
|
|
561
|
+
if (frame.cut) this.#shown = null;
|
|
562
|
+
if (frame.shot !== this.#windowShot) {
|
|
563
|
+
if (this.#windowShot !== undefined) {
|
|
564
|
+
if (this.#samples.length >= MIN_WINDOW_SAMPLES) this.#closeWindow();
|
|
565
|
+
else {
|
|
566
|
+
this.#samples = [];
|
|
567
|
+
this.#gaps = [];
|
|
568
|
+
}
|
|
569
|
+
this.#skip = SAMPLE_LAG_FRAMES;
|
|
570
|
+
}
|
|
571
|
+
this.#windowShot = frame.shot;
|
|
572
|
+
this.#shown = null;
|
|
573
|
+
}
|
|
574
|
+
if (this.#pending !== null && (frame.cut || this.#now)) this.#apply(this.#pending, this.#reason);
|
|
575
|
+
return this.#scale;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/** What a played frame renders at now: 1 while stopped, and before a frame
|
|
579
|
+
* asked this run. */
|
|
580
|
+
inForce(): number {
|
|
581
|
+
return this.#playing && this.#asked ? this.#scale : 1;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
reading(): RenderScaleReading {
|
|
585
|
+
const deadlines = this.#lastDeadlines;
|
|
586
|
+
return {
|
|
587
|
+
mode: this.#mode,
|
|
588
|
+
scale: this.inForce(),
|
|
589
|
+
next: this.#pending,
|
|
590
|
+
periodMs: this.#periodMs,
|
|
591
|
+
gpuP75Ms: this.#lastP75,
|
|
592
|
+
late: deadlines === null ? null : { frames: deadlines.late, of: deadlines.presented },
|
|
593
|
+
timer: this.#timer,
|
|
594
|
+
reason: this.#reason,
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/** The steps this mode may take, highest first. Without a timer nothing is
|
|
599
|
+
* measured, so nothing goes below where the mode starts. */
|
|
600
|
+
#steps(): readonly number[] {
|
|
601
|
+
if (this.#mode === "full") return [1];
|
|
602
|
+
const steps = this.#mode === "fast" ? RENDER_SCALE_STEPS.filter((step) => step <= FAST_RENDER_SCALE) : RENDER_SCALE_STEPS;
|
|
603
|
+
return this.#timer ? steps : steps.slice(0, 1);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/** The mode or the timer moved the steps: the highest one at or below the
|
|
607
|
+
* scale, else the lowest, at once. A setting the author changed shows. */
|
|
608
|
+
#retarget(reason: string): void {
|
|
609
|
+
const steps = this.#steps();
|
|
610
|
+
this.#pending = null;
|
|
611
|
+
this.#now = false;
|
|
612
|
+
this.#undoing = false;
|
|
613
|
+
this.#clean = 0;
|
|
614
|
+
// Other steps: what the old ones learned says nothing about them.
|
|
615
|
+
this.#decided = null;
|
|
616
|
+
this.#trials.clear();
|
|
617
|
+
this.#floors.clear();
|
|
618
|
+
const target = steps.find((step) => step <= this.#scale) ?? steps.at(-1)!;
|
|
619
|
+
if (target !== this.#scale) this.#apply(target, reason);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
#apply(scale: number, reason: string | null): void {
|
|
623
|
+
const decided = this.#decided;
|
|
624
|
+
this.#pending = null;
|
|
625
|
+
this.#now = false;
|
|
626
|
+
this.#undoing = false;
|
|
627
|
+
this.#decided = null;
|
|
628
|
+
if (scale === this.#scale) return;
|
|
629
|
+
// A step down the windows of a Shot chose is on trial until a window of
|
|
630
|
+
// that Shot judges it, whatever steps follow it, and a scale above a
|
|
631
|
+
// step's ends its trial (the header's "A STEP DOWN MUST PAY").
|
|
632
|
+
for (const [shot, trial] of this.#trials) if (trial.to < scale) this.#trials.delete(shot);
|
|
633
|
+
if (decided !== null && decided.shot !== null && decided.from === this.#scale && decided.to === scale) this.#trials.set(decided.shot, decided);
|
|
634
|
+
this.#scale = scale;
|
|
635
|
+
this.#reason = reason;
|
|
636
|
+
this.#clean = 0;
|
|
637
|
+
this.#samples = [];
|
|
638
|
+
this.#gaps = [];
|
|
639
|
+
this.#settle = true;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
#judge(window: readonly Sample[], deadlines: Deadlines | null, shot: string | null): void {
|
|
643
|
+
const p75 = percentile(window.map((sample) => sample.gpuMs), 0.75)!;
|
|
644
|
+
this.#lastP75 = p75;
|
|
645
|
+
this.#lastDeadlines = deadlines;
|
|
646
|
+
const steps = this.#steps();
|
|
647
|
+
const scale = this.#scale;
|
|
648
|
+
const down = DOWN_SHARE * this.#periodMs;
|
|
649
|
+
// The header's "A REAL PROBLEM, AND THE GPU'S": its frames late.
|
|
650
|
+
const late = deadlines !== null && deadlines.late >= LATE_SHARE * deadlines.presented;
|
|
651
|
+
// This Shot's step down in force, judged by the first window of the Shot
|
|
652
|
+
// at its scale or below that says anything of deadlines (the header's "A
|
|
653
|
+
// STEP DOWN MUST PAY"). Another Shot's window leaves it waiting.
|
|
654
|
+
const trial = shot === null ? undefined : this.#trials.get(shot);
|
|
655
|
+
if (shot !== null && trial !== undefined && deadlines !== null) {
|
|
656
|
+
this.#trials.delete(shot);
|
|
657
|
+
if (late && !(deadlines.typicalMs <= (1 - RELIEF_SHARE) * trial.typicalMs && p75 <= (1 - RELIEF_SHARE) * trial.p75)) {
|
|
658
|
+
this.#floors.set(shot, { scale: trial.from, load: trial.p75 });
|
|
659
|
+
this.#clean = 0;
|
|
660
|
+
this.#pending = trial.from;
|
|
661
|
+
this.#undoing = true;
|
|
662
|
+
this.#decided = null;
|
|
663
|
+
this.#now = false;
|
|
664
|
+
this.#reason = `no relief at ${scale}: ${lateText(deadlines)}, gpu p75 ${ms(p75)} against ${ms(trial.p75)} at ${trial.from}`;
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
// The undo of a step that bought nothing waits for its cut, whatever the
|
|
669
|
+
// windows before it say: over the budget, the floor leaves them no step
|
|
670
|
+
// to take, and under it, no step up to keep.
|
|
671
|
+
if (this.#undoing) return;
|
|
672
|
+
// A load past the one a step did not pay at, in that Shot, is new
|
|
673
|
+
// conditions there.
|
|
674
|
+
let floor = shot === null ? undefined : this.#floors.get(shot);
|
|
675
|
+
if (shot !== null && floor !== undefined && scale === floor.scale && p75 > RETRY_LOAD * floor.load) {
|
|
676
|
+
this.#floors.delete(shot);
|
|
677
|
+
floor = undefined;
|
|
678
|
+
}
|
|
679
|
+
if (scale < 1) {
|
|
680
|
+
this.#lastCanvas = {
|
|
681
|
+
copyMs: percentile(window.map((sample) => sample.copyMs), 0.75)!,
|
|
682
|
+
presentMs: percentile(window.map((sample) => sample.presentMs), 0.75)!,
|
|
683
|
+
};
|
|
684
|
+
this.#canvas.set(shot, this.#lastCanvas);
|
|
685
|
+
}
|
|
686
|
+
// What the window's own passes would have cost at the display's density:
|
|
687
|
+
// a step's prediction is this times its area, the present on top of it,
|
|
688
|
+
// and the copy below 1 (the header's prediction). At 1 the present is in
|
|
689
|
+
// the frames' time untagged, three's own output pass.
|
|
690
|
+
const passes = this.#canvas.get(shot) ?? this.#lastCanvas;
|
|
691
|
+
const own = percentile(window.map((sample) => sample.gpuMs - sample.copyMs - sample.presentMs), 0.75)!;
|
|
692
|
+
const perPixel = Math.max(0, own - (scale === 1 ? passes.presentMs : 0)) / (scale * scale);
|
|
693
|
+
const cost = (step: number): number => perPixel * step * step + passes.presentMs + (step < 1 ? passes.copyMs : 0);
|
|
694
|
+
if (late && p75 > down) {
|
|
695
|
+
this.#clean = 0;
|
|
696
|
+
// Not below where a step did not pay in this Shot.
|
|
697
|
+
const lowest = floor?.scale ?? 0;
|
|
698
|
+
const below = steps.filter((step) => step < scale && step >= lowest);
|
|
699
|
+
const target = below.find((step) => cost(step) <= down) ?? below.at(-1);
|
|
700
|
+
if (target === undefined) {
|
|
701
|
+
this.#pending = null;
|
|
702
|
+
this.#decided = null;
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
this.#pending = target;
|
|
706
|
+
this.#decided = { from: scale, to: target, p75, predicted: cost(target), shot, typicalMs: deadlines.typicalMs };
|
|
707
|
+
this.#now = p75 > SEVERE_SHARE * this.#periodMs;
|
|
708
|
+
this.#reason = `${lateText(deadlines)}, gpu p75 ${ms(p75)} over ${ms(down)}${this.#now ? ", severe" : ""}`;
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
if (this.#pending !== null && this.#pending < scale) {
|
|
712
|
+
this.#pending = null;
|
|
713
|
+
this.#decided = null;
|
|
714
|
+
}
|
|
715
|
+
// The step just above: the last of those higher, highest first.
|
|
716
|
+
const up = steps.filter((step) => step > scale).at(-1);
|
|
717
|
+
const headroom = UP_SHARE * this.#periodMs;
|
|
718
|
+
if (up !== undefined && cost(up) <= headroom) {
|
|
719
|
+
this.#clean += 1;
|
|
720
|
+
if (this.#clean < UP_WINDOWS) return;
|
|
721
|
+
this.#pending = up;
|
|
722
|
+
this.#decided = null;
|
|
723
|
+
this.#now = false;
|
|
724
|
+
this.#reason = `${UP_WINDOWS} windows with ${ms(cost(up))} predicted under ${ms(headroom)}`;
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
this.#clean = 0;
|
|
728
|
+
if (this.#pending !== null && this.#pending > scale) this.#pending = null;
|
|
729
|
+
}
|
|
730
|
+
}
|