carefulnow1
carefulnow1

Reputation: 841

Double buffering in Direct2D?

I'm very new to Direct2D programming and have been following a tutorial. I've adapted the example given in the tutorial to a slightly more complicated program that bounces a ball off the boundaries of the window.


My main program (main.cpp):

#include "Graphics.h"

Graphics* graphics;

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    // Exit handler

    if (uMsg == WM_DESTROY)
    {
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE prevInstance, LPWSTR cmd, int nCmdShow)
{
    WNDCLASSEX windowClass;
    SecureZeroMemory(&windowClass, sizeof(WNDCLASSEX));

    // Set up window

    windowClass.cbSize = sizeof(WNDCLASSEX);
    windowClass.hbrBackground = (HBRUSH)COLOR_WINDOW;
    windowClass.hInstance = hInstance;
    windowClass.lpfnWndProc = WindowProc;
    windowClass.lpszClassName = "MainWindow";
    windowClass.style = CS_HREDRAW | CS_VREDRAW;

    // Register window class and handle

    RegisterClassEx(&windowClass);

    RECT rect = { 0, 0, 800, 600 };
    AdjustWindowRectEx(&rect, WS_OVERLAPPEDWINDOW, false, WS_EX_OVERLAPPEDWINDOW);

    HWND windowHandle = CreateWindowEx(WS_EX_OVERLAPPEDWINDOW, "MainWindow", "Test Window", WS_OVERLAPPEDWINDOW, 100, 100,
        rect.right - rect.left, rect.bottom - rect.top, NULL, NULL, hInstance, 0);

    if (!windowHandle)
        return -1;

    graphics = new Graphics();

    if (!graphics->Init(windowHandle))
    {
        delete graphics;
        return -1;
    }

    ShowWindow(windowHandle, nCmdShow);

    // Message loop

    float x = 51.0, xSpeed = 5.0f, y = 0.0, ySpeed = 5.0f;

    MSG message;
    message.message = WM_NULL;

    while (message.message != WM_QUIT)
    {
        if (PeekMessage(&message, NULL, 0, 0, PM_REMOVE))
            DispatchMessage(&message);
        else
        {
            // Ball physics

            //xSpeed += 0.6f;
            x += xSpeed;

            ySpeed += 0.2f;
            y += ySpeed;

            if (y > rect.bottom - 50)
            {
                ySpeed = -ySpeed;
            }

            if (x > rect.right - 50)
            {
                xSpeed = -xSpeed;
            }
            else if (x < 50)
            {
                xSpeed = -xSpeed;
            }

            // Redraw ball

            graphics->beginDraw();

            graphics->clearScreen(0.0f, 0.0f, 0.5f);
            graphics->drawCircle(x, y, 50.0f, 1.0f, 1.0f, 1.0f, 1.0f);

            graphics->endDraw();
        }
    }

    delete graphics;

    return 0;
}

My header file (Graphics.h):

#pragma once

#include <Windows.h>
#include <d2d1.h>

class Graphics
{
    ID2D1Factory* factory;
    ID2D1HwndRenderTarget* renderTarget;
    ID2D1SolidColorBrush* brush;

public:
    Graphics();
    ~Graphics();

    bool Init(HWND windowHandle);

    void beginDraw() { renderTarget->BeginDraw(); }
    void endDraw() { renderTarget->EndDraw(); }

    void clearScreen(float r, float g, float b);
    void drawCircle(float x, float y, float radius, float r, float g, float b, float a);
};

My graphics functions (Graphics.cpp):

#include "Graphics.h"

#define CHECKRES if (res != S_OK) return false

Graphics::Graphics()
{
    factory = NULL;
    renderTarget = NULL;
    brush = NULL;
}

Graphics::~Graphics()
{
    if (factory)
        factory->Release();

    if (renderTarget)
        renderTarget->Release();

    if (brush)
        brush->Release();
}

bool Graphics::Init(HWND windowHandle)
{
    HRESULT res = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &factory);

    CHECKRES;

    RECT rect;
    GetClientRect(windowHandle, &rect);

    res = factory->CreateHwndRenderTarget(
        D2D1::RenderTargetProperties(),
        D2D1::HwndRenderTargetProperties(windowHandle, D2D1::SizeU(rect.right, rect.bottom)),
        &renderTarget
    );

    CHECKRES;

    res = renderTarget->CreateSolidColorBrush(D2D1::ColorF(0, 0, 0, 0), &brush);

    CHECKRES;

    return true;
}

void Graphics::clearScreen(float r, float g, float b)
{
    renderTarget->Clear(D2D1::ColorF(r, g, b));
}

void Graphics::drawCircle(float x, float y, float radius, float r, float g, float b, float a)
{
    brush->SetColor(D2D1::ColorF(r, g, b, a));
    renderTarget->DrawEllipse(D2D1::Ellipse(D2D1::Point2F(x, y), radius, radius), brush, 3.0f);
}

While this program does work fine, there is some minor tearing on the ball's bounce. I have seen this question which lead me to this MSDN article. Despite reading the article, I do still not fully understand how to implement double buffering, to hopefully reduce tearing. Can someone provide a concise example and explanation of the ID2D1RenderTarget::CreateCompatibleRenderTarget, as this high level Windows programming is quite different from what I'm used to?

Upvotes: 0

Views: 2101

Answers (1)

Janne
Janne

Reputation: 1725

Check article here. ID2D1HwndRenderTarget objects are double buffered by nature and drawing is done to the offscreen buffer first and when drawing ends it will be blitted to the screen.

Upvotes: 3

Related Questions