Skip to content

[DRAFT] (RFC) feat: Unified "prepare" APIs #1734

@iwoplaza

Description

@iwoplaza

I would like to preface this by apologizing for the constant API changes to the prepareDispatch API 🙇

root.prepare*

Though I was initially resistant to putting prepareDispatch on the root, as it seemed that putting more and more utilities on it would quickly balloon its size, the prepare APIs (prepareDispatch being the one implemented so far) seem to cover most compute and rendering use-cases, meaning it would make sense to showcase them early in the documentation and use them extensively in our examples. That would mean shoving them into the "Utils" section of our docs is counterintuitive.

It also, imo, looks better:

const foo = prepareDispatch(root, () => {
  // ...
});

// vs

const foo = root.prepareDispatch(() => {
  // ...
});

Note

If you agree with the above, l suggest we put the "prepare" APIs on the root

What should root.prepare* return?

After recent changes to the API to allow "root.prepareDispatch" to use bind group layouts, the API no longer returns a function, but an object with a "dispatch" method. After taking a closer look, it really seems not that far away from creating a compute pipeline. We call an API on the root, we provide a shader function, and it returns an object that we call dispatch (or dispatchWorkgroups) on. The only problem is that dispatch works in units of threads, and dispatchWorkgroups works in units of workgroups. It's fine for prepareDispatch to work in units of threads, as it has automatic boundary checks. We could do the same for regular pipelines, but we either introduce additional overhead, or let users debug why it's not running the exact number of threads they requested.

We could make this behavior more explicit by renaming dispatch to e.g. dispatchAtLeast 🤔

This change would also remove the confusion as to how to call objects returned by prepareDispatch, since they're just pipelines.

prepareCompute and prepareRender

If we do go ahead with returning pipelines, prepare* APIs become basically shorthands for creating compute and render pipelines. To avoid adding another keyword to the mix ("dispatch"), I suggest we call the APIs prepareCompute and prepareRender respectively.

// Compute pipeline

const pipeline = root.prepareCompute(() => {
  'kernel';
  counter.$ += 1;
});

pipeline.dispatchAtLeast(1);

// Render pipeline

const pipeline = root.prepareRender({
  // -- No attributes by default
  // attributes: {},
  // -- Preferred presentation format by default
  // output: {},
  vertex: ({ $vertexIndex }) => {
    'kernel';
    const pos = [d.vec2f(-1, -1), d.vec2f(3, -1), d.vec2f(-1, 3)];
    return {
      $position: d.vec4f(pos[$vertexIndex], 0, 1),
    };
  },
  fragment: () => {
    'kernel';
    return d.vec4f(1, 0, 0, 1);
  },
});

pipeline.withColorAttachment({ view }).draw(3);

Radical idea number 1543

We're sure that the pipelines returned by prepareCompute can execute an "exact" number of threads. We could tag them on the type level, which would unlock the dispatch() method.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions