microsoft / WPFDXInterop

Repo for WPF DX Interop support
MIT License
307 stars 98 forks source link

Screen flickering on lower end system. Where does SwapChain.Present happen? #27

Open malixg opened 7 years ago

malixg commented 7 years ago

Before I started using WPFDXInterop image I would end my rendering with a call to SwapChain.Present, but it seems like that's not needed with this, and if I do call it, my image flickers like crazy, even on my development system. I'm thinking maybe there's somewhere I should be locking on, but I've tried locking outside of the call to InteropImage.RequestRender, which is where I assume the swap happens, and it hasn't fixed the problem. Any input would be appreciated. Thank you.

andyb1979 commented 7 years ago

Hopefully this isn't the same issue as in the old D3DImage: D3DImage and SharpDX flickering on slow hardware. Would the contributors like to comment?

weltkante commented 7 years ago

As far as I remember the D3DImage/SharpDX issues you find on the net are due to improper locking. The SharpDX guys don't really like the way D3DImage is implemented (can't blame them) but unfortunately this means there is some confusion about this on the net because they insist the current behavior is a "bug" on the Microsoft side (which it isn't as far as I can tell).

As far as I know you are supposed to lock the D3DImage during the entire rendering. Quote from MSDN:

While the D3DImage is locked, your application can also render to the Direct3D surface assigned to the back buffer.

Yes this has a performance impact, but to avoid that Microsoft would have to port WPF to DirectX11 and implement something like a SwapChainPanel which currently is only available for UWP because you can't implement it properly on DirectX9 which WPF still runs on.

For now you are stuck with doing proper locking if you want to use D3DImage (doesn't matter if you use this library or not). One idea I have (but never tested) is that if you want to reduce the lock duration you could possibly trade off some memory by rendering to an offscreen surface and reduce the lock duration to copying that offscreen surface to the D3DImage surface. No idea if that would pay off performance wise but it would be something one could try.

[edit] I actually tried this idea during my last project and it turns out the additional surface copy costs more than the reduced locking wins; overall it was a performance loss for me. It may still pay off in complex scenes where the lock duration would be relatively long, but in my own projects it doesn't.

andyb1979 commented 7 years ago

Thanks for that, I meant to comment / try it earlier but had no chance to.

Our flicker problem came back with a vengeance recently when drawing some very large dynamic textures into the DirectX scene (above 4k * 4k size) so I came back to this project & thread. We are still using D3DImage (not D3D11Image) however I can report that locking the D3DImage for the entire duration of rendering has no effect on flicker which is still present.

We will try D3D11Image but I'm not holding my breath

weltkante commented 7 years ago

D3D11Image is based on D3DImage, it just lets you skip the interop code with D3D9

If you are locking correctly, do you also Flush? Didn't mention it explicitely before because it's kinda obvious that you need to ensure that D3D11 is done rendering before you pass the results to WPF, but you may not expect that if you are used to SwapChain rendering where the synchronization is part of the Present call.

You saying the behavior changes when rendering large textures sounds like D3D11 isn't done at the point when you pass data to D3D9/WPF; otherwise flickering can't possible depend on your scene, because at the time WPF grabs the image you should already be done rendering (if you aren't thats a bug on your side) and WPF only ever sees the results, it doesn't matter how you got there.

There's also the very real possibility of driver bugs in D3D11, which is much more common than it should be. So if you do lock and flush but still get artifacts you may want to look for other causes and check rendering into HWND, rendering on different hardware, and rendering using the WARP device.

andyb1979 commented 7 years ago

We actually use SwapChain.Present instead of flush (makes flicker less apparent as it is a blocking call). We did used to use Flush.

Actually, we found one way to eliminate flicker: Don't use D3DImage at all! Instead render to Texture and read back to main memory and write to WriteableBitmap. A terrible workaround but one that works nonetheless.

We are about to experiment with another workaround which is triple buffering the DX11 Texture we render to. E.g. having two alternate Render Targets which we swap and present on D3DImage when ready. I will let you know the results ...

On Sun, Jan 22, 2017 at 10:44 AM, Tobias Käs notifications@github.com wrote:

D3D11Image is based on D3DImage, it just lets you skip the interop code with D3D9

If you are locking correctly, do you also Flush? Didn't mention it explicitely before because it's kinda obvious that you need to ensure that D3D11 is done rendering before you pass the results to WPF.

You saying it happens with rendering large textures kinda sounds like D3D11 isn't done at the point when you pass data to D3D9/WPF; otherwise flickering can't possible depend on your scene, because at the time WPF grabs the image you are already done rendering and WPF only ever sees the results, it doesn't matter how you got there.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/Microsoft/WPFDXInterop/issues/27#issuecomment-274323195, or mute the thread https://github.com/notifications/unsubscribe-auth/ANZB1ZRM48mjEgD-RSbZZrgFM0K62gF2ks5rUzL5gaJpZM4JIU88 .

weltkante commented 7 years ago

Swap chains are a presentation method. D3DImage is a presentation method. You are basically rendering against two different presentation methods if you are doing that. If you get flickering doing this then you didn't get your synchronization right and WPF sees the image before the GPU is done rendering. You probably use a Present call which doesn't wait for the GPU, so when you unlock the texture and WPF copies the content the GPU isn't done yet.

Why are you using a swap chain in the first place? Do you know what you are doing or did you just experiment until something looked like it might work? You are not supposed to use a swap chain when working with D3DImage, you are supposed to allocate a texture as render target and share it with D3D9/WPF through D3DImage.

If you use the D3D11Image class from this repository (or its nuget version) then you can't do this particular mistake because D3D11Image will allocate the render target for you, just take whatever back buffer it hands you and render into it.

andyb1979 commented 7 years ago

I told you, we tried D3D11Device.Flush() and that didn't work. That's when we introduced a DX11 swapchain calling SwapChain.Present which greatly improved matters.

Anyway, we have a solution now, and that is to not use D3DImage at all. Rendering to texture and readback and download to WritableBitmap allows up to 60FPS rendering (even on a 4K screen) and no flicker whatsoever.

On Sun, 22 Jan 2017 at 19:46, Tobias Käs notifications@github.com wrote:

Swap chains are a presentation method. D3DImage is a presentation method. You are basically rendering against two different presentation methods if you are doing that. If you get flickering doing this then you didn't get your synchronization right and WPF sees the image before the GPU is done rendering. You probably use a Present call which doesn't wait for the GPU, so when you unlock the texture and WPF copies the content the GPU isn't done yet.

Why are you using a swap chain in the first place? Do you know what you are doing or did you just experiment until something looked like it might work? You are not supposed to use a swap chain when working with D3DImage, you are supposed to allocate a texture as render target and share it with D3D9/WPF through D3DImage.

If you use the D3D11Image class from this repository (or its nuget version) then you can't do anything wrong in the WPF interop because it is done for you, D3D11Image hands you the target texture. So just get rid of your swap chain thing and use whatever D3D11Image hands you as a back buffer.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/Microsoft/WPFDXInterop/issues/27#issuecomment-274353978, or mute the thread https://github.com/notifications/unsubscribe-auth/ANZB1TYLliqg0uXaCLn3LMuKTyEToSUDks5rU7IYgaJpZM4JIU88 .

weltkante commented 7 years ago

It appears you are just trying random things without understanding how it works or what is broken, but meh, do whatever you want, I was just trying to point out how to use D3DImage correctly. Since WPF is just copying the surface, flickering should be technically impossible except for driver bugs or you not using it correctly. By locking and marking D3DImage dirty you are explaining via API when it is safe to copy and what it should copy.

Your implementation with WriteableBitmap is conceptually doing the same as a (correctly used) D3DImage except that the texture is transferred through CPU instead of GPU, so it's not exactly a hack/workaround, it just has different trade-offs. If it works for you, great.

(Just for the record in case you weren't aware, I'm not related to Microsoft or this project, I was just trying to help out, having used D3DImage in several projects and knowing the problems it has and how it works.)

andyb1979 commented 7 years ago

Hi Tobias,

If we check out the Microsoft WPFInterop sample unchanged, compile it, and run it on a low end GPU, it flickers on resize.

I'm assuming the sample provided by MS doesn't "try random things rather than understand how it works" yet the problem is evident there too.

But I guess "meh" if you don't want to take the feedback of the community and would rather insult them, then go ahead!

Sun, 22 Jan 2017 at 20:15, Tobias Käs notifications@github.com wrote:

It appears you are just trying random things without understanding how it works or what is broken, but meh, do whatever you want, I was just trying to point out how to use D3DImage correctly. Since WPF is just copying the surface flickering should be technically impossible except for driver bugs or you not using it correctly.

Your implementation with WriteableBitmap is conceptually doing the same as a (correctly used) D3DImage except that the texture is transferred through CPU instead of GPU, so it's not exactly a hack/workaround, it just has different trade-offs. If it works for you, great.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/Microsoft/WPFDXInterop/issues/27#issuecomment-274355860, or mute the thread https://github.com/notifications/unsubscribe-auth/ANZB1WSvGhs0E-5eNJUMtp-TkHFUE1thks5rU7jrgaJpZM4JIU88 .

weltkante commented 7 years ago

it flickers on resize

This is the first time "resize" is mentioned, I was assuming you were talking about flickering during normal rendering. During resize the situation is more complicated since WPF is rendering asynchronously (on a different thread). In my own projects I tend to ignore resizing issues (because WPF in general lags behind the resize), so I can't help out here with practical experience.

If we check out the Microsoft WPFInterop sample unchanged, compile it, and run it on a low end GPU, it flickers on resize.

Thats a valid complaint and reason for an issue in this repository and someone should look at it. May be worth making a separate issue out of this so it isn't buried 10 posts deep in this issue, but your call.

But I guess "meh" if you don't want to take the feedback of the community

As I edited above I'm neither the owner of the project nor related to them, you apparently did take my comments wrong. Your answers are looking to me like you don't know what you do and don't care about understanding why your code is not working. If you don't need or want the help I can provide that's ok. Explaining how D3DImage works and is supposed to be used is the only thing I can contribute here.

malixg commented 7 years ago

All the locking and optimization in the world hasn't remedied the flickering for me, but if you're saying that even the sample flickers - well, I don't think there's anything I can do then.

Anyway, we have a solution now, and that is to not use D3DImage at all. Rendering to texture and readback and download to WritableBitmap allows up to 60FPS rendering (even on a 4K screen) and no flicker whatsoever.

I think I'll try this but doesn't this put a lot of load on the CPU? Do you have any code snippets for what you're doing?

andyb1979 commented 4 years ago

HI malixg, unfortunately not, this is commercial software so I can't share a code snippet. Save to say that the method of reading back pixels from GPU and download to WriteableBitmap is reliable and solves problems in 99.9% of cases. Yes it ads some overhead, particularly on lower end GPU hardware but on modern hardware the slowdown is unnoticeable.

georg-eckert-zeiss commented 2 years ago

For everyone stumbling over this. It seems that D3D11Device::Flush is asynchronous. Therefore even doing the locking right, won't help, since when D3DImage::Unlock is called, the back buffer content is copied over to the front buffer, even when rendering has not finished yet, resulting in black frames.

The solution I found was using DeviceContext::ResolveSubresource to copy the contents from the render target to the back buffer, since it waits for D3D11Device::Flush to finish. This of course has other drawbacks like being GPU bound.

It helps getting rid of the occasional flicker, but not of the resizing flickers.

Please correct me if I'm wrong somewhere.

Best regards, Georg

davideberly commented 1 year ago

My experiences are the same with flickering during window resizing. In the D3D11 back end, I render to a render target that shares the surface with D3D9. The D3D11Device::Flush call is non-blocking in the main thread where the D3D11 rendering occurs, but WPF has a worker thread in which it does its "swap buffers" and cannot know whether the GPU has finished drawing the D3D11 stuff (verified this with the Concurrency Visualizer extension to Visual Studio). I replaced the immediate-mode context's Flush() call with a D3D11 query that waits until the GPU is finished. This is a blocking call, but for my applications I do not have any noticeable performance hit. My test sample is a 2D fluid dynamic simulation that runs at 60 FPS in my C++/DX11 native environment. It runs at the same rate in a WPF window. After this modification, I do not see flickering.