11

I need to draw a list of shapes and I am using Direct2D. I get the list of shapes from a file. The list is sorted and the order of the elements inside the file represents the order these shapes will be drawn. So, if for example the file specifies two rectangle in the same position and with the same sizes, only the second one will be visible (since the first will be overwritten).

Given my list of shapes I proceede to its drawing in the following way:

list<Shape> shapes;

for (const auto& shape : shapes)
   shape.draw();

It is straightforward to see that if I have two shapes I cannot invert the order of the drawing operations, and this means that I must be sure that shape2 will be always drawn after shape1 and so on. Follows that I can not use multiple threads to draw my shapes, and this is a huge disadvantage in terms of performances.

I read that Direct3D supports the depth buffer (or z-buffer), which specifies for each pixel its z-coordinate, such that only the "visible" pixels (the onces closer to the viewer) will be drawn, regardless of the order in which the shapes are drawn. And I have the depth information of each shape when I read the file.

Is there a way to use the depth buffer in Direct2D, or a similar technique which allows me the use of multiple threads to draw my shapes?

Nick
  • 9,515
  • 17
  • 75
  • 175
  • I'm not sure about `Direct2D` itself - but typically you will assign the shape a `z` coord, sort by that coord and then render respectively, thereby implementing a simple Z-buffer. – rhughes May 06 '16 at 14:52
  • 2
    @rhughes I do not need to sort them, since the are already sorted when I read the file. Also, using this method prevents the use of multiple threads. – Nick May 06 '16 at 14:54
  • I mean sort by the z-coord. You should not need to render these on separate threads. Render the shape with the lowest z-cood first and then work your way to the shape with the highest z-coord. – rhughes May 06 '16 at 15:03
  • I think you are indeed supposed to reorder the paints because there is no depth there. Then your draw is (or might be) a queuing for later paint (esp. in `EndDraw`), so doing that from threads directly is not going to work out anyway because you are supposed to serialize the calls (or have them serialized by the implementation which is pretty much the same). – Roman R. May 06 '16 at 16:34
  • What kind are the shapes, are they rectangles or more complex polygons? – Anton Angelov May 09 '16 at 20:32
  • @AntonAngelov Each kind of shape, from rectangles to polylines to texts and so on. But for each graphic element I have its bounds (a rectangle). – Nick May 10 '16 at 07:00

2 Answers2

4

Is there a way to use the depth buffer in Direct2D, or a similar technique which allows me the use of multiple threads to draw my shapes?

The answer here is no. Althought the Direct2D library is built on top of Direct3D, it doesn't provide the user such feature through the API, since the primitives you can draw are only described by two-dimensional coordinates. The last primitive you draw to the render target is ensured to be visible, so no depth testing is taking place. Also, the depth buffer in Direct3D doesn't have much to do with multi-threading on the CPU side.

Also note that even if you are issuing drawing commands using multiple threads they will be serialized by the Direct3D driver and performed sequentially. Some newer graphical APIs like Direct3D 12 and Vulkan does provide multithreaded drivers which allows you to effectively draw different content from different threads, but they come with higher complexity.

So eventually if you stick to Direct2D you are left with the option of drawing each shape sequentially using a single thread.

But what can be done is that you can eliminate the effectively occluded shapes by testing for occlusion each shape against all others. So the occluded shapes can be discarded from the list and never rendered at all. The trick here is that some of the shapes does not fill their bounds rect entirely, due to transparent regions (like text) or if the shape is a complex polygon. Such shapes can not be easily tested or will need more complex algorithms.

So you have to iterate thourgh all shapes and if the current shape is a rectangle only then perform occlusion testing with all previous shapes' bounds rects.

The following code should be considered pseudo-code, it is intended just to demonstrates the idea.

#define RECTANGLE 0
#define TEXT      1
#define TRIANGLE  2
//etc

typedef struct {
    int type; //We have a type field
    Rect bounds_rect; //Bounds rect
    Rect coordinates; //Coordinates, which count vary according to shape type
    //Probably you have many other fields here
} Shape;

//We have all shapes in a vector
std::vector<Shape> shapes;

Iterate all shapes.

for (int i=1; i<shapes.size; i++) {
  if(shape[i].type != RECTANGLE) {
    //We will not perform testing if the current shape is not rectangle.
    continue; 
  }

  for(int j=0; j<i; j++) {
    if(isOccluded(&shape[j], &shape[i])) {
      //shape[j] is totally invisible, so remove it from 'shapes' list
    }
  }
}

Occlusion testing is something like this

bool isOccluded(Shape *a, Shape *b) {
  return (a.bounds_rect.left > b.coordinates.left && a.bounds_rect.right < b.coordinates.right &&
          a.bounds_rect.top > b.coordinates.to && a.bounds_rect.bottom < b.coordinates.bottom);
}

And you don't have to iterate all shapes with a single thread, you can create multiple threads to perform tests for different parts of the shape list. Of course you will need some locking technique like mutex when deleting shapes from the list, but that is another topic.

Anton Angelov
  • 1,206
  • 7
  • 18
0

The depth buffer is used to discard primitives that will be occluded by something in front of it in the 3D space, saving on redrawing time by not bothering with stuff that won't be seen anyway. If you think of a scene with a tall, thin candle in front of a ball facing the camera, the entire ball is not drawn and then the candle drawn over it, just the visible sides of the ball are. This is how order of drawing does not matter

I have not heard of the use of a depth buffer in D2D as it is somewhat meaningless; everything is drawn onto one plane in D2D, how can something be in front of or behind something else? The API may support it but I doubt it as it makes no abstract sense. The depth information on each shape is just the order to draw it in essentially which you already have

Instead what you could do, divide and allocate your shapes to your threads while maintaining order, ie

t1 { shape1, shape2, shape3 } = shape123
t2 { shape4, shape5, shape6 } = shape456
...

And draw the shapes onto a new object (but not the backbuffer), depending on your shape class you maybe be able to represent the result as a shape. This will leave you with t many shapes which are still in order but have been computed in parallel. You can then gradually compose your final result by drawing the results in order, ie

t1 { shape123, shape456, shape789 }
t2 { shape101112, shape131415 }

t1 { shape123456789, shape101112131415 } = final shape

Now you have the final shape you can just draw that as normal

Madden
  • 903
  • 6
  • 25
  • Your procedure seems really interesting, but unless you specify what exactly should be this new object where to draw the shapes, the whole procedure could be unfeasible. Please consider that I draw geometries as well as line, ellipses, rectangles, texts (these ones could be treated separately). – Nick May 10 '16 at 18:47
  • You could draw each 'composed shape' onto a texture object and then combine the textures as above drawing the end result onto a quad, this will allow you to represent any primitive (whether text or geometry or even other images) in a generic form. This may require some delving into d3d however, I am not sure however extensive d2d is – Madden May 11 '16 at 09:04