You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

无XAML控件/窗口下,在Direct2D中绘制WinRT Composition Visual并取像素

问题描述

我正尝试在Direct2D应用中绘制cppwinrt Lottie动画,但不清楚具体实现方法。请问能否不借助XAML控件和窗口,绘制WinRT::Windows::UI::Composition::Visual动画并获取像素数据?

我已成功加载Lottie动画,代码如下:

auto bmv = winrt::make<AnimatedVisuals::LottieLogo1>();
auto avptr = bmv.TryCreateAnimatedVisual(compositor, diags);
        
m_visual = avptr.RootVisual();

auto progressAnimation = compositor.CreateScalarKeyFrameAnimation();
progressAnimation.Duration(std::chrono::seconds(5));
progressAnimation.IterationBehavior(winrt::Windows::UI::Composition::AnimationIterationBehavior::Forever);
progressAnimation.Direction(winrt::Windows::UI::Composition::AnimationDirection::Alternate);
auto linearEasing = compositor.CreateLinearEasingFunction();
progressAnimation.InsertKeyFrame(0, 0, linearEasing);
progressAnimation.InsertKeyFrame(1, 1, linearEasing);

m_visual.Properties().StartAnimation(L"Progress", progressAnimation);

现在我需要将该动画绘制到D2D Bitmap上或获取像素信息。我尝试创建帧池,但未收到帧,相关代码如下:

winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice m_device;
{
    auto d3dDevice = CreateD3DDevice();
    auto dxgiDevice = d3dDevice.as<IDXGIDevice>();
    if (dxgiDevice)
    {
        hr = CreateDirect3DDevice(dxgiDevice.get(), &m_device);
    }
}
m_framePool = winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool::Create(
    m_device,
    winrt::Windows::Graphics::DirectX::DirectXPixelFormat::B8G8R8A8UIntNormalized,
    3,
    winrt::Windows::Graphics::SizeInt32({ 400, 400 }));
m_session = m_framePool.CreateCaptureSession(winrt::Windows::Graphics::Capture::GraphicsCaptureItem::CreateFromVisual(m_visual));
m_frameArrived = m_framePool.FrameArrived(winrt::auto_revoke, { this, &CLottie::OnFrameArrived });
m_session.StartCapture();

再次询问:能否不使用XAML控件和窗口实现该动画的绘制与数据获取?

更新内容

auto controller = CreateDispatcherQueueController();

winrt::Windows::UI::Composition::Compositor compositor{};   

CComPtr<ABI::Windows::UI::Composition::ICompositorInterop> compositorInterop;

hr = winrt::get_unknown(compositor)->QueryInterface(&compositorInterop);
winrt::com_ptr<ABI::Windows::UI::Composition::ICompositionGraphicsDevice> device = nullptr;

hr = compositorInterop->CreateGraphicsDevice(direct2dDevice, device.put());
auto gDevice = device.as<winrt::Windows::UI::Composition::CompositionGraphicsDevice>();

winrt::Windows::Foundation::Size surfaceSize{ 400, 400 };
winrt::Windows::UI::Composition::CompositionDrawingSurface surface = gDevice.CreateDrawingSurface(surfaceSize,
    winrt::Windows::Graphics::DirectX::DirectXPixelFormat::B8G8R8A8UIntNormalized,
    winrt::Windows::Graphics::DirectX::DirectXAlphaMode::Premultiplied);

auto brush = compositor.CreateSurfaceBrush(surface);
float width = 400.0f, height = 400.0f;

winrt::Windows::UI::Composition::SpriteVisual visual = compositor.CreateSpriteVisual();
visual.Brush(brush);
visual.Size({ width, height });

winrt::Windows::UI::Composition::ShapeVisual shape = compositor.CreateShapeVisual();
shape.Size({ 100.0f,100.0f });
auto circleGeometry = compositor.CreateEllipseGeometry();
circleGeometry.Radius(winrt::Windows::Foundation::Numerics::float2(30, 30));
auto circleShape = compositor.CreateSpriteShape(circleGeometry);
circleShape.FillBrush(compositor.CreateColorBrush(winrt::Windows::UI::Colors::Orange()));
circleShape.Offset(winrt::Windows::Foundation::Numerics::float2(50, 50));
shape.Shapes().Append(circleShape);
visual.Children().InsertAtTop(shape);

auto surfaceInterop = surface.as<ABI::Windows::UI::Composition::ICompositionDrawingSurfaceInterop>();

CComPtr<ID2D1DeviceContext> dc;
POINT offset = {};

hr = surfaceInterop->BeginDraw(nullptr,
    __uuidof(dc),
    reinterpret_cast<void**>(&dc),
    &offset);

CComPtr<ID2D1Bitmap1> bitmap = nullptr;
D2D1_BITMAP_PROPERTIES1 prop;
prop.pixelFormat.format = DXGI_FORMAT_B8G8R8A8_UNORM;
prop.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED;
prop.dpiX = 96;
prop.dpiY = 96;
prop.bitmapOptions = D2D1_BITMAP_OPTIONS_CPU_READ | D2D1_BITMAP_OPTIONS_CANNOT_DRAW;
prop.colorContext = NULL;

hr = dc->CreateBitmap(D2D1::SizeU(width, height), NULL, 0, prop, &bitmap);

hr = bitmap->CopyFromRenderTarget(NULL, dc, NULL);

hr = surfaceInterop->EndDraw();

解决方案

完全可以不依赖XAML控件和窗口实现需求,核心思路是利用Composition离线渲染结合Direct3D/Direct2D互操作,或者修正Graphics Capture的使用问题。以下是两种可行方案:

方案一:修正Graphics Capture获取帧失败问题

你当前用Graphics Capture没收到帧,核心原因是GraphicsCaptureItem::CreateFromVisual要求目标Visual必须属于一个有激活的Composition Host的树(即使是离线Host),且需要确保Compositor和DispatcherQueue正确初始化。

修复步骤:

  1. 确保DispatcherQueue生命周期稳定
    离线使用Composition必须先创建DispatcherQueue,你更新代码里的CreateDispatcherQueueController()是对的,但要确保它的生命周期和Compositor、Visual树一致,不能提前销毁。

  2. 创建离线Composition目标并挂载Lottie Visual
    必须把Lottie Visual挂载到根ContainerVisual,并关联到离线CompositionTarget:

    // 初始化Compositor后创建离线目标
    auto target = compositor.CreateCompositionTargetForOffscreen();
    auto rootVisual = compositor.CreateContainerVisual();
    rootVisual.Size({400, 400});
    // 将你的Lottie Visual添加到根视觉树
    rootVisual.Children().InsertAtTop(m_visual);
    target.Root(rootVisual);
    
  3. 重新创建Capture Session
    用CreateFromVisual传入rootVisual来创建CaptureItem,此时帧池就能正常收到渲染后的帧。

  4. 在FrameArrived中读取像素数据
    在回调里通过帧的Direct3DSurface拿到ID3D11Texture2D,映射到CPU读取像素:

    void CLottie::OnFrameArrived(winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool const& sender, winrt::Windows::Foundation::IInspectable const&)
    {
        auto frame = sender.TryGetNextFrame();
        auto d3dSurface = frame.Direct3DSurface();
        winrt::com_ptr<ID3D11Texture2D> texture;
        winrt::check_hresult(d3dSurface.as<::Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess>()->GetInterface(__uuidof(texture), texture.put_void()));
    
        // 映射纹理到CPU
        D3D11_MAPPED_SUBRESOURCE mapped{};
        winrt::check_hresult(m_d3dDeviceContext->Map(texture.get(), 0, D3D11_MAP_READ, 0, &mapped));
        // mapped.pData即为B8G8R8A8_UNORM格式的像素数据
        // 处理数据...
        m_d3dDeviceContext->Unmap(texture.get(), 0);
    }
    

方案二:直接将Composition Visual渲染到D2D Bitmap

利用Composition和Direct2D的互操作,直接把Lottie Visual的渲染结果绘制到可读取的D2D Bitmap上:

实现步骤:

  1. 关联CompositionGraphicsDevice与Direct2D设备
    确保用同一个Direct3D设备创建CompositionGraphicsDevice和Direct2D设备,你更新代码里的这一步是正确的。

  2. 创建渲染Surface并渲染Lottie Visual
    将Lottie Visual添加到SpriteVisual,再通过CompositionRenderer渲染到DrawingSurface:

    // 创建SpriteVisual并挂载Lottie Visual
    winrt::Windows::UI::Composition::SpriteVisual renderVisual = compositor.CreateSpriteVisual();
    renderVisual.Size({400, 400});
    renderVisual.Children().InsertAtTop(m_visual);
    
    // 创建用于渲染的DrawingSurface
    auto renderSurface = gDevice.CreateDrawingSurface(
        winrt::Windows::Foundation::Size{400, 400},
        winrt::Windows::Graphics::DirectX::DirectXPixelFormat::B8G8R8A8UIntNormalized,
        winrt::Windows::Graphics::DirectX::DirectXAlphaMode::Premultiplied);
    
    // 渲染Visual到Surface
    auto renderer = compositor.CreateRenderer();
    renderer.RenderTarget(renderSurface);
    renderer.Render(renderVisual);
    
  3. 从Surface复制像素到可CPU读取的D2D Bitmap
    通过ICompositionDrawingSurfaceInterop获取D2D DeviceContext,将Surface内容复制到指定Bitmap后读取像素:

    auto surfaceInterop = renderSurface.as<ABI::Windows::UI::Composition::ICompositionDrawingSurfaceInterop>();
    CComPtr<ID2D1DeviceContext> dc;
    POINT offset{};
    winrt::check_hresult(surfaceInterop->BeginDraw(nullptr, __uuidof(dc), reinterpret_cast<void**>(&dc), &offset));
    
    // 创建可CPU读取的Bitmap
    D2D1_BITMAP_PROPERTIES1 prop{};
    prop.pixelFormat = D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED);
    prop.dpiX = 96;
    prop.dpiY = 96;
    prop.bitmapOptions = D2D1_BITMAP_OPTIONS_CPU_READ | D2D1_BITMAP_OPTIONS_CANNOT_DRAW;
    
    CComPtr<ID2D1Bitmap1> cpuBitmap;
    winrt::check_hresult(dc->CreateBitmap(D2D1::SizeU(400, 400), nullptr, 0, prop, &cpuBitmap));
    
    // 复制Surface内容到Bitmap
    winrt::check_hresult(cpuBitmap->CopyFromRenderTarget(nullptr, dc, nullptr));
    
    // 读取像素数据
    D2D1_MAPPED_RECT mappedRect{};
    winrt::check_hresult(cpuBitmap->Map(D2D1_MAP_OPTIONS_READ, &mappedRect));
    // mappedRect.pBits即为像素数据
    cpuBitmap->Unmap();
    
    winrt::check_hresult(surfaceInterop->EndDraw());
    

关键注意事项

  • 所有Composition对象(Compositor、Visuals、Target)的生命周期必须和DispatcherQueue绑定,DispatcherQueueController不能提前销毁。
  • Direct3D设备需支持至少Feature Level 11.0,且创建时要开启D3D11_CREATE_DEVICE_BGRA_SUPPORT标志。
  • 确保DispatcherQueue的消息循环正常运行,否则Lottie的Progress动画无法更新。

内容的提问来源于stack exchange,提问作者Nikolay

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 21:18:49