0
\$\begingroup\$

I have an XNA app, but I really need to add multiple render windows, which XNA doesn't do. I'm looking at SharpDX (both for multi-window support and for DX11 / Metro / many other reasons). I decided to hack up the SharpDX DX11 MultiCubeTexture sample to see if I could make it work.

My changes are pretty trivial. The original sample had:

[STAThread]
private static void Main()
{
    var form = new RenderForm("SharpDX - MiniCubeTexture Direct3D11 Sample");
    ...

I changed this to:

        struct RenderFormWithActions
        {
            internal readonly RenderForm Form;
            // should just be Action but it's not in System namespace?!
            internal readonly Action RenderAction;
            internal readonly Action DisposeAction;
            internal RenderFormWithActions(RenderForm form, Action renderAction, Action disposeAction)
            {
                Form = form;
                RenderAction = renderAction;
                DisposeAction = disposeAction;
            }
        }

        [STAThread]
        private static void Main()
        {
            // hackity hack
            new Thread(new ThreadStart(() =>
            {
                RenderFormWithActions form1 = CreateRenderForm();
                RenderLoop.Run(form1.Form, () => form1.RenderAction(0));
                form1.DisposeAction(0);
            })).Start();
            new Thread(new ThreadStart(() =>
            {
                RenderFormWithActions form2 = CreateRenderForm();
                RenderLoop.Run(form2.Form, () => form2.RenderAction(0));
                form2.DisposeAction(0);
            })).Start();
        }

        private static RenderFormWithActions CreateRenderForm()
        {
            var form = new RenderForm("SharpDX - MiniCubeTexture Direct3D11 Sample");
            ...

Basically, I split out all the Main() code into a separate method which creates a RenderForm and two delegates (a render delegate, and a dispose delegate), and bundles them all together into a struct. I call this method twice, each time from a separate, new thread. Then I just have one RenderLoop on each new thread.

I was thinking this wouldn't work because of the [STAThread] declaration -- I thought I would need to create the RenderForm on the main (STA) thread, and run only a single RenderLoop on that thread. Fortunately, it seems I was wrong. This works quite well -- if you drag one of the forms around, it stops rendering while being dragged, but starts again when you drop it; and the other form keeps chugging away.

My questions are pretty basic:

  1. Is this a reasonable approach, or is there some lurking threading issue that might make trouble?
  2. My code simply duplicates all the setup code -- it makes a duplicate SwapChain, Device, Texture2D, vertex buffer, everything. I don't have a problem with this level of duplication -- my app is not intensive enough to suffer resource issues -- but nonetheless, is there a better practice? Is there any good reference for which DirectX structures can safely be shared, and which can't?
  3. It appears that RenderLoop.Run calls the render delegate in a tight loop. Is there any standard way to limit the frame rate of RenderLoop.Run, if you don't want a 400FPS app eating 100% of your CPU? Should I just Thread.Sleep(30) in the render delegate?

(I asked on the sharpdx.org forums as well, but Alexandre is on vacation for two weeks, and my sister wants me to do a performance with my app at her wedding in three and a half weeks, so I'm mighty incented here! http://robjsoftware.org for details of what I'm building....)

\$\endgroup\$
1
  • \$\begingroup\$ Well using winforms sample for XNA you can render to multiple forms. \$\endgroup\$
    – Kikaimaru
    Commented Nov 23, 2012 at 9:13

2 Answers 2

3
\$\begingroup\$

Okay, first of all, I would like you to consider forfeiting the notion of "best practice". There are no such things as "best practices". Some things work, some don't, and none do under all assumptions and scenarios, therefore being "best". Every time you use the words "best practice", God kills a kitten, so please think of the kittens.

Now, onto your problem. You certainly have given it a lot of thought, and most of your assumptions are correct. Let's start:

  • A Direct3D device can't render to multiple windows at the same time. In general, you will only have one window per Direct3D device. However, as you already know, you can certainly have multiple Direct3D devices per process, and you certainly have one per thread, which leads us to.

  • You can have multiple Direct3D devices in the same thread! Particularly, if my program wasn't very CPU intensive, I would create two Direct3D devices in the same thread, and render serially. For each iteration of your render loop, do everything you need to do for each Direct3D device. If, as you say, you can have 400+ fps, then you shouldn't have problem with two devices in the same thread. Keep everything in the same thread, and you won't have to care about synchronization at all.

  • DirectX is generally not thread-safe. This is different from the thread-hostility of Windows Forms, in which objects are owned by a thread and you can't generally do anything with them from a different thread (apartment). DirectX (9, which is what I mostly know) doesn't really care which thread you call functions for, as long as you don't call two API functions at the same time from different threads. You can opt in/out of thread safety when you create your device, but in general, as long as you synchronize access to your device, you can certainly do things from multiple threads. This doesn't mean you should though.

  • Windows Forms is not thread-friendly, so you certainly have to have all your windows in the same thread.

  • If you must separate the logic and Direct3D devices into different threads, then I would have three threads: one for the COM stuff (windows forms), and one for each device. You must then use some kind of synchronization to pass messages or data between threads, so make sure you keep it as simple as possible, because that is likely to become one of the most complex parts of your program.

  • There's nothing wrong with duplicating data for all devices. Video cards have humongous memory these days, and if you're reaching the limit with one device, there are other things you should worry about before adding a second device. However, you can share data between devices, and in fact, you can share data across processes, and even across different versions of Direct3D. That's a bit out of the scope of this question, but I have a more detailed answer, so check that one out as well.

In general, do what's best for your assumptions. I'd recommend you do what's easiest for you, and don't try to hunt for holy grails or "best practices". Do what's best for you, and go with that!

\$\endgroup\$
2
  • 1
    \$\begingroup\$ And I feel like an idiot for answering a question almost a year old... \$\endgroup\$ Commented Aug 20, 2013 at 13:28
  • \$\begingroup\$ Five years later, I'm very glad for the answer that came a year after the question. Answering old questions may even be a best practice (couldn't resist). \$\endgroup\$ Commented May 4, 2018 at 19:24
0
\$\begingroup\$

You can't do the rendering of multiple viewport in separated threads, D3D doesn't work this way, it's not thread safe (unless you use the deferred rendering feature of D3D11).

In multiple viewport case you have to adopt a structure that looks like this:

  • A RenderingDevice class that takes care of all the D3DRendering device setup.
  • A Viewport class that takes care of the Viewport related initialization (use RenderingDevice, create the SwapChain, rendering target, etc.). In case of 3D, the Viewport is attached to a Camera which is in a scene that can be shared among multiple Viewport.
  • A Scene class, which contains all the 3D resources (Mesh, Texture, Camera, etc.)

Concerning the rendering loop, there's no a "best way" of doing this, in Winform, you can:

1) Use CompositionTarget.Rendering += OnRendering; And call the rendering loop of your viewports sequentially. This is ok if your scene render fast, if it takes more time than the frequency of CompositionTarget.Rendering you'll got some stuttering and bad interaction issues.

2) Use Application.Idle += ApplicationOnIdle; with a timer like : _ticker = new Timer(); with an interval base on the frequency of rendering you aim (_ticker.Interval = 1000/100; for 100hz). The timer only set a bool "DoRender = true" and the ApplicationOnIdle checks this bool for true to perform the render. This way works best if you rendering loop take a lots of time, but the fluidity of the overall animations/interaction my not be the best as OnIdle may not be called as much as it would be necessary.

3) Hooking the Windows Proc of you Winform control like Alexandre did in his sample. I can't comment this as I don't know it much, but I know this may not be the easiest way in your case.

By the way, use RenderControl and not RenderForm in case of multiple viewport scenario.

Try 1) it's a quick test, then 2) and see what works best for you.

\$\endgroup\$
2
  • \$\begingroup\$ Even with D3D11's multithreaded rendering, you're only building command lists on multiple threads, the actual present of all outputs is done by one thread in serial. \$\endgroup\$
    – user1430
    Commented May 22, 2013 at 16:08
  • \$\begingroup\$ You can have separate devices on separate threads, as long as you don't call stuff from one device in a separate thread without synchronization. I'd say the OP's approach is quire okay. \$\endgroup\$ Commented Aug 20, 2013 at 12:37

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .