wpf 如何使用WinRT从后台线程截图?

thtygnil  于 2023-04-07  发布在  其他
关注(0)|答案(1)|浏览(127)

我遇到WinRT ScreenCapture阻止/冻结我的UI的问题。
我的应用程序在选定的窗口中循环,每隔几秒就对每个窗口进行一次截图。每次截图时,UI动画都会滞后,有时UI会完全冻结,直到我专注于另一个应用程序,该应用程序以某种方式释放UI块。
使用此ScreenCapture存储库作为参考-https://github.com/microsoft/Windows.UI.Composition-Win32-Samples/tree/master/dotnet/WPF/ScreenCapture
为了增加截图功能,我相应地调整了BasicCapture.cs-

public class BasicCapture : IDisposable
{
    ...
    private Texture2D cpuTexture; // added this
    private bool screenshotReady; // added this

    public BasicCapture(IDirect3DDevice d, GraphicsCaptureItem i)
    {
        ...
        cpuTexture = CreateTexture2D(item.Size.Width, item.Size.Height); // added this
    }

    public void Dispose()
    {
        session?.Dispose();
        framePool?.Dispose();
        swapChain?.Dispose();
        d3dDevice?.Dispose();
        cpuTexture?.Dispose(); // added this
    }

    private Texture2D CreateTexture2D(int width, int height)
    {
        // create add texture2D 2D accessible by the CPU
        var desc = new Texture2DDescription()
        {
            Width = width,
            Height = height,
            CpuAccessFlags = CpuAccessFlags.Read,
            Usage = ResourceUsage.Staging,
            Format = Format.B8G8R8A8_UNorm,
            ArraySize = 1,
            MipLevels = 1,
            SampleDescription = new SampleDescription(1, 0),
        };
        return new Texture2D(d3dDevice, desc);
    }

    private void OnFrameArrived(Direct3D11CaptureFramePool sender, object args)
    {
        using (var frame = sender.TryGetNextFrame())
        {
            ...

            using (var backBuffer = swapChain.GetBackBuffer<SharpDX.Direct3D11.Texture2D>(0))
            using (var bitmap = Direct3D11Helper.CreateSharpDXTexture2D(frame.Surface))
            {
                d3dDevice.ImmediateContext.CopyResource(bitmap, backBuffer);
                
                if (screenshotReady == false)
                {
                    // added this to copy the DirectX resource into the CPU-readable texture2D
                    d3dDevice.ImmediateContext.CopyResource(bitmap, cpuTexture);
                    screenshotReady = true;
                }
            }
        }
    }

        public async Task<Image> ScreenshotWindow()
        {
            screenshotReady = false;

            StartCapture();

            //await for screenshot to be captured
            while (screenshotReady == false) await Task.Delay(50);

            // get IDirect3DSurface from texture
            using var surf = Direct3D11Helper.CreateDirect3DSurfaceFromSharpDXTexture(cpuTexture);

            // build a WinRT's SoftwareBitmap from this surface/texture
            using var softwareBitmap = await SoftwareBitmap.CreateCopyFromSurfaceAsync(surf);

            using var InMemoryStream = new InMemoryRandomAccessStream();            
            BitmapEncoder encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, InMemoryStream);

            encoder.SetSoftwareBitmap(softwareBitmap);
            await encoder.FlushAsync();
            using var stream = InMemoryStream.AsStream();
            return Image.Load(stream);            
        }
}

BasicSampleApplication.cs中,Iv 'e添加了这个-

public async Task<Image> ScreenshotWindow(GraphicsCaptureItem item)
        {
            capture = new BasicCapture(device, item);

            var surface = capture.CreateSurface(compositor);
            brush.Surface = surface;

            var img = await capture.ScreenshotWindow();

            StopCapture();

            return img;
        }

由于我实际上不需要在应用程序上可视化地显示捕获的屏幕,因此我示例化BasicSampleApplication,而不使用任何可视化属性,并且在应用程序启动时执行一次-

// Create the compositor.
            compositor = new Compositor();
            _screenshotService = new BasicSampleApplication(compositor);

现在剩下的就是遍历选定的Windows并截图-

private async Task ScreenshotSelectedWindows()
        {
            while (ScreenshotsEnabled)
            {
                foreach (var wnd in _selectedWindows)
                {
                    GraphicsCaptureItem item = CaptureHelper.CreateItemForWindow(wnd.WindowHandler);
                    using var img = await _screenshotService.ScreenshotWindow(item);
                }
                await Task.Delay(TimeSpan.FromSeconds(10));
            }
        }

现在,当我使用ScreenshotSelectedWindows()时,它可以工作,但正如我最初所说的那样,它使UI动画滞后,有时UI只是完全冻结,直到我删除焦点。
我试过从后台任务调用这个方法-

Task.Run(()=>ScreenshotSelectedWindows());

由于某些原因,这样做不会起作用,OnFrameArrived回调永远不会被触发。Iv 'e还尝试在后台线程上示例化BasicSampleApplication,但在后台线程上创建合成器会抛出错误System.UnauthorizedAccessException: 'Access is denied. '
有什么建议可以帮助我在不阻塞/冻结UI的情况下实现截图目标吗?
谢谢!
编辑-我将添加一个指向GitHub问题的链接,因为它似乎特别引用了我的问题,我无法将其转换为我的代码:D
https://github.com/microsoft/Windows.UI.Composition-Win32-Samples/issues/69#issuecomment-1498188216

jvidinwx

jvidinwx1#

问题解决了
在后台线程上创建合成器需要创建调度器队列-https://github.com/microsoft/CsWinRT/issues/617#issuecomment-740757111
在我的用例中实际上并不需要,因为需要使用-Direct3D11CaptureFramePool.CreateFreeThreaded创建帧池,所以在后台线程上没有触发OnFrameArrived
一切都用比我在Github Issue评论中分享的更好的代码示例来解释-
https://github.com/microsoft/Windows.UI.Composition-Win32-Samples/issues/69#issuecomment-1498245106

相关问题