Skip to content

feat(core): Enhance onAfterRender with pass parameter and add hasActiveTransitions#10361

Draft
akre54 wants to merge 1 commit into
visgl:masterfrom
akre54:gpu-completion-callback
Draft

feat(core): Enhance onAfterRender with pass parameter and add hasActiveTransitions#10361
akre54 wants to merge 1 commit into
visgl:masterfrom
akre54:gpu-completion-callback

Conversation

@akre54

@akre54 akre54 commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Summary

Enhances existing APIs instead of adding a new callback, reducing API proliferation while providing the same functionality.

Changes

1. onAfterRender Enhancement

Added pass parameter to distinguish between render pass types:

deck.setProps({
  onAfterRender: ({device, gl, pass}) => {
    if (pass === 'screen') {
      // Main render to screen - safe to capture
      captureFrame(gl);
    }
    // pass === 'picking' for mouse picking
    // pass === 'shadow' for shadow maps
  }
});

2. New hasActiveTransitions() Method

Checks for active viewport and layer uniform transitions:

if (!deck.hasActiveTransitions()) {
  // Scene is settled - no animations in progress
  captureFrame();
}

Why This Approach

Per @chrisgervang's feedback, enhancing existing APIs is better than adding new callbacks:

  • No new API surface to maintain
  • Composable with existing patterns
  • Clear separation of concerns
  • Backward compatible

Use Cases

Video Export

async function captureFrame(deck: Deck) {
  // Wait for data
  const allLoaded = deck.props.layers.every(l => l.isLoaded);
  if (!allLoaded) return;

  // Wait for animations
  if (deck.hasActiveTransitions()) {
    await new Promise(resolve => {
      const check = () => {
        if (!deck.hasActiveTransitions()) resolve();
        else requestAnimationFrame(check);
      };
      check();
    });
  }

  // Capture on next screen render
  return new Promise(resolve => {
    deck.setProps({
      onAfterRender: ({pass}) => {
        if (pass === 'screen') {
          captureFrameData();
          resolve();
        }
      }
    });
    deck.redraw();
  });
}

Screenshots

deck.setProps({
  onAfterRender: ({pass}) => {
    if (pass === 'screen' && !deck.hasActiveTransitions()) {
      const allLoaded = deck.props.layers.every(l => l.isLoaded);
      if (allLoaded) {
        screenshot();
      }
    }
  }
});

Comparison to Original PR

Original (new callback):

  • Added onFrameComplete callback
  • GPU timing (can be measured externally)
  • layersRendered count (trivially available via deck.props.layers.length)

New Approach (enhance existing):

  • Enhanced onAfterRender with pass parameter
  • Added hasActiveTransitions() method
  • Same functionality, less API surface

Testing

  • ✅ Unit tests for onAfterRender pass parameter
  • ✅ Unit tests for hasActiveTransitions() with layer transitions
  • ✅ Unit tests for hasActiveTransitions() with viewport transitions
  • ✅ Complete documentation with examples

Related

This addresses the feedback from the original PR discussion about reducing API proliferation. Instead of a new callback, we enhance what exists.

@chrisgervang chrisgervang left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Let's see if we can get the same end result without adding another render callback to the core API. I think there's additive way to get the same end result by enhancing onAfterRender with pass and adding deck.hasActiveTransitions

Comment thread modules/core/src/lib/deck.ts Outdated
gpuTime: number | null;
gpuTimingSupported: boolean;
timestamp: number;
layersRendered: number;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Wouldn't a user have this trivially available in the callback? deck.props.layers.length

Comment thread modules/core/src/lib/deck.ts Outdated

// Fire onFrameComplete only for actual on-screen draws so picking/texture
// passes don't pollute the timing stream consumed by capture pipelines.
if (opts.pass === 'screen' && this.props.onFrameComplete !== noop) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What if instead of adding a new API we passed opts.pass through onAfterRender?

Comment thread modules/core/src/lib/deck.ts Outdated
Comment on lines +1547 to +1549
const animationsInProgress =
Boolean(this.layerManager?.hasActiveTransitions()) ||
Boolean(this.viewManager?.hasActiveTransitions());

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We could add a top level deck.hasActiveTransitions(), add pass to onAfterRender, and then an application can implement this on their own.

This pattern could be shared in documentation in tips and tricks

Comment thread modules/core/src/lib/deck.ts Outdated
Comment on lines +225 to +227
* Useful for performance instrumentation, frame pacing, and headless capture
* pipelines that need to know when the GPU is finished with a frame and
* whether any animations would change the next frame.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can an application monitor GPU metrics timings on their own?

See getStats

@akre54

akre54 commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator Author

@chrisgervang - Great point about API proliferation. Your suggestion to enhance existing APIs is cleaner than adding a new callback.

Revised approach:

  1. Add pass parameter to existing onAfterRender(info) where info.pass indicates screen render vs picking pass
  2. Add deck.hasActiveTransitions() method to check for:
    • Layer uniform transitions
    • GPU attribute interpolations
    • Viewport transitions
  3. Document the "safe to capture" pattern in tips/tricks

For GPU timing, I'll check whether deck.getStats() already provides what we need.

Re: layersRendered field - You're right, it's trivially available via deck.props.layers.length. I'll remove it.

If this can all be achieved by enhancing existing APIs + documentation rather than adding a new callback, that's definitely better. Let me revise the approach and come back with an enhancement proposal instead.

Enhances existing APIs instead of adding new callbacks, reducing API
proliferation while providing the same functionality.

Changes:
--------
1. onAfterRender callback now includes 'pass' parameter
   - Distinguishes 'screen' from 'picking' and other render passes
   - Enables efficient frame capture (skip non-screen renders)

2. New deck.hasActiveTransitions() method
   - Checks for active viewport transitions
   - Checks for active layer uniform transitions
   - Returns true if any animations are in progress

Use Cases:
----------
- Video export: Wait for transitions before capturing frames
- Screenshots: Ensure scene is settled before capture
- Testing: Verify animations complete

Example:
--------
deck.setProps({
  onAfterRender: ({pass}) => {
    if (pass === 'screen' && !deck.hasActiveTransitions()) {
      const allLoaded = deck.props.layers.every(l => l.isLoaded);
      if (allLoaded) {
        captureFrame();
      }
    }
  }
});

Benefits over new callback:
---------------------------
- No new API surface (enhances existing onAfterRender)
- Composable with existing patterns
- Clear separation of concerns (pass detection vs transition detection)
- Backward compatible (pass parameter is new but optional)

Testing:
--------
- Unit tests for onAfterRender pass parameter
- Unit tests for hasActiveTransitions() with layer transitions
- Documentation with complete examples
@akre54 akre54 changed the title feat(core): add Deck#onFrameComplete callback with CPU/GPU timing and animation state feat(core): Enhance onAfterRender with pass parameter and add hasActiveTransitions Jun 12, 2026
@akre54 akre54 force-pushed the gpu-completion-callback branch from 0b51ee8 to 3d7d186 Compare June 12, 2026 02:18
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.

2 participants