0

I am porting a toolkit (namely the EFL) engine from pure GDI to Direct2D. More precisely, I am adding a Direct2D engine. The purpose is to have a faster engine than the GDI one.

This toolkit is cross platform, has existed for a long time, so i can't modify it. I can just use what it provides me.

It has a state full canvas. When the content of the window has to be drawn, it provides an array of BGRA rectangles (x, y, w, h and BGRA data) that has been modified since the previous window content.

I have set up D2D with DirectComposition in mind (to also have per-pixel transparency, and Kenny Kerr have said that it is faster than without direct composition btw).

The naive way is to create a bitmap of size the size of the widow, and keep it for rendering. Then I do the following steps:

  1. I loop over the rectangles and for each, I call CopyFromMemory() to update each rectangle region in the bitmap
  2. I call BeginDraw()
  3. I call DrawBitmap() to render the whole bitmap
  4. I call EndDraw()

It works. Here is the complete code:

#define _WIN32_WINNT 0x0A00

#include <stdio.h> /* I know, not C++ stuff */

#include <windows.h>
#include <d3d11.h>
#include <dxgi1_3.h>
#include <dcomp.h>
#include <d2d1_1.h>

#define E_DEBUG 1

typedef struct
{
    HINSTANCE instance;
    HWND win;
} Window;

typedef struct
{
    ID3D11Device *d3d_device;
    IDXGIDevice *dxgi_device;
    IDXGIFactory2 *dxgi_factory;
    ID2D1Factory1 *d2d_factory;
    ID2D1Device *d2d_device;
    ID2D1DeviceContext *d2d_device_ctx;
    IDCompositionDevice *dcomp_device;
    IDCompositionVisual *dcomp_visual;
    IDCompositionTarget *dcomp_target;
    IDXGISwapChain1 *dxgi_swapchain;
} D2d;

ID2D1Bitmap1 *bitmap;
void render(D2d* d2d);
void draw(D2d* d2d);


/***************************** Window *****************************/

LRESULT CALLBACK
_window_procedure(HWND   window,
                  UINT   message,
                  WPARAM window_param,
                  LPARAM data_param)
{
  switch (message)
    {
    case WM_CLOSE:
      PostQuitMessage(0);
      return 0;
    case WM_KEYUP:
      if (window_param == 'Q')
        {
          PostQuitMessage(0);
        }
      if (window_param == 'T')
        {
            printf("key 'T'\n");
            fflush(stdout);
        }
      if (window_param == 'D')
        {
            D2d *d2d;

            d2d = (D2d *)GetWindowLongPtr(window, GWLP_USERDATA);
            printf("key 'D'\n");
            fflush(stdout);
            draw(d2d);
        }
      return 0;
      /* GDI notifications */
    case WM_CREATE:
        return 0;
    case WM_PAINT:
      {
        RECT rect;

        printf("paint\n");
        fflush(stdout);

        if (GetUpdateRect(window, &rect, FALSE))
          {
            PAINTSTRUCT ps;
            D2d *d2d = NULL;;

            BeginPaint(window, &ps);

            d2d = (D2d *)GetWindowLongPtr(window, GWLP_USERDATA);
            render(d2d);

            EndPaint(window, &ps);
          }
        return 0;
      }
    default:
      return DefWindowProc(window, message, window_param, data_param);
    }
}

Window *window_new(int x, int y, int w, int h)
{
    WNDCLASS wc;
    RECT r;
    Window *win;
    DWORD style;
    DWORD exstyle;

    win = (Window *)calloc(1, sizeof(Window));
    if (!win)
        return NULL;

    win->instance = GetModuleHandle(NULL);
    if (!win->instance)
        goto free_win;

    memset (&wc, 0, sizeof (WNDCLASS));
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = _window_procedure;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = win->instance;
    wc.hIcon = LoadIcon (NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursor (NULL, IDC_ARROW);
    wc.hbrBackground = NULL;
    wc.lpszMenuName =  NULL;
    wc.lpszClassName = "D2D";

    if(!RegisterClass(&wc))
        goto free_library;

    style = WS_OVERLAPPEDWINDOW | WS_SIZEBOX;
    exstyle = WS_EX_NOREDIRECTIONBITMAP;

    r.left = 0;
    r.top = 0;
    r.right = w;
    r.bottom = h;
    if (!AdjustWindowRectEx(&r, style, FALSE, exstyle))
        goto unregister_class;

    win->win = CreateWindowEx(exstyle,
                              "D2D", "Test",
                              style,
                              x, y,
                              r.right - r.left,
                              r.bottom - r.top,
                              NULL,
                              NULL, win->instance, NULL);
    if (!win->win)
        goto unregister_class;

    printf("win new %p\n", win);
    fflush(stdout);
    printf("win->win new %p\n", win->win);
    fflush(stdout);

    return win;

  unregister_class:
    UnregisterClass("D2D", win->instance);
  free_library:
    FreeLibrary(win->instance);
  free_win:
    free(win);

    return NULL;
}

void window_del(Window *win)
{
    if (!win)
        return;

    DestroyWindow(win->win);
    UnregisterClass("D2D", win->instance);
    FreeLibrary(win->instance);
    free(win);
}

void window_show(Window *win)
{
    ShowWindow(win->win, SW_SHOWNORMAL);
}




/***************************** Direct2D *****************************/

/***** helper functions for rectangles to display *****/

int rect_1(int w, int h, unsigned int **data)
{
    unsigned int *d;
    unsigned int *iter;
    int i;
    int j;

    d = (unsigned int *)malloc(w * h * sizeof(unsigned int));
    if (!d)
    {
        *data = NULL;
        return 0;
    }

    iter = d;
    for (i = 0; i < h; i++)
    {
        for (j = 0; j < w; j++, iter++)
        {
            if (i >= 50 && j >= 50)
                *iter = 0xaa0000ff;
            else
                *iter = 0xffff0000;
        }
    }

    *data = d;
    return 1;
}

int rect_2(int w, int h, unsigned int **data)
{
    unsigned int *d;
    unsigned int *iter;
    int i;
    int j;

    d = (unsigned int *)malloc(w * h * sizeof(unsigned int));
    if (!d)
    {
        *data = NULL;
        return 0;
    }

    iter = d;
    for (i = 0; i < h; i++)
    {
        int g;
        for (j = 0; j < w; j++, iter++)
        {
            g = (j * 255) / (h - 1);
            *iter = (255 << 24) | (g << 8);
        }
    }

    *data = d;
    return 1;
}

int rect_3(int w, int h, unsigned int **data)
{
    unsigned int *d;
    unsigned int *iter;
    int i;
    int j;

    d = (unsigned int *)malloc(w * h * sizeof(unsigned int));
    if (!d)
    {
        *data = NULL;
        return 0;
    }

    iter = d;
    for (i = 0; i < h; i++)
    {
        int b;
        int g;
        b = (i * 255) / (w - 1);
        for (j = 0; j < w; j++, iter++)
        {
            g = (j * 255) / (h - 1);
            *iter = (255 << 24) | (g << 8) | b;
        }
    }

    *data = d;
    return 1;
}

D2d *d2d_init(Window *win)
{
    const D3D_FEATURE_LEVEL levels[] =
    {
        D3D_FEATURE_LEVEL_12_1,
        D3D_FEATURE_LEVEL_12_0,
        D3D_FEATURE_LEVEL_11_1,
        D3D_FEATURE_LEVEL_11_0,
        D3D_FEATURE_LEVEL_10_1,
        D3D_FEATURE_LEVEL_10_0
    };
    D2D1_FACTORY_OPTIONS opt;
    DXGI_SWAP_CHAIN_DESC1 desc;
    D2d *d2d;
    IDXGISurface *surface;
    ID2D1Bitmap1 *bmp;
    RECT r;
    HRESULT res;

    d2d = (D2d *)calloc(1, sizeof(D2d));
    if (!d2d)
        return NULL;

    /* direct3d device */
    res = D3D11CreateDevice(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL,
                            D3D11_CREATE_DEVICE_BGRA_SUPPORT
#if E_DEBUG
                            | D3D11_CREATE_DEVICE_DEBUG
#endif
                            ,
                            levels, sizeof(levels) / sizeof(D3D_FEATURE_LEVEL),
                            D3D11_SDK_VERSION, &d2d->d3d_device, NULL, NULL);
    if (FAILED(res))
        goto free_d2d;

    /* dxgi device */
    res = d2d->d3d_device->QueryInterface(&d2d->dxgi_device);
    if (FAILED(res))
        goto release_d3d_device;

    /* dxgi factory */
    res = CreateDXGIFactory2(
#if E_DEBUG
                             DXGI_CREATE_FACTORY_DEBUG,
#else
                             0,
#endif
                             __uuidof(d2d->dxgi_factory),
                             (void **)&d2d->dxgi_factory);
    if (FAILED(res))
        goto release_dxgi_device;

    /* d2d factory */
#if E_DEBUG
    opt.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
#else
    opt.debugLevel = D2D1_DEBUG_LEVEL_NONE;
#endif
    res = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED,
                            __uuidof(d2d->d2d_factory), &opt,
                            (void **)&d2d->d2d_factory);
    if (FAILED(res))
        goto release_dxgi_factory;

    /* d2d device */

    res = d2d->d2d_factory->CreateDevice(d2d->dxgi_device, &d2d->d2d_device);
    if (FAILED(res))
        goto release_d2d_factory;

    /* d2d device context */

    // FIXME : D2D1_DEVICE_CONTEXT_OPTIONS_ENABLE_MULTITHREADED_OPTIMIZATIONS ?
    res = d2d->d2d_device->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
                                               &d2d->d2d_device_ctx);
    if (FAILED(res))
        goto release_d2d_device;

    /* dcomp device */
    res = DCompositionCreateDevice(d2d->dxgi_device,
                                   __uuidof(d2d->dcomp_device),
                                   (void **)&d2d->dcomp_device);
    if (FAILED(res))
        goto release_d2d_device_ctx;

    /* dcomp visual */
    res = d2d->dcomp_device->CreateVisual(&d2d->dcomp_visual);
    if (FAILED(res))
        goto release_dcomp_device;

    /* dcomp target */
    res = d2d->dcomp_device->CreateTargetForHwnd(win->win, TRUE,
                                                 &d2d->dcomp_target);
    if (FAILED(res))
        goto release_dcomp_visual;

    /* dxgi swapchain */
    if (!GetClientRect(win->win, &r))
        goto release_dcomp_target;

    desc.Width = r.right - r.left; /* width of client area */
    desc.Height = r.bottom - r.top; /* height of client area */
    desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
    desc.Stereo = FALSE;
    desc.SampleDesc.Count = 1;
    desc.SampleDesc.Quality = 0;
    desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    desc.BufferCount = 2;
    desc.Scaling = DXGI_SCALING_STRETCH;
    desc.SwapEffect= DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
    desc.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED;
    desc.Flags = 0;

    res = d2d->dxgi_factory->CreateSwapChainForComposition(d2d->dxgi_device,
                                                           &desc,
                                                           NULL,
                                                           &d2d->dxgi_swapchain);
    if (FAILED(res))
        goto release_dcomp_target;

    printf(" * %s : swapchain : %p  %p\n", __FUNCTION__, d2d, d2d->dxgi_swapchain);
    fflush(stdout);

    /* swapchain buffer in a IDXGISurface */
    res = d2d->dxgi_swapchain->GetBuffer(0, __uuidof(surface),
                                         (void **)&surface);
    if (FAILED(res))
        goto release_dxgi_swapchain;

    printf("backbuffer1\n");
    res = d2d->d2d_device_ctx->CreateBitmapFromDxgiSurface(surface,
                                                           NULL,
                                                           &bmp);
    if (!FAILED(res))
    {
        printf("backbuffer2\n");
        d2d->d2d_device_ctx->SetTarget(bmp);
        bmp->Release();
    }

    surface->Release();

    return d2d;

  release_dxgi_swapchain:
    d2d->dxgi_swapchain->Release();
  release_dcomp_target:
    d2d->dcomp_target->Release();
  release_dcomp_visual:
    d2d->dcomp_visual->Release();
  release_dcomp_device:
    d2d->dcomp_device->Release();
  release_d2d_device_ctx:
    d2d->d2d_device_ctx->Release();
  release_d2d_device:
    d2d->d2d_device->Release();
  release_d2d_factory:
    d2d->d2d_factory->Release();
  release_dxgi_factory:
    d2d->dxgi_factory->Release();
  release_dxgi_device:
    d2d->dxgi_device->Release();
  release_d3d_device:
    d2d->d3d_device->Release();
  free_d2d:
    free(d2d);

    return NULL;
}

void d2d_shutdown(D2d *d2d)
{
    d2d->dxgi_swapchain->Release();
    d2d->dcomp_target->Release();
    d2d->dcomp_visual->Release();
    d2d->dcomp_device->Release();
    d2d->d2d_device_ctx->Release();
    d2d->d2d_device->Release();
    d2d->d2d_factory->Release();
    d2d->dxgi_factory->Release();
    d2d->dxgi_device->Release();
    d2d->d3d_device->Release();
}

void render(D2d* d2d)
{
    D2D1_BITMAP_PROPERTIES1 properties;
    DXGI_SWAP_CHAIN_DESC1 desc;
    D2D1_COLOR_F c;
    HRESULT res;

    printf(" * %s : %p\n", __FUNCTION__, d2d);
    fflush(stdout);
    printf(" * %s : %p\n", __FUNCTION__, d2d->dxgi_swapchain);
    fflush(stdout);

    // get size from swapchain (for example)
    d2d->dxgi_swapchain->GetDesc1(&desc);
    D2D1_SIZE_U s = { desc.Width, desc.Height };

    unsigned int* data;
    if (!rect_1(s.width, s.height, &data))
    {
        printf(" * %s malloc failed\n", __FUNCTION__);
        fflush(stdout);
        return;
    }

    // create a bitmap from properties & pixel buffer
    // hint: in general for most structures, it's much easier to use ZeroMemory or memset(0) so by default values are automatically set
    ZeroMemory(&properties, sizeof(properties));
    properties.pixelFormat.format = desc.Format;
    properties.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED;

    res = d2d->d2d_device_ctx->CreateBitmap(s, data, 4 * s.width, properties, &bitmap);
    if (FAILED(res))
    {
        printf(" * %s CreateBitmap failed\n", __FUNCTION__);
        fflush(stdout);
        goto data_free;
    }

    d2d->d2d_device_ctx->BeginDraw();

    c.r = 0.0f;
    c.g = 0.0f;
    c.b = 0.0f;
    c.a = 0.0f;
    d2d->d2d_device_ctx->Clear(&c);

    D2D1_RECT_F rect;
    rect.left = 0.0f;
    rect.top = 0.0f;
    rect.right = (float)s.width;
    rect.bottom = (float)s.height;
    d2d->d2d_device_ctx->DrawBitmap(bitmap, rect, 1.0f, D2D1_BITMAP_INTERPOLATION_MODE_LINEAR, rect);

    d2d->d2d_device_ctx->EndDraw(NULL, NULL);

    d2d->dxgi_swapchain->Present(1, 0);
    d2d->dcomp_visual->SetContent(d2d->dxgi_swapchain);
    d2d->dcomp_target->SetRoot(d2d->dcomp_visual);
    d2d->dcomp_device->Commit();

data_free:
    free(data);
}

void draw(D2d *d2d)
{
    D2D1_RECT_U r;
    unsigned int *data;
    int x, y, w, h;

    x = 10;
    y = 10;
    w = 100;
    h = 100;
    if (!rect_2(w, h, &data))
    {
        printf("rect_2 failed\n");
        return;
    }

    r.left = x;
    r.top= y;
    r.right = x + w - 1;
    r.bottom = y + h - 1;
    bitmap->CopyFromMemory(&r, data, 4 * w);

    x = 150;
    y = 150;
    w = 100;
    h = 100;
    if (!rect_3(w, h, &data))
    {
        printf("rect_2 failed\n");
        return;
    }

    r.left = x;
    r.top= y;
    r.right = x + w - 1;
    r.bottom = y + h - 1;
    bitmap->CopyFromMemory(&r, data, 4 * w);

    d2d->d2d_device_ctx->BeginDraw();

    D2D1_RECT_F rect;
    rect.left = 0.0f;
    rect.top = 0.0f;
    rect.right = (float)640;
    rect.bottom = (float)480;
    d2d->d2d_device_ctx->DrawBitmap(bitmap, rect, 1.0f,
                                    D2D1_BITMAP_INTERPOLATION_MODE_LINEAR, rect);

    d2d->d2d_device_ctx->EndDraw(NULL, NULL);

    d2d->dxgi_swapchain->Present(1, 0);
    d2d->dcomp_visual->SetContent(d2d->dxgi_swapchain);
    d2d->dcomp_target->SetRoot(d2d->dcomp_visual);
    d2d->dcomp_device->Commit();
}


/***************************** main *****************************/

int main()
{
    Window *win;
    D2d *d2d;

    win = window_new(100, 100, 640, 480);
    if (!win)
        return 1;

    d2d = d2d_init(win);
    if (!d2d)
        goto win_del;

    SetWindowLongPtr(win->win, GWLP_USERDATA, (LONG_PTR)d2d);

    window_show(win);

    /* msg loop */
    while(1)
    {
        MSG msg;
        BOOL ret;

        ret = PeekMessage(&msg, NULL, 0, 0, PM_REMOVE);
        if (ret)
        {
            do
            {
                if (msg.message == WM_QUIT)
                  goto beach;
                TranslateMessage(&msg);
                DispatchMessageW(&msg);
            } while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE));
        }
    }

  beach:
    d2d_shutdown(d2d);
  win_del:
    window_del(win);

    return 0;
}

The first part of this code is windows creation and destruction, the second deals with Direct2D. The function render() renders the background. The function draw() renders 2 BGRA rectangles (well squares, actually...).

My question: is there a faster way than calling several times CopyFromMemory(), like in my draw() function, to achieve this ?

thank you

vtorri
  • 166
  • 13
  • If you have every frame in a CPU buffer, you'll have to transfer it to GPU anyway (this is the costly part). Not sure you will gain any performance in doing that with DirectX instead of GDI, if you can't change what's built upfront. An engine must be thought from the ground up with understanding of GPU vs CPU architectures. PS: Note your program eats a logical core even when doing nothing. The whole purpose of direct composition is to ... well... compose; ie: render only when needed. Great seminal sample of using Dcomp is here https://gist.github.com/kennykerr/62923cdacaba28fedc4f3dab6e0c12ec – Simon Mourier Oct 24 '21 at 17:52
  • there is an opengl engine, so the EFL engine is also thought with gpu in mind. There also was a d3d9 engine some years ago. As you say, my code is an example. I just want to experiment d2d. Here, direct composition is needed for alpha transparency, to later implement CSD. Note that i have said "When the content of the window has to be drawn etc..." so the rendering is done when needed. – vtorri Oct 24 '21 at 18:12

0 Answers0