Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Managed DirectDraw with Windows Mobile

0.00/5 (No votes)
4 Jun 2009 2  
Using Managed DirectDraw with Windows Mobile.

WindowsMoibleManagedDDraw/ddraw.gif

Introduction

Over the last couple days, I have been researching managed 2D graphics APIs for the Windows Mobile platform. There are a lot of choices out there. Strictly for 2D, we have GDI, GAPI, DirectDraw, and the GetRawFramebuffer function. On the 3D side, we have OpenGL ES and Direct3D. Each of these APIs has its pros and cons.

OpenGL ES (Embedded Systems)

OpenGL ES is a lightweight subset of OpenGL for small devices. There is no official managed OpenGL ES wrapper, but Koushik Dutta has created a managed wrapper which you can download off his blog. In order to get decent performance out of OpenGL ES, your hardware needs drivers. There is a software driver though.

Direct3D Mobile

Managed Direct3D has been in Compact Framework since 2.0. If your target device does not have hardware support with an accompanying driver (htcclassaction), then forget about Direct3D. The software driver is incredibly slow and is not really good for anything except debugging in the emulator.

You can use the code from this link to see if your device has a Direct3D driver.

GDI

GDI stands for Graphic Device Interface and is the primary API for 2D graphics in windows.

GAPI (Game API)

GAPI is an old API for CE that Microsoft created to ease game development. You can find a managed wrapper for GAPI on MSDN here.

GAPI is officially deprecated. All documentation has been removed from MSDN (except GXInput). You will still find GX.dll on your phone though, and the graphics related functions still exist. Microsoft is going to remove it in a future version.

Using an API that was going away didn't jive with me. The zombies sample application did not run too fast on my phone either.

DirectDraw

DirectDraw is a 2D API from Microsoft. DirectDraw gives us direct access to video memory, hardware blting, and flipping. Microsoft is pushing DirectDraw as the recommended API for fast 2Dd graphics in CE now that GAPI is being deprecated (see the "Have you migrated to DirectDraw yet?" link at the bottom of the page). Unfortunately, there is no managed wrapper for DirectDraw in the Compact Framework. Today, we will create one.

DirectDraw wrapper

DirectDraw is implemented as a COM library. The initial version of the Compact Framework did not support COM interop, but it was added in version 2.0. Using DirectDraw from managed code should be extremely simple. We should be able to run tlbimp to generate the stubs we need for the interop, but unfortunately, I had some issues, so I manually wrote the interop code.

Initializing DirectDraw

The first thing we need to is create an instance of the main DirectDraw object. We will use the DirectDrawCreate method to create this object. Even though DirectDraw is implemented as a COM library, we need to call this method to pass in the device ID that we get using DirectDrawEnumerateEx, or pass in null for the default device. We will also need to pass in a pointer to a IDirectDraw interface. The last argument is not used, and we can ignore and pass in null.

[DllImport("ddraw.dll", CallingConvention = CallingConvention.Winapi)]
public static extern uint DirectDrawCreate(IntPtr lpGUID, 
              out IDirectDraw lplpDD, IntPtr pUnkOuter);

[Guid("9c59509a-39bd-11d1-8c4a-00c04fd930c5"), 
  InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IDirectDraw
{
    uint CreateClipper(uint dwFlags, out IDirectDrawClipper lplpDDClipper, 
                       IntPtr pUnkOuter);
    uint CreatePalette(CreatePaletteFlags dwFlags, 
                       ref tagPALETTEENTRY lpDDColorArray, 
                       out IDirectDrawPalette lplpDDPalette, 
                       IntPtr pUnkOuter);
    uint CreateSurface(ref DDSURFACEDESC lpDDSurfaceDesc, 
         out IDDrawSurface lplpDDSurface, IntPtr pUnkOuter);
    uint EnumDisplayModes(uint dwFlags, ref DDSURFACEDESC lpDDSurfaceDesc2, 
         IntPtr lpContext, IntPtr lpEnumModesCallback);
    uint EnumSurfaces(uint dwFlags, ref DDSURFACEDESC lpDDSD2, 
         IntPtr lpContext, IntPtr lpEnumSurfacesCallback);
    uint FlipToGDISurface();
    uint GetCaps(out DDCAPS halCaps, out DDCAPS helCaps);
    uint GetDisplayMode(out DDSURFACEDESC lpDDSurfaceDesc2);
    uint GetFourCCCodes(ref int lpNumCodes, IntPtr lpCodes);

    uint GetGDISurface(out IDDrawSurface lplpGDIDDSSurface4);
    uint GetMonitorFrequency(ref uint lpdwFrequency);
    uint GetScanLine(ref uint lpdwScanLine);
    uint GetVerticalBlankStatus(ref bool lpbIsInVB);
    uint RestoreDisplayMode();
    uint SetCooperativeLevel(IntPtr hWnd, CooperativeFlags flags);
    uint SetDisplayMode(uint dwWidth, uint dwHeight, uint dwBPP, 
         uint dwRefreshRate, SetDisplayModeFlags dwFlags);
    uint WaitForVerticalBlank(WaitForVBlankFlags flags, IntPtr handle);

    uint GetAvailableVidMem(ref DDSCAPS lpDDSCaps2, ref uint lpdwTotal, ref uint lpdwFree);
    uint GetSurfaceFromDC(IntPtr hdc, out IDDrawSurface lpDDS4);
    uint RestoreAllSurfaces();
    uint TestCooperativeLevel();
    uint GetDeviceIdentifier(ref DDDEVICEIDENTIFIER lpDDDeviceIdentifier, uint dwFlags);
}

Before we can start, we need to set the cooperative level using the SetCooperativeLevel method. The first parameter of this method is the control handle, and the second parameter is a value indicating if the application will be windowed or full screen.

Primary surface and backbuffers

Now, we must create our primary surface/framebuffer and any backbuffers. The primary surface is the screen itself. A backbuffer is another image in memory that is the same dimensions as the framebuffer. Applications draw the current frame to the backbuffer, and then copies the entire backbuffer to the framebuffer when the frame has been completely rendered. The video hardware is constantly drawing the screen based on the refresh rate. The screen will get updated while you are in the middle of drawing a frame, and your game graphics will appear to flicker. A backbuffer will prevent this flickering from occurring. Shown below is our initialization code; windowed mode is not really supported right now.

public DirectDrawGraphics(Control control, CooperativeFlags flags, 
                          BackbufferMode backBufferMode)
{
    if (control == null)
    {
        throw new ArgumentNullException("control");
    }

    // Init direct draw
    IDirectDraw draw;
    uint result = NativeMethods.DirectDrawCreate(IntPtr.Zero, 
                                out draw, IntPtr.Zero);
    _ddraw = draw;
    if (result != 0)
    {
        throw ExceptionUtil.Create(result);
    }

    // Set the cooperative level.
    result = _ddraw.SetCooperativeLevel(control.Handle, flags);
    if (result != 0)
    {
        throw ExceptionUtil.Create(result);
    }
    _hostControl = control;

    // Get the device capabilities.
    DDCAPS halCaps = new DDCAPS(), helCaps = new DDCAPS();
    halCaps.dwSize =(uint) Marshal.SizeOf(typeof(DDCAPS));
    helCaps.dwSize = halCaps.dwSize;
    result = _ddraw.GetCaps(out halCaps, out helCaps);

    // check if we use flip and video memory
    _supportHardwareFlip = (halCaps.ddsCaps.dwCaps & 
           SurfaceCapsFlags.BACKBUFFER) == SurfaceCapsFlags.BACKBUFFER;
    _supportHardwareFlip &= ((halCaps.ddsCaps.dwCaps & 
           SurfaceCapsFlags.FLIP) == SurfaceCapsFlags.FLIP);
    _supportHardwareFlip &= (backBufferMode != BackbufferMode.None);
    if ( !_supportHardwareFlip && backBufferMode == BackbufferMode.Hardware )
    {
        throw new NotSupportedException("Device does not support " + 
                  "the minimum hardware options.");
    }

    // Check the surface flags to see if these surfaces
    // will be in video memory or system memory
    _surfaceFlags = ((halCaps.ddsCaps.dwCaps & SurfaceCapsFlags.VIDEOMEMORY) == 
                      SurfaceCapsFlags.VIDEOMEMORY) ?
                      SurfaceCapsFlags.VIDEOMEMORY : SurfaceCapsFlags.SYSTEMMEMORY;
    

    // The desc for the primary surface
    DDSURFACEDESC desc = new DDSURFACEDESC();
    desc.dwFlags = SurfaceDescFlags.CAPS;
    desc.ddsCaps.dwCaps = SurfaceCapsFlags.PRIMARYSURFACE | _surfaceFlags;

    // Create the primary and backbuffer surfaces
    if (_supportHardwareFlip && backBufferMode != BackbufferMode.None && 
        backBufferMode != BackbufferMode.Software)
    {
        desc.dwFlags |= SurfaceDescFlags.BACKBUFFERCOUNT;
        desc.ddsCaps.dwCaps |= SurfaceCapsFlags.FLIP;
        desc.dwBackBufferCount = 1;
        _primarySurface = CreateSurface(desc);


        // Get ptr for callback. 
        EnumSurfacesCallback callback = 
           new EnumSurfacesCallback(EnumAttachSurfacesCallback);
        IntPtr callbackPtr = Marshal.GetFunctionPointerForDelegate(callback); 

        // Get the backbuffer. 
        _primarySurface._surface.EnumAttachedSurfaces(IntPtr.Zero, callbackPtr);

        // Keep the calback alive until at least here.
        GC.KeepAlive(callback);
    }
    else
    {
        _primarySurface = CreateSurface(desc);
        
        // Create the software backbuffer.
        if (backBufferMode != BackbufferMode.None)
        {
            desc = new DDSURFACEDESC();
            desc.ddsCaps.dwCaps |= _surfaceFlags;
            desc.dwFlags = SurfaceDescFlags.CAPS | SurfaceDescFlags.WIDTH | 
                           SurfaceDescFlags.HEIGHT;
            desc.dwHeight = (uint)_primarySurface.Height;
            desc.dwWidth = (uint)_primarySurface.Width;
            _backbuffer = CreateSurface(desc);
        }
    }

    if (flags == CooperativeFlags.Normal)
    {
        result = _ddraw.CreateClipper(0, out _clipper, IntPtr.Zero);
        if (result != 0)
        {
            throw ExceptionUtil.Create(result);



        }
        result = _clipper.SetHWnd(_hostControl.Handle);
        if (result != 0)
        {
            throw ExceptionUtil.Create(result);
        }
        result = _primarySurface._surface.SetClipper(_clipper);
        if (result != 0)
        {
            throw ExceptionUtil.Create(result);
        }
    }

    _screenArea = new Rectangle(0, 0, _primarySurface.Width, _primarySurface.Height);

}

Creating off screen surfaces

To create surfaces, we need to call the IDirectDraw.CreateSurface method. This method takes a surface description as an input parameter and returns an IDirectDraw surface as an output parameter. The surface description contains various information such as width, height, and the capabilities of the new surface. We need to indicate which fields we have set using the dwFlags field. Shown below is our wrapper code.

public Surface CreateSurface(int width, int height)
{
    DDSURFACEDESC desc = new DDSURFACEDESC();
    desc.dwSize = (uint)Marshal.SizeOf(typeof(DDSURFACEDESC));
    desc.dwHeight = (uint)height;
    desc.dwWidth = (uint)width;
    desc.ddsCaps.dwCaps = _surfaceFlags;
    desc.dwFlags = SurfaceDescFlags.CAPS | SurfaceDescFlags.WIDTH |
        SurfaceDescFlags.HEIGHT;

    return CreateSurface(desc);
}

public Surface CreateSurface(DDSURFACEDESC surfaceDesc)
{
    DDSURFACEDESC desc = surfaceDesc;
    desc.dwSize = (uint)Marshal.SizeOf(typeof(DDSURFACEDESC));
    IDDrawSurface surface;
    _ddraw.CreateSurface(ref desc, out surface, IntPtr.Zero);
    return new Surface(this, surface);
}

Loading a surface from a bitmap

To load a bitmap onto a surface, we need to use GDI. The GetDC method on a surface will get us a pointer to a device context that we can use in various GDI methods that take an HDC. After performing any GDI work, we need to make sure we can use the surface ReleaseDC method. In the example code below, we use the BitBlt method to copy the image on to the surface.

internal void LoadImage(Bitmap image)
{
    IntPtr hBitmap = image.GetHbitmap();
    IntPtr hdcImage, hdc = IntPtr.Zero;
    
    // Create the device context for image and select the image.
    hdcImage = GDI.CreateCompatibleDC(IntPtr.Zero);
    GDI.SelectObject(hdcImage, hBitmap);

    // Get the device context for surface.
    _surface.GetDC(ref hdc);
    // Blt the image on to the surface.
    GDI.BitBlt(hdc, 0, 0, image.Width, image.Height, hdcImage, 0, 0, 
               TernaryRasterOperations.SRCCOPY);
    // Release surface hdc.
    _surface.ReleaseDC(hdc);
    // Delete the image hdc.
    GDI.DeleteDC(hdcImage);
}

Bliting

Bliting is short for bit block transfer. Bliting is basically copying some memory from one location to another. The term is used when referring to images that are combined in memory. When bliting, many libraries will use bitwise operators to combine images and perform effects like transparencies and overlays. In our example DirectDraw wrapper, we expose a color fill method and a DrawImage method that supports color keys.

Flipping

There can be some performance issues copying the backbuffer on each frame. Hardware page flipping removes this step. Instead of copying the backbuffer to the framebuffer, the backbuffer becomes framebuffer and the framebuffer becomes the backbuffer. The flip method tells the video adapter to use a different video memory address for the framebuffer. You can chain multiple backbuffers. In our example, we will just be using one backbuffer.

public void Flip()
{
    if (_supportsHardware)
    {
        _primarySurface._surface.Flip(IntPtr.Zero, FlipFlags.WAITNOTBUSY);
    }
    else
    {
        tagRECT dest = _screenArea;
        _primarySurface.Draw(ref dest, _backbuffer, ref dest, BltFlags.DDBLT_WAITNOTBUSY);
    }
}

Conclusions

In the emulator, I noticed DirectDraw is slightly slower than GDI. On a HTC TOUCH DUAL, DirectDraw is slightly slower than GDI only when we are not using hardware page flipping. With hardware page flipping enabled, it is 4x slower than GDI! This device does not have very good drivers (see the HTC class action) so I am going to wait to get a better hardware before drawing any conclusions.

Next time

After I get some better hardware, we will compare our wrapper against unmanaged DirectDraw, GDI, and some of the 3D APIs as well (if the phone has hardware support).

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here