Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

(feat): Upgrade deck.gl and luma.gl #805

Open
wants to merge 56 commits into
base: main
Choose a base branch
from

Conversation

xinaesthete
Copy link

Addresses #
#804

Background

As discussed #790

Fairly extensive changes will be required; discussion should probably migrate to here.

@ilan-gold
Copy link
Collaborator

I made some fixes (commits should be messaged clearly). Thanks for getting this started. More to come tomorrow!

I am not clear what was previously dataFormat should be now, but that might not be what is causing the current error:

deck: drawing XRLayer({id: 'image-sub-layer-0,135,97,0-Background-Image-ZarrPixelSource-#detail#'}) to screen: cyclic object value TypeError: cyclic object value
    setUniformsWebGL webgl-render-pipeline.js:197
    setUniformsWebGL webgl-render-pipeline.js:196
    setUniforms model.js:464
    draw xr-layer.js:257
    _drawLayer layer.js:851
    withGLParameters with-parameters.js:17
    withParametersWebGL webgl-device.js:269
    _drawLayer layer.js:845
    _drawLayersInViewport layers-pass.js:153
    _drawLayers layers-pass.js:54
    render layers-pass.js:32
    renderLayers deck-renderer.js:47
    _drawLayers deck.js:691
    redrawDeck deckgl.js:42
    _customRender deckgl.js:65
    redraw deck.js:369
    _onRenderFrame deck.js:723
    _renderFrame animation-loop.js:256
    redraw animation-loop.js:154
    _animationFrame animation-loop.js:244
    requestAnimationFrame request-animation-frame.js:9
    _requestAnimationFrame animation-loop.js:225
    _animationFrame animation-loop.js:245
    requestAnimationFrame request-animation-frame.js:9
    _requestAnimationFrame animation-loop.js:225
    _animationFrame animation-loop.js:245
    requestAnimationFrame request-animation-frame.js:9
    _requestAnimationFrame animation-loop.js:225
    _animationFrame animation-loop.js:245
    requestAnimationFrame request-animation-frame.js:9
    _requestAnimationFrame animation-loop.js:225
    _animationFrame animation-loop.js:245
    requestAnimationFrame request-animation-frame.js:9
    _requestAnimationFrame animation-loop.js:225
    _animationFrame animation-loop.js:245
    requestAnimationFrame request-animation-frame.js:9
    _requestAnimationFrame animation-loop.js:225
    start animation-loop.js:119
    _Deck deck.js:251
    createDeckInstance deckgl.js:47
    DeckGLWithRef deckgl.js:138
    React 8
    workLoop scheduler.development.js:266
    flushWork scheduler.development.js:239
    performWorkUntilDeadline scheduler.development.js:533
    scheduler chunk-KSA3GRLC.js:405
    scheduler chunk-KSA3GRLC.js:453
    __require2 chunk-LK32TJAX.js:18
    scheduler chunk-KSA3GRLC.js:465
    __require2 chunk-LK32TJAX.js:18
    React 2
    __require2 chunk-LK32TJAX.js:18
    dom React
    __require2 chunk-LK32TJAX.js:18
    dom React
    __require2 chunk-LK32TJAX.js:18
    <anonymous> react-dom_client.js:38

@xinaesthete
Copy link
Author

Yeah - as for clear messaging of commits, I'm generally very on-board with that, previous commit was a result of starting to mess around & seeing how far I got, then figured it was more useful to commit than just leave on my machine...

I see that as far up as model.setUniforms() is marked as deprecated, so that may be the point at which to change the code for the current issue.

@xinaesthete
Copy link
Author

https://github.com/visgl/luma.gl/blob/master/docs/legacy/porting-guide.md#quick-v9-api-overview

In progress
Reading and writing buffers is now an async operation. While WebGL does not support async reads and writes on MacOS, the API is still async to ensure portability to WebGPU.
Uniform buffers are now the standard way for the application to specify uniforms. Uniform buffers are "emulated" under WebGL.

@xinaesthete
Copy link
Author

I think that what's happening is that likely the dataFormat is indeed causing it to raise an error, and then in the process of raising it it gets circular references because the texture objects refer back to the device...

I think setting uniforms like that should work in the short-term although it'll clearly need changing once it gets to making it run on WebGPU etc.

@xinaesthete
Copy link
Author

xinaesthete commented Jun 17, 2024

https://luma.gl/docs/api-reference/core/texture-formats/ (not sure how helpful I'm being here, just dropping things in that seem relevant)

Note that luma.gl deduces dataFormat and type from format by taking the first value from the data format and data type entries in this table.

@xinaesthete
Copy link
Author

I optimistically tried changing draw() in xr-layer.js:256

      model
        .setBindings({uniforms: {
          ...uniforms,
          contrastLimits: paddedContrastLimits,
          ...textures
        }})
      model.draw(); //n.b. took a few moments to realise setBindings() returns undefined...

I've not really processed whether that's actually a correct way to be passing uniforms, but it does change the kind of error we see...

webgl-render-pipeline.js:243 Uncaught (in promise) 
Error: Error during linking: Vertex shader is not compiled.

    at WEBGLRenderPipeline._reportLinkStatus (webgl-render-pipeline.js:243:23)
    at WEBGLRenderPipeline._linkShaders (webgl-render-pipeline.js:218:18)
    at new WEBGLRenderPipeline (webgl-render-pipeline.js:54:14)
    at _WebGLDevice.createRenderPipeline (webgl-device.js:226:16)
    at _PipelineFactory.createRenderPipeline (pipeline-factory.js:29:42)
    at _Model._updatePipeline (model.js:551:50)
    at new _Model (model.js:160:30)
    at XRLayer._getModel (xr-layer.js:189:12)
    at XRLayer.updateState (xr-layer.js:159:35)
    at XRLayer._update (layer.js:778:22)

I don't know how important the warnings that appear before that (luma.gl: Model(image-sub-layer-0,131,175,0-Background-Image-TiffPixelSource-#detail#): Missing layout for buffer "instancePickingColors". etc) are.

Actually, the error thrown in _reportLinkStatus() happens before we call setUniforms/Bindings() - it's just that when we call setUniforms() then it raises another error in the process, which makes it onto the console before the other error is reported, whereas when we call setBindings() it appears happier, then in the process of drawing picks up on the link status & outputs the earlier error. Then having printed that error once, the app is running moderately happily and it'll draw the scale-bar layers (with some one-frame-behind annoyingness, at least with devtools open, I've seen that behave differently based on those kinds of conditions at times in MDV) - so it's passing through the XRLayer.draw() without incident, updating the app state, just not managing to draw anything particularly useful.

This is where things get flagged in lumagl webgl-render-pipeline.js

    // PRIVATE METHODS
    // setAttributes(attributes: Record<string, Buffer>): void {}
    // setBindings(bindings: Record<string, Binding>): void {}
    async _linkShaders() {
        const { gl } = this.device;
        gl.attachShader(this.handle, this.vs.handle);
        gl.attachShader(this.handle, this.fs.handle);
        log.time(LOG_PROGRAM_PERF_PRIORITY, `linkProgram for ${this.id}`)();
        gl.linkProgram(this.handle);
        log.timeEnd(LOG_PROGRAM_PERF_PRIORITY, `linkProgram for ${this.id}`)();
        // TODO Avoid checking program linking error in production
        if (log.level === 0) {
            // return;
        }
        if (!this.device.features.has('compilation-status-async-webgl')) {
            const status = this._getLinkStatus();
            this._reportLinkStatus(status); //<<<<<<<
            return;
        }
        // async case
        log.once(1, 'RenderPipeline linking is asynchronous')();
        await this._waitForLinkComplete();
        log.info(2, `RenderPipeline ${this.id} - async linking complete: ${this.linkStatus}`)();
        const status = this._getLinkStatus();
        this._reportLinkStatus(status);
    }

FWIW on my M1 running Chrome stable in Sonoma, it doesn't have the ''compilation-status-async-webgl' feature. Hopefully we're fairly close to the point of seeing why it hasn't linked at this stage... both fs and vs of the WebGLRenderPipeline in question have compilationStatus: "pending".

I might commit my minor change without great confidence in its correctness... just going to dig through the debugger & docs a bit more and see if I make any more progress...

@xinaesthete
Copy link
Author

ok... so a couple of things of note.

When I changed const programManager = ProgramManager.getDefaultProgramManager(device) to ShaderAssembler.getDefaultShaderAssembler(), at no point did I make any change meaning that the assembleShader() method was called on it - indeed, that seems not to be happening at present, and it seems likely this may be needed before getting to shader linking.

I could do with better understanding how pipelines are managed - bearing in mind that PipelineManager is the thing that's supposed to replace ProgramManager, I guess although changing to ShaderAssembler was enough to pass a basic build step, there may be a need to involve PipelineManager... I'm also not entirely clear on how attributes are supposed to work... in AttributeManager vs Geometry, and the new bufferLayout prop for Model... So I think I need to a) take a rest, and b) read through more of the docs.

When we call new Model(...), it creates a pipeline with its pipelineFactory, which is PipelineFactory.getDefaultPipelineFactory(this.device). That is used to make a ShaderFactory (not ShaderAssembler), which is then used in Model._updatePipeline():

    /** Update pipeline if needed */
    _updatePipeline() {
        if (this._pipelineNeedsUpdate) {
            let prevShaderVs = null;
            let prevShaderFs = null;
            if (this.pipeline) {
                log.log(1, `Model ${this.id}: Recreating pipeline because "${this._pipelineNeedsUpdate}".`)();
                prevShaderVs = this.pipeline.vs;
                prevShaderFs = this.pipeline.fs;
            }
            this._pipelineNeedsUpdate = false;
            const vs = this.shaderFactory.createShader({
                id: `${this.id}-vertex`,
                stage: 'vertex',
                source: this.source || this.vs,
                debug: this.props.debugShaders
            });
            let fs = null;
            if (this.source) {
                fs = vs;
            }
            else if (this.fs) {
                fs = this.shaderFactory.createShader({
                    id: `${this.id}-fragment`,
                    stage: 'fragment',
                    source: this.source || this.fs,
                    debug: this.props.debugShaders
                });
            }
            this.pipeline = this.pipelineFactory.createRenderPipeline({
                ...this.props,
                bufferLayout: this.bufferLayout,
                topology: this.topology,
                parameters: this.parameters,
                vs,
                fs
            });
            this._attributeInfos = getAttributeInfosFromLayouts(this.pipeline.shaderLayout, this.bufferLayout);
            if (prevShaderVs)
                this.shaderFactory.release(prevShaderVs);
            if (prevShaderFs)
                this.shaderFactory.release(prevShaderFs);
        }
        return this.pipeline;
    }

When that gets into this.pipelineFactory.createRenderPipeline(...), it has props including shaderAssembler which is indeed a reference to getShaderAssembler(device) with relevant hooks added... when it eventually gets in to new WEBGLRenderPipeline() and _linkShaders(), as noted before, the shaders have compilationStatus: "pending" and I'm not sure on what basis they'd be compiled by then. But maybe my problem is that I've spent more time stepping through code than actually reading the docs...

I notice that the _hookFunctions property is meant to be private... I wonder if there might be somewhere else to add these hooks rather than initializeState(), but that's a relatively minor concern - I don't think it'd stop anything working.

Anyway, I suppose I'll push what I have currently - not confident it's 100% correct.

@xinaesthete xinaesthete marked this pull request as draft June 19, 2024 10:19
@xinaesthete
Copy link
Author

xinaesthete commented Jun 19, 2024

OK... I think I've made relevant shader edits. Now I get

deck: drawing XRLayer({id: 'image-sub-layer-0,131,175,0-Background-Image-TiffPixelSource-#detail#'}) to screen: No value for binding channel0 in image-sub-layer-0,131,175,0-Background-Image-TiffPixelSource-#detail#-cached Error: No value for binding channel0 in image-sub-layer-0,131,175,0-Background-Image-TiffPixelSource-#detail#-cached
    at WEBGLRenderPipeline._applyBindings (webgl-render-pipeline.js:314:23)
    at WEBGLRenderPipeline.draw (webgl-render-pipeline.js:167:14)
    at _Model.draw (model.js:256:41)
    at XRLayer.draw (xr-layer.js:278:13)
    at layer.js:851:22
    at withGLParameters (with-parameters.js:17:16)
    at _WebGLDevice.withParametersWebGL (webgl-device.js:269:16)
    at XRLayer._drawLayer (layer.js:845:28)
    at DrawLayersPass._drawLayersInViewport (layers-pass.js:153:27)
    at DrawLayersPass._drawLayers (layers-pass.js:54:36)

I suspect we need to work on how the uniform buffers are arranged.

The bindings of the WebGLRenderPipeline that throws the error is looking for 'channel0' etc, but there is only key, 'pickingUniforms', associated with a WEBGLBuffer.

Putting a breakpoint on setBindings(), I see that the image layer is calling it with WEBGLBuffers for various modules, plus our plain js object 'uniforms':

Screenshot 2024-06-19 at 14 50 47

@xinaesthete
Copy link
Author

xinaesthete commented Jun 19, 2024

OK, I think we're getting close: I actually see some graphics getting drawn, albeit not very useful.

I think maybe the out fragColor just isn't bound to an actual framebuffer; if I put fragColor.r = 1.0; at the end of the fs it does nothing. more like the geometry indices / texCoords etc are dodgy - toggling channels changes colors! Exciting times!

@xinaesthete
Copy link
Author

It's starting to look like image data... something bad with some constants / parameters for setting sampler attribs or something I guess...

Screenshot 2024-06-19 at 16 26 18

@xinaesthete
Copy link
Author

At some point, moving towards eventual WebGPU support, we'll need to change the shaders to use Uniform Buffer Objects, I'm not entirely sure the exact change that'll entail.

@ilan-gold ilan-gold changed the title chore: wip attempt to upgrade deck&luma (feat): wip attempt to upgrade deck&luma Sep 26, 2024
@ilan-gold ilan-gold changed the title (feat): wip attempt to upgrade deck&luma (feat): Upgrade deck.gl and luma.gl Sep 26, 2024
@ilan-gold
Copy link
Collaborator

ilan-gold commented Sep 26, 2024

I totally agree with @manzt about splitting the PR + separate branch. I am doing code review now but I notice some things:

1. Here and on the release, firefox is quite slow (for me) on mouse wheel events at least, both 3D and 2D. If you are seeing this too, I will make an issue for deck.gl. Profiling didn't reveal anything. And I think we ruled out zustand.
2. Safari + Chrome seem a bit sketchy - for a while chrome wasn't loading volumes and now safari all of a sudden is refusing to zoom

@ilan-gold
Copy link
Collaborator

ilan-gold commented Sep 26, 2024

@ilan-gold
Copy link
Collaborator

I get identical behavior without the scale bar and on avivator.gehlenborglab.org so that's not relevant here.

Copy link
Collaborator

@ilan-gold ilan-gold left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great. Tried to suggest changes where possible. I hope you had fun at the conference! Really trying to get this merged soon!

};
}
export function getRenderingAttrs(dtype, device, interpolation) {
/// - WebGL1 is no longer supported by lumagl etc.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should maybe separately completely deprecate webgl1? Maybe in a different PR? What is the status of this PR? It seems that within this PR you have also deprecated WebGL1. That would be good to note in a release note

import GL from '@luma.gl/constants';
import { Geometry, Model, Texture2D } from '@luma.gl/core';
import { ProgramManager } from '@luma.gl/engine';
// ... needed to destructure for it to build with luma.gl 9, but we probably need to change these anyway
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a bug?

packages/layers/src/xr-layer/xr-layer.js Outdated Show resolved Hide resolved
// This tells WebGL how to read row data from the texture. For example, the default here is 4 (i.e for RGBA, one byte per channel) so
// each row of data is expected to be a multiple of 4. This setting (i.e 1) allows us to have non-multiple-of-4 row sizes. For example, for 2 byte (16 bit data),
// we could use 2 as the value and it would still work, but 1 also works fine (and is more flexible for 8 bit - 1 byte - textures as well).
// https://stackoverflow.com/questions/42789896/webgl-error-arraybuffer-not-big-enough-for-request-in-case-of-gl-luminance
gl.pixelStorei(GL.UNPACK_ALIGNMENT, 1);
gl.pixelStorei(GL.PACK_ALIGNMENT, 1);
// -- this is now applying relevant parameters, but not helping with the rendering...
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it not helping? Do you have a test to show this?

packages/layers/src/xr-layer/xr-layer.js Outdated Show resolved Hide resolved
packages/layers/src/xr-3d-layer/xr-3d-layer.js Outdated Show resolved Hide resolved
packages/layers/src/xr-layer/xr-layer.js Show resolved Hide resolved
packages/layers/src/xr-3d-layer/xr-3d-layer.js Outdated Show resolved Hide resolved
packages/layers/src/xr-layer/xr-layer.js Outdated Show resolved Hide resolved
packages/layers/src/xr-layer/xr-layer.js Outdated Show resolved Hide resolved
Comment on lines 94 to 102
// This tells WebGL how to read row data from the texture. For example, the default here is 4 (i.e for RGBA, one byte per channel) so
// each row of data is expected to be a multiple of 4. This setting (i.e 1) allows us to have non-multiple-of-4 row sizes. For example, for 2 byte (16 bit data),
// we could use 2 as the value and it would still work, but 1 also works fine (and is more flexible for 8 bit - 1 byte - textures as well).
// https://stackoverflow.com/questions/42789896/webgl-error-arraybuffer-not-big-enough-for-request-in-case-of-gl-luminance
// -- this is now applying relevant parameters, but not helping with the rendering...
device.setParametersWebGL({
[GL.UNPACK_ALIGNMENT]: 1,
[GL.PACK_ALIGNMENT]: 1
});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this happen by default now?

@xinaesthete
Copy link
Author

xinaesthete commented Sep 30, 2024

ok @ilan-gold thanks for reviewing (good timing, I just got back from holiday), we're hopefully almost there. This is getting a bit big and noisy...

  1. For the [GL.PACK_ALIGNMENT] thing, maybe the best option is to revert that change for now, with the knowledge that there'll be a few such things to look at in the not-too-distant future.
  2. The test script still hangs on packages/layers test: ScaleBarLayer#Empty props. finally fixed this... 🤞
  3. pnpm i --prod command fails with exit code 1. Hopefully that won't take long to resolve.

That last point also seems improved by moving glsl-colormap in package/extensions/package.json into dependencies rather than devDependencies. I have no idea why that would have been a regression in this branch.

@ilan-gold
Copy link
Collaborator

For the [GL.PACK_ALIGNMENT] thing, maybe the best option is to revert that change for now, with the knowledge that there'll be a few such things to look at in the not-too-distant future.

Thanks, yea. I think the comment is fairly extensive, but Trevor finally discovered why that was necessary to add and it makes sense so I think we should leave it. Maybe luma.gl does it by default or something but best to be on the safe side rather than have images not render with impossible-to-parse error messages.

I also agree with Trevor on the branching issue - let's split of the avivator/zustand updates from this and put in a separate small PR.

I am not sure we need a dev branch...@manzt why do a dev branch rather than just splitting this one into the Avivator-related changes and the deck.gl changes?

@xinaesthete
Copy link
Author

Some slight extra noise with moving dependencies in Avivator to get vercel deployment working with this. Can revert again if you prefer.

Comment on lines +21 to +23
"zustand": "^3.4.1",
"vite": "^5.3.2",
"@vitejs/plugin-react": "^4.2.1"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, I might remove TBH. But before that why is this necessary for vercel builds? @manzt might have something more graceful up his sleeve?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm alI'm also confused. Why did we get rid of devDeps for vercel? what about Vercel requires this?

@manzt
Copy link
Member

manzt commented Oct 1, 2024

I am not sure we need a dev branch...@manzt why do a dev branch rather than just splitting this one into the Avivator-related changes and the deck.gl changes?

I'm just far enough away that I wasn't sure how large the changes would be to either part. Right now the policy to merge into main is generally that we aren't putting the repo in a broken state. I would assume that changes to the core require changes to avivator, so merging in those will will have broken builds of avivator until the remaining stuff is merged.... which is what worries me and hence a separate dev branch were we can review smaller PRs (even have a roadmap if necessary) seemed fitting. That said, I'm not the one making the changes or giving the reviews and I trust your judgement on which direction to go.

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

Successfully merging this pull request may close these issues.

3 participants