🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Rendering from DX11 on WinForms control

Started by
3 comments, last by Steven Ford 3 years, 5 months ago

Hi guys,

I was wondering if someone could put me out of my misery please? I've got a custom, tile based, WinForms level designer which uses a custom user control to render the various tile layers / game objects. To do so, it currently uses GDI, mainly calls to

  • DrawImage( .. )
  • DrawLine( .. )

As my levels are getting bigger, the profiler's telling me that the bottleneck of the code is the GDI rendering of the levels. Given that my game itself is in C++ and uses DX11 and happily renders comfortably in a few ms my thought process is to create a C++ component and expose it via COM which is initialized with the image data from the managed layer and then, on rendering, I just send the tile information to it and it renders to a texture. That texture could then be rendered directly to my control and any additional GDI functionality (highlighting selected tiles etc.) could be also performed.

Looking on the web, it's not that obvious how to do the actual rendering portion to a control from DX11. Note that don't want to render to a whole window, merely to a control.

My ‘this is technically feasible but really ugly’ approach if all else fails would be to simply pass in a giant byte array to my C++ renderer component and have that copy the contents of the buffer from the GPU and then to that array, and subsequently have my c# layer use that array to construct an image for rendering with GDI. But this sounds completely crazy and inefficient.

If needs be then I could use the managed DX 9 DLLs but sticking to DX11 if possible would reduce the number of technologies that I'm using / have to remember.

Any suggestions gratefully received.

Regards

Steve

Advertisement

It should be simple to set a WinForm or Control as render target for your DX11 context thought I didn't do that before. I'm more from the OGL/ Vulkan faction ?. But I guess DX11 also allows to create a render context based on a window handle right?

Every C# WinForms Control, which also the Form inherits from, has a Control.Handle Property which is a usual marshalled to an HWND window pointer. From there, you could create a render context in your C++ code and should be able to render directly to the Form/Control. However, you should inherit a new Control and override some of it's styles to it isn't rendered by GDI anymore (also enabling DoubleBuffer will really help). And you should implement a small message loop to it so it will be frequently updated.

Btw. I wouldn't go for COM but expose some interface methods that could be used from C# p/invoke without it. DllImport is your friend and with CriticalFinalizerObject you can allocate memory in your engine heap and still make sure to free it when GC collects the managed object which is holding a pointer to that memory in C#. I did the same (wrote a code generator in the end that works on some special comments in my C++ source that act like C# attributes) and it worked quite well, also on marshalling data from the engine heap into managed C# and back. As an alternative, you can also pin a managed byte array in the GC and marshal it as pointer into your C++ code but in my opinion it is more efficient if every part of the solution manages it's own memory.

As an alternative to use your engine to render everything, you can get a massive speed improvement by maintaining your own render buffer in C#. You start with a class which manages the data in memory.

GCHandle handle = GCHandle.Alloc(new byte[length], GCHandleType.Pinned);
pixelData = Marshal.UnsafeAddrOfPinnedArrayElement(handle.Target as Array, 0);
memoryHandle = GCHandle.ToIntPtr(handle);

This creates an array of byte in a static memory location and pushes the pointer to the first element into a managed IntPtr struct. I also create a second pointer to the pinned type in order to be able to view and manipulate the array as a managed C# object.

public byte[] ToArray()
{
    return GCHandle.FromIntPtr(memoryHandle).Target as byte[];
}

In my render buffer class I then use the pixel data to feed it into a Bitmap object (it is important to use PixelFormat.Format32bppPArgb because GDI renders faster when the pixel data already match the GDI pixel format)

public bool Resize(Size size)
{
    int stride = size.Width * 4;
    int padding = (stride % 4);
    if (padding != 0)
        padding = 4 - padding;

    stride += padding;
    int length = stride * size.Height;

    return Resize(size, length, stride);
}
public bool Resize(Size size, int length, int stride)
{
    if (pbuffer.Length != length)
    {
        if (renderTarget != null)
            renderTarget.Dispose();
        if (graphics != null)
            graphics.Dispose();

        if (pbuffer.Length < length)
            pbuffer.Resize(length);

        renderTarget = new Bitmap(size.Width, size.Height, stride, System.Drawing.Imaging.PixelFormat.Format32bppPArgb, pbuffer);
        return true;
    }
    else return false;
}

Now you have a Bitmap which points to the pixel data in memory. What you now can do is to manipulate those data either from hand using bit operations or a Graphics object created from the Bitmap. But you'll ask why not just create a plain Bitmap and that's it, I guess. Simply because you don't have to recreate the pixel data array in memory for as long as your Bitmap partial or entirely covers it. This means that you can offscreen render into areas of your pixel data without the need to reallocate something. So it is quiet easy to use the Graphics object which from the Bitmap to update certain region in your trendered tile data which will tear down the time you spend on render everything while the rest of the buffered data is still intact. Another benefit is that you can update your buffer in multiple threads without issues, for as long as each thread has it's own Bitmap instance pointing to the same pixel data in memory.

Everything you then have to do is overriding the paint event in your Control and instead draw the area of your pixel data to it.

protected virtual void OnPaint (System.Windows.Forms.PaintEventArgs e)
{
    e.Graphics.DrawImageUnscaled(buffer.RenderTarget, 0, 0);
}

I use this technique and some other C# sugar like Reactive in my custom WinForms UI Framework in order to render fully customizeable windows and UI components

Shaarigan said:
But I guess DX11 also allows to create a render context based on a window handle right?

yes ?

@shaarigan 's advice here is sound and clear;

@Steven Ford, so yes look into:

  • Option1: Control.Handle & Dllimport
  • Option2: Shaarigan's advice on use Bitmap( ) as a render target

If you're funky with it and have time, you could try both options for experience, but don't get stuck in it too long if one option works for you and just go along with it;

If memory serves well, i think Mike wrote a book called “Game Coding Complete, Fourth Edition Paperback” and in there he uses the Dllimport method… if not then it's a good book to have anyway -lol-

have fun ?

Thanks both; I'll see where I get with the purely managed option for now and then see whether I need to go beyond that

This topic is closed to new replies.

Advertisement