1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

WPFでD3DImageを使って画像を表示する(第1回)

1
Last updated at Posted at 2026-07-14

D3DImageを使って黒い画像を表示してみる

はじめに

WPFで動画や高速な画像を表示する方法を調べると、Media FoundationやEVRなど、さまざまな技術が登場します。しかし、それらの記事では最初から複雑な構成になっていることが多く、「結局D3DImageって何なの?」という疑問を持ったまま読み進めることになりがちです。

そこで本シリーズでは、できるだけシンプルなサンプルから順番に説明していきます。

第1回では、Direct3D9Exで黒い画像を作成し、それをWPFのD3DImageへ表示するだけのサンプルを作成します。

まずは、「DirectXで作成した画像がWPFへ表示される仕組み」を理解することを目的とします。


D3DImageとは?

D3DImageは、Direct3D9で作成した画像をWPF上へ表示するためのクラスです。

通常、WPFで画像を表示する場合は、次のようなクラスを利用します。

  • Image
  • BitmapSource
  • WriteableBitmap

これらはCPU上の画像データを表示するための仕組みです。

一方、動画や高速な画像処理ではGPU上のテクスチャをそのまま表示したい場合があります。

そのような用途で利用されるのがD3DImageです。

D3DImageは画像データを保持しているわけではありません。

IDirect3DSurface9を参照し、その内容をWPFへ表示します。

イメージとしては次のようになります。

Direct3D9
    │
    ▼
Texture
    │
    ▼
IDirect3DSurface9
    │
    ▼
D3DImage
    │
    ▼
Imageコントロール

つまり、WPFはGPU上のサーフェスを直接表示していることになります。


なぜDirect3D9を使うのか?

最近ではDirect3D11やDirect3D12を利用する機会が多くなっています。

しかし、WPFのD3DImageが表示できるのはDirect3D9のサーフェスです。

そのため、最終的にD3DImageへ表示する場合は、Direct3D9Exを利用する必要があります。

Direct3D11を利用する場合でも、共有テクスチャを介してDirect3D9へ受け渡す構成になるのが一般的です。


今回作成するプログラム

今回は非常にシンプルな構成です。

C++
    │
    ├─ Direct3D9Ex 初期化
    ├─ Texture作成
    ├─ 黒で塗りつぶす
    └─ Surface取得
            │
            ▼
        D3DImage
            │
            ▼
         Image表示

単に黒い画像を作成して表示するだけです。


XAMLによる画面レイアウトの作成とD3DImageの設定

MainWindow.xaml

表示用のImageコントロールを配置します。

<Window x:Class="D3DImageSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid>
        <Image x:Name="preview" Stretch="Fill"/>
    </Grid>
</Window>

MainWindow.xaml.cs

コンストラクタではD3DImageを生成し、ImageコントロールのSourceへ設定します。

public partial class MainWindow : Window
{
    private D3DImage? d3dimg;
    private bool backBufferInitialized = false;

    public MainWindow()
    {
        InitializeComponent();

        d3dimg = new D3DImage();
        d3dimg.IsFrontBufferAvailableChanged += d3dimg_IsFrontBufferAvailableChanged;
        preview.Source = d3dimg;
    }

    private void d3dimg_IsFrontBufferAvailableChanged(
        object? sender,
        DependencyPropertyChangedEventArgs e)
    {
        if (d3dimg!.IsFrontBufferAvailable)
        {
            // フロントバッファが無効になった
            backBufferInitialized = false;
        }
    }
}

この時点では、まだ何も表示されません。

D3DImageは表示先となるIDirect3DSurface9が設定されて初めて画像を表示できるようになります。

今回は、Direct3D9Exを扱う部分はC++/CLIで作成したライブラリへ実装します。

WPF(C#)からは、このC++/CLIライブラリのクラスを呼び出すことで、Direct3D9Exの初期化やIDirect3DSurface9の生成を行います。

役割を分けると、次のような構成になります。

WPF (C#)
    │
    │ D3DImage
    │
    ▼
C++/CLI ライブラリ
    │
    ├─ Direct3D9Ex 初期化
    ├─ Device 作成
    ├─ Texture 作成
    ├─ Surface 作成
    └─ Surface を C# へ渡す

この構成にすることで、WPF側は画面表示に専念でき、Direct3Dに関する処理をC++側へまとめることができます。

以降は、このC++/CLIライブラリを実装しながら、Direct3D9Exの初期化と表示用のSurfaceを作成していきます。


Direct3Dの初期化、表示用のSurface作成

D3DPreview.cpp

まずは、Direct3D9Exの初期化と表示用のSurfaceを作成します。

今回は、D3DPreviewクラスのInitialize()関数で初期化を行います。

bool D3DPreview::Initialize(int width, int height)
{
    // Direct3D9Exを生成
    Direct3DCreate9Ex(D3D_SDK_VERSION, &m_d3d_ex);

    // Device作成
    D3DPRESENT_PARAMETERS pp = {};
    pp.Windowed = TRUE;
    pp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    pp.BackBufferFormat = D3DFMT_UNKNOWN;
    pp.BackBufferWidth = 1;
    pp.BackBufferHeight = 1;
    pp.BackBufferCount = 1;

    m_d3d_ex->CreateDeviceEx(
        D3DADAPTER_DEFAULT,
        D3DDEVTYPE_HAL,
        GetDesktopWindow(),
        D3DCREATE_HARDWARE_VERTEXPROCESSING |
        D3DCREATE_MULTITHREADED |
        D3DCREATE_FPU_PRESERVE,
        &pp,
        nullptr,
        &m_d3d_device_ex);

    // GPU上のレンダリングターゲットを作成
    m_d3d_device_ex->CreateTexture(
        width,
        height,
        1,
        D3DUSAGE_RENDERTARGET,
        D3DFMT_A8R8G8B8,
        D3DPOOL_DEFAULT,
        &m_d3d_texture9,
        nullptr);

    // D3DImageへ渡すSurfaceを取得
    m_d3d_texture9->GetSurfaceLevel(
        0,
        &m_d3d_surface9);

    // CPUからアクセス可能なSurfaceを作成
    m_d3d_device_ex->CreateOffscreenPlainSurface(
        width,
        height,
        D3DFMT_A8R8G8B8,
        D3DPOOL_SYSTEMMEM,
        &m_d3d_sys_surface9,
        nullptr);

    m_width = width;
    m_height = height;

    return true;
}

ここでは、表示に必要となる3つのオブジェクトを作成しています。

  • GPU上のレンダリングターゲット (Texture)
  • D3DImageへ渡すSurface
  • CPUから書き込みを行うためのSYSTEMMEM Surface

GPU上のTextureはCPUから直接アクセスできないため、一旦SYSTEMMEM Surfaceへ画像を書き込み、後からGPUへ転送する構成としています。


黒い画像を作成する

GPU上のレンダリングターゲットはCPUから直接アクセスできません。

そのため、一旦CPUアクセス可能なSYSTEMMEM Surfaceへ画像を書き込み、その後GPUへ転送します。

流れは次のようになります。

SYSTEMMEM Surface
        │
        │ LockRect()
        ▼
CPUで黒く塗りつぶす
        │
        ▼
UpdateSurface()
        │
        ▼
GPU Surface

今回はSYSTEMMEM Surfaceを黒で塗りつぶし、UpdateSurface()でGPUへ転送します。

D3DPreview.cpp

void D3DPreview::ClearSurface()
{
    D3DLOCKED_RECT lockedRect;

    m_d3d_sys_surface9->LockRect(
        &lockedRect,
        nullptr,
        0);

    for (UINT y = 0; y < m_height; y++)
    {
        BYTE* line =
            static_cast<BYTE*>(lockedRect.pBits) +
            y * lockedRect.Pitch;

        for (UINT x = 0; x < m_width; x++)
        {
            // A8R8G8B8 (1画素4バイト)
            line[x * 4 + 0] = 0x00;    // B
            line[x * 4 + 1] = 0x00;    // G
            line[x * 4 + 2] = 0x00;    // R
            line[x * 4 + 3] = 0xff;    // A
        }
    }

    m_d3d_sys_surface9->UnlockRect();

    // GPU上のSurfaceへ転送
    m_d3d_device_ex->UpdateSurface(
        m_d3d_sys_surface9,
        nullptr,
        m_d3d_surface9,
        nullptr);
}

A8R8G8B8形式では、アルファ値(A)も1バイト含まれています。

今回は黒色を表示したいので、RGBは0x00、アルファ値は0xff(不透明)を設定しています。

最後にUpdateSurface()を呼び出すことで、CPUから書き込んだ画像をGPU上のSurfaceへ転送できます。


作成したSurfaceをD3DImageへ渡す

ここまでで、Direct3D9Ex上に表示用のIDirect3DSurface9を作成し、黒い画像を書き込むことができました。

次に、このSurfaceをD3DImageへ渡します。

D3DPreview.cpp

まず、作成したSurfaceを取得するための関数を用意します。

void* D3DPreview::GetSurface()
{
    return m_d3d_surface9;
}

MainWindow.xaml.cs

UpdatePreview()では、D3DImageへSurfaceを設定し、表示の更新を通知します。

private void UpdatePreview()
{
    if (d3dimg.IsFrontBufferAvailable)
    {
        unsafe
        {
            d3dimg.Lock();
    
            if (!backBufferInitialized)
            {
                IntPtr surface = (IntPtr)previewControl.GetSurface();
    
                d3dimg.SetBackBuffer(
                    D3DResourceType.IDirect3DSurface9,
                    surface,
                    true);
    
                backBufferInitialized = true;
            }
    
            d3dimg.AddDirtyRect(
                new Int32Rect(
                    0,
                    0,
                    d3dimg.PixelWidth,
                    d3dimg.PixelHeight));
    
            d3dimg.Unlock();
        }
    }
}

SetBackBuffer()では、C++側で作成したIDirect3DSurface9D3DImageへ関連付けます。

一度関連付けが完了すると、以降はAddDirtyRect()を呼び出すだけで、Surfaceの内容がWPFへ反映されます。

これで、C++側で作成した黒い画像がWPFのImageコントロールへ表示されます。


まとめ

今回は、D3DImageの基本的な仕組みを理解するために、WPFでDirectXの画像を表示するシンプルなサンプルを作成しました。

内容は次の通りです。

  • D3DImageとは何か
  • Direct3D9Exで画像を作成する
  • CPUで画像を書き込む方法
  • GPUへ転送する方法
  • WPFへ表示を反映する方法

本記事では、D3DImageの動作の流れを分かりやすくすることを目的としているため、エラー処理やリソースの解放処理、C++/CLIのインターフェース実装など、一部のコードは省略しています。

実際のアプリケーションでは、適切なエラー処理やリソース管理を実装してください。


次回予告

今回作成した黒い画像の代わりに、USBカメラから取得したNV12画像を表示してみます。

Media Foundationで取得した画像をDirect3Dへ転送し、WPFへ表示するところまでを紹介する予定です。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?