diff --git a/typescript/packages/subsurface-viewer/src/SubsurfaceViewer.tsx b/typescript/packages/subsurface-viewer/src/SubsurfaceViewer.tsx index f659efc292..4290163854 100644 --- a/typescript/packages/subsurface-viewer/src/SubsurfaceViewer.tsx +++ b/typescript/packages/subsurface-viewer/src/SubsurfaceViewer.tsx @@ -18,6 +18,26 @@ import { TGrid3DColoringMode } from "./layers/grid3d/grid3dLayer"; import Map, { createLayers } from "./components/Map"; +// Type extracted from Deck.gl. +export type DeckMetrics = { + /* eslint-disable*/ + fps: number; // fps - average number of frames rendered per second + setPropsTime: number; // setPropsTime - time spent setting deck properties + updateAttributesTime: number; // updateAttributesTime - time spent updating layer attributes + framesRedrawn: number; // framesRedrawn - number of times the scene was rendered + pickTime: number; // pickTime - total time spent on picking operations + pickCount: number; // pickCount - number of times a pick operation was performed + gpuTime: number; // gpuTime - total time spent on GPU processing + gpuTimePerFrame: number; // gpuTimePerFrame - average time spent on GPU processing per frame + cpuTime: number; // cpuTime - total time spent on CPU processing + cpuTimePerFrame: number; // cpuTimePerFrame - average time spent on CPU processing per frame + bufferMemory: number; // bufferMemory - total GPU memory allocated for buffers + textureMemory: number; // textureMemory - total GPU memory allocated for textures + renderbufferMemory: number; // renderbufferMemory - total GPU memory allocated for renderbuffers + gpuMemory: number; // gpuMemory - total allocated GPU memory + /* eslint-enable*/ +}; + export type { BoundsAccessor, colorTablesArray, @@ -80,6 +100,11 @@ export interface SubsurfaceViewerProps * instead. */ setProps?: (data: Record) => void; + + /** + * Callback called from deck.gl whith metrics data. + */ + onMetrics?: ((m: DeckMetrics) => void) | null; } const SubsurfaceViewer: React.FC = ({ @@ -108,6 +133,7 @@ const SubsurfaceViewer: React.FC = ({ lights, children, verticalScale, + onMetrics, ...args }: SubsurfaceViewerProps) => { // Contains layers data received from map layers by user interaction @@ -188,6 +214,7 @@ const SubsurfaceViewer: React.FC = ({ triggerResetMultipleWells={triggerResetMultipleWells} lights={lights} verticalScale={verticalScale} + onMetrics={onMetrics ?? undefined} {...args} > {children} diff --git a/typescript/packages/subsurface-viewer/src/components/Map.tsx b/typescript/packages/subsurface-viewer/src/components/Map.tsx index 133e9479c4..78a3daf281 100644 --- a/typescript/packages/subsurface-viewer/src/components/Map.tsx +++ b/typescript/packages/subsurface-viewer/src/components/Map.tsx @@ -69,7 +69,11 @@ import StatusIndicator from "./StatusIndicator"; import type { Unit } from "convert-units"; import IntersectionView from "../views/intersectionView"; -import type { LightsType, TLayerDefinition } from "../SubsurfaceViewer"; +import type { + LightsType, + TLayerDefinition, + DeckMetrics, +} from "../SubsurfaceViewer"; import { getZoom, useLateralZoom } from "../utils/camera"; import { useScaleFactor, useShiftHeld } from "../utils/event"; @@ -399,6 +403,11 @@ export interface MapProps { * The reference to the deck.gl instance. */ deckGlRef?: React.ForwardedRef; + + /** + * Callback called from deck.gl whith metrics data. + */ + onMetrics?: ((m: DeckMetrics) => void) | null; } function defaultTooltip(info: PickingInfo) { @@ -446,6 +455,7 @@ const Map: React.FC = ({ innerRef, pickingRadius, deckGlRef, + onMetrics, }: MapProps) => { // From react doc, ref should not be read nor modified during rendering. const deckRef = React.useRef(null); @@ -884,6 +894,7 @@ const Map: React.FC = ({ onDrag={onDrag} onResize={onResize} pickingRadius={pickingRadius} + _onMetrics={onMetrics} > {children} diff --git a/typescript/packages/subsurface-viewer/src/components/Metrics.tsx b/typescript/packages/subsurface-viewer/src/components/Metrics.tsx new file mode 100644 index 0000000000..6b21ded96e --- /dev/null +++ b/typescript/packages/subsurface-viewer/src/components/Metrics.tsx @@ -0,0 +1,60 @@ +import React, { useState } from "react"; +import { forwardRef, useImperativeHandle } from "react"; +import type { DeckMetrics } from "../SubsurfaceViewer"; + +export const Metrics = forwardRef((_props, ref) => { + const [metrics, setMetrics] = useState(); + + const publicRef = { + updateMetrics: (m: DeckMetrics) => { + setMetrics({ ...m }); + }, + }; + + useImperativeHandle(ref, () => publicRef); + + const isDefined = typeof metrics === "object"; + if (!isDefined) { + return null; + } + + const tableRows = []; + for (const key in metrics) { + if (Object.prototype.hasOwnProperty.call(metrics, key)) { + const typedKey = key as keyof DeckMetrics; + tableRows.push( + + {typedKey} + {metrics[typedKey]?.toFixed(1)} + + ); + } + } + + return ( +
+ + + + + + {tableRows} + +
{"Metrics"}
+
+ ); +}); + +Metrics.displayName = "MetricsComponent"; +export default Metrics; diff --git a/typescript/packages/subsurface-viewer/src/storybook/layers/MapLayer.stories.tsx b/typescript/packages/subsurface-viewer/src/storybook/layers/MapLayer.stories.tsx index 3ce4817186..e6e49a09bc 100644 --- a/typescript/packages/subsurface-viewer/src/storybook/layers/MapLayer.stories.tsx +++ b/typescript/packages/subsurface-viewer/src/storybook/layers/MapLayer.stories.tsx @@ -7,9 +7,12 @@ import { ClipExtension } from "@deck.gl/extensions"; import type { SubsurfaceViewerProps } from "../../SubsurfaceViewer"; import SubsurfaceViewer from "../../SubsurfaceViewer"; import InfoCard from "../../components/InfoCard"; +import Metrics from "../../components/Metrics"; +import type { DeckMetrics } from "../../SubsurfaceViewer"; import type { BoundingBox2D, BoundingBox3D, + BoundsAccessor, ViewsType, } from "../../components/Map"; import { useHoverInfo } from "../../components/Map"; @@ -315,9 +318,47 @@ export const BigMap: StoryObj = { }, }; -export const BigMap3d: StoryObj = { +interface BigMapComponentProps { + showMetrics: boolean; + layers: Array>; // Array of layer objects + bounds: BoundingBox2D | BoundsAccessor | undefined; // Can be 2D or 3D bounding box + views: ViewsType; // Type for views +} + +const BigMapComponent: React.FC = ( + props: BigMapComponentProps +) => { + const metricsComponentRef = React.useRef<{ + updateMetrics?: (m: DeckMetrics) => void; + } | null>(null); + const updateMetrics = (m: DeckMetrics) => { + if (!metricsComponentRef.current) { + return; + } + if ("updateMetrics" in metricsComponentRef.current) { + metricsComponentRef.current.updateMetrics?.(m); + } + }; + + const onMetrics = (m: DeckMetrics) => { + updateMetrics(m); + }; + + return ( +
+ + {props.showMetrics ? : null} +
+ ); +}; + +export const BigMap3d: StoryObj = { args: { - id: "map", + showMetrics: false, layers: [huginAxes3DLayer, hugin5mKhNetmapMapLayer, northArrowLayer], bounds: hugin2DBounds, views: default3DViews, @@ -330,6 +371,7 @@ export const BigMap3d: StoryObj = { }, }, }, + render: (args) => , }; const axes_small = {