Some server applications need to render for scenarios like charting and sending back the generated bitmaps to users connected through web clients. During the planning of Direct2D, we decided to put the support of this scenario as one of our design goals.
Requirements of server-side rendering
There are several specific requirements for this scenario. First, usually the process is going to be run in Session 0 for security reason. Session 0 is dedicated for system processes and services, and isolated from user applications. It has higher privilege in some aspects but no privilege in interactive features (details here). Second, most servers only have low-end graphics cards, or there may even be no graphics cards inside, thus the availability of hardware acceleration cannot be expected. Third, the application should be able to handle multiple concurrent requests efficiently on a multi-core machine.
Options of available APIs
There are three native 2D rendering APIs that applications can use in Windows Server 2008 R2: GDI, GDI+ and the new Direct2D. I will go through each of them and talk about whether they fit with this scenario.
GDI does not support high-quality drawing. It lacks the support of anti-aliasing, which makes sloped lines jaggy. Moreover, it does not have full support for transparency, such as the support of semi-transparent brushes and pens.
GDI was not designed for parallel processing and suffers from an inefficient global lock. Windows 7 and Windows Server 2008 R2 have redesigned this lock to be more granular (details here), but based on our test results, D2D still scales better.
Another problem is that the total number of GDI handles is limited to 10240 per process and 65536 per session, because internally Windows uses a 16-bit WORD to store the index of them for each session. Stock objects are excluded from this limitation. Spawning new sessions is one way to overcome the limit, but it increases the memory usage.
The main problem with GDI+ for server scenarios is that it does not officially support running in Session 0. Functions that try to interact with the display device will receive errors because it is not allowed in Session 0. Other functions may also fail because even if the function doesn’t seem related to the display device, it may use some paths underneath that are forbidden.
GDI+ supports anti-aliasing and alpha-blending, thus it doesn’t have the quality issue of GDI. However, there is also a performance issue due to the locking mechanism, and no work in Windows 7 or Windows Server 2008 R2 has been done to improve it in GDI+.
D2D was designed with multi-threading and Session 0 in mind from the very beginning. In most multi-threaded cases, D2D can be lock-free by using multiple single-threaded factories, one for each thread. There will be a factory-wide lock if the application has to use the multi-threaded mode of the factory, but it is more granular than those in GDI and GDI+. The impact of this lock is negligible and won’t grow exponentially when the number of threads increases.
How to use Direct2D for server-side rendering
A typical server application for this scenario will generate pictures inside the memory using D2D software rendering using multiple threads where each thread draws one picture.
- Software rendering
To use D2D on a server without hardware acceleration, the key step is to use the software rasterizer by creating a WIC bitmap render target with D2D1_RENDER_TARGET_TYPE_SOFTWARE or D2D1_RENDER_TARGET_TYPE_DEFAULT. The default property will also use software rendering because WIC bitmap render target does not support hardware rendering.
Usually a multi-threaded architecture is good for server rendering. Take a banking application that generates charts for example. The typical case is that there will be many users connected to the server at the same time, and the charts they are asking for can be generated independently. When running on a multi-core server machine, it would be very efficient to create multiple rendering threads and put one charting task to each thread, so that the OS can fully utilize the CPU cores to run rendering tasks in parallel.
One concern about the design of multi-threaded architecture for D2D is how to create and share factories and render targets across threads. The following figures show three different potential approaches.
Figure 1 shows different threads sharing the same factory and render target. This architecture is dangerous even if the factory uses multi-threaded mode. The internal lock does not prevent situations like overlapping BeginDraw() and EndDraw() to the same render target, as depicted in the picture. The BeginDraw() in thread 1 will fail because the render target is being used for the drawing of thread 2. We do not support this architecture since it’s unpredictable when multiple threads are changing the state of the same render target simultaneously, e.g. setting the transformation matrix.
Figure 2 works but only has the same performance as single thread. The D2D locking in multi-threaded mode factory is in function level and all the D2D calls in the same factory will be serialized. As a result, if thread 1 tries to enter a D2D function while thread 2 is in the middle of executing another function, thread 1 will be blocked till thread 2 finishes.
Figure 3 is what we suggest for server-side rendering. A separate factory is created for each thread. The factory can be put in single-threaded mode if it’s only going to be accessed from within the thread. D2D functions in different threads are lock-free and can run in parallel. The cost of having multiple factories is much less than creating multiple D3D devices, just about 60k each for the memory.
Although Figure 3 has the best concurrency, you may consider Figure 2 for the benefit of sharing D2D resources that are created from the same factory.
- Generate an image file
Here are the main steps to generate an image file:
- Create an IWICBitmap,
- Pass the bitmap to CreateWicBitmapRenderTarget(),
- Use ID2D1RenderTarget::Clear() to clear the background. To have a transparent background in the output picture, use a color with 0 in the alpha channel for this function.
- Draw to the render target,
- Set up an IWICStream that either writes to a file (InitializeFromFilename) or writes inside the memory (InitializeFromMemory),
- Use IWICBitmapFrameEncode to encode the bitmap into the picture format you want.
For a detailed example see the Save as an Image File Sample on MSDN.
As you can see above, using Direct2D for server rendering is simple and straight-forward. It provides high-quality and highly-parallelizable rendering that can be run in low privilege environments of the server. More information about how to use Direct2D can be found on MSDN here.