-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Shared Context #12635
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Shared Context #12635
Changes from 34 commits
Commits
Show all changes
36 commits
Select commit
Hold shift + click to select a range
58ea366
reference-counting option for PrimitiveCollection constructor
pmconne 6909c7d
Reference-counting in PrimitiveCollection
pmconne ca9939a
createContextFromSharedContext
pmconne 54fedc0
SharedSceneContext
pmconne a52323e
SharedContextSpec
pmconne 329e797
auto-destroy option
pmconne 706b751
Scene tests
pmconne 75d44ed
don't use context._gl.drawingBufferWidth/Height directly
pmconne 10ee496
drawingBufferWidth/Height
pmconne 3c1b668
readPixels gets default width/height from this, not gl
pmconne 9a6159e
docs
pmconne cdb10c8
test rendering background color using shared context.
pmconne 0325745
blit
pmconne 03eb3af
Viewer accepts SharedContext
pmconne 6bd19ae
WIP sandcastle
pmconne 8213b0b
DataSourceDisplay reference-counting
pmconne de6c74a
primitives
pmconne df48524
Revert "DataSourceDisplay reference-counting"
pmconne 2be94f7
clean up sandcastle
pmconne 795e308
add a working shared primitive
pmconne e219831
Fix ImageBasedLighting; tweak sandcastle.
pmconne ad03f92
Merge branch 'main' into pmc/shared-context
pmconne 1fddcff
Make new APIs private; split reference-counting from destroyPrimitives
pmconne cc811ba
Try to indicate tests require WebGL.
pmconne 54ded89
skip tests if WebGL stubbed.
pmconne 7d8a499
Remove doc refs to SharedContext
pmconne 1a9530b
not helpful
pmconne bad8500
Move sandcastle to development directory
pmconne 697514b
fine jasmine you win
pmconne b2bfe97
I yield!
pmconne 852582f
rm SharedContext from API doc
pmconne 378e1eb
missing file extension in import statement
pmconne 96b703e
Tweak inline docs
ggetz e13f814
Fix docs tag, update unit tests
ggetz 481cb57
Remove TODO (issue filed)
pmconne 18bf6de
Merge branch 'main' into pmc/shared-context
pmconne File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| { | ||
| "version": "0.2.0", | ||
| "configurations": [ | ||
|
|
||
| { | ||
| "type": "node", | ||
| "request": "launch", | ||
|
|
||
123 changes: 123 additions & 0 deletions
123
Apps/Sandcastle/gallery/development/Shared Context.html
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| <!doctype html> | ||
| <html lang="en"> | ||
| <head> | ||
| <meta charset="utf-8" /> | ||
| <meta http-equiv="X-UA-Compatible" content="IE=edge" /> | ||
| <meta | ||
| name="viewport" | ||
| content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" | ||
| /> | ||
| <meta name="description" content="Multiple views synced across time and space." /> | ||
| <meta name="cesium-sandcastle-labels" content="Beginner, Showcases,New in 1.129" /> | ||
| <title>Cesium Demo</title> | ||
| <script type="text/javascript" src="../Sandcastle-header.js"></script> | ||
| <script type="module" src="../load-cesium-es6.js"></script> | ||
| </head> | ||
| <body class="sandcastle-loading" data-sandcastle-bucket="bucket-requirejs.html"> | ||
| <style> | ||
| @import url(../templates/bucket.css); | ||
| #cesiumContainer { | ||
| display: flex; | ||
| width: 100%; | ||
| height: 100%; | ||
| } | ||
| #view3D { | ||
| display: inline-block; | ||
| width: 100%; | ||
| } | ||
| #view2D { | ||
| display: inline-block; | ||
| width: 100%; | ||
| } | ||
| </style> | ||
| <div id="cesiumContainer" class="fullSize"> | ||
| <div id="view3D"></div> | ||
| <div id="view2D"></div> | ||
| </div> | ||
| <div id="loadingOverlay"><h1>Loading...</h1></div> | ||
| <div id="toolbar"></div> | ||
| <script id="cesium_sandcastle_script"> | ||
| window.startup = async function (Cesium) { | ||
| "use strict"; | ||
| //Sandcastle_Begin | ||
| const contextOptions = new Cesium.SharedContext(); | ||
| // const contextOptions = undefined; | ||
| // Uncomment the line above and comment out the one preceding it to illustrate how primitives cannot be shared between scenes by default. | ||
|
|
||
| const options = { | ||
| contextOptions, | ||
| fullscreenButton: false, | ||
| sceneModePicker: false, | ||
| }; | ||
|
|
||
| const view1 = new Cesium.Viewer("view3D", options); | ||
| const view2 = new Cesium.Viewer("view2D", options); | ||
|
|
||
| // Add the same entity to both viewers. Each viewer will create separate WebGL resources to draw it. | ||
| const greenCylinder = { | ||
| name: "Green cylinder with black outline", | ||
| position: Cesium.Cartesian3.fromDegrees(-100.0, 40.0, 200000.0), | ||
| cylinder: { | ||
| length: 400000.0, | ||
| topRadius: 200000.0, | ||
| bottomRadius: 200000.0, | ||
| material: Cesium.Color.GREEN.withAlpha(0.5), | ||
| outline: true, | ||
| outlineColor: Cesium.Color.BLACK, | ||
| }, | ||
| }; | ||
|
|
||
| view1.entities.add(greenCylinder); | ||
| view2.entities.add(greenCylinder); | ||
|
|
||
| // Add the same cylinder primitive to both viewers. Each will use the same WebGL resources to draw it. | ||
| const cylinder = new Cesium.CylinderGeometry({ | ||
| length: 400000.0, | ||
| topRadius: 200000.0, | ||
| bottomRadius: 200000.0, | ||
| }); | ||
| const geometry = Cesium.CylinderGeometry.createGeometry(cylinder); | ||
| const primitive = new Cesium.Primitive({ | ||
| geometryInstances: new Cesium.GeometryInstance({ | ||
| geometry, | ||
| modelMatrix: Cesium.Matrix4.multiplyByTranslation( | ||
| Cesium.Transforms.eastNorthUpToFixedFrame( | ||
| Cesium.Cartesian3.fromDegrees(-95.59777, 40.03883), | ||
| ), | ||
| new Cesium.Cartesian3(0.0, 0.0, 500000.0), | ||
| new Cesium.Matrix4(), | ||
| ), | ||
| id: "red cylinder", | ||
| attributes: { | ||
| color: Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.RED), | ||
| }, | ||
| }), | ||
| appearance: new Cesium.PerInstanceColorAppearance(), | ||
| asynchronous: false, | ||
| }); | ||
|
|
||
| view1.scene.primitives.add(primitive); | ||
| view2.scene.primitives.add(primitive); | ||
|
|
||
| // Add the same tileset to both viewers. Each viewer will use the same WebGL resources to draw it. | ||
| const tileset = await Cesium.Cesium3DTileset.fromIonAssetId(2464651); | ||
| for (const view of [view1, view2]) { | ||
| view.scene.primitives.add(tileset); | ||
| view.zoomTo( | ||
| tileset, | ||
| new Cesium.HeadingPitchRange(0.5, -0.2, tileset.boundingSphere.radius * 4.0), | ||
| ); | ||
| } | ||
| //Sandcastle_End | ||
| Sandcastle.finishedLoading(); | ||
| }; | ||
| if (typeof Cesium !== "undefined") { | ||
| window.startupCalled = true; | ||
| window.startup(Cesium).catch((error) => { | ||
| "use strict"; | ||
| console.error(error); | ||
| }); | ||
| } | ||
| </script> | ||
| </body> | ||
| </html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,187 @@ | ||
| import clone from "../Core/clone.js"; | ||
| import destroyObject from "../Core/destroyObject.js"; | ||
| import DeveloperError from "../Core/DeveloperError.js"; | ||
| import Context from "./Context.js"; | ||
|
|
||
| /** | ||
| * Enables a single WebGL context to be used by any number of {@link Scene}s. | ||
| * You can pass a SharedContext in place of a {@link ContextOptions} to the constructors of {@link Scene}, {@link CesiumWidget}, and {@link Viewer}. | ||
| * {@link Primitive}s associated with the shared WebGL context can be displayed in any Scene that uses the same context. | ||
| * The context renders each Scene to an off-screen canvas, then blits the result to that Scene's on-screen canvas. | ||
| * | ||
| * @private | ||
| * @alias SharedContext | ||
| * @constructor | ||
| * | ||
| * @param {object} [options] Object with the following properties: | ||
| * @param {ContextOptions} [options.contextOptions] Context and WebGL creation properties. | ||
| * @param {boolean} [options.autoDestroy=true] Destroys this context and all of its WebGL resources after all Scenes using the context are destroyed. | ||
|
|
||
| * @see {@link http://www.khronos.org/registry/webgl/specs/latest/#5.2|WebGLContextAttributes} | ||
| * | ||
| * @example | ||
| * // Create two Scenes sharing a single WebGL context | ||
| * const context = new Cesium.SharedContext(); | ||
| * const scene1 = new Cesium.Scene({ | ||
| * canvas: canvas1, | ||
| * contextOptions: context, | ||
| * }); | ||
| * const scene2 = new Cesium.Scene({ | ||
| * canvas: canvas2, | ||
| * contextOptions: context, | ||
| * }); | ||
| */ | ||
| function SharedContext(options) { | ||
| this._autoDestroy = options?.autoDestroy ?? true; | ||
| this._canvas = document.createElement("canvas"); | ||
| this._context = new Context(this._canvas, clone(options?.contextOptions)); | ||
| this._canvases = []; | ||
| } | ||
|
|
||
| /** | ||
| * Creates an instance of {@link Context} that manages the shared WebGL context for a specific canvas. | ||
| * @param {HTMLCanvasElement} canvas The canvas element to which the context will be associated | ||
| * @returns {Context} The created context instance | ||
| * @private | ||
| */ | ||
| SharedContext.prototype.createSceneContext = function (canvas) { | ||
| const context2d = canvas.getContext("2d", { alpha: true }); | ||
|
|
||
| //>>includeStart('debug', pragmas.debug); | ||
| if (!context2d) { | ||
| throw new DeveloperError( | ||
| "canvas used with SharedContext must provide a 2d context", | ||
| ); | ||
| } | ||
|
|
||
| if (this._canvases.includes(canvas)) { | ||
| throw new DeveloperError("canvas is already associated with a scene"); | ||
| } | ||
| //>>includeEnd('debug'); | ||
|
|
||
| const sharedContext = this; | ||
| sharedContext._canvases.push(canvas); | ||
|
|
||
| let isDestroyed = false; | ||
| const destroy = function () { | ||
| isDestroyed = true; | ||
| const index = sharedContext._canvases.indexOf(canvas); | ||
| if (-1 !== index) { | ||
| sharedContext._canvases.splice(index, 1); | ||
| if (sharedContext._autoDestroy && sharedContext._canvases.length === 0) { | ||
| sharedContext.destroy(); | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| const beginFrame = function () { | ||
| // Ensure the off-screen canvas is at least as large as the on-screen canvas. | ||
| const sharedCanvas = sharedContext._context.canvas; | ||
|
|
||
| const width = this.drawingBufferWidth; | ||
| if (sharedCanvas.width < width) { | ||
| sharedCanvas.width = width; | ||
| } | ||
|
|
||
| const height = this.drawingBufferHeight; | ||
| if (sharedCanvas.height < height) { | ||
| sharedCanvas.height = height; | ||
| } | ||
| }; | ||
|
|
||
| const endFrame = function () { | ||
| // Blit the image from the off-screen canvas to the on-screen canvas. | ||
| const w = this.drawingBufferWidth; | ||
| const h = this.drawingBufferHeight; | ||
| const yOffset = sharedContext._context.canvas.height - h; // drawImage has top as Y=0, GL has bottom as Y=0 | ||
| context2d.drawImage( | ||
| sharedContext._context.canvas, | ||
| 0, | ||
| yOffset, | ||
| w, | ||
| h, | ||
| 0, | ||
| 0, | ||
| w, | ||
| h, | ||
| ); | ||
|
|
||
| // Do normal post-frame cleanup. | ||
| sharedContext._context.endFrame(); | ||
| }; | ||
|
|
||
| const proxy = new Proxy(this._context, { | ||
| get(target, prop, receiver) { | ||
| if (prop === "isDestroyed") { | ||
| return function () { | ||
| return isDestroyed; | ||
| }; | ||
| } else if (isDestroyed) { | ||
| //>>includeStart('debug', pragmas.debug); | ||
| throw new DeveloperError( | ||
| "This object was destroyed, i.e., destroy() was called.", | ||
| ); | ||
| //>>includeEnd('debug'); | ||
| } | ||
|
|
||
| switch (prop) { | ||
| case "_canvas": | ||
| return canvas; | ||
| case "destroy": | ||
| return destroy; | ||
| // ###TODO: When will this be inaccurate? device pixel ratio? Canvas larger than maximum drawing buffer dimensions supported by WebGL implementation? | ||
| case "drawingBufferWidth": | ||
| return canvas.width; | ||
| case "drawingBufferHeight": | ||
| return canvas.height; | ||
| case "beginFrame": | ||
| return beginFrame; | ||
| case "endFrame": | ||
| return endFrame; | ||
| default: | ||
| return Reflect.get(target, prop, receiver); | ||
| } | ||
| }, | ||
| }); | ||
|
|
||
| return proxy; | ||
| }; | ||
|
|
||
| /** | ||
| * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic | ||
| * release of WebGL resources, instead of relying on the garbage collector to destroy this object. | ||
| * <br /><br /> | ||
| * Once an object is destroyed, it should not be used; calling any function other than | ||
| * <code>isDestroyed</code> will result in a {@link DeveloperError} exception. Therefore, | ||
| * assign the return value (<code>undefined</code>) to the object as done in the example. | ||
| * <br /><br /> | ||
| * By default, a SharedContext is destroyed automatically once the last Scene using it is destroyed, in which case it | ||
| * is not necessary to call this method directly. | ||
| * | ||
| * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. | ||
| * | ||
| * @example | ||
| * context = context && context.destroy(); | ||
| * | ||
| * @see SharedContext#isDestroyed | ||
| */ | ||
| SharedContext.prototype.destroy = function () { | ||
| this._context.destroy(); | ||
| destroyObject(this); | ||
| }; | ||
|
|
||
| /** | ||
| * Returns true if this object was destroyed; otherwise, false. | ||
| * <br /><br /> | ||
| * If this object was destroyed, it should not be used; calling any function other than | ||
| * <code>isDestroyed</code> will result in a {@link DeveloperError} exception. | ||
| * | ||
| * @returns {boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>. | ||
| * | ||
| * @see SharedContext#destroy | ||
| */ | ||
| SharedContext.prototype.isDestroyed = function () { | ||
| return false; | ||
| }; | ||
|
|
||
| export default SharedContext; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it make sense to address this now, or in a later PR?
We typically don't merge code into main with
TODOcomments, and prefer to document them in an issue if they are not addressed.The max viewport dimensions are available in
ContextLimits.maximumViewportWidth/ContextLimits.maximumViewportHeight.Pixel ratio is less straightforward, as it's "owned" by
Scene.frameState. Perhaps it should live inContextinstead.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As discussed here, pixel ratio is tricky and not 100% solved. An API to obtain the dimensions of a canvas (or other element) in integer device pixels solves the problem, except it's not yet supported in Safari.
iTwin.js attempts to account for DPR here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I created #12762 to track this outside the code / this PR. I propose removing this TODO from this code, resolving this conversation, possibly merging this PR, and then scheduling some work on that issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ggetz Feel free to resolve your comment here if you are satisfied with the above plan.