简体   繁体   中英

Direct2D Depth Buffer

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?

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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM