2

The task at hand is to add a band selection tool to a Dart WebGL application. The tool will be used to draw a rectangle over multiple objects by dragging the mouse. Thus multiple objects can be selected/picked in a single user action. I'm currently using gl.readPixels() to read colors from an off-screen renderbuffer. Problem is, when a large area is band-selected, gl.readPixels() issues millions of pixels. Scanning such a big amount of colors wastes precious seconds just to locate few objects.

Please anyone point possibly faster methods for band-selecting multiple objects with Dart+WebGL.

For reference, I show below the current main portion of the band selection tool.

Uint8List _color = new Uint8List(4);

void bandSelection(int x, y, width, height, PickerShader picker, RenderingContext gl, bool shift) {

  if (picker == null) {
    err("bandSelection: picker not available");
    return;
  }

  int size = 4 * width * height;
  if (size > _color.length) {
    _color = new Uint8List(size);
  }

  gl.bindFramebuffer(RenderingContext.FRAMEBUFFER, picker.framebuffer);
  gl.readPixels(x, y, width, height, RenderingContext.RGBA, RenderingContext.UNSIGNED_BYTE, _color);

  if (!shift) {
    // shift is released
    _selection.clear();
  }

  for (int i = 0; i < size; i += 4) {

    if (_selection.length >= picker.numberOfInstances) {
      // selected all available objects, no need to keep searching
      break;
    }

    PickerInstance pi = picker.findInstanceByColor(_color[i], _color[i+1], _color[i+2]);
    if (pi == null) {
      continue;
    }

    _selection.add(pi);
  }

  debug("bandSelection: $_selection");  
}

// findInstanceByColor is method from PickerShader
PickerInstance findInstanceByColor(int r, g, b) {
  return colorHit(_instanceList, r, g, b);
}

PickerInstance colorHit(Iterable<Instance> list, int r,g,b) {

  bool match(Instance i) {
    Float32List f = i.pickColor;
    return (255.0*f[0] - r.toDouble()).abs() < 1.0 &&
        (255.0*f[1] - g.toDouble()).abs() < 1.0 &&
        (255.0*f[2] - b.toDouble()).abs() < 1.0;
  }

  Instance pi;

  try {
    pi = list.firstWhere(match);
  } catch (e) {
    return null;
  }

  return pi as PickerInstance;
}
Everton
  • 12,589
  • 9
  • 47
  • 59
  • can we see how ‘findInstanceByColor‘ is implemented ? – nesderl Dec 21 '13 at 09:00
  • How did the pixels get to where they are in the first place, are they objects you already know the shape and position of because you rendered them? If so could you use the intersection of the drawn band shape with the known shapes to get what you want or is there some other reason you need to select by colour? – ianmjones Dec 22 '13 at 10:35
  • @nesderl: method findInstanceByColor included. – Everton Dec 23 '13 at 13:37
  • @ianmjones: I'm using color-coding picking. i render every pickable object with an unique color to an off-screen renderbuffer. In order to know which object is rendered to pixel (x,y), i just need to read the pixel color and use it to lookup the object. Notice that off-screen renderbuffer has only color information; it does not hold geometry info so i could do intersection processing. I guess i could do some geometric intersection, but the band shape is 2d (located at the near plane) while object shape is 3d located at arbitrary position. I'm unsure on the "best" wait to intersect them. – Everton Dec 23 '13 at 13:51

1 Answers1

1

Right now I can see small solutions that might speed up your algorithm to limit as much as possible iterating over all of your elements,

The first thing you can do is have a default colour. When you see that colour, you know you don't need to iterate all over your array of elements. It will accelerate large poorly populated areas. It's very easy to implement, just adding a if.

For more dense areas you can implement some kind of colour caching. That means you store an array of colour you encountered. When you check a pixel, you first check the cache and then go over the entire list of elements, and if you find the element, add it to the cache. It should accelerate cases with few big elements but will be bad if you have lots of small elements, which is very unlikely if you have picking... You can accelerate your cache buy sorting your cached elements by last hit or/and by number of hits, it's very likely to find the same element in a continuous raw of pixels. It's more work but stays relatively easy and short to implement.

Last optimisation would be to implement a space partitioning algorithm to filter the elements you want to check. That would be more work but will pay better of on the long run.

edit : I'm not a dart guy but this is how it would look like to implement in a basic way the first two optimisations:

var cache = new Map<UInt32, PickerInstance>();

for (int i = 0; i < size; i += 4) {
  UInt32 colour = _color[i] << 24 | _color[i+1] << 16 | _color[i+2] << 8 | 0; // I guess we can just skip transparency.

  if (_selection.length >= picker.numberOfInstances) {
    // selected all available objects, no need to keep searching
    break;
  }

  // black is a good default colour.
  if(colour == 0) {
    // if the pixel is black we didn't hit any element :(
    continue;
  }

  // check the cache
  if(cache[colour] != null) {
    _selection.add(cache[colour]);    
    continue;
  }

  // valid colour and cache miss, we can't avoid iterating the list.
  PickerInstance pi = picker.findInstanceByColor(_color[i], _color[i+1], _color[i+2]);
  if (pi == null) {
    continue;
  }

  _selection.add(pi);
  // update cache
  cache[colour] = pi;
}
nesderl
  • 325
  • 1
  • 5
  • Thanks for the great optimizations hints. I'll try them to see how much speed up I get, before resorting to geometric methods. – Everton Dec 23 '13 at 17:36
  • Your suggestions have greatly improved the results. From seconds, I now see ~400 msecs under JS (Dart compiled to JS) and ~40 msecs under Dart. – Everton Dec 23 '13 at 20:32
  • Great! 400ms is still too slow for any real time application, how many elements, elements on screen and pixels do you test to have such bad performances ? – nesderl Dec 23 '13 at 21:36
  • The test has these numbers: pickable_elements=3 pixels_total=1542688 for_loops=224640 background_color_hits=111906 cache_hits=112731 – Everton Dec 24 '13 at 03:43