UAC prompt [SlimDX]

Started by
10 comments, last by Promit 14 years, 10 months ago
Hi, I hope you can help me, my app is a CAD system programmed in C# using DirectX 9.0 (SlimDX). At the moment I use a render loop like a game during the application idle event. Two problems: on some machines when the user selects a menu command and the menu opens over the DirectX WinForm, the edges of the menu flicker with the render loop, it looks really bad. Second problem is that the users just leave my app running all day and the PC can get quite hot (I do throttle back when in the background). I want to get rid of the render loop, I only really need to render when they are drawing. In fact that was my original approach, but I have a problem with Vista, in that if the user causes the UAC prompt to be shown, when it closes my app is left with a black screen. I render in the paint event but Windows is not firing this after the UAC prompt, therefore the Form stays black, they need to do something that starts the render loop to refresh the view. So I really what to address the UAC prompt issue but am struggling to find a way to refresh the view after. If any one has any ideas I would be so grateful Thanks Julian
Advertisement
I suspect these problems are related to how you handle your rendering. Drawing in the paint event is generally a bad idea and could cause flicker and other undesirable render artifacts (Windows Forms will also try to paint, unless you tell it all drawing is to be handled in WM_PAINT, and even then this is not a great approach). Can you show us your render loop?

The UAC issue might be related, or it might be because of a failure on your part to handle a lost device (I don't know if switching to the secure desktop causes the device to be lost, I've never actually tried it).
Quote:Original post by jpetrie
I suspect these problems are related to how you handle your rendering. Drawing in the paint event is generally a bad idea and could cause flicker and other undesirable render artifacts (Windows Forms will also try to paint, unless you tell it all drawing is to be handled in WM_PAINT, and even then this is not a great approach). Can you show us your render loop?

The UAC issue might be related, or it might be because of a failure on your part to handle a lost device (I don't know if switching to the secure desktop causes the device to be lost, I've never actually tried it).


jpetrie, I would love to know why painting in WM_PAINT or OnPaint is a bad thing. We are writing a similar program. Some of our painting is done in the viewport's OnPaint() method, which means our application only redraws the scene when the viewport is invalidated. Unfortunately, it does not work under Aero w/Vista - we actually disable Aero when our app begins (much like 3D Studio Max 2009).

Talyrond, I don't have an answer for you but I wanted to suggest some changes that might help your rendering. We're using SlimDX and D3D9. We used to use the idle loop as well, but as you note, performance on the machine is effected even if the user is doing nothing. We changed our system to respond to OnPaint() requests in our Viewport control, which is setup to handle all drawing itself. Different actions can invalidate the viewport either immediately, or 'whenever you have time'. Furthermore, when we trap all the mouse commands for manipulation of the scene, we enter into a special operational mode - at the begining of OnMouseMove, for example, we clear a global dirty flag (in a singleton), and begin our mouse operations. If any of the operations decide they need to invalidate the viewport, they call the InvalidateViewport( bool redrawNow ) or InvalidateAllViewporets( bool redrawNow ). We cache the invalidation request instead of honouring it until the very end of OnMouseMove() - should the underlying logic have requested an immediate redraw, we simply call the viewport's Invalidate() and Update() method, which perform a synchronous redraw. If the underlying logic requested a simple invalidation, we just call Invalidate() and move on. We tie this into mouse move, button down, button up, wheel scroll, etc. The underlying code never knows if InvalidateViewport() is going to be queued or happen immediately, so its great.

This has the benefit of letting the underlying logic request full immediate refreshes as many times as it wants without it having to know that they are being queued up. It also makes all the drawing respond to either true windows-notified dirty window drawing or invalidation during user interaction through mouse moves. I puzzled through this for quite awhile with a lot of iterations before this simple solution hit me.

Now all I need to do is get it working with Aero.

Best of luck,

S.
Thanks for the reply,

I render by calling the invalidate method (from the app idle event) of my Direct3DControl that I have created.
I inherit from UserControl and override the OnPaint Method:
   protected override void OnPaint(PaintEventArgs e)        {            if (!this.DesignMode && Device != null && !Device.Disposed)            {                Result result;                if (this.DeviceIsLost)                {                    Thread.Sleep(50);                    result = Device.TestCooperativeLevel();                    if (result == ResultCode.DeviceLost)                    {                        return;                    }                    if (result == ResultCode.DeviceNotReset)                    {                        result = ResetDevice();                        if (result.IsFailure)                        {                            return;                        }                    }                    this.DeviceIsLost = false;                }                try                {                    Device.SetRenderTarget(0, this.BackBuffer);                    Device.DepthStencilSurface = this.DepthStencilBuffer;                    Device.Clear(ClearFlags.ZBuffer, Color.White, 1.0f, 0);                    Device.BeginScene();                    base.OnPaint(e);                    Device.EndScene();                    result = this.SwapChain.Present(Present.None);                    if (result == ResultCode.DeviceLost)                    {                        this.DeviceIsLost = true;                    }                }                catch (Direct3D9Exception)                {                    this.DeviceIsLost = true;                }            }            else if (this.DesignMode && Device != null && !Device.Disposed)            {                Device.SetRenderTarget(0, this.BackBuffer);                Device.DepthStencilSurface = this.DepthStencilBuffer;                Device.Clear(ClearFlags.Target, Color.White, 1.0f, 0);                this.SwapChain.Present(Present.None);            }        }


So I hook the paint event and draw my object in the hooked method.

Yes I can confirm that switching to the secure desktop does cause a lost device, but I have no problem resetting as far as I am aware. The device is only lost when the app becomes active though.

I am also interested in the reason for not using the paint event. Your advice would be appreciated.

I think I will take a look at the samples in the SDK, I am sure your approach is different.

Sphet, thanks for the info, I will need to read your post a few times to get my head round it!

Cheers

Julian
Rendering during paint is only bad idea because You're Not Supposed To(tm). You can certainly do it, but it's a broken methodology for games and even for non-game applications like what you guys seem to be working on, the hoops you need to jump through are annoying. Mostly though, you have to be a bit careful with what you do in the paint event and how you set up the control or you will allow Windows Forms to paint, which can cause flicker as its painting conflicts with the D3D rendering.

Another problem, which may not be an issue for your type of app, is that WM_PAINT is low-priority and so will deferred in favor of other things like mouse motion or... well, pretty much any other event. This can cause stutter in your rendering, as the event is delayed, since Invalidate() just indirectly queues up a paint event.

Is the cost of rendering continually, as you would in the canonical PeekMessage/ApplicationIdle loop used for D3D rendering in managed code, so high for your applications that you have a justification in all that additional code complexity? I've never worked on CAD software, so maybe it is, but.... The traditional approach is so much simpler (it sounds like you were originally using this half-way -- you had Invalidate() called from inside the idle handler... you shouldn't need to do that, you should just be able to render directly in there, let me know if you want an example of what I mean).

Anyway.

What's with the ~50ms sleep during the paint? That feels hackish.

Are you setting the appropriate control styles on your control? You probably want UserPaint and AllPaintingInWmPaint.

I don't know what you can do about the UAC issue though. Sort of by design, you don't get a lot of notice that the secure desktop switch happened. I guess you could get it indirectly via the device lost notification, but only if you are continually polling for device lost since SlimDX does not support events like MDX did (and for very good reasons). Once you got the device lost notification you could then Invalidate() at least once, which would cause a repaint.
Quote:Original post by jpetrie
Rendering during paint is only bad idea because You're Not Supposed To(tm). You can certainly do it, but it's a broken methodology for games and even for non-game applications like what you guys seem to be working on, the hoops you need to jump through are annoying. Mostly though, you have to be a bit careful with what you do in the paint event and how you set up the control or you will allow Windows Forms to paint, which can cause flicker as its painting conflicts with the D3D rendering.


Yes, you have to disable all the built-in form rendering, including overriding OnPaintBackground(). Make sure you set:

// in constructorSetstyle(Controlstyles.AllPaintingInWmPaint, true);Setstyle(Controlstyles.UserPaint, true);/// override OnPaintBackgroundprotected override void OnPaintBackground(PaintEventArgs e){   // ... do nothing here to avoid flickering.}


Quote:Original post by jpetrie
Another problem, which may not be an issue for your type of app, is that WM_PAINT is low-priority and so will deferred in favor of other things like mouse motion or... well, pretty much any other event. This can cause stutter in your rendering, as the event is delayed, since Invalidate() just indirectly queues up a paint event.


Yes, this IS a problem, which is why we trap the invalidation code in our mouse handlers and force the refresh at the end of OnMouseDown, etc. It may seem like a hack, but I think it works quite cleanly.

Quote:Original post by jpetrie
Is the cost of rendering continually, as you would in the canonical PeekMessage/ApplicationIdle loop used for D3D rendering in managed code, so high for your applications that you have a justification in all that additional code complexity? I've never worked on CAD software, so maybe it is, but.... The traditional approach is so much simpler (it sounds like you were originally using this half-way -- you had Invalidate() called from inside the idle handler... you shouldn't need to do that, you should just be able to render directly in there, let me know if you want an example of what I mean).


Our editor renders ~10.000.000 primitives when all the level art is visible. The artists tend to also have 3D Studio Max or Maya open, as well as our game running. If we run our editor in a fixed peek/app loop we cause the game to stutter and Max to run slowly. By respecting the paint notifications, our application takes 0% of the CPU when max is being used, or the game is being run.

Quote:Original post by jpetrie
Anyway.

What's with the ~50ms sleep during the paint? That feels hackish.



When the device is lost, you have to wait to restore it - but I think it should be a loop, not just a wait.

Quote:Original post by jpetrie
I don't know what you can do about the UAC issue though. Sort of by design, you don't get a lot of notice that the secure desktop switch happened. I guess you could get it indirectly via the device lost notification, but only if you are continually polling for device lost since SlimDX does not support events like MDX did (and for very good reasons). Once you got the device lost notification you could then Invalidate() at least once, which would cause a repaint.


Also with you on that.

S
The reason I used the paint event is that before the UAC problem, I only rendered when necessary, so the paint event took care of updating the window when it was uncovered etc.

So really you are suggesting to call the render method directly from the idle event? A small example just to clarify would be great. Of course I guess that I could have a problem with the smoothness of the animation when moving the mouse as the idle event will halt then.

The 50ms sleep was from a Drunken Hyena example, the description was “Can't Reset yet, wait for a bit”.

At the moment I set the control styles:

this.Setstyle(Controlstyles.Opaque, true);

The UAC prompt only causes the device lost when the app regains the focus, so that is too late, as after the prompt the app does not have the focus. The constant render loop does sort that out…

Just had a quick look at the sample frame work and the paint event is hooked as well as rendering directly in the idle event, why is that?

Must go, getting really tired now!

Thanks for your help

Julian
Quote:
When the device is lost, you have to wait to restore it - but I think it should be a loop, not just a wait.

Quote:
The 50ms sleep was from a Drunken Hyena example, the description was “Can't Reset yet, wait for a bit”.

Hur, I'm dumb, I completely spaced on the guard for that sleep, nevermind.

Quote:
Our editor renders ~10.000.000 primitives when all the level art is visible. The artists tend to also have 3D Studio Max or Maya open, as well as our game running. If we run our editor in a fixed peek/app loop we cause the game to stutter and Max to run slowly. By respecting the paint notifications, our application takes 0% of the CPU when max is being used, or the game is being run.

I suppose that's valid. I can't help but think there's still a way you could do this with the traditional loop, but it would probably be just as involved as what you're doing now, and really that's my primary aversion to hooking paint -- the extra code.

Quote:
Just had a quick look at the sample frame work and the paint event is hooked as well as rendering directly in the idle event, why is that?

No idea, but it's not necessary. The framework should not be construed as a best-practices type thing -- it's getting completely rewritten right now, but even then, it's supposed to be a framework for quickly building samples that can clearly illustrate how to use SlimDX. That goal doesn't always align with doing things correctly for a production application, although in this case it's probably similar. Mike wrote the paint hooks, I'll ask him why he bothered.

EDIT: Mike says this is only to handle the case when the regular render loop is paused and the window gets moved; this repaints and prevents artifacts.

Quote:
this.Setstyle(Controlstyles.Opaque, true);

I would set the others, as described above.
I'd like to point out that just because you're not running a render loop doesn't mean you can't wake up every so often on a timer trigger (50ms, 100ms, whatever makes you happy) and either update the view or just check that the device is still available for use. For added cleverness, you can reset the timer every time a render happens, so that you don't run it more than necessary. Just make sure to get the threading right.
SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.
Thanks for all the advice guys.

I tried rendering directly in the idle event and the menu’s still flicker.

So I only render when I need to now, and introduced a time like Promit suggested to render periodically to ensure the UAC black screen is cleared

Cheers

Julian

This topic is closed to new replies.

Advertisement