Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extremely slow to set individual colors to many elements in EmphasizeElements API #7888

Open
MichaelBelousov opened this issue Mar 25, 2025 · 4 comments

Comments

@MichaelBelousov
Copy link
Contributor

MichaelBelousov commented Mar 25, 2025

Describe the bug

Extremely slow to set individual colors to many elements in EmphasizeElements API,
on my machine 30,000 elements took 18 seconds.

To Reproduce
Steps to reproduce the behavior:

  1. open the 3d viewer sandbox
  2. Replace the content of ViewportOnlyApp.tsx with:
import React, { useEffect } from "react";
import { UiFramework } from "@itwin/appui-react";
import { Viewer, ViewerNavigationToolsProvider } from "@itwin/web-viewer-react";
import { authClient } from "./common/AuthorizationClient";
import { mapLayerOptions } from "./common/MapLayerOptions";
import { ViewSetup } from "./common/ViewSetup";
import { InstructionsWidgetProvider } from "./InstructionsWidgetProvider";
import { ColorDef, FeatureOverrideType } from "@itwin/core-common";
import { EmphasizeElements, IModelApp } from "@itwin/core-frontend";

// START VIEW_SETUP
const uiProviders = [
  new InstructionsWidgetProvider(),
  new ViewerNavigationToolsProvider(),
];
const viewportOptions = {
  viewState: ViewSetup.getDefaultView,
};
// END VIEW_SETUP

const iTwinId = process.env.IMJS_ITWIN_ID;
const iModelId = process.env.IMJS_IMODEL_ID;

const ViewportOnlyApp = () => {
  /** Sign-in */
  useEffect(() => {
    void authClient.signIn();
  }, []);

  // START VIEWER
  return <Viewer
    iTwinId={iTwinId ?? ""}
    iModelId={iModelId ?? ""}
    authClient={authClient}
    enablePerformanceMonitors={false}
    viewportOptions={viewportOptions}
    defaultUiConfig={
      {
        hideStatusBar: true,
        hideToolSettings: true,
      }
    }
    uiProviders={uiProviders}
    mapLayerOptions={mapLayerOptions}
    theme={process.env.THEME ?? "dark"}
    onIModelConnected={(db) => {
      IModelApp.viewManager.onViewOpen.addListener(async (vp) => {
        const emph = EmphasizeElements.getOrCreate(vp)

        let minOriginX = Number.POSITIVE_INFINITY;
        let maxOriginX = Number.NEGATIVE_INFINITY;
        const idAndOriginX: { id: string, originX: number }[] = [];
        console.log("start query");
        for await (const row of db.createQueryReader("SELECT ECInstanceId, Origin.X FROM bis.GeometricElement3d LIMIT 30000")) {
          const [id, originX] = row.toArray();
          idAndOriginX.push({ id, originX });
          maxOriginX = Math.max(maxOriginX, originX);
          minOriginX = Math.min(minOriginX, originX);
        }
        console.log("query done")

        // 0 to 1.0 number which is how deep into the origin.X range this value sits  
        //const normalize = (originX: number) => (originX - minOriginX) / (maxOriginX - minOriginX);

        console.log("start loop")
        for (const { id, originX: _originX } of idAndOriginX) {
          // this doesn't work
          emph.overrideElements(id, vp, ColorDef.blue, FeatureOverrideType.ColorOnly, false);
          //const hue = normalize(originX) * 360;
          //emph.overrideElements(id, vp, ColorDef.fromHSV(new HSVColor(hue, 100, 100)), FeatureOverrideType.ColorOnly, true);
        }

        // this works
        //emph.overrideElements(idAndOriginX.map(o => o.id), vp, ColorDef.blue, FeatureOverrideType.ColorOnly, true);
        console.log("loop done");
      });
    }}
  />;
  // END VIEWER
};

// Define panel size
UiFramework.frontstages.onFrontstageReadyEvent.addListener((event) => {
  const { bottomPanel } = event.frontstageDef;
  bottomPanel && (bottomPanel.size = 110);
});

export default ViewportOnlyApp;
  1. Use the green play button and watch the application freeze for ~20 seconds

Expected behavior

Setting basic element color is a fast operation, probably over every element in the scene, especially if you have precomputed the colors before setting it.

Screenshots
Profile (didn't capture the query phase it seems, and the stack traces look useless so YMMVCTSLPU)
Image

Desktop (please complete the applicable information):

  • OS: Manjaro Linux
  • Browser Tested in Firefox, chromium and electron
  • Version latest idk
  • iTwin.js Version 4.8.6

Additional context

profile in chromium: 30000_elem_coloring_profile.zip

Let me know if you want a smaller set profiled or if I can provide anything else.

adapted this issue from this discussion: #7887

@MichaelBelousov
Copy link
Contributor Author

MichaelBelousov commented Mar 30, 2025

So clearly a local debuggable project gives a much better profile, but my code just isn't in a state that I can share atm.

Image

this is 5000 elements

@MichaelBelousov
Copy link
Contributor Author

would anyone like me to give a cloneable repro, or is it easier for you to just copy and paste the 10 relevant lines from my above code into display-test-app?

If it'll help you if I make a fork with a repro in display-test-app I could get that all setup, just ask.

@MichaelBelousov
Copy link
Contributor Author

Ok I think I see the issue. I will probably just patch it on my side with my package manager, and share the patch here for anyone else.

@MichaelBelousov
Copy link
Contributor Author

MichaelBelousov commented Mar 30, 2025

I didn't even use package manager patching, sorry I'm not really being fastidious here.
Below is the ugly thing I'm going with.
What I did above is slow because overrideElements reprocesses the existing overrides
every time with a lot of work that can be ignored if you know all the calls are recoloring in bulk from scratch.

In my case I can do much simpler:

emph["_overrideAppearance"] = new Map<string, string>();
for (const [id, attr] of meshData.attr) {
  const hue = (attr / maxForAttr) * 360; // TODO: fix gradient to be green->red, not rainbow
  const color = ColorDef.fromHSV(new HSVColor(hue, 100, 100));
  const ovrKey = emph["createOverrideKey"](color, FeatureOverrideType.ColorOnly);
  let overridden = emph["_overrideAppearance"].get(ovrKey);
  if (overridden === undefined) {
    overridden = new Set();
    emph["_overrideAppearance"].set(ovrKey, overridden);
  }
  overridden.add(id);
}
vp.setFeatureOverrideProviderChanged();

You could probably add like an clearAndOverrideElementsBulk method which takes a Map or callback or two iterators or something and it'd probably be fast enough. It works instantly on my machine on the metrostation model when coloring all elements now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant