none
WPF下如何开启垂直同步 RRS feed

  • 问题

  • 我在使用WPF开发应用程序界面,因为一些原因,不能开启桌面的Areo效果,但这样会造成界面的画面撕裂。既然WPF是封装了DirectX,那么如何开启垂直同步,来解决画面撕裂的问题?
    2017年6月30日 1:18

全部回复

  • 界面界面显示的时候在WPF下模拟WinForm的DoEvent函数,并调用。代码如下

            [SecurityPermissionAttribute(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)]
            public void DoEvents()
            {
                DispatcherFrame frame = new DispatcherFrame();
                Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
                    new DispatcherOperationCallback(ExitFrame), frame);
                Dispatcher.PushFrame(frame);
            }
    
            public object ExitFrame(object f)
            {
                ((DispatcherFrame)f).Continue = false;
    
                return null;
            }

    2017年6月30日 1:41
  • 界面界面显示的时候在WPF下模拟WinForm的DoEvent函数,并调用。代码如下

            [SecurityPermissionAttribute(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)]
            public void DoEvents()
            {
                DispatcherFrame frame = new DispatcherFrame();
                Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
                    new DispatcherOperationCallback(ExitFrame), frame);
                Dispatcher.PushFrame(frame);
            }
    
            public object ExitFrame(object f)
            {
                ((DispatcherFrame)f).Continue = false;
    
                return null;
            }

    用这个的话是否需要阻塞其他UI线程,因为我的应用需要在另外两个线程里调用Dispatcher.Invoke来分别刷新两个图片控件显示视频数据,因为单单这么用依然会出现图片控件显示的视频出现裂痕。

    另外我的应用里只在整个程序的构造函数里启动了定时器,每50ms循环一次,通过循环定时器来响应界面的改变,是不是说UI主线程在定时器里

    2017年6月30日 6:20
  • 如果你做过 DirectX 开发或玩过游戏就会知道 DirectX 一共有三种模式,全屏幕 fullscreen,窗口 windowed和窗口(全屏幕)windowed(fullscreen),而且在 DirectX 中也没有“垂直同步”一说,所谓的垂直同步实际上是将刷新率(BufferDesc.RefreshRate)设置为 60/1,当然也可以设置其它数值,比如 30/1。但是 RefreshRate 参数仅在 fullscreen 模式下有效,在 windowed 和 windowed(fullscreen) 中 RefreshRate、ScanlineOrdering 和 Scaling 三个参数都将被忽略掉,因此在任何非 fullscreen 程序中都不可能实现垂直同步。道理其实显而易见,在非 fullscreen 程序中你得到的 front buffer 是 desktop 而不是一个 dedicated front buffer,所以刷新率由 desktop 来决定。

    WPF 根本不支持 fullscreen 模式,只能是 windowed 和 windowed(fullscreen),因此在 WPF 中根本不存在垂直同步一说,因此 WPF 也没有这一选项,你只能在 desktop 上面绘制窗口范围内的内容并最终由 desktop 完成屏幕的整体绘制后一并推送给硬件。

    DirectX11里我可以在windowed模式下通过调用swapChain->Present(1,0)来实现垂直同步,如果Present(0,0)则帧数能到很高,那么swapChain->Present(1,0)其实也不是真的垂直同步吗

    2017年6月30日 6:22
  • DirectX11里我可以在windowed模式下通过调用swapChain->Present(1,0)来实现垂直同步,如果Present(0,0)则帧数能到很高,那么swapChain->Present(1,0)其实也不是真的垂直同步吗

    Present是从back buffer到front buffer的过程,当你处于窗口模式时,我认为你实际上是与desktop同步了。当然如果你的desktop本身就与显示器同步,那等于是间接达到了垂直同步的目的。WPF本身应该就是和desktop同步的,所以你的问题应该是你在WPF里面自己绘制东西,但没有和WPF的界面更新同步,要想与WPF的UI界面同步那你应该在UI线程上更新,这样UI线程就会被锁住,从而确保你的更新和WPF的界面一致。

    如果是这样,那我还真没注意哪个是我的UI主线程,我整个程序就创建了一个MainWindow类,这个类的构造函数里启动了定时器,开启了几个线程,然后这个构造函数就走完了。剩下的部分就是由定时器和开启的几个线程在运作,线程里调用的界面相关元素都需要委托或绑定来更新界面,但是定时器和控件事件响应函数里面调用的函数中对界面的更新不需要委托,那说明那些函数就运行在UI主线程里吗

    另外,我在WPF里自己绘制东西具体是指类似怎样的方法,是指调用画线画圆之类的函数吗,我里面更新界面控件的操作都是调用控件的子方法


    2017年6月30日 8:03
  •         private delegate void ImageLeftDelegate(byte[] buffer);//线程内调用控件的委托函数
            private void ImageShowLeft(byte[] buffer)          
            {
                this.DisplayLeft.Dispatcher.Invoke(new ImageLeftDelegate(ImageShowLeftAction), buffer);
            }
            private void ImageShowLeftAction(byte[] buffer)          
            {
                try
                {
                    if (USBdevOps.Successes[0] >= 3 || USBdevOps.Successes[1] >= 3)
                    {
                        if (bitmapImageLeft == null)
                        {
                            bitmapImageLeft = new WriteableBitmap(IMAGE_WIDTH, IMAGE_HEIGHT, 72, 72, PixelFormats.Bgr24, null);
                            bitmapImageLeft.WritePixels(new Int32Rect(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT), buffer, bitmapImageLeft.BackBufferStride, 0);
                            DisplayLeft.Source = bitmapImageLeft;
                            DisplayThird.Source = bitmapImageLeft;
                        }
                        else
                        {
                            bitmapImageLeft.WritePixels(new Int32Rect(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT), buffer, bitmapImageLeft.BackBufferStride, 0);
                            //App.DoEvents();
                        }
                    }

                    if (frame1 >= 10)
                    {
                        if (App.TestFlag)
                        {
                            testT2 = DateTime.Now;
                            testTime1 = testT2 - testT1;
                            if (testTime1.Milliseconds != 0)
                            {
                                if ((testTime1.Milliseconds / 10) != 0)
                                    strFPS1 = string.Format("{0}", 1000 / (testTime1.Milliseconds / 10));
                            }
                            frame1 = 1;
                            testT1 = DateTime.Now;
                            if (App.DelayFlag)
                            {
                                DelayLabel1.Content = timeConsumeShow[0].ToString();
                                timeConsumeShow[0] = 0;
                            }
                        }
            #if NEWREC
                        if (F8KeyDown != 3)
                        {
                            //F8释放
                            keybd_event((byte)(Keys.F8), 66, 2, 0);
                            F8KeyDown++;
                            //F9释放
                            //keybd_event((byte)(Keys.F9), 66, 2, 0);
                        }
            #endif
                    }
                    else
                        frame1++;
                }
                catch (Exception ex)
                {
                    string errHint = string.Format("ImageShowLeftAction功能异常:{0}", ex.Message);
                    ImageLogInformation.LocalErrDataSave(DateTime.Now, ErrorType.Error, errHint);
                }
            }

    另外一种方式就是通过绑定来实现,在XMAL里,如:

    <Image x:Name="ExceptionImage2" Grid.Row="0" Grid.Column="4"  Grid.ColumnSpan="1" Grid.RowSpan="1" Source="{Binding Source, ElementName=ExceptionImage, Mode=Default}" Width="30" Height="30" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="1865,40,-10000,0" MouseUp="ExceptionHintMSUp" Visibility="{Binding Visibility, ElementName=ExceptionBorder, Mode=Default}"/>

    以及C#代码中的绑定,如:

    FileManMenuVideoCntLabel.SetBinding(System.Windows.Controls.Label.ContentProperty, new System.Windows.Data.Binding("VideoSelCnt")
    {
            Source = this,
            Converter = ImageFileManSelCntConverter
    });

    定时器中的函数,如:

    FPSLabel1.Content = strFPS1;

    2017年6月30日 8:52
  • 这两个帖子都看过,确实他们都提到Vista的DWM,也就是桌面的Areo风格。我的系统是Win7的64位,当打开桌面Aero风格,也就是开启DWM确实解决了画面撕裂的问题,但是正如帖子说的那样,DWM极大的降低了图像显示的性能,大大增加了视频刷新的延迟(我们的视频来自摄像头的数据),这是我们无法接受的,因此才考虑采用关闭DWM的方案,同时也想找到方法解决画面撕裂的问题。

    不过顺着这个思路,偶然发现了右击“计算机”的属性,进入“高级系统设置”,在“高级”选项卡里的设置中可以在“视觉效果”里禁用一些选项,最终只保留“启用桌面组合”和“在窗口和按钮上使用视觉样式”两个选项,貌似性能改善了一点,并且没有画面撕裂,但是性能依然不够好,不知道还有没有其他方法


    2017年6月30日 9:49
  • 正如前面所说,如果你的应用不是全屏独占的,那就没有真正的垂直同步。除非desktop是同步的,那你才能间接实现一下,如果desktop不同步你再怎么做也没用。

    如果你想试试看的话,那么要不你用两个WriteableBitmap交换赋值给Image.Source试试,不知道行不行。

    另外就是直接用c++开发DirectX插件,不过这个我没弄过,没有经验可以分享。

    交换赋值依然不行,并且发生撕裂的不只是那个图像控件,其他控件在对应的水平线上也会有撕裂,比如Border控件,所以估计应该是在显示环节出的问题,在屏幕正在显示的过程中交换了前后帧缓存,导致所有控件都会撕裂。
    2017年7月3日 8:22
  • 交换赋值依然不行,并且发生撕裂的不只是那个图像控件,其他控件在对应的水平线上也会有撕裂,比如Border控件,所以估计应该是在显示环节出的问题,在屏幕正在显示的过程中交换了前后帧缓存,导致所有控件都会撕裂。

    你是说整个WPF窗口的内容全部撕裂了吗?还是很多的控件撕裂了?WPF是整个窗口内的全部内容一起绘制的,要撕裂按说应该是窗口内有一处横跨整个窗口的水平撕裂,应该不会有多出撕裂才对啊。能提供截图吗?我以前在Win7下做过很多WPF程序,Areo都是关闭的,但从没遇到过此类问题。显卡的型号、驱动、DirecrX版本、机器性能、Win7版本、.Net版本,等等这些因素可能都有关系。换其它设备试试也一样吗?所有.Net版本都有此问题吗?关闭Areo后所有WPF程序全部都有此问题吗?
    是一条水平撕裂,若经过其他控件,则控件对应的水平位置也会撕裂,不仅仅是bitmapImage那个控件撕裂(该控件窗口模式下是全屏显示),但并不是到处都撕裂。显卡型号是Quadro K2200,.NET4.5,DirectX11,内存8G,CPU是I7-3720QM

    我在想能不能在WPF里建立一个窗口,然后用来显示DirectX图像,这个窗口可以设置垂直同步,也就是调用swapChain->Present(1,0)来刷新,然后WPF其他控件浮在这个窗口上面
    2017年7月4日 7:06
  • 我根据你提供的代码,更改了我的代码,在Win7中依然存在撕裂现象。至于Win10下面没有撕裂,可能是系统方面的原因。

    另外,我仔细看了一下使用DirectX的效果,发现不论是否全屏,都会有撕裂,之前可能是摄像机角度不好,没有注意到。

    所以我估计在Win7下,只有打开DWM才能防止画面撕裂,我很好奇DWM到底采用了什么机制防止撕裂的

    另外真正的垂直同步是不是还和显示器和显卡硬件本身有关,因为上面你说了所谓的垂直同步只是将刷新率调到了60Hz,确实,我打开了一个DX11编写的游戏,就算使用垂直同步,游戏画面也会撕裂,只是恒定的在一个位置附近撕裂。所以不知道是不是有新显卡能够支持真正的垂直同步




    2017年7月5日 2:28
  • 你粘贴的那段代码,我已运行,做了两个改动,一个是加上unsafe关键字,另一个是在xmal里增加窗口大小的设置,宽为1920,高为1080,显示出来有明显的撕裂。

    这么看来是系统的问题,或者是哪里设置的问题?


    2017年7月5日 9:06
  • 如果这段最简单的代码仍然有撕裂,那一种可能是系统问题,另一种可能是显卡驱动的问题。在当前这种情况下,我建议用其它机器试一下看看。

    使用了另外一台Win7,硬件配置完全不同,软件安装的也不同,关闭Aero一样跑那个小程序会有明显裂痕;又使用了Win10的机器,安装显卡驱动前有画面撕裂,确实安装完显卡驱动后那个程序的撕裂现象消除,而且关闭Aero特效后依然不会出现画面撕裂,但是从图像显示的延迟上看其实跟Win7差不多,所以应该内部实际上还是有一种机制来防止撕裂,但增加了延迟。

    看来这和系统底层的显示驱动有关。


    2017年7月6日 8:58