Search code examples
c#sharpdxdirect2d

Unable to draw bitmaps on top of other bitmap using Direct2D


I want to write a Direct2D application that draws many objects on a background image using SharpDX. But after drawing a certain number of objects, Direct2D ignores all further objects. However only if the at least 1 object is overlapping with the background image.

This is a simplified version of my rending function:

private void Draw()
{
    var zoom = 0.6f;
    var destRect = new RectangleF(0, 0, _windowWidth, _windowHeight);
    RenderTarget2D.DrawBitmap(_backgroundBitmap, destRect, 1.0f, BitmapInterpolationMode.Linear);

    for (var j = 0; j < 10; j++)
    {
        for (var i = 0; i < 100; i++)
        {
            var d = (j % 10);
            destRect = new RectangleF(i * 22 * zoom, j* 40 * zoom, 22 * zoom, 40 * zoom);
            var srcRect = new RectangleF(d * 22, 40, 22, 40);
            RenderTarget2D.DrawBitmap(_digits, destRect, 1.0f, BitmapInterpolationMode.Linear, srcRect);
        }
    }
}

The result is

Row #9 is only partly drawn and row #10 is not drawn at all.

However when there is no background image or no foreground object is touching the background image then all 1000 objects are correctly rendered.

Result without background image

Even more confusing is that when I reduce the number of rows to 3 then row #3 is no longer drawing all its objects which did not happen when drawing more than 3 rows.

Result with 3 rows

What's wrong with my application?

Below is the rest of the source code (in case it's relevant) ...

private void Render()
{
    RenderTarget2D.BeginDraw();
    _deviceContext.Rasterizer.SetViewport(new Viewport(0, 0, _windowWidth, _windowHeight));
    _deviceContext.OutputMerger.SetTargets(_backBufferView);
    Draw();
    _swapChain.Present(0, PresentFlags.None);
    RenderTarget2D.EndDraw();
}

// Initialize DX objects
public void InitDX()
{
    // SwapChain description
    var desc = new SwapChainDescription()
    {
        BufferCount = 1,
        ModeDescription = new ModeDescription(_windowWidth, _windowHeight, new Rational(60, 1), Format.R8G8B8A8_UNorm),
        IsWindowed = true,
        OutputHandle = DisplayHandle,
        SampleDescription = new SampleDescription(1, 0),
        SwapEffect = SwapEffect.Discard,
        Usage = Usage.RenderTargetOutput
    };

    // Create Device and SwapChain
    Factory2D = new SharpDX.Direct2D1.Factory();
    SharpDX.Direct3D11.Device.CreateWithSwapChain(DriverType.Hardware, DeviceCreationFlags.BgraSupport, new[] { SharpDX.Direct3D.FeatureLevel.Level_10_0 }, desc, out _device, out _swapChain);
    _deviceContext = _device.ImmediateContext;

    // Ignore all windows events
    SharpDX.DXGI.Factory factory = _swapChain.GetParent<SharpDX.DXGI.Factory>();
    factory.MakeWindowAssociation(DisplayHandle, WindowAssociationFlags.IgnoreAll);

    ResizeSwapChain(_windowWidth, _windowHeight);
}

// Resizes the swap-chain
public void ResizeSwapChain(int width, int height)
{
    RenderTarget2D?.Dispose();
    _backBuffer?.Dispose();
    _backBufferView?.Dispose();
    _surface?.Dispose();

    _swapChain.ResizeBuffers(1, width, height, Format.Unknown, SwapChainFlags.AllowModeSwitch);

    _backBuffer = Resource.FromSwapChain<Texture2D>(_swapChain, 0);
    _backBufferView = new RenderTargetView(_device, _backBuffer);
    _surface = _backBuffer.QueryInterface<Surface>();
    var dpi = Factory2D.DesktopDpi;
    RenderTarget2D = new RenderTarget(Factory2D, _surface, new RenderTargetProperties(RenderTargetType.Hardware, new PixelFormat(Format.Unknown, AlphaMode.Premultiplied), dpi.Width, dpi.Height, RenderTargetUsage.None, SharpDX.Direct2D1.FeatureLevel.Level_DEFAULT));
}

// Loads a Direct2D Bitmap from a file
public static Bitmap LoadBitmapFromFile(string file)
{
    var winBitmap = (System.Drawing.Bitmap)System.Drawing.Image.FromFile(file);

    var dxBitmap = CreateDirect2DBitmap(winBitmap, true);
    dxBitmap.Tag = file;
    winBitmap.Dispose();
    return dxBitmap;
}

// Creates a Direct2D from a System.Drawing.Bitmap
public static Bitmap CreateDirect2DBitmap(System.Drawing.Bitmap src, bool swapChannels)
{
    var sourceArea = new System.Drawing.Rectangle(0, 0, src.Width, src.Height);
    var bitmapProperties = new BitmapProperties(new PixelFormat(Format.R8G8B8A8_UNorm, AlphaMode.Premultiplied));
    var size = new Size2(src.Width, src.Height);

    // Transform pixels from BGRA to RGBA
    int stride = src.Width * sizeof(int);
    using (var tempStream = new DataStream(src.Height * stride, true, true))
    {
        // Lock System.Drawing.Bitmap
        var bitmapData = src.LockBits(sourceArea, ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppPArgb);

        // Convert all pixels
        if (swapChannels)
        {
            for (int y = 0; y < src.Height; y++)
            {
                int offset = bitmapData.Stride * y;
                for (int x = 0; x < src.Width; x++)
                {
                    // Not optimized 
                    byte b = Marshal.ReadByte(bitmapData.Scan0, offset++);
                    byte g = Marshal.ReadByte(bitmapData.Scan0, offset++);
                    byte r = Marshal.ReadByte(bitmapData.Scan0, offset++);
                    byte a = Marshal.ReadByte(bitmapData.Scan0, offset++);
                    int rgba = r | (g << 8) | (b << 16) | (a << 24);
                    tempStream.Write(rgba);
                }
            }
        }

        Bitmap result;
        if (swapChannels)
        {
            tempStream.Position = 0;
            result = new Bitmap(RenderTarget2D, size, tempStream, stride, bitmapProperties);
        }
        else
        {
            result = new Bitmap(RenderTarget2D, size, new DataStream(bitmapData.Scan0, stride * src.Height, true, false), stride, bitmapProperties);
        }
        src.UnlockBits(bitmapData);

        return result;
    }
}

Solution

  • I think the problem is that you present before you finish drawing. You should reorder those calls:

    RenderTarget2D.EndDraw();
    _swapChain.Present(0, PresentFlags.None);