Skip to content

New deferred interface for command and resource definition? #576

@kevzettler

Description

@kevzettler

I may be missing some information here but i'm looking for a best pattern to define commands.
I would like to define regl draw commands as stand alone modules and import them as needed like:

import regl from './regl';
import flatVert from '../dsl/render/shaders/flat.vert';
import flatFrag from '../dsl/render/shaders/flat.frag';

export default regl({
  vert: flatVert,
  frag: flatFrag,
  elements: regl.prop('cells'),
  attributes: {
    position: regl.prop('positions'),
  },
  uniforms: {
    color: regl.prop('color'),
    model: regl.prop('model')
  }
});

This however is not possible with the current wrapREGL interface design. Because the default export of the regl module is actually the wrapREGL function, you need to first execute wrapRegl before getting a regl instance to defining any commands or resources:

import reglInit from 'regl';

const regl = reglInit({
      gl,
      extensions: ['oes_texture_half_float'],
    });

const drawFlat = regl({
  vert: flatVert,
  frag: flatFrag,
  elements: this.regl.prop('cells'),
  attributes: {
    position: this.regl.prop('positions'),
  },
  uniforms: {
    color: this.regl.prop('color'),
    model: this.regl.prop('model')
  }
});

This means in order to define standalone modules for the draw commands you need to first execute wrapREGL to get a reference to the actual regl instance and then some how pass that into the standalone modules you need a wrapper function and mskes things a bit awkward

// drawFlat.js
import flatVert from '../dsl/render/shaders/flat.vert';
import flatFrag from '../dsl/render/shaders/flat.frag';

export default function(regl){
  return regl({
    vert: flatVert,
    frag: flatFrag,
    elements: regl.prop('cells'),
    attributes: {
      position: regl.prop('positions'),
    },
    uniforms: {
      color: regl.prop('color'),
      model: regl.prop('model')
    }
  })
}
// main drawing app.js
import reglInit from 'regl';
import DrawFlat from 'drawFlat.js'

const regl = reglInit({
      gl,
      extensions: ['oes_texture_half_float'],
    });

const drawFlat = DrawFlat(regl);

In order to achieve the individual module pattern I outlined in the first example, I have hacked together a helper library I'm calling reglDefer. Its usuage looks like this:

import regl from './reglDefer.js';
import flatVert from '../dsl/render/shaders/flat.vert';
import flatFrag from '../dsl/render/shaders/flat.frag';

export default regl({
  vert: flatVert,
  frag: flatFrag,
  elements: regl.prop('cells'),
  attributes: {
    position: regl.prop('positions'),
  },
  uniforms: {
    color: regl.prop('color'),
    model: regl.prop('model')
  }
});

reglDefer is a meta library that lets you define regl commands and regl.props before regl is initalized and queues them up. Once a 'live' regl instance is registered it will flush the queues and initalize all the pending resource and command registration. Here is the reglDefer implementation:

import uuidv1 from 'uuid/v1';
const deferredMethods = {};
const deferredDefs = {};

let realRegl = null;

let reglDefer = function(reglDefinition){
  if(realRegl) return realRegl(reglDefinition);
  const callId = `defer-${uuidv1()}`;
  deferredDefs[callId] = reglDefinition
  return function(){
    return deferredDefs[callId](...arguments);
  }
};

['prop'].forEach((method) => {
 reglDefer[method] = function(){
    const args = [...arguments];
    if(realRegl) return realRegl[method](args);
    const callId = `defer-${uuidv1()}`;
    deferredMethods[callId] = [method, args]
    return callId;
  }
});
reglDefer.init = function(initalizedRegl){
  realRegl = initalizedRegl;
  Object.entries(deferredMethods).forEach(([callId, [method, args]]) => {
    deferredMethods[callId] = realRegl[method](args);
  });

  // recurse over the regl draw definition and replace any deffered props
  function replaceMethods(acc, key){
    if(deferredMethods[acc[key]]){
      acc[key] = deferredMethods[acc[key]]
      return acc
    }

    if(typeof acc[key] === 'object'){
      acc[key] = Object.keys(acc[key]).reduce(replaceMethods, acc[key])
      return acc;
    }

    acc[key] = acc[key];
    return acc;
  }


  Object.entries(deferredDefs).forEach(([key, definition]) => {
    deferredDefs[key] = Object.keys(definition).reduce(replaceMethods, definition);
    deferredDefs[key] = realRegl(deferredDefs[key]);
  });
};

export default reglDefer;

Please let me know if this is insane and I missed an already existing pattern for handling this. I'm curious how others are handling command definition. Am I missing some functionality that wrapREGL handles?

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