Browse Source

Merge branch 'master' of https://github.com/molstar/molstar into cellpack-tweaks

Alexander Rose 2 years ago
parent
commit
da2c893721
49 changed files with 911 additions and 268 deletions
  1. 8 0
      CHANGELOG.md
  2. 119 119
      package-lock.json
  3. 8 8
      package.json
  4. 1 1
      src/apps/viewer/app.ts
  5. 4 1
      src/apps/viewer/index.html
  6. 11 0
      src/examples/alpha-orbitals/index.html
  7. 6 2
      src/examples/alpha-orbitals/index.ts
  8. 5 4
      src/extensions/alpha-orbitals/density.ts
  9. 5 6
      src/extensions/alpha-orbitals/orbitals.ts
  10. 4 1
      src/mol-canvas3d/canvas3d.ts
  11. 6 1
      src/mol-canvas3d/passes/draw.ts
  12. 3 0
      src/mol-canvas3d/passes/fxaa.ts
  13. 3 0
      src/mol-canvas3d/passes/marking.ts
  14. 5 0
      src/mol-canvas3d/passes/multi-sample.ts
  15. 150 29
      src/mol-canvas3d/passes/pick.ts
  16. 3 0
      src/mol-canvas3d/passes/postprocessing.ts
  17. 3 2
      src/mol-canvas3d/passes/smaa.ts
  18. 3 1
      src/mol-canvas3d/passes/wboit.ts
  19. 7 0
      src/mol-geo/geometry/texture-mesh/color-smoothing.ts
  20. 18 7
      src/mol-gl/compute/grid3d.ts
  21. 4 1
      src/mol-gl/compute/histogram-pyramid/reduction.ts
  22. 4 1
      src/mol-gl/compute/histogram-pyramid/sum.ts
  23. 4 1
      src/mol-gl/compute/marching-cubes/active-voxels.ts
  24. 6 12
      src/mol-gl/compute/marching-cubes/isosurface.ts
  25. 23 0
      src/mol-gl/renderer.ts
  26. 42 10
      src/mol-gl/shader-code.ts
  27. 13 7
      src/mol-gl/shader/chunks/assign-color-varying.glsl.ts
  28. 0 2
      src/mol-gl/shader/chunks/assign-material-color.glsl.ts
  29. 14 2
      src/mol-gl/shader/chunks/color-frag-params.glsl.ts
  30. 14 2
      src/mol-gl/shader/chunks/color-vert-params.glsl.ts
  31. 8 1
      src/mol-gl/shader/cylinders.frag.ts
  32. 0 1
      src/mol-gl/shader/direct-volume.frag.ts
  33. 14 6
      src/mol-gl/shader/image.frag.ts
  34. 8 1
      src/mol-gl/shader/lines.frag.ts
  35. 8 1
      src/mol-gl/shader/mesh.frag.ts
  36. 8 1
      src/mol-gl/shader/points.frag.ts
  37. 8 1
      src/mol-gl/shader/spheres.frag.ts
  38. 21 8
      src/mol-gl/shader/text.frag.ts
  39. 82 1
      src/mol-gl/webgl/compat.ts
  40. 4 0
      src/mol-gl/webgl/context.ts
  41. 8 2
      src/mol-gl/webgl/extensions.ts
  42. 194 0
      src/mol-gl/webgl/timer.ts
  43. 9 2
      src/mol-math/geometry/gaussian-density/gpu.ts
  44. 11 6
      src/mol-plugin-state/formats/volume.ts
  45. 13 1
      src/mol-plugin/animation-loop.ts
  46. 5 12
      src/mol-repr/structure/visual/gaussian-surface-mesh.ts
  47. 1 1
      src/mol-task/util/user-timing.ts
  48. 11 2
      src/mol-util/debug.ts
  49. 2 1
      src/servers/model/utils/fetch-retry.ts

+ 8 - 0
CHANGELOG.md

@@ -6,6 +6,14 @@ Note that since we don't clearly distinguish between a public and private interf
 
 ## [Unreleased]
 
+## [v3.9.0] - 2022-05-30
+
+- Improve picking by using drawbuffers (when available) to reduce number of drawcalls
+- GPU timing support
+    - Add ``timing-mode`` Viewer GET param
+    - Add support for webgl timer queries
+    - Add timer marks around GPU render & compute operations
+- Volume Server CIF: Add check that a data block contains volume data before parsing
 - Fix ``Scene.clear`` not clearing primitives & volumes arrays (@JonStargaryen)
 - Fix rendering volumes when wboit is switched off and postprocessing is enabled
 

+ 119 - 119
package-lock.json

@@ -1,12 +1,12 @@
 {
   "name": "molstar",
-  "version": "3.8.2",
+  "version": "3.9.0",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "molstar",
-      "version": "3.8.2",
+      "version": "3.9.0",
       "license": "MIT",
       "dependencies": {
         "@types/argparse": "^2.0.10",
@@ -23,7 +23,7 @@
         "express": "^4.18.1",
         "h264-mp4-encoder": "^1.0.12",
         "immer": "^9.0.14",
-        "immutable": "^4.0.0",
+        "immutable": "^4.1.0",
         "node-fetch": "^2.6.7",
         "rxjs": "^7.5.5",
         "swagger-ui-dist": "^4.11.1",
@@ -53,11 +53,11 @@
         "@types/gl": "^4.1.0",
         "@types/jest": "^27.5.1",
         "@types/react": "^18.0.9",
-        "@types/react-dom": "^18.0.4",
-        "@typescript-eslint/eslint-plugin": "^5.25.0",
-        "@typescript-eslint/parser": "^5.25.0",
+        "@types/react-dom": "^18.0.5",
+        "@typescript-eslint/eslint-plugin": "^5.27.0",
+        "@typescript-eslint/parser": "^5.27.0",
         "benchmark": "^2.1.4",
-        "concurrently": "^7.2.0",
+        "concurrently": "^7.2.1",
         "cpx2": "^4.2.0",
         "crypto-browserify": "^3.12.0",
         "css-loader": "^6.7.1",
@@ -78,8 +78,8 @@
         "simple-git": "^3.7.1",
         "stream-browserify": "^3.0.0",
         "style-loader": "^3.3.1",
-        "ts-jest": "^28.0.2",
-        "typescript": "^4.6.4",
+        "ts-jest": "^28.0.3",
+        "typescript": "^4.7.2",
         "webpack": "^5.72.1",
         "webpack-cli": "^4.9.2"
       },
@@ -2852,9 +2852,9 @@
       }
     },
     "node_modules/@types/react-dom": {
-      "version": "18.0.4",
-      "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.4.tgz",
-      "integrity": "sha512-FgTtbqPOCI3dzZPZoC2T/sx3L34qxy99ITWn4eoSA95qPyXDMH0ALoAqUp49ITniiJFsXUVBtalh/KffMpg21Q==",
+      "version": "18.0.5",
+      "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.5.tgz",
+      "integrity": "sha512-OWPWTUrY/NIrjsAPkAk1wW9LZeIjSvkXRhclsFO8CZcZGCOg2G0YZy4ft+rOyYxy8B7ui5iZzi9OkDebZ7/QSA==",
       "dev": true,
       "dependencies": {
         "@types/react": "*"
@@ -2920,14 +2920,14 @@
       "dev": true
     },
     "node_modules/@typescript-eslint/eslint-plugin": {
-      "version": "5.25.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.25.0.tgz",
-      "integrity": "sha512-icYrFnUzvm+LhW0QeJNKkezBu6tJs9p/53dpPLFH8zoM9w1tfaKzVurkPotEpAqQ8Vf8uaFyL5jHd0Vs6Z0ZQg==",
+      "version": "5.27.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.27.0.tgz",
+      "integrity": "sha512-DDrIA7GXtmHXr1VCcx9HivA39eprYBIFxbQEHI6NyraRDxCGpxAFiYQAT/1Y0vh1C+o2vfBiy4IuPoXxtTZCAQ==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/scope-manager": "5.25.0",
-        "@typescript-eslint/type-utils": "5.25.0",
-        "@typescript-eslint/utils": "5.25.0",
+        "@typescript-eslint/scope-manager": "5.27.0",
+        "@typescript-eslint/type-utils": "5.27.0",
+        "@typescript-eslint/utils": "5.27.0",
         "debug": "^4.3.4",
         "functional-red-black-tree": "^1.0.1",
         "ignore": "^5.2.0",
@@ -2953,14 +2953,14 @@
       }
     },
     "node_modules/@typescript-eslint/parser": {
-      "version": "5.25.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.25.0.tgz",
-      "integrity": "sha512-r3hwrOWYbNKP1nTcIw/aZoH+8bBnh/Lh1iDHoFpyG4DnCpvEdctrSl6LOo19fZbzypjQMHdajolxs6VpYoChgA==",
+      "version": "5.27.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.27.0.tgz",
+      "integrity": "sha512-8oGjQF46c52l7fMiPPvX4It3u3V3JipssqDfHQ2hcR0AeR8Zge+OYyKUCm5b70X72N1qXt0qgHenwN6Gc2SXZA==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/scope-manager": "5.25.0",
-        "@typescript-eslint/types": "5.25.0",
-        "@typescript-eslint/typescript-estree": "5.25.0",
+        "@typescript-eslint/scope-manager": "5.27.0",
+        "@typescript-eslint/types": "5.27.0",
+        "@typescript-eslint/typescript-estree": "5.27.0",
         "debug": "^4.3.4"
       },
       "engines": {
@@ -2980,13 +2980,13 @@
       }
     },
     "node_modules/@typescript-eslint/scope-manager": {
-      "version": "5.25.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.25.0.tgz",
-      "integrity": "sha512-p4SKTFWj+2VpreUZ5xMQsBMDdQ9XdRvODKXN4EksyBjFp2YvQdLkyHqOffakYZPuWJUDNu3jVXtHALDyTv3cww==",
+      "version": "5.27.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.27.0.tgz",
+      "integrity": "sha512-VnykheBQ/sHd1Vt0LJ1JLrMH1GzHO+SzX6VTXuStISIsvRiurue/eRkTqSrG0CexHQgKG8shyJfR4o5VYioB9g==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "5.25.0",
-        "@typescript-eslint/visitor-keys": "5.25.0"
+        "@typescript-eslint/types": "5.27.0",
+        "@typescript-eslint/visitor-keys": "5.27.0"
       },
       "engines": {
         "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -2997,12 +2997,12 @@
       }
     },
     "node_modules/@typescript-eslint/type-utils": {
-      "version": "5.25.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.25.0.tgz",
-      "integrity": "sha512-B6nb3GK3Gv1Rsb2pqalebe/RyQoyG/WDy9yhj8EE0Ikds4Xa8RR28nHz+wlt4tMZk5bnAr0f3oC8TuDAd5CPrw==",
+      "version": "5.27.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.27.0.tgz",
+      "integrity": "sha512-vpTvRRchaf628Hb/Xzfek+85o//zEUotr1SmexKvTfs7czXfYjXVT/a5yDbpzLBX1rhbqxjDdr1Gyo0x1Fc64g==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/utils": "5.25.0",
+        "@typescript-eslint/utils": "5.27.0",
         "debug": "^4.3.4",
         "tsutils": "^3.21.0"
       },
@@ -3023,9 +3023,9 @@
       }
     },
     "node_modules/@typescript-eslint/types": {
-      "version": "5.25.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.25.0.tgz",
-      "integrity": "sha512-7fWqfxr0KNHj75PFqlGX24gWjdV/FDBABXL5dyvBOWHpACGyveok8Uj4ipPX/1fGU63fBkzSIycEje4XsOxUFA==",
+      "version": "5.27.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.27.0.tgz",
+      "integrity": "sha512-lY6C7oGm9a/GWhmUDOs3xAVRz4ty/XKlQ2fOLr8GAIryGn0+UBOoJDWyHer3UgrHkenorwvBnphhP+zPmzmw0A==",
       "dev": true,
       "engines": {
         "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -3036,13 +3036,13 @@
       }
     },
     "node_modules/@typescript-eslint/typescript-estree": {
-      "version": "5.25.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.25.0.tgz",
-      "integrity": "sha512-MrPODKDych/oWs/71LCnuO7NyR681HuBly2uLnX3r5i4ME7q/yBqC4hW33kmxtuauLTM0OuBOhhkFaxCCOjEEw==",
+      "version": "5.27.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.27.0.tgz",
+      "integrity": "sha512-QywPMFvgZ+MHSLRofLI7BDL+UczFFHyj0vF5ibeChDAJgdTV8k4xgEwF0geFhVlPc1p8r70eYewzpo6ps+9LJQ==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "5.25.0",
-        "@typescript-eslint/visitor-keys": "5.25.0",
+        "@typescript-eslint/types": "5.27.0",
+        "@typescript-eslint/visitor-keys": "5.27.0",
         "debug": "^4.3.4",
         "globby": "^11.1.0",
         "is-glob": "^4.0.3",
@@ -3063,15 +3063,15 @@
       }
     },
     "node_modules/@typescript-eslint/utils": {
-      "version": "5.25.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.25.0.tgz",
-      "integrity": "sha512-qNC9bhnz/n9Kba3yI6HQgQdBLuxDoMgdjzdhSInZh6NaDnFpTUlwNGxplUFWfY260Ya0TRPvkg9dd57qxrJI9g==",
+      "version": "5.27.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.27.0.tgz",
+      "integrity": "sha512-nZvCrkIJppym7cIbP3pOwIkAefXOmfGPnCM0LQfzNaKxJHI6VjI8NC662uoiPlaf5f6ymkTy9C3NQXev2mdXmA==",
       "dev": true,
       "dependencies": {
         "@types/json-schema": "^7.0.9",
-        "@typescript-eslint/scope-manager": "5.25.0",
-        "@typescript-eslint/types": "5.25.0",
-        "@typescript-eslint/typescript-estree": "5.25.0",
+        "@typescript-eslint/scope-manager": "5.27.0",
+        "@typescript-eslint/types": "5.27.0",
+        "@typescript-eslint/typescript-estree": "5.27.0",
         "eslint-scope": "^5.1.1",
         "eslint-utils": "^3.0.0"
       },
@@ -3087,12 +3087,12 @@
       }
     },
     "node_modules/@typescript-eslint/visitor-keys": {
-      "version": "5.25.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.25.0.tgz",
-      "integrity": "sha512-yd26vFgMsC4h2dgX4+LR+GeicSKIfUvZREFLf3DDjZPtqgLx5AJZr6TetMNwFP9hcKreTTeztQYBTNbNoOycwA==",
+      "version": "5.27.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.27.0.tgz",
+      "integrity": "sha512-46cYrteA2MrIAjv9ai44OQDUoCZyHeGIc4lsjCUX2WT6r4C+kidz1bNiR4017wHOPUythYeH+Sc7/cFP97KEAA==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "5.25.0",
+        "@typescript-eslint/types": "5.27.0",
         "eslint-visitor-keys": "^3.3.0"
       },
       "engines": {
@@ -4578,9 +4578,9 @@
       "dev": true
     },
     "node_modules/concurrently": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-7.2.0.tgz",
-      "integrity": "sha512-4KIVY5HopDRhN3ndAgfFOLsMk1PZUPgghlgTMZ5Pb5aTrqYg86RcZaIZC2Cz+qpZ9DsX36WHGjvWnXPqdnblhw==",
+      "version": "7.2.1",
+      "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-7.2.1.tgz",
+      "integrity": "sha512-7cab/QyqipqghrVr9qZmoWbidu0nHsmxrpNqQ7r/67vfl1DWJElexehQnTH1p+87tDkihaAjM79xTZyBQh7HLw==",
       "dev": true,
       "dependencies": {
         "chalk": "^4.1.0",
@@ -7013,9 +7013,9 @@
       }
     },
     "node_modules/immutable": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0.tgz",
-      "integrity": "sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw=="
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.1.0.tgz",
+      "integrity": "sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ=="
     },
     "node_modules/import-fresh": {
       "version": "3.3.0",
@@ -12141,15 +12141,15 @@
       }
     },
     "node_modules/ts-jest": {
-      "version": "28.0.2",
-      "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-28.0.2.tgz",
-      "integrity": "sha512-IOZMb3D0gx6IHO9ywPgiQxJ3Zl4ECylEFwoVpENB55aTn5sdO0Ptyx/7noNBxAaUff708RqQL4XBNxxOVjY0vQ==",
+      "version": "28.0.3",
+      "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-28.0.3.tgz",
+      "integrity": "sha512-HzgbEDQ2KgVtDmpXToqAcKTyGHdHsG23i/iUjfxji92G5eT09S1m9UHZd7csF0Bfgh9txM4JzwHnv7r1waFPlw==",
       "dev": true,
       "dependencies": {
         "bs-logger": "0.x",
         "fast-json-stable-stringify": "2.x",
         "jest-util": "^28.0.0",
-        "json5": "2.x",
+        "json5": "^2.2.1",
         "lodash.memoize": "4.x",
         "make-error": "1.x",
         "semver": "7.x",
@@ -12287,9 +12287,9 @@
       }
     },
     "node_modules/typescript": {
-      "version": "4.6.4",
-      "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz",
-      "integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==",
+      "version": "4.7.2",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.2.tgz",
+      "integrity": "sha512-Mamb1iX2FDUpcTRzltPxgWMKy3fhg0TN378ylbktPGPK/99KbDtMQ4W1hwgsbPAsG3a0xKa1vmw4VKZQbkvz5A==",
       "dev": true,
       "bin": {
         "tsc": "bin/tsc",
@@ -15190,9 +15190,9 @@
       }
     },
     "@types/react-dom": {
-      "version": "18.0.4",
-      "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.4.tgz",
-      "integrity": "sha512-FgTtbqPOCI3dzZPZoC2T/sx3L34qxy99ITWn4eoSA95qPyXDMH0ALoAqUp49ITniiJFsXUVBtalh/KffMpg21Q==",
+      "version": "18.0.5",
+      "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.5.tgz",
+      "integrity": "sha512-OWPWTUrY/NIrjsAPkAk1wW9LZeIjSvkXRhclsFO8CZcZGCOg2G0YZy4ft+rOyYxy8B7ui5iZzi9OkDebZ7/QSA==",
       "dev": true,
       "requires": {
         "@types/react": "*"
@@ -15258,14 +15258,14 @@
       "dev": true
     },
     "@typescript-eslint/eslint-plugin": {
-      "version": "5.25.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.25.0.tgz",
-      "integrity": "sha512-icYrFnUzvm+LhW0QeJNKkezBu6tJs9p/53dpPLFH8zoM9w1tfaKzVurkPotEpAqQ8Vf8uaFyL5jHd0Vs6Z0ZQg==",
+      "version": "5.27.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.27.0.tgz",
+      "integrity": "sha512-DDrIA7GXtmHXr1VCcx9HivA39eprYBIFxbQEHI6NyraRDxCGpxAFiYQAT/1Y0vh1C+o2vfBiy4IuPoXxtTZCAQ==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/scope-manager": "5.25.0",
-        "@typescript-eslint/type-utils": "5.25.0",
-        "@typescript-eslint/utils": "5.25.0",
+        "@typescript-eslint/scope-manager": "5.27.0",
+        "@typescript-eslint/type-utils": "5.27.0",
+        "@typescript-eslint/utils": "5.27.0",
         "debug": "^4.3.4",
         "functional-red-black-tree": "^1.0.1",
         "ignore": "^5.2.0",
@@ -15275,52 +15275,52 @@
       }
     },
     "@typescript-eslint/parser": {
-      "version": "5.25.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.25.0.tgz",
-      "integrity": "sha512-r3hwrOWYbNKP1nTcIw/aZoH+8bBnh/Lh1iDHoFpyG4DnCpvEdctrSl6LOo19fZbzypjQMHdajolxs6VpYoChgA==",
+      "version": "5.27.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.27.0.tgz",
+      "integrity": "sha512-8oGjQF46c52l7fMiPPvX4It3u3V3JipssqDfHQ2hcR0AeR8Zge+OYyKUCm5b70X72N1qXt0qgHenwN6Gc2SXZA==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/scope-manager": "5.25.0",
-        "@typescript-eslint/types": "5.25.0",
-        "@typescript-eslint/typescript-estree": "5.25.0",
+        "@typescript-eslint/scope-manager": "5.27.0",
+        "@typescript-eslint/types": "5.27.0",
+        "@typescript-eslint/typescript-estree": "5.27.0",
         "debug": "^4.3.4"
       }
     },
     "@typescript-eslint/scope-manager": {
-      "version": "5.25.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.25.0.tgz",
-      "integrity": "sha512-p4SKTFWj+2VpreUZ5xMQsBMDdQ9XdRvODKXN4EksyBjFp2YvQdLkyHqOffakYZPuWJUDNu3jVXtHALDyTv3cww==",
+      "version": "5.27.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.27.0.tgz",
+      "integrity": "sha512-VnykheBQ/sHd1Vt0LJ1JLrMH1GzHO+SzX6VTXuStISIsvRiurue/eRkTqSrG0CexHQgKG8shyJfR4o5VYioB9g==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/types": "5.25.0",
-        "@typescript-eslint/visitor-keys": "5.25.0"
+        "@typescript-eslint/types": "5.27.0",
+        "@typescript-eslint/visitor-keys": "5.27.0"
       }
     },
     "@typescript-eslint/type-utils": {
-      "version": "5.25.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.25.0.tgz",
-      "integrity": "sha512-B6nb3GK3Gv1Rsb2pqalebe/RyQoyG/WDy9yhj8EE0Ikds4Xa8RR28nHz+wlt4tMZk5bnAr0f3oC8TuDAd5CPrw==",
+      "version": "5.27.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.27.0.tgz",
+      "integrity": "sha512-vpTvRRchaf628Hb/Xzfek+85o//zEUotr1SmexKvTfs7czXfYjXVT/a5yDbpzLBX1rhbqxjDdr1Gyo0x1Fc64g==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/utils": "5.25.0",
+        "@typescript-eslint/utils": "5.27.0",
         "debug": "^4.3.4",
         "tsutils": "^3.21.0"
       }
     },
     "@typescript-eslint/types": {
-      "version": "5.25.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.25.0.tgz",
-      "integrity": "sha512-7fWqfxr0KNHj75PFqlGX24gWjdV/FDBABXL5dyvBOWHpACGyveok8Uj4ipPX/1fGU63fBkzSIycEje4XsOxUFA==",
+      "version": "5.27.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.27.0.tgz",
+      "integrity": "sha512-lY6C7oGm9a/GWhmUDOs3xAVRz4ty/XKlQ2fOLr8GAIryGn0+UBOoJDWyHer3UgrHkenorwvBnphhP+zPmzmw0A==",
       "dev": true
     },
     "@typescript-eslint/typescript-estree": {
-      "version": "5.25.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.25.0.tgz",
-      "integrity": "sha512-MrPODKDych/oWs/71LCnuO7NyR681HuBly2uLnX3r5i4ME7q/yBqC4hW33kmxtuauLTM0OuBOhhkFaxCCOjEEw==",
+      "version": "5.27.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.27.0.tgz",
+      "integrity": "sha512-QywPMFvgZ+MHSLRofLI7BDL+UczFFHyj0vF5ibeChDAJgdTV8k4xgEwF0geFhVlPc1p8r70eYewzpo6ps+9LJQ==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/types": "5.25.0",
-        "@typescript-eslint/visitor-keys": "5.25.0",
+        "@typescript-eslint/types": "5.27.0",
+        "@typescript-eslint/visitor-keys": "5.27.0",
         "debug": "^4.3.4",
         "globby": "^11.1.0",
         "is-glob": "^4.0.3",
@@ -15329,26 +15329,26 @@
       }
     },
     "@typescript-eslint/utils": {
-      "version": "5.25.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.25.0.tgz",
-      "integrity": "sha512-qNC9bhnz/n9Kba3yI6HQgQdBLuxDoMgdjzdhSInZh6NaDnFpTUlwNGxplUFWfY260Ya0TRPvkg9dd57qxrJI9g==",
+      "version": "5.27.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.27.0.tgz",
+      "integrity": "sha512-nZvCrkIJppym7cIbP3pOwIkAefXOmfGPnCM0LQfzNaKxJHI6VjI8NC662uoiPlaf5f6ymkTy9C3NQXev2mdXmA==",
       "dev": true,
       "requires": {
         "@types/json-schema": "^7.0.9",
-        "@typescript-eslint/scope-manager": "5.25.0",
-        "@typescript-eslint/types": "5.25.0",
-        "@typescript-eslint/typescript-estree": "5.25.0",
+        "@typescript-eslint/scope-manager": "5.27.0",
+        "@typescript-eslint/types": "5.27.0",
+        "@typescript-eslint/typescript-estree": "5.27.0",
         "eslint-scope": "^5.1.1",
         "eslint-utils": "^3.0.0"
       }
     },
     "@typescript-eslint/visitor-keys": {
-      "version": "5.25.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.25.0.tgz",
-      "integrity": "sha512-yd26vFgMsC4h2dgX4+LR+GeicSKIfUvZREFLf3DDjZPtqgLx5AJZr6TetMNwFP9hcKreTTeztQYBTNbNoOycwA==",
+      "version": "5.27.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.27.0.tgz",
+      "integrity": "sha512-46cYrteA2MrIAjv9ai44OQDUoCZyHeGIc4lsjCUX2WT6r4C+kidz1bNiR4017wHOPUythYeH+Sc7/cFP97KEAA==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/types": "5.25.0",
+        "@typescript-eslint/types": "5.27.0",
         "eslint-visitor-keys": "^3.3.0"
       }
     },
@@ -16538,9 +16538,9 @@
       "dev": true
     },
     "concurrently": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-7.2.0.tgz",
-      "integrity": "sha512-4KIVY5HopDRhN3ndAgfFOLsMk1PZUPgghlgTMZ5Pb5aTrqYg86RcZaIZC2Cz+qpZ9DsX36WHGjvWnXPqdnblhw==",
+      "version": "7.2.1",
+      "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-7.2.1.tgz",
+      "integrity": "sha512-7cab/QyqipqghrVr9qZmoWbidu0nHsmxrpNqQ7r/67vfl1DWJElexehQnTH1p+87tDkihaAjM79xTZyBQh7HLw==",
       "dev": true,
       "requires": {
         "chalk": "^4.1.0",
@@ -18385,9 +18385,9 @@
       "integrity": "sha512-ubBeqQutOSLIFCUBN03jGeOS6a3DoYlSYwYJTa+gSKEZKU5redJIqkIdZ3JVv/4RZpfcXdAWH5zCNLWPRv2WDw=="
     },
     "immutable": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0.tgz",
-      "integrity": "sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw=="
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.1.0.tgz",
+      "integrity": "sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ=="
     },
     "import-fresh": {
       "version": "3.3.0",
@@ -22253,15 +22253,15 @@
       "dev": true
     },
     "ts-jest": {
-      "version": "28.0.2",
-      "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-28.0.2.tgz",
-      "integrity": "sha512-IOZMb3D0gx6IHO9ywPgiQxJ3Zl4ECylEFwoVpENB55aTn5sdO0Ptyx/7noNBxAaUff708RqQL4XBNxxOVjY0vQ==",
+      "version": "28.0.3",
+      "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-28.0.3.tgz",
+      "integrity": "sha512-HzgbEDQ2KgVtDmpXToqAcKTyGHdHsG23i/iUjfxji92G5eT09S1m9UHZd7csF0Bfgh9txM4JzwHnv7r1waFPlw==",
       "dev": true,
       "requires": {
         "bs-logger": "0.x",
         "fast-json-stable-stringify": "2.x",
         "jest-util": "^28.0.0",
-        "json5": "2.x",
+        "json5": "^2.2.1",
         "lodash.memoize": "4.x",
         "make-error": "1.x",
         "semver": "7.x",
@@ -22341,9 +22341,9 @@
       }
     },
     "typescript": {
-      "version": "4.6.4",
-      "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz",
-      "integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==",
+      "version": "4.7.2",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.2.tgz",
+      "integrity": "sha512-Mamb1iX2FDUpcTRzltPxgWMKy3fhg0TN378ylbktPGPK/99KbDtMQ4W1hwgsbPAsG3a0xKa1vmw4VKZQbkvz5A==",
       "dev": true
     },
     "ua-parser-js": {

+ 8 - 8
package.json

@@ -1,6 +1,6 @@
 {
   "name": "molstar",
-  "version": "3.8.2",
+  "version": "3.9.0",
   "description": "A comprehensive macromolecular library.",
   "homepage": "https://github.com/molstar/molstar#readme",
   "repository": {
@@ -102,11 +102,11 @@
     "@types/gl": "^4.1.0",
     "@types/jest": "^27.5.1",
     "@types/react": "^18.0.9",
-    "@types/react-dom": "^18.0.4",
-    "@typescript-eslint/eslint-plugin": "^5.25.0",
-    "@typescript-eslint/parser": "^5.25.0",
+    "@types/react-dom": "^18.0.5",
+    "@typescript-eslint/eslint-plugin": "^5.27.0",
+    "@typescript-eslint/parser": "^5.27.0",
     "benchmark": "^2.1.4",
-    "concurrently": "^7.2.0",
+    "concurrently": "^7.2.1",
     "cpx2": "^4.2.0",
     "crypto-browserify": "^3.12.0",
     "css-loader": "^6.7.1",
@@ -127,8 +127,8 @@
     "simple-git": "^3.7.1",
     "stream-browserify": "^3.0.0",
     "style-loader": "^3.3.1",
-    "ts-jest": "^28.0.2",
-    "typescript": "^4.6.4",
+    "ts-jest": "^28.0.3",
+    "typescript": "^4.7.2",
     "webpack": "^5.72.1",
     "webpack-cli": "^4.9.2"
   },
@@ -147,7 +147,7 @@
     "express": "^4.18.1",
     "h264-mp4-encoder": "^1.0.12",
     "immer": "^9.0.14",
-    "immutable": "^4.0.0",
+    "immutable": "^4.1.0",
     "node-fetch": "^2.6.7",
     "rxjs": "^7.5.5",
     "swagger-ui-dist": "^4.11.1",

+ 1 - 1
src/apps/viewer/app.ts

@@ -47,7 +47,7 @@ import '../../mol-util/polyfill';
 import { ObjectKeys } from '../../mol-util/type-helpers';
 
 export { PLUGIN_VERSION as version } from '../../mol-plugin/version';
-export { setDebugMode, setProductionMode } from '../../mol-util/debug';
+export { setDebugMode, setProductionMode, setTimingMode } from '../../mol-util/debug';
 
 const CustomFormats = [
     ['g3d', G3dProvider] as const

+ 4 - 1
src/apps/viewer/index.html

@@ -46,7 +46,10 @@
             }
 
             var debugMode = getParam('debug-mode', '[^&]+').trim() === '1';
-            if (debugMode) molstar.setDebugMode(debugMode, debugMode);
+            if (debugMode) molstar.setDebugMode(debugMode);
+
+            var timingMode = getParam('timing-mode', '[^&]+').trim() === '1';
+            if (timingMode) molstar.setTimingMode(timingMode);
 
             var hideControls = getParam('hide-controls', '[^&]+').trim() === '1';
             var collapseLeftPanel = getParam('collapse-left-panel', '[^&]+').trim() === '1';

+ 11 - 0
src/examples/alpha-orbitals/index.html

@@ -55,6 +55,17 @@
             </a>
         </div>
         <script>
+            function getParam(name, regex) {
+                var r = new RegExp(name + '=' + '(' + regex + ')[&]?', 'i');
+                return decodeURIComponent(((window.location.search || '').match(r) || [])[1] || '');
+            }
+
+            var debugMode = getParam('debug-mode', '[^&]+').trim() === '1';
+            if (debugMode) AlphaOrbitalsExample.setDebugMode(debugMode);
+
+            var timingMode = getParam('timing-mode', '[^&]+').trim() === '1';
+            if (timingMode) AlphaOrbitalsExample.setTimingMode(timingMode);
+
             AlphaOrbitalsExample.init('app')
         </script>
         <!-- __MOLSTAR_ANALYTICS__ -->

+ 6 - 2
src/examples/alpha-orbitals/index.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
  */
@@ -25,6 +25,8 @@ import { DemoMoleculeSDF, DemoOrbitals } from './example-data';
 import './index.html';
 require('mol-plugin-ui/skin/light.scss');
 
+import { setDebugMode, setTimingMode } from '../../mol-util/debug';
+
 interface DemoInput {
     moleculeSdf: string,
     basis: Basis,
@@ -222,4 +224,6 @@ export class AlphaOrbitalsExample {
     }
 }
 
-(window as any).AlphaOrbitalsExample = new AlphaOrbitalsExample();
+(window as any).AlphaOrbitalsExample = new AlphaOrbitalsExample();
+(window as any).AlphaOrbitalsExample.setDebugMode = setDebugMode;
+(window as any).AlphaOrbitalsExample.setTimingMode = setTimingMode;

+ 5 - 4
src/extensions/alpha-orbitals/density.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
  */
@@ -8,6 +8,7 @@ import { sortArray } from '../../mol-data/util';
 import { canComputeGrid3dOnGPU } from '../../mol-gl/compute/grid3d';
 import { WebGLContext } from '../../mol-gl/webgl/context';
 import { Task } from '../../mol-task';
+import { isTimingMode } from '../../mol-util/debug';
 import { AlphaOrbital, createGrid, CubeGrid, CubeGridComputationParams, initCubeGrid } from './data-model';
 import { gpuComputeAlphaOrbitalsDensityGridValues } from './gpu/compute';
 
@@ -19,9 +20,9 @@ export function createSphericalCollocationDensityGrid(
 
         let matrix: Float32Array;
         if (canComputeGrid3dOnGPU(webgl)) {
-            // console.time('gpu');
-            matrix = await gpuComputeAlphaOrbitalsDensityGridValues(ctx, webgl!, cubeGrid, orbitals);
-            // console.timeEnd('gpu');
+            if (isTimingMode) webgl.timer.mark('createSphericalCollocationDensityGrid');
+            matrix = await gpuComputeAlphaOrbitalsDensityGridValues(ctx, webgl, cubeGrid, orbitals);
+            if (isTimingMode) webgl.timer.markEnd('createSphericalCollocationDensityGrid');
         } else {
             throw new Error('Missing OES_texture_float WebGL extension.');
         }

+ 5 - 6
src/extensions/alpha-orbitals/orbitals.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * Inspired by https://github.com/dgasmith/gau2grid.
  *
@@ -10,12 +10,11 @@ import { sortArray } from '../../mol-data/util';
 import { canComputeGrid3dOnGPU } from '../../mol-gl/compute/grid3d';
 import { WebGLContext } from '../../mol-gl/webgl/context';
 import { Task } from '../../mol-task';
+import { isTimingMode } from '../../mol-util/debug';
 import { sphericalCollocation } from './collocation';
 import { AlphaOrbital, createGrid, CubeGrid, CubeGridComputationParams, initCubeGrid } from './data-model';
 import { gpuComputeAlphaOrbitalsGridValues } from './gpu/compute';
 
-// setDebugMode(true);
-
 export function createSphericalCollocationGrid(
     params: CubeGridComputationParams, orbital: AlphaOrbital, webgl?: WebGLContext
 ): Task<CubeGrid> {
@@ -24,9 +23,9 @@ export function createSphericalCollocationGrid(
 
         let matrix: Float32Array;
         if (canComputeGrid3dOnGPU(webgl)) {
-            // console.time('gpu');
-            matrix = await gpuComputeAlphaOrbitalsGridValues(ctx, webgl!, cubeGrid, orbital);
-            // console.timeEnd('gpu');
+            if (isTimingMode) webgl.timer.mark('createSphericalCollocationGrid');
+            matrix = await gpuComputeAlphaOrbitalsGridValues(ctx, webgl, cubeGrid, orbital);
+            if (isTimingMode) webgl.timer.markEnd('createSphericalCollocationGrid');
         } else {
             // console.time('cpu');
             matrix = await sphericalCollocation(cubeGrid, orbital, ctx);

+ 4 - 1
src/mol-canvas3d/canvas3d.ts

@@ -30,7 +30,7 @@ import { PickData } from './passes/pick';
 import { PickHelper } from './passes/pick';
 import { ImagePass, ImageProps } from './passes/image';
 import { Sphere3D } from '../mol-math/geometry';
-import { isDebugMode } from '../mol-util/debug';
+import { isDebugMode, isTimingMode } from '../mol-util/debug';
 import { CameraHelperParams } from './helper/camera-helper';
 import { produce } from 'immer';
 import { HandleHelperParams } from './helper/handle-helper';
@@ -413,6 +413,7 @@ namespace Canvas3D {
                     cam = stereoCamera;
                 }
 
+                if (isTimingMode) webgl.timer.mark('Canvas3D.render');
                 const ctx = { renderer, camera: cam, scene, helper };
                 if (MultiSamplePass.isEnabled(p.multiSample)) {
                     const forceOn = !cameraChanged && markingUpdated && !controls.isAnimating;
@@ -420,6 +421,8 @@ namespace Canvas3D {
                 } else {
                     passes.draw.render(ctx, p, true);
                 }
+                if (isTimingMode) webgl.timer.markEnd('Canvas3D.render');
+
                 // if only marking has updated, do not set the flag to dirty
                 pickHelper.dirty = pickHelper.dirty || shouldRender;
                 didRender = true;

+ 6 - 1
src/mol-canvas3d/passes/draw.ts

@@ -20,6 +20,7 @@ import { WboitPass } from './wboit';
 import { AntialiasingPass, PostprocessingPass, PostprocessingProps } from './postprocessing';
 import { MarkingPass, MarkingProps } from './marking';
 import { CopyRenderable, createCopyRenderable } from '../../mol-gl/compute/util';
+import { isTimingMode } from '../../mol-util/debug';
 
 type Props = {
     postprocessing: PostprocessingProps
@@ -229,7 +230,9 @@ export class DrawPass {
             }
         }
 
-        renderer.renderBlendedTransparent(scene.primitives, camera, null);
+        if (scene.opacityAverage < 1) {
+            renderer.renderBlendedTransparent(scene.primitives, camera, null);
+        }
     }
 
     private _render(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, props: Props) {
@@ -309,6 +312,7 @@ export class DrawPass {
     }
 
     render(ctx: RenderContext, props: Props, toDrawingBuffer: boolean) {
+        if (isTimingMode) this.webgl.timer.mark('DrawPass.render');
         const { renderer, camera, scene, helper } = ctx;
         renderer.setTransparentBackground(props.transparentBackground);
         renderer.setDrawingBufferSize(this.colorTarget.getWidth(), this.colorTarget.getHeight());
@@ -320,6 +324,7 @@ export class DrawPass {
         } else {
             this._render(renderer, camera, scene, helper, toDrawingBuffer, props);
         }
+        if (isTimingMode) this.webgl.timer.markEnd('DrawPass.render');
     }
 
     getColorTarget(postprocessingProps: PostprocessingProps): RenderTarget {

+ 3 - 0
src/mol-canvas3d/passes/fxaa.ts

@@ -18,6 +18,7 @@ import { quad_vert } from '../../mol-gl/shader/quad.vert';
 import { fxaa_frag } from '../../mol-gl/shader/fxaa.frag';
 import { Viewport } from '../camera/util';
 import { RenderTarget } from '../../mol-gl/webgl/render-target';
+import { isTimingMode } from '../../mol-util/debug';
 
 export const FxaaParams = {
     edgeThresholdMin: PD.Numeric(0.0312, { min: 0.0312, max: 0.0833, step: 0.0001 }, { description: 'Trims the algorithm from processing darks.' }),
@@ -83,6 +84,7 @@ export class FxaaPass {
     }
 
     render(viewport: Viewport, target: RenderTarget | undefined) {
+        if (isTimingMode) this.webgl.timer.mark('FxaaPass.render');
         if (target) {
             target.bind();
         } else {
@@ -90,6 +92,7 @@ export class FxaaPass {
         }
         this.updateState(viewport);
         this.renderable.render();
+        if (isTimingMode) this.webgl.timer.markEnd('FxaaPass.render');
     }
 }
 

+ 3 - 0
src/mol-canvas3d/passes/marking.ts

@@ -20,6 +20,7 @@ import { Viewport } from '../camera/util';
 import { RenderTarget } from '../../mol-gl/webgl/render-target';
 import { Color } from '../../mol-util/color';
 import { edge_frag } from '../../mol-gl/shader/marking/edge.frag';
+import { isTimingMode } from '../../mol-util/debug';
 
 export const MarkingParams = {
     enabled: PD.Boolean(true),
@@ -117,6 +118,7 @@ export class MarkingPass {
     }
 
     render(viewport: Viewport, target: RenderTarget | undefined) {
+        if (isTimingMode) this.webgl.timer.mark('MarkingPass.render');
         this.edgesTarget.bind();
         this.setEdgeState(viewport);
         this.edge.render();
@@ -128,6 +130,7 @@ export class MarkingPass {
         }
         this.setOverlayState(viewport);
         this.overlay.render();
+        if (isTimingMode) this.webgl.timer.markEnd('MarkingPass.render');
     }
 }
 

+ 5 - 0
src/mol-canvas3d/passes/multi-sample.ts

@@ -25,6 +25,7 @@ import { StereoCamera } from '../camera/stereo';
 import { quad_vert } from '../../mol-gl/shader/quad.vert';
 import { compose_frag } from '../../mol-gl/shader/compose.frag';
 import { MarkingProps } from './marking';
+import { isTimingMode } from '../../mol-util/debug';
 
 const ComposeSchema = {
     ...QuadSchema,
@@ -126,6 +127,7 @@ export class MultiSamplePass {
         const { camera } = ctx;
         const { compose, composeTarget, drawPass, webgl } = this;
         const { gl, state } = webgl;
+        if (isTimingMode) webgl.timer.mark('MultiSamplePass.renderMultiSample');
 
         // based on the Multisample Anti-Aliasing Render Pass
         // contributed to three.js by bhouston / http://clara.io/
@@ -198,12 +200,14 @@ export class MultiSamplePass {
 
         camera.viewOffset.enabled = false;
         camera.update();
+        if (isTimingMode) webgl.timer.markEnd('MultiSamplePass.renderMultiSample');
     }
 
     private renderTemporalMultiSample(sampleIndex: number, ctx: RenderContext, props: Props, toDrawingBuffer: boolean) {
         const { camera } = ctx;
         const { compose, composeTarget, holdTarget, drawPass, webgl } = this;
         const { gl, state } = webgl;
+        if (isTimingMode) webgl.timer.mark('MultiSamplePass.renderTemporalMultiSample');
 
         // based on the Multisample Anti-Aliasing Render Pass
         // contributed to three.js by bhouston / http://clara.io/
@@ -301,6 +305,7 @@ export class MultiSamplePass {
 
         camera.viewOffset.enabled = false;
         camera.update();
+        if (isTimingMode) webgl.timer.markEnd('MultiSamplePass.renderTemporalMultiSample');
 
         return sampleIndex >= offsetList.length ? -2 : sampleIndex;
     }

+ 150 - 29
src/mol-canvas3d/passes/pick.ts

@@ -7,10 +7,15 @@
 import { PickingId } from '../../mol-geo/geometry/picking';
 import { PickType, Renderer } from '../../mol-gl/renderer';
 import { Scene } from '../../mol-gl/scene';
+import { isWebGL2 } from '../../mol-gl/webgl/compat';
 import { WebGLContext } from '../../mol-gl/webgl/context';
+import { Framebuffer } from '../../mol-gl/webgl/framebuffer';
 import { RenderTarget } from '../../mol-gl/webgl/render-target';
+import { Renderbuffer } from '../../mol-gl/webgl/renderbuffer';
+import { Texture } from '../../mol-gl/webgl/texture';
 import { Vec3 } from '../../mol-math/linear-algebra';
 import { spiral2d } from '../../mol-math/misc';
+import { isTimingMode } from '../../mol-util/debug';
 import { unpackRGBToInt, unpackRGBAToDepth } from '../../mol-util/number-packing';
 import { Camera, ICamera } from '../camera';
 import { StereoCamera } from '../camera/stereo';
@@ -24,10 +29,24 @@ const NullId = Math.pow(2, 24) - 2;
 export type PickData = { id: PickingId, position: Vec3 }
 
 export class PickPass {
-    readonly objectPickTarget: RenderTarget;
-    readonly instancePickTarget: RenderTarget;
-    readonly groupPickTarget: RenderTarget;
-    readonly depthPickTarget: RenderTarget;
+    private readonly objectPickTarget: RenderTarget;
+    private readonly instancePickTarget: RenderTarget;
+    private readonly groupPickTarget: RenderTarget;
+    private readonly depthPickTarget: RenderTarget;
+
+    private readonly framebuffer: Framebuffer;
+
+    private readonly objectPickTexture: Texture;
+    private readonly instancePickTexture: Texture;
+    private readonly groupPickTexture: Texture;
+    private readonly depthPickTexture: Texture;
+
+    private readonly objectPickFramebuffer: Framebuffer;
+    private readonly instancePickFramebuffer: Framebuffer;
+    private readonly groupPickFramebuffer: Framebuffer;
+    private readonly depthPickFramebuffer: Framebuffer;
+
+    private readonly depthRenderbuffer: Renderbuffer;
 
     private pickWidth: number;
     private pickHeight: number;
@@ -37,10 +56,89 @@ export class PickPass {
         this.pickWidth = Math.ceil(drawPass.colorTarget.getWidth() * pickScale);
         this.pickHeight = Math.ceil(drawPass.colorTarget.getHeight() * pickScale);
 
-        this.objectPickTarget = webgl.createRenderTarget(this.pickWidth, this.pickHeight);
-        this.instancePickTarget = webgl.createRenderTarget(this.pickWidth, this.pickHeight);
-        this.groupPickTarget = webgl.createRenderTarget(this.pickWidth, this.pickHeight);
-        this.depthPickTarget = webgl.createRenderTarget(this.pickWidth, this.pickHeight);
+        const { resources, extensions: { drawBuffers }, gl } = webgl;
+
+        if (drawBuffers) {
+            this.objectPickTexture = resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
+            this.objectPickTexture.define(this.pickWidth, this.pickHeight);
+
+            this.instancePickTexture = resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
+            this.instancePickTexture.define(this.pickWidth, this.pickHeight);
+
+            this.groupPickTexture = resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
+            this.groupPickTexture.define(this.pickWidth, this.pickHeight);
+
+            this.depthPickTexture = resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
+            this.depthPickTexture.define(this.pickWidth, this.pickHeight);
+
+            this.framebuffer = resources.framebuffer();
+
+            this.objectPickFramebuffer = resources.framebuffer();
+            this.instancePickFramebuffer = resources.framebuffer();
+            this.groupPickFramebuffer = resources.framebuffer();
+            this.depthPickFramebuffer = resources.framebuffer();
+
+            this.framebuffer.bind();
+            drawBuffers!.drawBuffers([
+                drawBuffers!.COLOR_ATTACHMENT0,
+                drawBuffers!.COLOR_ATTACHMENT1,
+                drawBuffers!.COLOR_ATTACHMENT2,
+                drawBuffers!.COLOR_ATTACHMENT3,
+            ]);
+
+            this.objectPickTexture.attachFramebuffer(this.framebuffer, 'color0');
+            this.instancePickTexture.attachFramebuffer(this.framebuffer, 'color1');
+            this.groupPickTexture.attachFramebuffer(this.framebuffer, 'color2');
+            this.depthPickTexture.attachFramebuffer(this.framebuffer, 'color3');
+
+            this.depthRenderbuffer = isWebGL2(gl)
+                ? resources.renderbuffer('depth32f', 'depth', this.pickWidth, this.pickHeight)
+                : resources.renderbuffer('depth16', 'depth', this.pickWidth, this.pickHeight);
+
+            this.depthRenderbuffer.attachFramebuffer(this.framebuffer);
+
+            this.objectPickTexture.attachFramebuffer(this.objectPickFramebuffer, 'color0');
+            this.instancePickTexture.attachFramebuffer(this.instancePickFramebuffer, 'color0');
+            this.groupPickTexture.attachFramebuffer(this.groupPickFramebuffer, 'color0');
+            this.depthPickTexture.attachFramebuffer(this.depthPickFramebuffer, 'color0');
+        } else {
+            this.objectPickTarget = webgl.createRenderTarget(this.pickWidth, this.pickHeight);
+            this.instancePickTarget = webgl.createRenderTarget(this.pickWidth, this.pickHeight);
+            this.groupPickTarget = webgl.createRenderTarget(this.pickWidth, this.pickHeight);
+            this.depthPickTarget = webgl.createRenderTarget(this.pickWidth, this.pickHeight);
+        }
+    }
+
+    bindObject() {
+        if (this.webgl.extensions.drawBuffers) {
+            this.objectPickFramebuffer.bind();
+        } else {
+            this.objectPickTarget.bind();
+        }
+    }
+
+    bindInstance() {
+        if (this.webgl.extensions.drawBuffers) {
+            this.instancePickFramebuffer.bind();
+        } else {
+            this.instancePickTarget.bind();
+        }
+    }
+
+    bindGroup() {
+        if (this.webgl.extensions.drawBuffers) {
+            this.groupPickFramebuffer.bind();
+        } else {
+            this.groupPickTarget.bind();
+        }
+    }
+
+    bindDepth() {
+        if (this.webgl.extensions.drawBuffers) {
+            this.depthPickFramebuffer.bind();
+        } else {
+            this.depthPickTarget.bind();
+        }
     }
 
     get drawingBufferHeight() {
@@ -56,19 +154,30 @@ export class PickPass {
             this.pickWidth = pickWidth;
             this.pickHeight = pickHeight;
 
-            this.objectPickTarget.setSize(this.pickWidth, this.pickHeight);
-            this.instancePickTarget.setSize(this.pickWidth, this.pickHeight);
-            this.groupPickTarget.setSize(this.pickWidth, this.pickHeight);
-            this.depthPickTarget.setSize(this.pickWidth, this.pickHeight);
+            if (this.webgl.extensions.drawBuffers) {
+                this.objectPickTexture.define(this.pickWidth, this.pickHeight);
+                this.instancePickTexture.define(this.pickWidth, this.pickHeight);
+                this.groupPickTexture.define(this.pickWidth, this.pickHeight);
+                this.depthPickTexture.define(this.pickWidth, this.pickHeight);
+
+                this.depthRenderbuffer.setSize(this.pickWidth, this.pickHeight);
+            } else {
+                this.objectPickTarget.setSize(this.pickWidth, this.pickHeight);
+                this.instancePickTarget.setSize(this.pickWidth, this.pickHeight);
+                this.groupPickTarget.setSize(this.pickWidth, this.pickHeight);
+                this.depthPickTarget.setSize(this.pickWidth, this.pickHeight);
+            }
         }
     }
 
     private renderVariant(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, variant: 'pick' | 'depth', pickType: number) {
         renderer.clear(false);
-
         renderer.update(camera);
         renderer.renderPick(scene.primitives, camera, variant, null, pickType);
-        renderer.renderPick(helper.handle.scene, camera, variant, null, pickType);
+
+        if (helper.handle.isEnabled) {
+            renderer.renderPick(helper.handle.scene, camera, variant, null, pickType);
+        }
 
         if (helper.camera.isEnabled) {
             helper.camera.update(camera);
@@ -78,18 +187,23 @@ export class PickPass {
     }
 
     render(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper) {
-        this.objectPickTarget.bind();
-        this.renderVariant(renderer, camera, scene, helper, 'pick', PickType.Object);
+        if (this.webgl.extensions.drawBuffers) {
+            this.framebuffer.bind();
+            this.renderVariant(renderer, camera, scene, helper, 'pick', PickType.None);
+        } else {
+            this.objectPickTarget.bind();
+            this.renderVariant(renderer, camera, scene, helper, 'pick', PickType.Object);
 
-        this.instancePickTarget.bind();
-        this.renderVariant(renderer, camera, scene, helper, 'pick', PickType.Instance);
+            this.instancePickTarget.bind();
+            this.renderVariant(renderer, camera, scene, helper, 'pick', PickType.Instance);
 
-        this.groupPickTarget.bind();
-        this.renderVariant(renderer, camera, scene, helper, 'pick', PickType.Group);
-        // printTexture(this.webgl, this.groupPickTarget.texture, { id: 'group' })
+            this.groupPickTarget.bind();
+            this.renderVariant(renderer, camera, scene, helper, 'pick', PickType.Group);
+            // printTexture(this.webgl, this.groupPickTarget.texture, { id: 'group' })
 
-        this.depthPickTarget.bind();
-        this.renderVariant(renderer, camera, scene, helper, 'depth', PickType.None);
+            this.depthPickTarget.bind();
+            this.renderVariant(renderer, camera, scene, helper, 'depth', PickType.None);
+        }
     }
 }
 
@@ -144,19 +258,21 @@ export class PickHelper {
     }
 
     private syncBuffers() {
+        if (isTimingMode) this.webgl.timer.mark('PickHelper.syncBuffers');
         const { pickX, pickY, pickWidth, pickHeight } = this;
 
-        this.pickPass.objectPickTarget.bind();
+        this.pickPass.bindObject();
         this.webgl.readPixels(pickX, pickY, pickWidth, pickHeight, this.objectBuffer);
 
-        this.pickPass.instancePickTarget.bind();
+        this.pickPass.bindInstance();
         this.webgl.readPixels(pickX, pickY, pickWidth, pickHeight, this.instanceBuffer);
 
-        this.pickPass.groupPickTarget.bind();
+        this.pickPass.bindGroup();
         this.webgl.readPixels(pickX, pickY, pickWidth, pickHeight, this.groupBuffer);
 
-        this.pickPass.depthPickTarget.bind();
+        this.pickPass.bindDepth();
         this.webgl.readPixels(pickX, pickY, pickWidth, pickHeight, this.depthBuffer);
+        if (isTimingMode) this.webgl.timer.markEnd('PickHelper.syncBuffers');
     }
 
     private getBufferIdx(x: number, y: number): number {
@@ -175,11 +291,12 @@ export class PickHelper {
     }
 
     private render(camera: Camera | StereoCamera) {
+        if (isTimingMode) this.webgl.timer.mark('PickHelper.render');
         const { pickX, pickY, pickWidth, pickHeight, halfPickWidth } = this;
         const { renderer, scene, helper } = this;
 
         renderer.setTransparentBackground(false);
-        renderer.setDrawingBufferSize(this.pickPass.objectPickTarget.getWidth(), this.pickPass.objectPickTarget.getHeight());
+        renderer.setDrawingBufferSize(pickWidth, pickHeight);
         renderer.setPixelRatio(this.pickScale);
 
         if (StereoCamera.is(camera)) {
@@ -194,6 +311,7 @@ export class PickHelper {
         }
 
         this.dirty = false;
+        if (isTimingMode) this.webgl.timer.markEnd('PickHelper.render');
     }
 
     private identifyInternal(x: number, y: number, camera: Camera | StereoCamera): PickData | undefined {
@@ -214,8 +332,10 @@ export class PickHelper {
         ) return;
 
         if (this.dirty) {
+            if (isTimingMode) this.webgl.timer.mark('PickHelper.identify');
             this.render(camera);
             this.syncBuffers();
+            if (isTimingMode) this.webgl.timer.markEnd('PickHelper.identify');
         }
 
         const xv = x - viewport.x;
@@ -237,6 +357,7 @@ export class PickHelper {
         if (groupId === -1 || groupId === NullId) return;
 
         const z = this.getDepth(xp, yp);
+        // console.log('z', z);
         const position = Vec3.create(x, viewport.height - y, z);
         if (StereoCamera.is(camera)) {
             const halfWidth = Math.floor(viewport.width / 2);
@@ -251,7 +372,7 @@ export class PickHelper {
             cameraUnproject(position, position, viewport, camera.inverseProjectionView);
         }
 
-        // console.log({ { objectId, instanceId, groupId }, position} );
+        // console.log({ id: { objectId, instanceId, groupId }, position });
         return { id: { objectId, instanceId, groupId }, position };
     }
 

+ 3 - 0
src/mol-canvas3d/passes/postprocessing.ts

@@ -27,6 +27,7 @@ import { Framebuffer } from '../../mol-gl/webgl/framebuffer';
 import { Color } from '../../mol-util/color';
 import { FxaaParams, FxaaPass } from './fxaa';
 import { SmaaParams, SmaaPass } from './smaa';
+import { isTimingMode } from '../../mol-util/debug';
 
 const OutlinesSchema = {
     ...QuadSchema,
@@ -549,6 +550,7 @@ export class PostprocessingPass {
     }
 
     render(camera: ICamera, toDrawingBuffer: boolean, transparentBackground: boolean, backgroundColor: Color, props: PostprocessingProps) {
+        if (isTimingMode) this.webgl.timer.mark('PostprocessingPass.render');
         this.updateState(camera, transparentBackground, backgroundColor, props);
 
         if (props.outline.name === 'on') {
@@ -585,6 +587,7 @@ export class PostprocessingPass {
         gl.clear(gl.COLOR_BUFFER_BIT);
 
         this.renderable.render();
+        if (isTimingMode) this.webgl.timer.markEnd('PostprocessingPass.render');
     }
 }
 

+ 3 - 2
src/mol-canvas3d/passes/smaa.ts

@@ -22,7 +22,7 @@ import { weights_frag } from '../../mol-gl/shader/smaa/weights.frag';
 import { edges_vert } from '../../mol-gl/shader/smaa/edges.vert';
 import { edges_frag } from '../../mol-gl/shader/smaa/edges.frag';
 import { Viewport } from '../camera/util';
-import { isDebugMode } from '../../mol-util/debug';
+import { isDebugMode, isTimingMode } from '../../mol-util/debug';
 
 export const SmaaParams = {
     edgeThreshold: PD.Numeric(0.1, { min: 0.05, max: 0.15, step: 0.01 }),
@@ -120,6 +120,7 @@ export class SmaaPass {
     }
 
     render(viewport: Viewport, target: RenderTarget | undefined) {
+        if (isTimingMode) this.webgl.timer.mark('SmaaPass.render');
         this.edgesTarget.bind();
         this.updateState(viewport);
         this.edgesRenderable.render();
@@ -135,8 +136,8 @@ export class SmaaPass {
         }
         this.updateState(viewport);
         this.blendRenderable.render();
+        if (isTimingMode) this.webgl.timer.markEnd('SmaaPass.render');
     }
-
 }
 
 //

+ 3 - 1
src/mol-canvas3d/passes/wboit.ts

@@ -17,7 +17,7 @@ import { quad_vert } from '../../mol-gl/shader/quad.vert';
 import { evaluateWboit_frag } from '../../mol-gl/shader/evaluate-wboit.frag';
 import { Framebuffer } from '../../mol-gl/webgl/framebuffer';
 import { Vec2 } from '../../mol-math/linear-algebra';
-import { isDebugMode } from '../../mol-util/debug';
+import { isDebugMode, isTimingMode } from '../../mol-util/debug';
 
 const EvaluateWboitSchema = {
     ...QuadSchema,
@@ -71,6 +71,7 @@ export class WboitPass {
     }
 
     render() {
+        if (isTimingMode) this.webgl.timer.mark('WboitPass.render');
         const { state, gl } = this.webgl;
 
         state.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
@@ -78,6 +79,7 @@ export class WboitPass {
 
         this.renderable.update();
         this.renderable.render();
+        if (isTimingMode) this.webgl.timer.markEnd('WboitPass.render');
     }
 
     setSize(width: number, height: number) {

+ 7 - 0
src/mol-geo/geometry/texture-mesh/color-smoothing.ts

@@ -20,6 +20,7 @@ import { accumulate_frag } from '../../../mol-gl/shader/compute/color-smoothing/
 import { accumulate_vert } from '../../../mol-gl/shader/compute/color-smoothing/accumulate.vert';
 import { isWebGL2 } from '../../../mol-gl/webgl/compat';
 import { TextureMeshValues } from '../../../mol-gl/renderable/texture-mesh';
+import { isTimingMode } from '../../../mol-util/debug';
 
 export const ColorAccumulateSchema = {
     drawCount: ValueSpec('number'),
@@ -255,6 +256,7 @@ export function calcTextureMeshColorSmoothing(input: ColorSmoothingInput, resolu
     const { drawBuffers } = webgl.extensions;
     if (!drawBuffers) throw new Error('need WebGL draw buffers');
 
+    if (isTimingMode) webgl.timer.mark('calcTextureMeshColorSmoothing');
     const { gl, resources, state, extensions: { colorBufferHalfFloat, textureHalfFloat } } = webgl;
 
     const isInstanceType = input.colorType.endsWith('Instance');
@@ -321,6 +323,7 @@ export function calcTextureMeshColorSmoothing(input: ColorSmoothingInput, resolu
 
     const { uCurrentSlice, uCurrentX, uCurrentY } = accumulateRenderable.values;
 
+    if (isTimingMode) webgl.timer.mark('ColorAccumulate.render');
     setAccumulateDefaults(webgl);
     gl.viewport(0, 0, width, height);
     gl.scissor(0, 0, width, height);
@@ -349,6 +352,7 @@ export function calcTextureMeshColorSmoothing(input: ColorSmoothingInput, resolu
     accumulateTexture.detachFramebuffer(framebuffer, 0);
     countTexture.detachFramebuffer(framebuffer, 1);
     drawBuffers.drawBuffers([gl.COLOR_ATTACHMENT0, gl.NONE]);
+    if (isTimingMode) webgl.timer.markEnd('ColorAccumulate.render');
 
     // const accImage = new Float32Array(width * height * 4);
     // accumulateTexture.attachFramebuffer(framebuffer, 0);
@@ -364,6 +368,7 @@ export function calcTextureMeshColorSmoothing(input: ColorSmoothingInput, resolu
 
     // normalize
 
+    if (isTimingMode) webgl.timer.mark('ColorNormalize.render');
     if (!texture) texture = resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
     texture.define(width, height);
 
@@ -376,6 +381,7 @@ export function calcTextureMeshColorSmoothing(input: ColorSmoothingInput, resolu
     gl.scissor(0, 0, width, height);
     gl.clear(gl.COLOR_BUFFER_BIT);
     normalizeRenderable.render();
+    if (isTimingMode) webgl.timer.markEnd('ColorNormalize.render');
 
     // const normImage = new Uint8Array(width * height * 4);
     // texture.attachFramebuffer(framebuffer, 0);
@@ -385,6 +391,7 @@ export function calcTextureMeshColorSmoothing(input: ColorSmoothingInput, resolu
 
     const gridTransform = Vec4.create(min[0], min[1], min[2], scaleFactor);
     const type = isInstanceType ? 'volumeInstance' : 'volume';
+    if (isTimingMode) webgl.timer.markEnd('calcTextureMeshColorSmoothing');
 
     return { texture, gridDim, gridTexDim: Vec2.create(width, height), gridTransform, type };
 }

+ 18 - 7
src/mol-gl/compute/grid3d.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
  */
@@ -18,8 +18,9 @@ import { createComputeRenderItem } from '../webgl/render-item';
 import { createComputeRenderable } from '../renderable';
 import { isLittleEndian } from '../../mol-util/is-little-endian';
 import { RuntimeContext } from '../../mol-task';
+import { isTimingMode } from '../../mol-util/debug';
 
-export function canComputeGrid3dOnGPU(webgl?: WebGLContext) {
+export function canComputeGrid3dOnGPU(webgl?: WebGLContext): webgl is WebGLContext {
     return !!webgl?.extensions.textureFloat;
 }
 
@@ -159,7 +160,8 @@ export function createGrid3dComputeRenderable<S extends RenderableSchema, P, CS>
 
         const array = new Uint8Array(uWidth * uWidth * 4);
         if (spec.cumulative) {
-            const { gl } = webgl;
+            const { gl, state } = webgl;
+            if (isTimingMode) webgl.timer.mark('Grid3dCompute.renderCumulative');
 
             const states = spec.cumulative.states(params);
 
@@ -167,7 +169,7 @@ export function createGrid3dComputeRenderable<S extends RenderableSchema, P, CS>
             tex[1].define(uWidth, uWidth);
 
             resetGl(webgl, uWidth);
-            gl.clearColor(0, 0, 0, 0);
+            state.clearColor(0, 0, 0, 0);
 
             tex[0].attachFramebuffer(framebuffer, 'color0');
             gl.clear(gl.COLOR_BUFFER_BIT);
@@ -175,12 +177,13 @@ export function createGrid3dComputeRenderable<S extends RenderableSchema, P, CS>
             tex[1].attachFramebuffer(framebuffer, 'color0');
             gl.clear(gl.COLOR_BUFFER_BIT);
 
-            if (spec.cumulative.yieldPeriod) {
+            if (spec.cumulative.yieldPeriod && !isTimingMode) {
                 await ctx.update({ message: 'Computing...', isIndeterminate: false, current: 0, max: states.length });
             }
 
             const yieldPeriod = Math.max(1, spec.cumulative.yieldPeriod ?? 1 | 0);
 
+            if (isTimingMode) webgl.timer.mark('Grid3dCompute.renderBatch');
             for (let i = 0; i < states.length; i++) {
                 ValueCell.update(cells.tCumulativeSum, tex[(i + 1) % 2]);
                 tex[i % 2].attachFramebuffer(framebuffer, 'color0');
@@ -191,23 +194,31 @@ export function createGrid3dComputeRenderable<S extends RenderableSchema, P, CS>
 
                 if (spec.cumulative.yieldPeriod && i !== states.length - 1) {
                     if (i % yieldPeriod === yieldPeriod - 1) {
-                        webgl.readPixels(0, 0, 1, 1, array);
+                        webgl.waitForGpuCommandsCompleteSync();
+                        if (isTimingMode) webgl.timer.markEnd('Grid3dCompute.renderBatch');
+                        if (isTimingMode) webgl.timer.mark('Grid3dCompute.renderBatch');
                     }
-                    if (ctx.shouldUpdate) {
+                    if (ctx.shouldUpdate && !isTimingMode) {
                         await ctx.update({ current: i + 1 });
                     }
                 }
             }
+            if (isTimingMode) webgl.timer.markEnd('Grid3dCompute.renderBatch');
+            if (isTimingMode) webgl.timer.markEnd('Grid3dCompute.renderCumulative');
         } else {
+            if (isTimingMode) webgl.timer.mark('Grid3dCompute.render');
             tex[0].define(uWidth, uWidth);
             tex[0].attachFramebuffer(framebuffer, 'color0');
             framebuffer.bind();
             resetGl(webgl, uWidth);
             renderable.update();
             renderable.render();
+            if (isTimingMode) webgl.timer.markEnd('Grid3dCompute.render');
         }
 
+        if (isTimingMode) webgl.timer.mark('Grid3dCompute.readPixels');
         webgl.readPixels(0, 0, uWidth, uWidth, array);
+        if (isTimingMode) webgl.timer.markEnd('Grid3dCompute.readPixels');
         return new Float32Array(array.buffer, array.byteOffset, nx * ny * nz);
     };
 }

+ 4 - 1
src/mol-gl/compute/histogram-pyramid/reduction.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -19,6 +19,7 @@ import { isPowerOfTwo } from '../../../mol-math/misc';
 import { quad_vert } from '../../../mol-gl/shader/quad.vert';
 import { reduction_frag } from '../../../mol-gl/shader/histogram-pyramid/reduction.frag';
 import { isWebGL2 } from '../../webgl/compat';
+import { isTimingMode } from '../../../mol-util/debug';
 
 const HistopyramidReductionSchema = {
     ...QuadSchema,
@@ -120,6 +121,7 @@ export interface HistogramPyramid {
 }
 
 export function createHistogramPyramid(ctx: WebGLContext, inputTexture: Texture, scale: Vec2, gridTexDim: Vec3): HistogramPyramid {
+    if (isTimingMode) ctx.timer.mark('createHistogramPyramid');
     const { gl } = ctx;
     const w = inputTexture.getWidth();
     const h = inputTexture.getHeight();
@@ -193,6 +195,7 @@ export function createHistogramPyramid(ctx: WebGLContext, inputTexture: Texture,
     }
 
     gl.finish();
+    if (isTimingMode) ctx.timer.markEnd('createHistogramPyramid');
 
     // printTexture(ctx, pyramidTex, 2)
 

+ 4 - 1
src/mol-gl/compute/histogram-pyramid/sum.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -16,6 +16,7 @@ import { QuadSchema, QuadValues } from '../util';
 import { quad_vert } from '../../../mol-gl/shader/quad.vert';
 import { sum_frag } from '../../../mol-gl/shader/histogram-pyramid/sum.frag';
 import { isWebGL2 } from '../../webgl/compat';
+import { isTimingMode } from '../../../mol-util/debug';
 
 const HistopyramidSumSchema = {
     ...QuadSchema,
@@ -66,6 +67,7 @@ const sumBytes = new Uint8Array(4);
 const sumInts = new Int32Array(4);
 
 export function getHistopyramidSum(ctx: WebGLContext, pyramidTopTexture: Texture) {
+    if (isTimingMode) ctx.timer.mark('getHistopyramidSum');
     const { gl, resources } = ctx;
 
     const renderable = getHistopyramidSumRenderable(ctx, pyramidTopTexture);
@@ -93,6 +95,7 @@ export function getHistopyramidSum(ctx: WebGLContext, pyramidTopTexture: Texture
 
     ctx.readPixels(0, 0, 1, 1, isWebGL2(gl) ? sumInts : sumBytes);
     ctx.unbindFramebuffer();
+    if (isTimingMode) ctx.timer.markEnd('getHistopyramidSum');
 
     return isWebGL2(gl)
         ? sumInts[0]

+ 4 - 1
src/mol-gl/compute/marching-cubes/active-voxels.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -16,6 +16,7 @@ import { QuadSchema, QuadValues } from '../util';
 import { getTriCount } from './tables';
 import { quad_vert } from '../../../mol-gl/shader/quad.vert';
 import { activeVoxels_frag } from '../../../mol-gl/shader/marching-cubes/active-voxels.frag';
+import { isTimingMode } from '../../../mol-util/debug';
 
 const ActiveVoxelsSchema = {
     ...QuadSchema,
@@ -83,6 +84,7 @@ function setRenderingDefaults(ctx: WebGLContext) {
 }
 
 export function calcActiveVoxels(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, isoValue: number, gridScale: Vec2) {
+    if (isTimingMode) ctx.timer.mark('calcActiveVoxels');
     const { gl, resources } = ctx;
     const width = volumeData.getWidth();
     const height = volumeData.getHeight();
@@ -115,6 +117,7 @@ export function calcActiveVoxels(ctx: WebGLContext, volumeData: Texture, gridDim
     // console.log('at', readTexture(ctx, activeVoxelsTex));
 
     gl.finish();
+    if (isTimingMode) ctx.timer.markEnd('calcActiveVoxels');
 
     return activeVoxelsTex;
 }

+ 6 - 12
src/mol-gl/compute/marching-cubes/isosurface.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -19,6 +19,7 @@ import { quad_vert } from '../../../mol-gl/shader/quad.vert';
 import { isosurface_frag } from '../../../mol-gl/shader/marching-cubes/isosurface.frag';
 import { calcActiveVoxels } from './active-voxels';
 import { isWebGL2 } from '../../webgl/compat';
+import { isTimingMode } from '../../../mol-util/debug';
 
 const IsosurfaceSchema = {
     ...QuadSchema,
@@ -122,6 +123,7 @@ export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Tex
     const { drawBuffers } = ctx.extensions;
     if (!drawBuffers) throw new Error('need WebGL draw buffers');
 
+    if (isTimingMode) ctx.timer.mark('createIsosurfaceBuffers');
     const { gl, resources, extensions } = ctx;
     const { pyramidTex, height, levels, scale, count } = histogramPyramid;
     const width = pyramidTex.getWidth();
@@ -192,6 +194,7 @@ export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Tex
     renderable.render();
 
     gl.finish();
+    if (isTimingMode) ctx.timer.markEnd('createIsosurfaceBuffers');
 
     return { vertexTexture, groupTexture, normalTexture, vertexCount: count };
 }
@@ -208,20 +211,11 @@ export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Tex
  * Implementation based on http://www.miaumiau.cat/2016/10/stream-compaction-in-webgl/
  */
 export function extractIsosurface(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, gridTexScale: Vec2, transform: Mat4, isoValue: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
-    // console.time('calcActiveVoxels');
+    if (isTimingMode) ctx.timer.mark('extractIsosurface');
     const activeVoxelsTex = calcActiveVoxels(ctx, volumeData, gridDim, gridTexDim, isoValue, gridTexScale);
-    // ctx.waitForGpuCommandsCompleteSync();
-    // console.timeEnd('calcActiveVoxels');
-
-    // console.time('createHistogramPyramid');
     const compacted = createHistogramPyramid(ctx, activeVoxelsTex, gridTexScale, gridTexDim);
-    // ctx.waitForGpuCommandsCompleteSync();
-    // console.timeEnd('createHistogramPyramid');
-
-    // console.time('createIsosurfaceBuffers');
     const gv = createIsosurfaceBuffers(ctx, activeVoxelsTex, volumeData, compacted, gridDim, gridTexDim, transform, isoValue, invert, packedGroup, axisOrder, vertexTexture, groupTexture, normalTexture);
-    // ctx.waitForGpuCommandsCompleteSync();
-    // console.timeEnd('createIsosurfaceBuffers');
+    if (isTimingMode) ctx.timer.markEnd('extractIsosurface');
 
     return gv;
 }

+ 23 - 0
src/mol-gl/renderer.ts

@@ -19,6 +19,7 @@ import { degToRad } from '../mol-math/misc';
 import { Texture, Textures } from './webgl/texture';
 import { arrayMapUpsert } from '../mol-util/array';
 import { clamp } from '../mol-math/interpolate';
+import { isTimingMode } from '../mol-util/debug';
 
 export interface RendererStats {
     programCount: number
@@ -360,6 +361,7 @@ namespace Renderer {
         };
 
         const renderPick = (group: Scene.Group, camera: ICamera, variant: GraphicsRenderVariant, depthTexture: Texture | null, pickType: PickType) => {
+            if (isTimingMode) ctx.timer.mark('Renderer.renderPick');
             state.disable(gl.BLEND);
             state.enable(gl.DEPTH_TEST);
             state.depthMask(true);
@@ -373,9 +375,11 @@ namespace Renderer {
                     renderObject(renderables[i], variant, Flag.None);
                 }
             }
+            if (isTimingMode) ctx.timer.markEnd('Renderer.renderPick');
         };
 
         const renderDepth = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
+            if (isTimingMode) ctx.timer.mark('Renderer.renderDepth');
             state.disable(gl.BLEND);
             state.enable(gl.DEPTH_TEST);
             state.depthMask(true);
@@ -386,9 +390,11 @@ namespace Renderer {
             for (let i = 0, il = renderables.length; i < il; ++i) {
                 renderObject(renderables[i], 'depth', Flag.None);
             }
+            if (isTimingMode) ctx.timer.markEnd('Renderer.renderDepth');
         };
 
         const renderDepthOpaque = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
+            if (isTimingMode) ctx.timer.mark('Renderer.renderDepthOpaque');
             state.disable(gl.BLEND);
             state.enable(gl.DEPTH_TEST);
             state.depthMask(true);
@@ -402,9 +408,11 @@ namespace Renderer {
                     renderObject(r, 'depth', Flag.None);
                 }
             }
+            if (isTimingMode) ctx.timer.markEnd('Renderer.renderDepthOpaque');
         };
 
         const renderDepthTransparent = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
+            if (isTimingMode) ctx.timer.mark('Renderer.renderDepthTransparent');
             state.disable(gl.BLEND);
             state.enable(gl.DEPTH_TEST);
             state.depthMask(true);
@@ -418,9 +426,11 @@ namespace Renderer {
                     renderObject(r, 'depth', Flag.None);
                 }
             }
+            if (isTimingMode) ctx.timer.markEnd('Renderer.renderDepthTransparent');
         };
 
         const renderMarkingDepth = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
+            if (isTimingMode) ctx.timer.mark('Renderer.renderMarkingDepth');
             state.disable(gl.BLEND);
             state.enable(gl.DEPTH_TEST);
             state.depthMask(true);
@@ -436,9 +446,11 @@ namespace Renderer {
                     renderObject(renderables[i], 'marking', Flag.None);
                 }
             }
+            if (isTimingMode) ctx.timer.markEnd('Renderer.renderMarkingDepth');
         };
 
         const renderMarkingMask = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
+            if (isTimingMode) ctx.timer.mark('Renderer.renderMarkingMask');
             state.disable(gl.BLEND);
             state.enable(gl.DEPTH_TEST);
             state.depthMask(true);
@@ -454,6 +466,7 @@ namespace Renderer {
                     renderObject(renderables[i], 'marking', Flag.None);
                 }
             }
+            if (isTimingMode) ctx.timer.markEnd('Renderer.renderMarkingMask');
         };
 
         const renderBlended = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
@@ -462,6 +475,7 @@ namespace Renderer {
         };
 
         const renderBlendedOpaque = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
+            if (isTimingMode) ctx.timer.mark('Renderer.renderBlendedOpaque');
             state.disable(gl.BLEND);
             state.enable(gl.DEPTH_TEST);
             state.depthMask(true);
@@ -477,9 +491,11 @@ namespace Renderer {
                     renderObject(r, 'colorBlended', Flag.BlendedBack);
                 }
             }
+            if (isTimingMode) ctx.timer.markEnd('Renderer.renderBlendedOpaque');
         };
 
         const renderBlendedTransparent = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
+            if (isTimingMode) ctx.timer.mark('Renderer.renderBlendedTransparent');
             state.enable(gl.DEPTH_TEST);
 
             updateInternal(group, camera, depthTexture, Mask.Transparent, false);
@@ -516,9 +532,11 @@ namespace Renderer {
                     }
                 }
             }
+            if (isTimingMode) ctx.timer.markEnd('Renderer.renderBlendedTransparent');
         };
 
         const renderBlendedVolume = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
+            if (isTimingMode) ctx.timer.mark('Renderer.renderBlendedVolume');
             state.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
             state.enable(gl.BLEND);
 
@@ -531,9 +549,11 @@ namespace Renderer {
                     renderObject(r, 'colorBlended', Flag.None);
                 }
             }
+            if (isTimingMode) ctx.timer.markEnd('Renderer.renderBlendedVolume');
         };
 
         const renderWboitOpaque = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
+            if (isTimingMode) ctx.timer.mark('Renderer.renderWboitOpaque');
             state.disable(gl.BLEND);
             state.enable(gl.DEPTH_TEST);
             state.depthMask(true);
@@ -551,9 +571,11 @@ namespace Renderer {
                     renderObject(r, 'colorWboit', Flag.None);
                 }
             }
+            if (isTimingMode) ctx.timer.markEnd('Renderer.renderWboitOpaque');
         };
 
         const renderWboitTransparent = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
+            if (isTimingMode) ctx.timer.mark('Renderer.renderWboitTransparent');
             updateInternal(group, camera, depthTexture, Mask.Transparent, false);
 
             const { renderables } = group;
@@ -567,6 +589,7 @@ namespace Renderer {
                     renderObject(r, 'colorWboit', Flag.None);
                 }
             }
+            if (isTimingMode) ctx.timer.markEnd('Renderer.renderWboitTransparent');
         };
 
         return {

+ 42 - 10
src/mol-gl/shader-code.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -236,6 +236,18 @@ function getDefinesCode(defines: ShaderDefines, ignore?: IgnoreDefine) {
     return lines.join('\n') + '\n';
 }
 
+function getGlsl100VertPrefix(extensions: WebGLExtensions, shaderExtensions: ShaderExtensions) {
+    const prefix: string[] = [];
+    if (shaderExtensions.drawBuffers) {
+        if (extensions.drawBuffers) {
+            prefix.push('#define requiredDrawBuffers');
+        } else if (shaderExtensions.drawBuffers === 'required') {
+            throw new Error(`required 'GL_EXT_draw_buffers' extension not available`);
+        }
+    }
+    return prefix.join('\n') + '\n';
+}
+
 function getGlsl100FragPrefix(extensions: WebGLExtensions, shaderExtensions: ShaderExtensions) {
     const prefix: string[] = [
         '#extension GL_OES_standard_derivatives : enable'
@@ -271,7 +283,7 @@ function getGlsl100FragPrefix(extensions: WebGLExtensions, shaderExtensions: Sha
     return prefix.join('\n') + '\n';
 }
 
-const glsl300VertPrefix = `#version 300 es
+const glsl300VertPrefixCommon = `
 #define attribute in
 #define varying out
 #define texture2D texture
@@ -288,24 +300,42 @@ const glsl300FragPrefixCommon = `
 #define depthTextureSupport
 `;
 
+function getGlsl300VertPrefix(extensions: WebGLExtensions, shaderExtensions: ShaderExtensions) {
+    const prefix = [
+        '#version 300 es',
+    ];
+    if (shaderExtensions.drawBuffers) {
+        if (extensions.drawBuffers) {
+            prefix.push('#define requiredDrawBuffers');
+        }
+    }
+    prefix.push(glsl300VertPrefixCommon);
+    return prefix.join('\n') + '\n';
+}
+
 function getGlsl300FragPrefix(gl: WebGL2RenderingContext, extensions: WebGLExtensions, shaderExtensions: ShaderExtensions, outTypes: FragOutTypes) {
     const prefix = [
         '#version 300 es',
         `layout(location = 0) out highp ${outTypes[0] || 'vec4'} out_FragData0;`
     ];
-
     if (shaderExtensions.fragDepth) {
-        prefix.push('#define enabledFragDepth');
+        if (extensions.fragDepth) {
+            prefix.push('#define enabledFragDepth');
+        }
     }
     if (shaderExtensions.drawBuffers) {
-        prefix.push('#define requiredDrawBuffers');
-        const maxDrawBuffers = gl.getParameter(gl.MAX_DRAW_BUFFERS) as number;
-        for (let i = 1, il = maxDrawBuffers; i < il; ++i) {
-            prefix.push(`layout(location = ${i}) out highp ${outTypes[i] || 'vec4'} out_FragData${i};`);
+        if (extensions.drawBuffers) {
+            prefix.push('#define requiredDrawBuffers');
+            const maxDrawBuffers = gl.getParameter(gl.MAX_DRAW_BUFFERS) as number;
+            for (let i = 1, il = maxDrawBuffers; i < il; ++i) {
+                prefix.push(`layout(location = ${i}) out highp ${outTypes[i] || 'vec4'} out_FragData${i};`);
+            }
         }
     }
     if (shaderExtensions.shaderTextureLod) {
-        prefix.push('#define enabledShaderTextureLod');
+        if (extensions.shaderTextureLod) {
+            prefix.push('#define enabledShaderTextureLod');
+        }
     }
     prefix.push(glsl300FragPrefixCommon);
     return prefix.join('\n') + '\n';
@@ -318,7 +348,9 @@ function transformGlsl300Frag(frag: string) {
 export function addShaderDefines(gl: GLRenderingContext, extensions: WebGLExtensions, defines: ShaderDefines, shaders: ShaderCode): ShaderCode {
     const vertHeader = getDefinesCode(defines, shaders.ignoreDefine);
     const fragHeader = getDefinesCode(defines, shaders.ignoreDefine);
-    const vertPrefix = isWebGL2(gl) ? glsl300VertPrefix : '';
+    const vertPrefix = isWebGL2(gl)
+        ? getGlsl300VertPrefix(extensions, shaders.extensions)
+        : getGlsl100VertPrefix(extensions, shaders.extensions);
     const fragPrefix = isWebGL2(gl)
         ? getGlsl300FragPrefix(gl, extensions, shaders.extensions, shaders.outTypes)
         : getGlsl100FragPrefix(extensions, shaders.extensions);

+ 13 - 7
src/mol-gl/shader/chunks/assign-color-varying.glsl.ts

@@ -56,13 +56,19 @@ export const assign_color_varying = `
         vSubstance.rgb = mix(vec3(uMetalness, uRoughness, uBumpiness), vSubstance.rgb, vSubstance.a);
     #endif
 #elif defined(dRenderVariant_pick)
-    if (uPickType == 1) {
-        vColor = vec4(packIntToRGB(float(uObjectId)), 1.0);
-    } else if (uPickType == 2) {
-        vColor = vec4(packIntToRGB(aInstance), 1.0);
-    } else {
-        vColor = vec4(packIntToRGB(group), 1.0);
-    }
+    #ifdef requiredDrawBuffers
+        vObject = vec4(packIntToRGB(float(uObjectId)), 1.0);
+        vInstance = vec4(packIntToRGB(aInstance), 1.0);
+        vGroup = vec4(packIntToRGB(group), 1.0);
+    #else
+        if (uPickType == 1) {
+            vColor = vec4(packIntToRGB(float(uObjectId)), 1.0);
+        } else if (uPickType == 2) {
+            vColor = vec4(packIntToRGB(aInstance), 1.0);
+        } else {
+            vColor = vec4(packIntToRGB(group), 1.0);
+        }
+    #endif
 #endif
 
 #ifdef dTransparency

+ 0 - 2
src/mol-gl/shader/chunks/assign-material-color.glsl.ts

@@ -28,8 +28,6 @@ export const assign_material_color = `
         roughness = mix(roughness, vSubstance.g, vSubstance.a);
         bumpiness = mix(bumpiness, vSubstance.b, vSubstance.a);
     #endif
-#elif defined(dRenderVariant_pick)
-    vec4 material = vColor;
 #elif defined(dRenderVariant_depth)
     if (fragmentDepth > getDepth(gl_FragCoord.xy / uDrawingBufferSize)) {
         discard;

+ 14 - 2
src/mol-gl/shader/chunks/color-frag-params.glsl.ts

@@ -28,9 +28,21 @@ uniform float uBumpiness;
     #endif
 #elif defined(dRenderVariant_pick)
     #if __VERSION__ == 100
-        varying vec4 vColor;
+        #ifdef requiredDrawBuffers
+            varying vec4 vObject;
+            varying vec4 vInstance;
+            varying vec4 vGroup;
+        #else
+            varying vec4 vColor;
+        #endif
     #else
-        flat in vec4 vColor;
+        #ifdef requiredDrawBuffers
+            flat in vec4 vObject;
+            flat in vec4 vInstance;
+            flat in vec4 vGroup;
+        #else
+            flat in vec4 vColor;
+        #endif
     #endif
 #endif
 

+ 14 - 2
src/mol-gl/shader/chunks/color-vert-params.glsl.ts

@@ -56,9 +56,21 @@ uniform float uBumpiness;
     #endif
 #elif defined(dRenderVariant_pick)
     #if __VERSION__ == 100
-        varying vec4 vColor;
+        #ifdef requiredDrawBuffers
+            varying vec4 vObject;
+            varying vec4 vInstance;
+            varying vec4 vGroup;
+        #else
+            varying vec4 vColor;
+        #endif
     #else
-        flat out vec4 vColor;
+        #ifdef requiredDrawBuffers
+            flat out vec4 vObject;
+            flat out vec4 vInstance;
+            flat out vec4 vGroup;
+        #else
+            flat out vec4 vColor;
+        #endif
     #endif
 #endif
 

+ 8 - 1
src/mol-gl/shader/cylinders.frag.ts

@@ -121,7 +121,14 @@ void main() {
 
     #if defined(dRenderVariant_pick)
         #include check_picking_alpha
-        gl_FragColor = material;
+        #ifdef requiredDrawBuffers
+            gl_FragColor = vObject;
+            gl_FragData[1] = vInstance;
+            gl_FragData[2] = vGroup;
+            gl_FragData[3] = packDepthToRGBA(fragmentDepth);
+        #else
+            gl_FragColor = vColor;
+        #endif
     #elif defined(dRenderVariant_depth)
         gl_FragColor = material;
     #elif defined(dRenderVariant_marking)

+ 0 - 1
src/mol-gl/shader/direct-volume.frag.ts

@@ -68,7 +68,6 @@ uniform float uFogFar;
 uniform vec3 uFogColor;
 
 uniform float uAlpha;
-uniform float uPickingAlphaThreshold;
 uniform bool uTransparentBackground;
 uniform float uXrayEdgeFalloff;
 

+ 14 - 6
src/mol-gl/shader/image.frag.ts

@@ -103,13 +103,21 @@ void main() {
     #if defined(dRenderVariant_pick)
         if (imageData.a < 0.3)
             discard;
-        if (uPickType == 1) {
+        #ifdef requiredDrawBuffers
             gl_FragColor = vec4(packIntToRGB(float(uObjectId)), 1.0);
-        } else if (uPickType == 2) {
-            gl_FragColor = vec4(packIntToRGB(vInstance), 1.0);
-        } else {
-            gl_FragColor = vec4(texture2D(tGroupTex, vUv).rgb, 1.0);
-        }
+            gl_FragData[1] = vec4(packIntToRGB(vInstance), 1.0);
+            gl_FragData[2] = vec4(texture2D(tGroupTex, vUv).rgb, 1.0);
+            gl_FragData[3] = packDepthToRGBA(gl_FragCoord.z);
+        #else
+            gl_FragColor = vColor;
+            if (uPickType == 1) {
+                gl_FragColor = vec4(packIntToRGB(float(uObjectId)), 1.0);
+            } else if (uPickType == 2) {
+                gl_FragColor = vec4(packIntToRGB(vInstance), 1.0);
+            } else {
+                gl_FragColor = vec4(texture2D(tGroupTex, vUv).rgb, 1.0);
+            }
+        #endif
     #elif defined(dRenderVariant_depth)
         if (imageData.a < 0.05)
             discard;

+ 8 - 1
src/mol-gl/shader/lines.frag.ts

@@ -21,7 +21,14 @@ void main(){
 
     #if defined(dRenderVariant_pick)
         #include check_picking_alpha
-        gl_FragColor = material;
+        #ifdef requiredDrawBuffers
+            gl_FragColor = vObject;
+            gl_FragData[1] = vInstance;
+            gl_FragData[2] = vGroup;
+            gl_FragData[3] = packDepthToRGBA(fragmentDepth);
+        #else
+            gl_FragColor = vColor;
+        #endif
     #elif defined(dRenderVariant_depth)
         gl_FragColor = material;
     #elif defined(dRenderVariant_marking)

+ 8 - 1
src/mol-gl/shader/mesh.frag.ts

@@ -37,7 +37,14 @@ void main() {
 
     #if defined(dRenderVariant_pick)
         #include check_picking_alpha
-        gl_FragColor = material;
+        #ifdef requiredDrawBuffers
+            gl_FragColor = vObject;
+            gl_FragData[1] = vInstance;
+            gl_FragData[2] = vGroup;
+            gl_FragData[3] = packDepthToRGBA(fragmentDepth);
+        #else
+            gl_FragColor = vColor;
+        #endif
     #elif defined(dRenderVariant_depth)
         gl_FragColor = material;
     #elif defined(dRenderVariant_marking)

+ 8 - 1
src/mol-gl/shader/points.frag.ts

@@ -33,7 +33,14 @@ void main(){
 
     #if defined(dRenderVariant_pick)
         #include check_picking_alpha
-        gl_FragColor = material;
+        #ifdef requiredDrawBuffers
+            gl_FragColor = vObject;
+            gl_FragData[1] = vInstance;
+            gl_FragData[2] = vGroup;
+            gl_FragData[3] = packDepthToRGBA(fragmentDepth);
+        #else
+            gl_FragColor = vColor;
+        #endif
     #elif defined(dRenderVariant_depth)
         gl_FragColor = material;
     #elif defined(dRenderVariant_marking)

+ 8 - 1
src/mol-gl/shader/spheres.frag.ts

@@ -86,7 +86,14 @@ void main(void){
 
     #if defined(dRenderVariant_pick)
         #include check_picking_alpha
-        gl_FragColor = material;
+        #ifdef requiredDrawBuffers
+            gl_FragColor = vObject;
+            gl_FragData[1] = vInstance;
+            gl_FragData[2] = vGroup;
+            gl_FragData[3] = packDepthToRGBA(fragmentDepth);
+        #else
+            gl_FragColor = vColor;
+        #endif
     #elif defined(dRenderVariant_depth)
         gl_FragColor = material;
     #elif defined(dRenderVariant_marking)

+ 21 - 8
src/mol-gl/shader/text.frag.ts

@@ -36,7 +36,9 @@ void main(){
     #include assign_material_color
 
     if (vTexCoord.x > 1.0) {
-        gl_FragColor = vec4(uBackgroundColor, uBackgroundOpacity * material.a);
+        #if defined(dRenderVariant_color)
+            material = vec4(uBackgroundColor, uBackgroundOpacity * material.a);
+        #endif
     } else {
         // retrieve signed distance
         float sdf = texture2D(tFont, vTexCoord).a + uBorderWidth;
@@ -49,24 +51,35 @@ void main(){
         a = pow(a, 1.0 / gamma);
 
         if (a < 0.5) discard;
-        material.a *= a;
 
-        // add border
-        float t = 0.5 + uBorderWidth;
-        if (uBorderWidth > 0.0 && sdf < t) {
-            material.xyz = mix(uBorderColor, material.xyz, smoothstep(t - w, t, sdf));
-        }
+        #if defined(dRenderVariant_color)
+            material.a *= a;
 
-        gl_FragColor = material;
+            // add border
+            float t = 0.5 + uBorderWidth;
+            if (uBorderWidth > 0.0 && sdf < t) {
+                material.xyz = mix(uBorderColor, material.xyz, smoothstep(t - w, t, sdf));
+            }
+        #endif
     }
 
     #if defined(dRenderVariant_pick)
         #include check_picking_alpha
+        #ifdef requiredDrawBuffers
+            gl_FragColor = vObject;
+            gl_FragData[1] = vInstance;
+            gl_FragData[2] = vGroup;
+            gl_FragData[3] = packDepthToRGBA(fragmentDepth);
+        #else
+            gl_FragColor = vColor;
+        #endif
     #elif defined(dRenderVariant_depth)
         gl_FragColor = material;
     #elif defined(dRenderVariant_marking)
         gl_FragColor = material;
     #elif defined(dRenderVariant_color)
+        gl_FragColor = material;
+
         #include apply_marker_color
         #include apply_fog
         #include wboit_write

+ 82 - 1
src/mol-gl/webgl/compat.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -320,6 +320,87 @@ export function getSRGB(gl: GLRenderingContext): COMPAT_sRGB | null {
     }
 }
 
+export interface COMPAT_disjoint_timer_query {
+    /** A GLint indicating the number of bits used to hold the query result for the given target. */
+    QUERY_COUNTER_BITS: number
+    /** A WebGLQuery object, which is the currently active query for the given target. */
+    CURRENT_QUERY: number
+    /** A GLuint64EXT containing the query result. */
+    QUERY_RESULT: number
+    /** A GLboolean indicating whether or not a query result is available. */
+    QUERY_RESULT_AVAILABLE: number
+    /** Elapsed time (in nanoseconds). */
+    TIME_ELAPSED: number
+    /** The current time. */
+    TIMESTAMP: number
+    /** A GLboolean indicating whether or not the GPU performed any disjoint operation. */
+    GPU_DISJOINT: number
+
+    /** Creates a new WebGLTimerQueryEXT. */
+    createQuery: () => WebGLQuery
+    /** Deletes a given WebGLTimerQueryEXT. */
+    deleteQuery: (query: WebGLQuery) => void
+    /** Returns true if a given object is a valid WebGLTimerQueryEXT. */
+    isQuery: (query: WebGLQuery) => boolean
+    /** The timer starts when all commands prior to beginQueryEXT have been fully executed. */
+    beginQuery: (target: number, query: WebGLQuery) => void
+    /** The timer stops when all commands prior to endQueryEXT have been fully executed. */
+    endQuery: (target: number) => void
+    /** Records the current time into the corresponding query object. */
+    queryCounter: (query: WebGLQuery, target: number) => void
+    /** Returns information about a query target. */
+    getQuery: (target: number, pname: number) => WebGLQuery | number
+    /** Return the state of a query object. */
+    getQueryParameter: (query: WebGLQuery, pname: number) => number | boolean
+}
+
+export function getDisjointTimerQuery(gl: GLRenderingContext): COMPAT_disjoint_timer_query | null {
+    if (isWebGL2(gl)) {
+        // Firefox has EXT_disjoint_timer_query in webgl2
+        const ext = gl.getExtension('EXT_disjoint_timer_query_webgl2') || gl.getExtension('EXT_disjoint_timer_query');
+        if (ext === null) return null;
+        return {
+            QUERY_COUNTER_BITS: ext.QUERY_COUNTER_BITS_EXT,
+            CURRENT_QUERY: gl.CURRENT_QUERY,
+            QUERY_RESULT: gl.QUERY_RESULT,
+            QUERY_RESULT_AVAILABLE: gl.QUERY_RESULT_AVAILABLE,
+            TIME_ELAPSED: ext.TIME_ELAPSED_EXT,
+            TIMESTAMP: ext.TIMESTAMP_EXT,
+            GPU_DISJOINT: ext.GPU_DISJOINT_EXT,
+
+            createQuery: gl.createQuery.bind(gl),
+            deleteQuery: gl.deleteQuery.bind(gl),
+            isQuery: gl.isQuery.bind(gl),
+            beginQuery: gl.beginQuery.bind(gl),
+            endQuery: gl.endQuery.bind(gl),
+            queryCounter: ext.queryCounterEXT.bind(ext),
+            getQuery: gl.getQuery.bind(gl),
+            getQueryParameter: gl.getQueryParameter.bind(gl),
+        };
+    } else {
+        const ext = gl.getExtension('EXT_disjoint_timer_query');
+        if (ext === null) return null;
+        return {
+            QUERY_COUNTER_BITS: ext.QUERY_COUNTER_BITS_EXT,
+            CURRENT_QUERY: ext.CURRENT_QUERY_EXT,
+            QUERY_RESULT: ext.QUERY_RESULT_EXT,
+            QUERY_RESULT_AVAILABLE: ext.QUERY_RESULT_AVAILABLE_EXT,
+            TIME_ELAPSED: ext.TIME_ELAPSED_EXT,
+            TIMESTAMP: ext.TIMESTAMP_EXT,
+            GPU_DISJOINT: ext.GPU_DISJOINT_EXT,
+
+            createQuery: ext.createQueryEXT.bind(ext),
+            deleteQuery: ext.deleteQueryEXT.bind(ext),
+            isQuery: ext.isQueryEXT.bind(ext),
+            beginQuery: ext.beginQueryEXT.bind(ext),
+            endQuery: ext.endQueryEXT.bind(ext),
+            queryCounter: ext.queryCounterEXT.bind(ext),
+            getQuery: ext.getQueryEXT.bind(ext),
+            getQueryParameter: ext.getQueryObjectEXT.bind(ext),
+        };
+    }
+}
+
 //
 
 const TextureTestVertShader = `

+ 4 - 0
src/mol-gl/webgl/context.ts

@@ -17,6 +17,7 @@ import { BehaviorSubject } from 'rxjs';
 import { now } from '../../mol-util/now';
 import { Texture, TextureFilter } from './texture';
 import { ComputeRenderable } from '../renderable';
+import { createTimer, WebGLTimer } from './timer';
 
 export function getGLContext(canvas: HTMLCanvasElement, attribs?: WebGLContextAttributes & { preferWebGl1?: boolean }): GLRenderingContext | null {
     function get(id: 'webgl' | 'experimental-webgl' | 'webgl2') {
@@ -186,6 +187,7 @@ export interface WebGLContext {
     readonly state: WebGLState
     readonly stats: WebGLStats
     readonly resources: WebGLResources
+    readonly timer: WebGLTimer
 
     readonly maxTextureSize: number
     readonly max3dTextureSize: number
@@ -221,6 +223,7 @@ export function createContext(gl: GLRenderingContext, props: Partial<{ pixelScal
     const state = createState(gl);
     const stats = createStats();
     const resources = createResources(gl, state, stats, extensions);
+    const timer = createTimer(gl, extensions);
 
     const parameters = {
         maxTextureSize: gl.getParameter(gl.MAX_TEXTURE_SIZE) as number,
@@ -289,6 +292,7 @@ export function createContext(gl: GLRenderingContext, props: Partial<{ pixelScal
         state,
         stats,
         resources,
+        timer,
 
         get maxTextureSize() { return parameters.maxTextureSize; },
         get max3dTextureSize() { return parameters.max3dTextureSize; },

+ 8 - 2
src/mol-gl/webgl/extensions.ts

@@ -1,10 +1,10 @@
 /**
- * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { GLRenderingContext, COMPAT_instanced_arrays, COMPAT_standard_derivatives, COMPAT_vertex_array_object, getInstancedArrays, getStandardDerivatives, COMPAT_element_index_uint, getElementIndexUint, COMPAT_texture_float, getTextureFloat, COMPAT_texture_float_linear, getTextureFloatLinear, COMPAT_blend_minmax, getBlendMinMax, getFragDepth, COMPAT_frag_depth, COMPAT_color_buffer_float, getColorBufferFloat, COMPAT_draw_buffers, getDrawBuffers, getShaderTextureLod, COMPAT_shader_texture_lod, getDepthTexture, COMPAT_depth_texture, COMPAT_sRGB, getSRGB, getTextureHalfFloat, getTextureHalfFloatLinear, COMPAT_texture_half_float, COMPAT_texture_half_float_linear, COMPAT_color_buffer_half_float, getColorBufferHalfFloat, getVertexArrayObject } from './compat';
+import { GLRenderingContext, COMPAT_instanced_arrays, COMPAT_standard_derivatives, COMPAT_vertex_array_object, getInstancedArrays, getStandardDerivatives, COMPAT_element_index_uint, getElementIndexUint, COMPAT_texture_float, getTextureFloat, COMPAT_texture_float_linear, getTextureFloatLinear, COMPAT_blend_minmax, getBlendMinMax, getFragDepth, COMPAT_frag_depth, COMPAT_color_buffer_float, getColorBufferFloat, COMPAT_draw_buffers, getDrawBuffers, getShaderTextureLod, COMPAT_shader_texture_lod, getDepthTexture, COMPAT_depth_texture, COMPAT_sRGB, getSRGB, getTextureHalfFloat, getTextureHalfFloatLinear, COMPAT_texture_half_float, COMPAT_texture_half_float_linear, COMPAT_color_buffer_half_float, getColorBufferHalfFloat, getVertexArrayObject, getDisjointTimerQuery, COMPAT_disjoint_timer_query } from './compat';
 import { isDebugMode } from '../../mol-util/debug';
 
 export type WebGLExtensions = {
@@ -25,6 +25,7 @@ export type WebGLExtensions = {
     drawBuffers: COMPAT_draw_buffers | null
     shaderTextureLod: COMPAT_shader_texture_lod | null
     sRGB: COMPAT_sRGB | null
+    disjointTimerQuery: COMPAT_disjoint_timer_query | null
 }
 
 export function createExtensions(gl: GLRenderingContext): WebGLExtensions {
@@ -99,6 +100,10 @@ export function createExtensions(gl: GLRenderingContext): WebGLExtensions {
     if (isDebugMode && sRGB === null) {
         console.log('Could not find support for "sRGB"');
     }
+    const disjointTimerQuery = getDisjointTimerQuery(gl);
+    if (isDebugMode && disjointTimerQuery === null) {
+        console.log('Could not find support for "disjoint_timer_query"');
+    }
 
     return {
         instancedArrays,
@@ -118,5 +123,6 @@ export function createExtensions(gl: GLRenderingContext): WebGLExtensions {
         drawBuffers,
         shaderTextureLod,
         sRGB,
+        disjointTimerQuery,
     };
 }

+ 194 - 0
src/mol-gl/webgl/timer.ts

@@ -0,0 +1,194 @@
+/**
+ * Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { GLRenderingContext } from './compat';
+import { WebGLExtensions } from './extensions';
+
+export type TimerResult = {
+    readonly label: string
+    readonly timeElapsed: number
+    readonly children: TimerResult[]
+}
+
+function getQuery(extensions: WebGLExtensions) {
+    return extensions.disjointTimerQuery ? extensions.disjointTimerQuery.createQuery() : null;
+}
+
+export type WebGLTimer = {
+    /** Check with GPU for finished timers. */
+    resolve: () => TimerResult[]
+    mark: (label: string) => void
+    markEnd: (label: string) => void
+    clear: () => void
+    destroy: () => void
+}
+
+type Measure = { label: string, queries: WebGLQuery[], children: Measure[], root: boolean, timeElapsed?: number };
+type QueryResult = { timeElapsed?: number, refCount: number };
+
+export function createTimer(gl: GLRenderingContext, extensions: WebGLExtensions): WebGLTimer {
+    const dtq = extensions.disjointTimerQuery;
+
+    const queries = new Map<WebGLQuery, QueryResult>();
+    const pending = new Map<string, Measure>();
+    const stack: Measure[] = [];
+
+    let measures: Measure[] = [];
+    let current: WebGLQuery | null = null;
+
+    const clear = () => {
+        if (!dtq) return;
+
+        queries.forEach((_, query) => {
+            dtq.deleteQuery(query);
+        });
+        pending.clear();
+        measures = [];
+        current = null;
+    };
+
+    const add = () => {
+        if (!dtq) return;
+
+        const query = getQuery(extensions);
+        if (!query) return;
+
+        dtq.beginQuery(dtq.TIME_ELAPSED, query);
+        pending.forEach((measure, _) => {
+            measure.queries.push(query);
+        });
+        queries.set(query, { refCount: pending.size });
+        current = query;
+    };
+
+    return {
+        resolve: () => {
+            const results: TimerResult[] = [];
+            if (!dtq || !measures.length) return results;
+            // console.log('resolve');
+            queries.forEach((result, query) => {
+                if (result.timeElapsed !== undefined) return;
+
+                const available = dtq.getQueryParameter(query, dtq.QUERY_RESULT_AVAILABLE);
+                const disjoint = gl.getParameter(dtq.GPU_DISJOINT);
+
+                if (available && !disjoint) {
+                    const timeElapsed = dtq.getQueryParameter(query, dtq.QUERY_RESULT) as number;
+                    result.timeElapsed = timeElapsed;
+                    // console.log('timeElapsed', result.timeElapsed);
+                }
+
+                if (available || disjoint) {
+                    dtq.deleteQuery(query);
+                }
+            });
+
+            const unresolved: Measure[] = [];
+            for (const measure of measures) {
+                if (measure.queries.every(q => queries.get(q)?.timeElapsed !== undefined)) {
+                    let timeElapsed = 0;
+                    for (const query of measure.queries) {
+                        const result = queries.get(query)!;
+                        timeElapsed += result.timeElapsed!;
+                        result.refCount -= 1;
+                    }
+                    measure.timeElapsed = timeElapsed;
+                    if (measure.root) {
+                        const children: TimerResult[] = [];
+                        const add = (measures: Measure[], children: TimerResult[]) => {
+                            for (const measure of measures) {
+                                const result: TimerResult = {
+                                    label: measure.label,
+                                    timeElapsed: measure.timeElapsed!,
+                                    children: []
+                                };
+                                children.push(result);
+                                add(measure.children, result.children);
+                            }
+                        };
+                        add(measure.children, children);
+                        results.push({ label: measure.label, timeElapsed, children });
+                    }
+                } else {
+                    unresolved.push(measure);
+                }
+            }
+            measures = unresolved;
+
+            queries.forEach((result, query) => {
+                if (result.refCount === 0) {
+                    queries.delete(query);
+                }
+            });
+
+            return results;
+        },
+        mark: (label: string) => {
+            if (!dtq) return;
+
+            if (pending.has(label)) {
+                throw new Error(`Timer mark for '${label}' already exists`);
+            }
+
+            if (current !== null) {
+                dtq.endQuery(dtq.TIME_ELAPSED);
+            }
+            const measure: Measure = { label, queries: [], children: [], root: current === null };
+            pending.set(label, measure);
+
+            if (stack.length) {
+                stack[stack.length - 1].children.push(measure);
+            }
+            stack.push(measure);
+
+            add();
+        },
+        markEnd: (label: string) => {
+            if (!dtq) return;
+
+            const measure = pending.get(label);
+            if (!measure) {
+                throw new Error(`Timer mark for '${label}' does not exist`);
+            }
+
+            if (stack.pop()?.label !== label) {
+                throw new Error(`Timer mark for '${label}' has pending nested mark`);
+            }
+
+            dtq.endQuery(dtq.TIME_ELAPSED);
+            pending.delete(label);
+            measures.push(measure);
+
+            if (pending.size > 0) {
+                add();
+            } else {
+                current = null;
+            }
+        },
+        clear,
+        destroy: () => {
+            clear();
+        }
+    };
+}
+
+function formatTimerResult(result: TimerResult) {
+    const timeElapsed = result.timeElapsed / 1000 / 1000;
+    return `${result.label} ${timeElapsed.toFixed(2)}ms`;
+}
+
+export function printTimerResults(results: TimerResult[]) {
+    return results.map(r => {
+        const f = formatTimerResult(r);
+        if (r.children.length) {
+            console.groupCollapsed(f);
+            printTimerResults(r.children);
+            console.groupEnd();
+        } else {
+            console.log(f);
+        }
+    });
+}

+ 9 - 2
src/mol-math/geometry/gaussian-density/gpu.ts

@@ -21,6 +21,7 @@ import { ValueSpec, AttributeSpec, UniformSpec, TextureSpec, DefineSpec, Values
 import { gaussianDensity_vert } from '../../../mol-gl/shader/gaussian-density.vert';
 import { gaussianDensity_frag } from '../../../mol-gl/shader/gaussian-density.frag';
 import { Framebuffer } from '../../../mol-gl/webgl/framebuffer';
+import { isTimingMode } from '../../../mol-util/debug';
 
 const GaussianDensitySchema = {
     drawCount: ValueSpec('number'),
@@ -85,11 +86,17 @@ export function GaussianDensityTexture(webgl: WebGLContext, position: PositionDa
 }
 
 export function GaussianDensityTexture2d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, powerOfTwo: boolean, props: GaussianDensityProps, oldTexture?: Texture): GaussianDensityTextureData {
-    return finalizeGaussianDensityTexture(calcGaussianDensityTexture2d(webgl, position, box, radius, powerOfTwo, props, oldTexture));
+    if (isTimingMode) webgl.timer.mark('GaussianDensityTexture2d');
+    const data = calcGaussianDensityTexture2d(webgl, position, box, radius, powerOfTwo, props, oldTexture);
+    if (isTimingMode) webgl.timer.markEnd('GaussianDensityTexture2d');
+    return finalizeGaussianDensityTexture(data);
 }
 
 export function GaussianDensityTexture3d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps, oldTexture?: Texture): GaussianDensityTextureData {
-    return finalizeGaussianDensityTexture(calcGaussianDensityTexture3d(webgl, position, box, radius, props, oldTexture));
+    if (isTimingMode) webgl.timer.mark('GaussianDensityTexture3d');
+    const data = calcGaussianDensityTexture3d(webgl, position, box, radius, props, oldTexture);
+    if (isTimingMode) webgl.timer.markEnd('GaussianDensityTexture3d');
+    return finalizeGaussianDensityTexture(data);
 }
 
 function finalizeGaussianDensityTexture({ texture, scale, bbox, gridDim, gridTexDim, gridTexScale, radiusFactor, resolution, maxRadius }: _GaussianDensityTextureData): GaussianDensityTextureData {

+ 11 - 6
src/mol-plugin-state/formats/volume.ts

@@ -1,8 +1,9 @@
 /**
- * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ * @author Aliaksei Chareshneu <chareshneu.tech@gmail.com>
  */
 
 import { StateTransforms } from '../transforms';
@@ -182,7 +183,6 @@ export const CubeProvider = DataFormatProvider({
     }
 });
 
-
 type DsCifParams = { entryId?: string | string[] };
 
 export const DscifProvider = DataFormatProvider({
@@ -197,16 +197,21 @@ export const DscifProvider = DataFormatProvider({
     parse: async (plugin, data, params?: DsCifParams) => {
         const cifCell = await plugin.build().to(data).apply(StateTransforms.Data.ParseCif).commit();
         const b = plugin.build().to(cifCell);
-        const blocks = cifCell.obj!.data.blocks.slice(1); // zero block contains query meta-data
+        const blocks = cifCell.obj!.data.blocks;
 
-        if (blocks.length !== 1 && blocks.length !== 2) throw new Error('unknown number of blocks');
+        if (blocks.length === 0) throw new Error('no data blocks');
 
         const volumes: StateObjectSelector<PluginStateObject.Volume.Data>[] = [];
         let i = 0;
         for (const block of blocks) {
+            // Skip "server" data block.
+            if (block.header.toUpperCase() === 'SERVER') continue;
+
             const entryId = Array.isArray(params?.entryId) ? params?.entryId[i] : params?.entryId;
-            volumes.push(b.apply(StateTransforms.Volume.VolumeFromDensityServerCif, { blockHeader: block.header, entryId }).selector);
-            i++;
+            if (block.categories['volume_data_3d_info']?.rowCount > 0) {
+                volumes.push(b.apply(StateTransforms.Volume.VolumeFromDensityServerCif, { blockHeader: block.header, entryId }).selector);
+                i++;
+            }
         }
 
         await b.commit();

+ 13 - 1
src/mol-plugin/animation-loop.ts

@@ -1,12 +1,15 @@
 /**
- * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
 import { PluginContext } from './context';
 import { now } from '../mol-util/now';
 import { PluginAnimationManager } from '../mol-plugin-state/manager/animation';
+import { isTimingMode } from '../mol-util/debug';
+import { printTimerResults } from '../mol-gl/webgl/timer';
 
 export class PluginAnimationLoop {
     private currentFrame: any = void 0;
@@ -19,6 +22,15 @@ export class PluginAnimationLoop {
     async tick(t: number, options?: { isSynchronous?: boolean, manualDraw?: boolean, animation?: PluginAnimationManager.AnimationInfo }) {
         await this.plugin.managers.animation.tick(t, options?.isSynchronous, options?.animation);
         this.plugin.canvas3d?.tick(t as now.Timestamp, options);
+
+        if (isTimingMode) {
+            const timerResults = this.plugin.canvas3d?.webgl.timer.resolve();
+            if (timerResults) {
+                for (const result of timerResults) {
+                    printTimerResults([result]);
+                }
+            }
+        }
     }
 
     private frame = () => {

+ 5 - 12
src/mol-repr/structure/visual/gaussian-surface-mesh.ts

@@ -27,6 +27,7 @@ import { applyMeshColorSmoothing } from '../../../mol-geo/geometry/mesh/color-sm
 import { applyTextureMeshColorSmoothing } from '../../../mol-geo/geometry/texture-mesh/color-smoothing';
 import { ColorSmoothingParams, getColorSmoothingProps } from '../../../mol-geo/geometry/base';
 import { Vec3 } from '../../../mol-math/linear-algebra';
+import { isTimingMode } from '../../../mol-util/debug';
 
 const SharedParams = {
     ...GaussianDensityParams,
@@ -213,6 +214,7 @@ const GaussianSurfaceName = 'gaussian-surface';
 async function createGaussianSurfaceTextureMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: GaussianDensityProps, textureMesh?: TextureMesh): Promise<TextureMesh> {
     if (!ctx.webgl) throw new Error('webgl context required to create gaussian surface texture-mesh');
 
+    if (isTimingMode) ctx.webgl.timer.mark('createGaussianSurfaceTextureMesh');
     const { namedTextures, resources, extensions: { colorBufferFloat, textureFloat, colorBufferHalfFloat, textureHalfFloat } } = ctx.webgl;
     if (!namedTextures[GaussianSurfaceName]) {
         namedTextures[GaussianSurfaceName] = colorBufferHalfFloat && textureHalfFloat
@@ -222,18 +224,13 @@ async function createGaussianSurfaceTextureMesh(ctx: VisualContext, unit: Unit,
                 : resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
     }
 
-    // console.time('computeUnitGaussianDensityTexture2d');
     const densityTextureData = await computeUnitGaussianDensityTexture2d(structure, unit, theme.size, true, props, ctx.webgl, namedTextures[GaussianSurfaceName]).runInContext(ctx.runtime);
-    // console.log(densityTextureData);
-    // console.log('vertexGroupTexture', readTexture(ctx.webgl, densityTextureData.texture));
-    // ctx.webgl.waitForGpuCommandsCompleteSync();
-    // console.timeEnd('computeUnitGaussianDensityTexture2d');
-
     const isoLevel = Math.exp(-props.smoothness) / densityTextureData.radiusFactor;
 
     const axisOrder = Vec3.create(0, 1, 2);
     const buffer = textureMesh?.doubleBuffer.get();
     const gv = extractIsosurface(ctx.webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.gridTexScale, densityTextureData.transform, isoLevel, false, true, axisOrder, buffer?.vertex, buffer?.group, buffer?.normal);
+    if (isTimingMode) ctx.webgl.timer.markEnd('createGaussianSurfaceTextureMesh');
 
     const boundingSphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, densityTextureData.maxRadius);
     const surface = TextureMesh.create(gv.vertexCount, 1, gv.vertexTexture, gv.groupTexture, gv.normalTexture, boundingSphere, textureMesh);
@@ -290,6 +287,7 @@ export function GaussianSurfaceTextureMeshVisual(materialId: number): UnitsVisua
 async function createStructureGaussianSurfaceTextureMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: GaussianDensityProps, textureMesh?: TextureMesh): Promise<TextureMesh> {
     if (!ctx.webgl) throw new Error('webgl context required to create structure gaussian surface texture-mesh');
 
+    if (isTimingMode) ctx.webgl.timer.mark('createStructureGaussianSurfaceTextureMesh');
     const { namedTextures, resources, extensions: { colorBufferFloat, textureFloat, colorBufferHalfFloat, textureHalfFloat } } = ctx.webgl;
     if (!namedTextures[GaussianSurfaceName]) {
         namedTextures[GaussianSurfaceName] = colorBufferHalfFloat && textureHalfFloat
@@ -299,18 +297,13 @@ async function createStructureGaussianSurfaceTextureMesh(ctx: VisualContext, str
                 : resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
     }
 
-    // console.time('computeUnitGaussianDensityTexture2d');
     const densityTextureData = await computeStructureGaussianDensityTexture2d(structure, theme.size, true, props, ctx.webgl, namedTextures[GaussianSurfaceName]).runInContext(ctx.runtime);
-    // console.log(densityTextureData);
-    // console.log('vertexGroupTexture', readTexture(ctx.webgl, densityTextureData.texture));
-    // ctx.webgl.waitForGpuCommandsCompleteSync();
-    // console.timeEnd('computeUnitGaussianDensityTexture2d');
-
     const isoLevel = Math.exp(-props.smoothness) / densityTextureData.radiusFactor;
 
     const axisOrder = Vec3.create(0, 1, 2);
     const buffer = textureMesh?.doubleBuffer.get();
     const gv = extractIsosurface(ctx.webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.gridTexScale, densityTextureData.transform, isoLevel, false, true, axisOrder, buffer?.vertex, buffer?.group, buffer?.normal);
+    if (isTimingMode) ctx.webgl.timer.markEnd('createStructureGaussianSurfaceTextureMesh');
 
     const boundingSphere = Sphere3D.expand(Sphere3D(), structure.boundary.sphere, densityTextureData.maxRadius);
     const surface = TextureMesh.create(gv.vertexCount, 1, gv.vertexTexture, gv.groupTexture, gv.normalTexture, boundingSphere, textureMesh);

+ 1 - 1
src/mol-task/util/user-timing.ts

@@ -7,7 +7,7 @@
 import { Task } from '../task';
 import { isProductionMode } from '../../mol-util/debug';
 
-const hasPerformance = (typeof performance !== 'undefined') && performance.mark && performance.measure;
+const hasPerformance = (typeof performance !== 'undefined') && !!performance.mark && performance.measure;
 const timingEnabled = hasPerformance && !isProductionMode;
 
 export namespace UserTiming {

+ 11 - 2
src/mol-util/debug.ts

@@ -30,7 +30,12 @@ let isDebugMode = function getIsDebug() {
     }
 }();
 
-export { isProductionMode, isDebugMode };
+/**
+ * set to true to gather timings, mostly used in `mol-gl`
+ */
+let isTimingMode = false;
+
+export { isProductionMode, isDebugMode, isTimingMode };
 
 export function setProductionMode(value?: boolean) {
     if (typeof value !== 'undefined') isProductionMode = value;
@@ -38,4 +43,8 @@ export function setProductionMode(value?: boolean) {
 
 export function setDebugMode(value?: boolean) {
     if (typeof value !== 'undefined') isDebugMode = value;
-}
+}
+
+export function setTimingMode(value?: boolean) {
+    if (typeof value !== 'undefined') isTimingMode = value;
+}

+ 2 - 1
src/servers/model/utils/fetch-retry.ts

@@ -19,7 +19,8 @@ function isRetriableNetworkError(error: any) {
 export async function fetchRetry(url: string, timeout: number, retryCount: number, onRetry?: () => void): Promise<Response> {
     const controller = new AbortController();
     const id = setTimeout(() => controller.abort(), timeout);
-    const result = await retryIf(() => fetch(url, { signal: controller.signal }), {
+    const signal = controller.signal as any; // TODO: fix type
+    const result = await retryIf(() => fetch(url, { signal }), {
         retryThenIf: r => r.status === 408 /** timeout */ || r.status === 429 /** too many requests */ || (r.status >= 500 && r.status < 600),
         // TODO test retryCatchIf
         retryCatchIf: e => isRetriableNetworkError(e),