Size of a Empty Class with Virtual Function?

What is the size of the below class?

struct MyClass
{
    virtual ~MyClass();
};

The answer is size of a pointer(4 bytes or 8 bytes depending on the platform). That pointer is a vptr to vtbl of virtual functions. That being said, it is not a good idea to initialize a class consisting of POD members but with virtual functions by zeroing its memory with memset() because its vptr ended up overwritten.

Avoid False Sharing With C++11 alignas Keyword

When two threads are frequently accessing and updating the 2 different but independent variables on the same cacheline, cache thrashing occurs as a result because when the thread updates its variable, the thread on another CPU core has its cacheline invalidated and have to fetch from the main memory again even though it is not interested in the updated variable. This is called false sharing. With C++11’s alignas keyword, the variable alignment can be set to be 64 bytes which is a common cacheline size to avoid false sharing.

alignas(64) int foo;
alignas(64) int bar;

WiX: Escape Square Blanket

In WiX, property is referenced by enclosing its name in square blanket. For example, “[foobar]”. When a text (which we have no control over) is enclosed in blankets but is not a property, how do we escape it? This is not a WiX problem but a MSI method of deducing whether a value is a property. After searching the web with no answer, I decide to set the opening and closing blanket as properties(See below).

<Property Id="OpenBracket" Value="[" />
<Property Id="CloseBracket" Value="]" />

So the value becomes “[OpenBracket]foobar[CloseBracket]” which is deduced to “[foobar]” during installation. It is a silly workaround but it works perfectly for me.

Making HTTP REST Request in C++ With WinHTTP

WinHTTP Wrapper

The example code is hosted at Github.

The WinHTTP Wrapper consists of one main class that is HttpRequest class. In its constructor, the following parameters: user_agent, proxy_username, proxy_password, server_username and server_password are optional. When accessing the website through a proxy that needs logon, pass in the proxy_username, proxy_password to the constructor. Sometimes, the webserver needs to be logon as well, in that case, give server_username and server_password. For server logins, the following authentication types are supported.

  • HTTP Basic Authentication
  • HTTP Digest Authentication
  • Passport Authentication
  • NTLM Authentication
  • Kerberos Authentication

When several authentication methods are available, it tries to select the most secure one first. As a word of caution, never use HTTP Basic Authentication, as this method sends user name and password in plaintext that means it is susceptible to Man-In-The-Middle attacks.

The HttpRequest class has 4 public functions that correspond to the 4 HTTP verbs to perform CRUD operations: PUT, GET, POST and DELETE. Each of them receives and returns a HttpResponse object about HTTP operation.

class HttpRequest
{
public:
    HttpRequest(
        const std::wstring& domain,
        int port,
        bool secure,
        const std::wstring& user_agent = L"WinHttpClient",
        const std::wstring& proxy_username = L"",
        const std::wstring& proxy_password = L"",
        const std::wstring& server_username = L"",
        const std::wstring& server_password = L"");

    bool Get(const std::wstring& rest_of_path,
        const std::wstring& requestHeader,
        HttpResponse& response);

    bool Post(const std::wstring& rest_of_path,
        const std::wstring& requestHeader,
        const std::string& body,
        HttpResponse& response);

    bool Put(const std::wstring& rest_of_path,
        const std::wstring& requestHeader,
        const std::string& body,
        HttpResponse& response);

    bool Delete(const std::wstring& rest_of_path,
        const std::wstring& requestHeader,
        const std::string& body,
        HttpResponse& response);
};

This is the HttpResponse class which consists of 1 Reset function and 4 data members that contains information about the HTTP operation.

struct HttpResponse
{
    HttpResponse() : statusCode(0) {}
    void Reset()
    {
        text = "";
        header = L"";
        statusCode = 0;
        error = L"";
    }

    std::string text;
    std::wstring header;
    DWORD statusCode;
    std::wstring error;
};

Usage

Please open the RestWebApp solution in Visual Studio and run it with Ctrl-F5 before running the example code below. You will encounter an error in the web browser when running it, this is due to RestWebApp is a Web API that contains no HTML pages to be viewed on a web browser.

Create 1 product

This is example code to create 1 product in the website, using HTTP POST

using namespace std;
const wstring domain = L"localhost";
const wstring requestHeader = L"Content-Type: application/json";
int port = 51654;
bool https = false;

using namespace WinHttpWrapper;

HttpRequest req(domain, port, https);
HttpResponse response;

cout << "Action: Create Product with Id = 1" << endl;
req.Post(L"/api/products/create", 
    requestHeader,
    R"({"Id":1, "Name":"ElectricFan","Qty":14,"Price":20.90})", 
    response);
cout << "Returned Status:" << response.statusCode << endl << endl;
response.Reset();

The output is below.

Action: Create Product with Id = 1
Returned Status:200

Retrieve 1 product

Then we retrieve the newly created product which has the id = 1, using HTTP GET.

cout << "Action: Retrieve the product with id = 1" << endl;
req.Get(L"/api/products/1", L"", response);
cout << "Returned Text:" << response.text << endl << endl;
response.Reset();

The output is below.

Action: Retrieve the product with id = 1
Returned Text:{"Id":1,"Name":"ElectricFan","Qty":14,"Price":20.90}

Update 1 product

The product is then updated with a new price, using HTTP POST again.

cout << "Action: Update Product with Id = 1" << endl;
req.Post(L"/api/products/1", 
    requestHeader,
    R"({"Id":1, "Name":"ElectricFan","Qty":15,"Price":29.80})", 
    response);
cout << "Returned Status:" << response.statusCode << endl << endl;
response.Reset();

The output is below.

Action: Update Product with Id = 1
Returned Status:200

Retrieve all products

All products are retrieved, using HTTP GET to see if the new price is reflected.

cout << "Action: Retrieve all products" << endl;
req.Get(L"/api/products", L"", response);
cout << "Returned Text:" << response.text << endl << endl;
response.Reset();

The output is below.

Action: Retrieve all products
Returned Text:[{"Id":1,"Name":"ElectricFan","Qty":15,"Price":29.80}]

Delete 1 product

The only one product is deleted, using HTTP DELETE.

cout << "Action: Delete the product with id = 1" << endl;
req.Delete(L"/api/products/1", L"", "", response);
cout << "Returned Status:" << response.statusCode << endl << endl;
response.Reset();

The output is below.

Action: Delete the product with id = 1
Returned Status:200

Retrieve all products

All products are retrieved, using HTTP GET, to see if the product is deleted.

cout << "Action: Retrieve all products" << endl;
req.Get(L"/api/products", L"", response);
cout << "Returned Text:" << response.text << endl << endl;
response.Reset();

The output is below.

Action: Retrieve all products
Returned Text:[]

Pros and Cons of WinHTTP

Pros

  • WinHTTP comes bundled with Windows which means you do not have to include the code in your project.
  • Comes with Windows NTLM and Kerberos authentication out of the box.
  • Comes with web proxy authentication out of the box.

Cons

  • WinHTTP is tied to each Windows version. Take, for example, Windows XP which is End-Of_life, does not receive updates from Microsoft anymore, so its WinHTTP is stuck without TLS 1.3 support. It may not be a problem if you are only accessing your old intranet website whose web server is not the latest.
  • Windows only. Not cross-platform.
  • A minor code amendment is needed for WinHTTP on older Windows.

Making HTTP REST Request in C++ (With CPR)

C++: Faster to Retrieve Data By Ref Parameter or Returning a Ref?

In C++, there are two options to retrieve data from a object: either passing a reference parameter to be filled up or returning a reference. The former is making a copy of data (which may be what the developer wants) while the latter is returning a memory address of the data.

The example code is hosted at Github.

void PassByReference(std::vector<int>& vec)
{
    vec = m_vec; // make a copy
}

std::vector<int>& ReturnReference()
{
    return m_vec; // return memory address
}

Since reference in C++ is a syntactic sugar for pointer, the pointer equivalent of the 2 above functions are presented below. By right, they should have the same performance characteristic.

void PassByPointer(std::vector<int>* vec)
{
    *vec = m_vec; // make a copy
}

std::vector<int>* ReturnPointer()
{
    return &m_vec; // return memory address
}

Benchmark Code

The code used for benchmark is below.

const int MAX_LOOP = 100000;
timer stopwatch;

{
    stopwatch.start("PassByReference");
    Foo foo;
    std::vector<int> vec;
    for (int i = 0; i < MAX_LOOP; ++i)
    {
        foo.Add(i);
        foo.PassByReference(vec);
    }
    stopwatch.stop();
}
{
    stopwatch.start("ReturnReference");
    Foo foo;
    std::vector<int>* p = nullptr;
    for (int i = 0; i < MAX_LOOP; ++i)
    {
        foo.Add(i);
        p = &foo.ReturnReference();
    }
    stopwatch.stop();
}
{
    stopwatch.start("PassByPointer");
    Foo foo;
    std::vector<int> vec;
    for (int i = 0; i < MAX_LOOP; ++i)
    {
        foo.Add(i);
        foo.PassByPointer(&vec);
    }
    stopwatch.stop();
}
{
    stopwatch.start("ReturnPointer");
    Foo foo;
    std::vector<int>* p = nullptr;
    for (int i = 0; i < MAX_LOOP; ++i)
    {
        foo.Add(i);
        p = foo.ReturnPointer();
    }
    stopwatch.stop();
}

Benchmark Results

The benchmark result for different C++ compilers are presented below.

Visual C++ 2019 16.7 Update on Windows 10 20.04 Update

     PassByReference timing:  565ms
     ReturnReference timing:    0ms
       PassByPointer timing:  572ms
       ReturnPointer timing:    0ms

G++ 9.3.0 Ubuntu 20.04 WSL2

G++ build command is as follows.

g++ -std=c++11 -O3 TestReference.cpp
     PassByReference timing: 8945ms
     ReturnReference timing:    0ms
       PassByPointer timing:  598ms
       ReturnPointer timing:    1ms

Clang++ 10.0.0 Ubuntu 20.04 WSL2

Clang++ build command is as follows.

clang++ -std=c++11 -O3 TestReference.cpp
     PassByReference timing: 8824ms
     ReturnReference timing:    0ms
       PassByPointer timing:  617ms
       ReturnPointer timing:    0ms

Direct2D Tutorial Part 4: Gradient Brush

Table of Contents

The example code is hosted at Github.

Introduction

In this article, we’ll look at how to draw with linear and radial gradient colors in Direct2D. The prerequisite of the article is the knowledge to set up the RenderTarget. If you haven’t got a clue of what RenderTarget is, please go read the RenderTarget article first and then come back to read this article.

Linear Gradient

d2d_linear_grad

To create a linear gradient brush, an array of gradient stop has to be defined. A gradient stop consists of its position and color. The first gradient stop position should be 0 and the last be 1. Then CreateGradientStopCollection() is called to create ID2D1GradientStopCollection from stops[] which is followed by CreateLinearGradientBrush() to create the ID2D1LinearGradientBrush.

ComPtr<ID2D1LinearGradientBrush> m_LinearBrush;

void CD2DGradientDlg::CreateLinearGradientBrush()
{
    D2D1_GRADIENT_STOP stops[] =
    {
        { 0.0f, ColorF(ColorF::Cyan) },
        { 1.0f, ColorF(ColorF::DarkBlue) }
    };

    ComPtr<ID2D1GradientStopCollection> collection;

    HR(m_Target->CreateGradientStopCollection(stops, _countof(stops),
        collection.GetAddressOf()));

    D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES props = {};

    HR(m_Target->CreateLinearGradientBrush(props, collection.Get(),
        m_LinearBrush.ReleaseAndGetAddressOf()));
}

After the gradient brush is created, we can draw with it. First, we set the start and end point with SetStartPoint() and SetEndPoint(). Alternatively, the start and end point can be specified in D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES structure during the brush creation code above. Our gradient starts from position (0,0) to a position of the dialog width and height, meaning the gradient is diagonal.

void CD2DGradientDlg::DrawLinearGradientRect()
{
    auto size = m_Target->GetSize();

	m_LinearBrush->SetStartPoint(Point2F(0.0f, 0.0f));

    m_LinearBrush->SetEndPoint(Point2F(size.width, size.height));

    auto r = RectF(0.0f, 0.0f, size.width, size.height);

    m_Target->FillRectangle(r, m_LinearBrush.Get());
}

Rainbow Linear Gradient

Next, we’ll create a horizontal linear gradient with rainbow colors (meaning more than 2 colors). The previous gradient stops are commented out and a new stops comprised of 4 colors are specified. The 2nd and 3rd stops are positioned at 0.33 and 0.66. Any colors in-between these stops are linearly interpolated.

ComPtr<ID2D1LinearGradientBrush> m_LinearBrush;

void CD2DGradientDlg::CreateLinearGradientBrush()
{
    /*
    D2D1_GRADIENT_STOP stops[] =
    {
        { 0.0f, ColorF(ColorF::Cyan) },
        { 1.0f, ColorF(ColorF::DarkBlue) }
    };
    */
    D2D1_GRADIENT_STOP stops[] =
    {
        { 0.0f, ColorF(227.0f / 255.0f, 9.0f / 255.0f, 64.0f / 255.0f, 1.0f) },
        { 0.33f, ColorF(231.0f / 255.0f, 215.0f / 255.0f, 2.0f / 255.0f, 1.0f) },
        { 0.66f, ColorF(15.0f / 255.0f, 168.0f / 255.0f, 149.0f / 255.0f, 1.0f) },
        { 1.0f, ColorF(19.0f / 255.0f, 115.0f / 255.0f, 232.0f / 255.0f, 1.0f) }
    };
    ComPtr<ID2D1GradientStopCollection> collection;

    HR(m_Target->CreateGradientStopCollection(stops, _countof(stops),
        collection.GetAddressOf()));

    D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES props = {};

    HR(m_Target->CreateLinearGradientBrush(props, collection.Get(),
        m_LinearBrush.ReleaseAndGetAddressOf()));
}

In the drawing, code remains unchanged except for the end point’s y coordinate which is changed to 0 to create a horizontal gradient.

m_LinearBrush->SetStartPoint(Point2F(0.0f, 0.0f));

m_LinearBrush->SetEndPoint(Point2F(size.width, 0.0f));

This is the rainbow gradient we created.

d2d_rainbow_linear_grad

Rainbow Linear Gradient Text

In this section, we look at how to apply the gradient brush to text. In Direct2D, the brush can be applied to any drawing whose function accepts a brush parameter. To display text with DrawText, a device independent resource, IDWriteTextFormat, has to created with CreateTextFormat() from the DirectWrite factory.

ComPtr<IDWriteTextFormat> m_TextFormat;

void CD2DAffineTransformDlg::CreateDeviceIndependentResources()
{
    HR(FactorySingleton::GetDWriteFactory()->CreateTextFormat(L"Arial Black",
        nullptr, DWRITE_FONT_WEIGHT_ULTRA_BOLD, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 40, L"",
        m_TextFormat.ReleaseAndGetAddressOf()));
}

To draw text, the FillRectangle() in the above drawing code is replaced by DrawText().

void CD2DGradientDlg::DrawLinearGradientText()
{
    auto size = m_Target->GetSize();

    m_LinearBrush->SetStartPoint(Point2F(0.0f, 0.0f));

    m_LinearBrush->SetEndPoint(Point2F(size.width, 0.0f));

    auto r = RectF(0.0f, 0.0f, size.width, size.height);

    m_Target->DrawTextW((LPCTSTR)m_Text, m_Text.GetLength(), m_TextFormat.Get(), 
        &r, m_LinearBrush.Get());
}

This is gradient text output.

d2d_rainbow_linear_grad_text

Radial Gradient

d2d_radial_grad

In this section, we’ll show how to create and use a radial gradient brush. Its creation function is almost similar to the linear one, except the functions are related to radial gradient brush. Notice, in D2D1_RADIAL_GRADIENT_BRUSH_PROPERTIES, its center point is set.

ComPtr<ID2D1RadialGradientBrush> m_RadialBrush;

void CD2DGradientDlg::CreateRadialGradientBrush()
{
    D2D1_GRADIENT_STOP stops[] =
    {
        { 0.0f, ColorF(ColorF::Cyan) },
        { 1.0f, ColorF(ColorF::DarkBlue) }
    };

    ComPtr<ID2D1GradientStopCollection> collection;
    HR(m_Target->CreateGradientStopCollection(stops, _countof(stops), 
        collection.GetAddressOf()));

    D2D1_RADIAL_GRADIENT_BRUSH_PROPERTIES props = {};
    props.center = Point2F(50.0f, 50.0f);
    HR(m_Target->CreateRadialGradientBrush(props, collection.Get(), 
        m_RadialBrush.ReleaseAndGetAddressOf()));
}

Then FillRectangle() is called with the radial gradient brush.

void CD2DGradientDlg::DrawRadialGradientRect()
{
    auto size = m_Target->GetSize();
    auto radius = min(size.width, size.height);

    m_RadialBrush->SetRadiusX(radius);
    m_RadialBrush->SetRadiusY(radius);

    m_Target->FillRectangle(RectF(0.0f, 0.0f, size.width, size.height), 
        m_RadialBrush.Get());
}

Radial Gradient Text

d2d_radial_grad_text

To draw text with radial gradient brush, the code is the same as linear gradient text, except the radial gradient brush is passed to DrawText().

void CD2DGradientDlg::DrawRadialGradientText()
{
    auto size = m_Target->GetSize();
    auto radius = min(size.width, size.height);

    m_RadialBrush->SetRadiusX(radius);
    m_RadialBrush->SetRadiusY(radius);

    auto r = RectF(0.0f, 0.0f, size.width, size.height);

    m_Target->DrawTextW((LPCTSTR)m_Text, m_Text.GetLength(), m_TextFormat.Get(), 
        &r, m_RadialBrush.Get());
}

Demo Code

All the code shown in the article is put in one single demo. To see certain gradient demo, just comment and uncomment the function you want to see in the Draw().

void CD2DGradientDlg::Draw()
{
    m_Target->Clear(ColorF(ColorF::White));

    //DrawLinearGradientRect();
    //DrawLinearGradientText();
    //DrawRadialGradientRect();
    DrawRadialGradientText();
}

History

  • 18th September, 2020: First release

Articles in the Series

Direct2D Tutorial Part 3: Affine Transforms

 

 

Table of Content

The example code is hosted at Github.

Introduction

In this article, we’ll look at how to do affine transform using matrices in Direct2D. The transformation has to be set up before any drawing that is to be transformed. The prerequisite of the article is the knowledge to set up the RenderTarget. If you haven’t got a clue what RenderTarget is, please go read the RenderTarget article first and then come back to read this article.

Text Format

To display the text in Direct2D, IDWriteTextFormat has to be created first. IDWriteTextFormat is a device-independent resource that is created in CreateDeviceIndependentResources() which is in turn called once by OnInitDialog() or in any initialization function you may have. A device-independent resource does not need to be recreated when the Direct2D target is lost. IDWriteTextFormat and its creation function, CreateTextFormat() shall be examined in more detail in a future text drawing tutorial.

ComPtr<IDWriteTextFormat> m_TextFormat;

void CD2DAffineTransformDlg::CreateDeviceIndependentResources()
{
    HR(FactorySingleton::GetDWriteFactory()->CreateTextFormat(L"Arial Black",
        nullptr, DWRITE_FONT_WEIGHT_ULTRA_BOLD, DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL, 40, L"",
        m_TextFormat.ReleaseAndGetAddressOf()));
}

Translation

d2d_trans

In this section, the text is translated (or moved) downward along the y-axis by 50 pixels. Before SetTransform() is set with our translation matrix, it has to be called with IdentityMatrix() to reset whatever matrix set prior to this stage. The translate matrix is created from Matrix3x2F::Translation(). The Matrix3x2F::Translation()‘s 2 parameters are the amount of X and Y coordinates to ‘move’ from the current point. Then m_FillBrush is set to a Direct2D predefined black color. You can choose other predefined or custom colors. To define a custom color to be used in SetColor(), give ColorF() 4 floating-point numbers in RGBA order. Then to draw the text, DrawTextW is called on the m_Target.

CRect rectClient;
GetClientRect(&rectClient);

auto rect = RectF(0.0f, 0.0f, rectClient.Width(), rectClient.Height());

m_Target->SetTransform(D2D1::IdentityMatrix());

D2D1::Matrix3x2F trans = D2D1::Matrix3x2F::Translation(0, 50);

m_Target->SetTransform(trans);

m_FillBrush->SetColor(ColorF(ColorF::Black));
m_Target->DrawTextW((LPCTSTR)m_Text, m_Text.GetLength(), m_TextFormat.Get(), &rect, m_FillBrush.Get());

Skew

d2d_skew

Next, we’ll see how to do a skew to make the text look italic and then followed by the same translation. GetClientRect() code from the previous section is not shown as they are unchanged. The transformations are chained by the matrix multiplication. You see the order of matrix multiplication is very important here. The order of matrix multiplication must be the opposite of the order of the desired transformation. For instance, we want the skew to be the first transformation, then in the matrix multiplication, then the skew matrix has to be the last operand. Vice-versa for the translation matrix. See the skew matrix is created from Matrix3x2F::Skew(). The Matrix3x2F::Skew()‘s first 2 parameters are the angle(degree) to skew the along the x-axis and y-axis and the last parameter is the center point of skew. Note: The translation matrix creation is discussed above.

m_Target->SetTransform(D2D1::IdentityMatrix());

D2D1::Matrix3x2F trans = D2D1::Matrix3x2F::Translation(0, 50);
D2D1::Matrix3x2F skew = D2D1::Matrix3x2F::Skew(-10.0f, 0.0f, Point2F(rectClient.Width() / 2, rectClient.Height() / 2));

m_Target->SetTransform(trans * skew);

m_FillBrush->SetColor(ColorF(ColorF::Black));
m_Target->DrawTextW((LPCTSTR)m_Text, m_Text.GetLength(), m_TextFormat.Get(), &rect, m_FillBrush.Get());

Rotation

d2d_rotate

In this section, the text is rotated upward by -10 degrees. The skew and rotation has to be performed before translation, therefore the matrix multiplication is trans * rotate * skew. The rotate matrix is created from Matrix3x2F::Rotation() which has an angle as its first parameter. The optional center point parameter defaults to 0,0 when left out.

m_Target->SetTransform(D2D1::IdentityMatrix());

D2D1::Matrix3x2F trans = D2D1::Matrix3x2F::Translation(0, 50);
D2D1::Matrix3x2F rotate = D2D1::Matrix3x2F::Rotation(-10.0f);
D2D1::Matrix3x2F skew = D2D1::Matrix3x2F::Skew(-10.0f, 0.0f, Point2F(rectClient.Width() / 2, rectClient.Height() / 2));

m_Target->SetTransform(trans * rotate * skew);

m_FillBrush->SetColor(ColorF(ColorF::Black));
m_Target->DrawTextW((LPCTSTR)m_Text, m_Text.GetLength(), m_TextFormat.Get(), &rect, m_FillBrush.Get());

Scale

d2d_scale

Scaling is normally used to implement zoom-in and zoom-out in computer drawing. In this last section, we’ll scale down the text drawing(make smaller). Scale has to be performed before all other transformation, so it is the last operand in the matrix multiplication. See the scale matrix is created from Matrix3x2F::Scale() which has 2 parameters to specify the scaling in the x-axis and y-axis.

m_Target->SetTransform(D2D1::IdentityMatrix());

D2D1::Matrix3x2F trans = D2D1::Matrix3x2F::Translation(0, 50);
D2D1::Matrix3x2F rotate = D2D1::Matrix3x2F::Rotation(-10.0f);
D2D1::Matrix3x2F skew = D2D1::Matrix3x2F::Skew(-10.0f, 0.0f, Point2F(rectClient.Width() / 2, rectClient.Height() / 2));
D2D1::Matrix3x2F scale = D2D1::Matrix3x2F::Scale(0.6f, 0.6f);

m_Target->SetTransform(trans * rotate * skew * scale);

m_FillBrush->SetColor(ColorF(ColorF::Black));
m_Target->DrawTextW((LPCTSTR)m_Text, m_Text.GetLength(), m_TextFormat.Get(), &rect, m_FillBrush.Get());

Other articles in the series

 

Direct2D Tutorial Part 2: Basic Shapes

Table of Content

The example code is hosted at Github.

Introduction

In this article, we’ll look at how to draw lines and basic shapes in Direct2D. The prerequisite of the article is the knowledge to set up the RenderTarget. If you haven’t got a clue what RenderTarget is, please go read the RenderTarget article first and then come back to read this article.

Stroke Style

In order to draw shapes, a helper function, CreateStrokeStyle(), is needed to create a ID2D1StrokeStyle to define the cap and join style. ID2D1StrokeStyle is a device-independent resource. In the StrokeStyleProperties constructor, the style of the start cap, end cap, and dash cap is round as indicated by 3 D2D1_CAP_STYLE_ROUND arguments. The specified dash cap style is unused because the stroke style does not have any dash as specified by D2D1_DASH_STYLE_SOLID and the line join style as round as indicated by D2D1_LINE_JOIN_ROUND. The reader can experiment with other cap and join styles by modifying the CreateStrokeStyle().

ComPtr CD2DShapesDlg::CreateStrokeStyle()
{
    ComPtr strokeStyle;

    HR(FactorySingleton::GetGraphicsFactory()->CreateStrokeStyle(
        D2D1::StrokeStyleProperties(
            D2D1_CAP_STYLE_ROUND,
            D2D1_CAP_STYLE_ROUND,
            D2D1_CAP_STYLE_ROUND,
            D2D1_LINE_JOIN_ROUND,
            0.0f,
            D2D1_DASH_STYLE_SOLID,
            0.0f),
        nullptr,
        0,
        strokeStyle.GetAddressOf()
    ));

    return strokeStyle;
}

Solid Color Brush

The two solid color brushes used for stroke and fill are created, using CreateSolidColorBrush(). Because brush is a device-dependent resource, so they should be created inside CreateDeviceResources(). Its color can be changed on the fly with SetColor() member function, as you shall see later.

void CD2DShapesDlg::CreateDeviceResources()
{
    HR(m_Target->CreateSolidColorBrush(ColorF(ColorF::Red),
        m_StrokeBrush.ReleaseAndGetAddressOf()));
    HR(m_Target->CreateSolidColorBrush(ColorF(ColorF::Yellow),
        m_FillBrush.ReleaseAndGetAddressOf()));
}

Line

d2d_line

A line is drawn with DrawLine() from one start point and one end point. The line thickness is specified as 8 pixels.

m_StrokeBrush->SetColor(ColorF(ColorF::Black));

ComPtr stroke = CreateStrokeStyle();

m_Target->DrawLine(
    Point2F(10.0f, 40.0f),
    Point2F(110.0f, 40.0f),
    m_StrokeBrush.Get(),
    8.0f,
    stroke.Get());

Rectangle

d2d_rect

The stroke brush and fill brush are set to red and yellow colors respectively. A rect variable is used to specify the rectangle boundary. The rectangle whose outline is drawn with DrawRectangle() and filled with yellow color from FillRectangle(). The 3rd parameter of DrawRectangle() is the line thickness which is 10 pixels.

m_StrokeBrush->SetColor(ColorF(ColorF::Red));
m_FillBrush->SetColor(ColorF(ColorF::Yellow));

ComPtr stroke = CreateStrokeStyle();
const D2D1_RECT_F rect = RectF(10, 10, 110, 60);
m_Target->DrawRectangle(
    rect,
    m_StrokeBrush.Get(),
    10.0f,
    stroke.Get());

m_Target->FillRectangle(
    rect,
    m_FillBrush.Get());

Rounded Rectangle

d2d_round_rect

I often used rounded rectangles to draw the block diagrams used in my articles because the rectangles look more pleasing this way. Creators of Direct2D also knew the rounded rectangle is a common drawing primitive and decided to provide this convenience to its user. A roundedRect variable is used to specify the rectangle boundary (from rect variable), and x and y radius of the rounded corner. The outline and fill is done with DrawRoundedRectangle() and FillRoundedRectangle(). The 3rd parameter of DrawRoundedRectangle() is the line thickness which is 10 pixels.

m_StrokeBrush->SetColor(ColorF(ColorF::LightSeaGreen));
m_FillBrush->SetColor(ColorF(ColorF::Yellow));

ComPtr stroke = CreateStrokeStyle();
const D2D1_RECT_F rect = RectF(10, 10, 110, 60);

const D2D1_ROUNDED_RECT roundedRect = D2D1::RoundedRect(
    rect,
    10.f,
    10.f
);

m_Target->DrawRoundedRectangle(
    roundedRect,
    m_StrokeBrush.Get(),
    10.0f,
    stroke.Get());

m_Target->FillRoundedRectangle(
    roundedRect,
    m_FillBrush.Get());

Triangle

d2d_triangle

Unfortunately, there is no function in Direct2D to draw a triangle. We can always make use of ID2D1PathGeometry to create any shapes we desired. GenTriangleGeometry() is a helper function to create triangle geometry which is then drawn. ID2D1PathGeometry variable is created with CreatePathGeometry(). Then that ID2D1PathGeometry variable, m_pPathGeometry, is used to open a ID2D1GeometrySink variable, pSink. BeginFigure() is called on pSink to specify the first point and drawing to be filled. 2 line primitives are added with AddLine from 2 points. D2D1_FIGURE_END_CLOSED tells EndFigure() to close the first point and last point with a line. If you have already closed the first point and last point yourself by specifying the last point to be the same as the first point and you may not want EndFigure() to draw a line between them, then D2D1_FIGURE_END_OPEN should be specified instead.

ComPtr CD2DShapesDlg::GenTriangleGeometry(D2D1_POINT_2F pt1, D2D1_POINT_2F pt2, D2D1_POINT_2F pt3)
{
    ID2D1GeometrySink* pSink = NULL;
    HRESULT hr = S_OK;
    ComPtr m_pPathGeometry;
    // Create a path geometry.
    if (SUCCEEDED(hr))
    {
        hr = FactorySingleton::GetGraphicsFactory()->CreatePathGeometry(m_pPathGeometry.ReleaseAndGetAddressOf());

        if (SUCCEEDED(hr))
        {
            // Write to the path geometry using the geometry sink.
            hr = m_pPathGeometry->Open(&pSink);

            if (SUCCEEDED(hr))
            {
                pSink->BeginFigure(
                    pt1,
                    D2D1_FIGURE_BEGIN_FILLED
                );

                pSink->AddLine(pt2);


                pSink->AddLine(pt3);

                pSink->EndFigure(D2D1_FIGURE_END_CLOSED);

                hr = pSink->Close();
            }
            SafeRelease(&pSink);
        }
    }
    return m_pPathGeometry;
}

The triangle outline and fill are drawn with DrawGeometry() and FillGeometry() respectively.

m_StrokeBrush->SetColor(ColorF(ColorF::Purple));
m_FillBrush->SetColor(ColorF(ColorF::Yellow));

ComPtr stroke = CreateStrokeStyle();
ComPtr geometry = GenTriangleGeometry(
    Point2F(60, 10), Point2F(110, 70), Point2F(10, 70));
m_Target->DrawGeometry(
    geometry.Get(),
    m_StrokeBrush.Get(),
    10.0f,
    stroke.Get());

m_Target->FillGeometry(
    geometry.Get(),
    m_FillBrush.Get());

Circle

d2d_circle

A circle can be drawn with ellipse drawing functions. In the code below, an ellipse variable, ell, is created with a center point and x and y radius. The circle outline and fill are drawn with DrawEllipse() and FillEllipse() respectively.

m_StrokeBrush->SetColor(ColorF(ColorF::BlueViolet));
m_FillBrush->SetColor(ColorF(ColorF::Yellow));

ComPtr stroke = CreateStrokeStyle();
const D2D1_ELLIPSE ell = Ellipse(Point2F(50.0f, 50.0f), 40, 40);
m_Target->DrawEllipse(
    ell,
    m_StrokeBrush.Get(),
    10.0f,
    stroke.Get());

m_Target->FillEllipse(
    ell,
    m_FillBrush.Get());

 

Other articles in the series

 

Optimizations That You Have to Wring From WebAssembly

Introduction

First of all, I want to put up a disclaimer: I am not a WebAsssembly expert. All the 5 tips mentioned in this article are gained from my C++ OpenGL slideshow application. I have to admit I have no working knowledge on Blazor, an implementation of Webassembly for C# and .NET. And I am not exactly sure if the tips are relevant to Blazor, most likely they do not apply to Blazor. Let us get started now.

Inline Shader Code with C++11 string literals

In a typical OpenGL project, shader code are stored separately in the file from the C++ source code. For the uninitiated, WebGL 1.0 standard is based on OpenGL 2.0 ES and these 2 are very similar in that you can translate OpenGL 2.0 ES calls into WebGL 1.0 one to one parity. For OpenGL 2.0 ES and its corresponding WebGL 1.0, there are 2 types of shaders, namely vertex shader and fragment shader. In Direct3D terminology, fragment shader’s counterpart is pixel shader but this name is not exactly right because this shader does not operate on pixel but visible texels (short for texture pixels). But most people prefer the name pixel shader as opposed to fragment shader. Let me show you a simple vertex shader followed by the fragment shader.

attribute vec2 a_texCoord;
varying vec2 v_texCoord;
void main()
{
    gl_Position = WorldViewProjection * vec4(a_position, 1.0);
    v_texCoord = a_texCoord;
}

This is a simple fragment shader with a float variable called s_alpha which controls the transparency of texel.

varying vec2 v_texCoord;
uniform sampler2D s_texture;
uniform float s_alpha;
void main()
{
    vec4 color = texture2D( s_texture, v_texCoord );
    color.a = color.a * s_alpha;
    gl_FragColor = color;
}

These are the same previous shaders stored in vert_shader and frag_shader variables in classic C++ string literals. Notice every line is enclosed in quotes and ended with a newline!

const char* vert_shader = 
"vert(uniform mat4 WorldViewProjection;                                \n"
"attribute vec3 a_position;                                            \n"
"attribute vec2 a_texCoord;                                            \n"
"varying vec2 v_texCoord;                                              \n"
"void main()                                                           \n"
"{                                                                     \n"
"    gl_Position = WorldViewProjection * vec4(a_position, 1.0);        \n"
"    v_texCoord = a_texCoord;                                          \n"
"}                                                                     \n";

const char* frag_shader = 
"frag(varying vec2 v_texCoord;                                         \n"
"uniform sampler2D s_texture;                                          \n"
"uniform float s_alpha;                                                \n"
"void main()                                                           \n"
"{                                                                     \n"
"   vec4 color = texture2D( s_texture, v_texCoord );                   \n"
"   color.a = color.a * s_alpha;                                       \n"
"   gl_FragColor = color;                                              \n"
"}                                                                     \n";

These are the same 2 shaders stored in vert_shader and frag_shader variables, this time in modern C++11 string literals. You can see the code is cleaner. By inlining the shader code, the application does not have to download and handle the shader code separately, you save 2 download connections for every OpenGL object which can add up to many. For my application, I am saving 50 downloads. Why inlining the shader into C++ code? Most of the time, when you change the shader code, more often than not, you also have to modify the C++ code that interacts with it. The only downside I could see to inlining is when there are many lines of shader code and a compilation error occur, the developer may have a hard time discerning out which is the offending line number.

const char* vert_shader = 
R"vert(uniform mat4 WorldViewProjection;
attribute vec3 a_position;
attribute vec2 a_texCoord;
varying vec2 v_texCoord;
void main()
{
    gl_Position = WorldViewProjection * vec4(a_position, 1.0);
    v_texCoord = a_texCoord;
}
)vert";

const char* frag_shader = 
R"frag(varying vec2 v_texCoord;
uniform sampler2D s_texture;
uniform float s_alpha;
void main()
{
    vec4 color = texture2D( s_texture, v_texCoord );
    color.a = color.a * s_alpha;
    gl_FragColor = color;
}
)frag";

Run on GPU whenever possible

h264_small

Whenever it is possible, write as much code to run on the GPU, instead of the CPU. My animating sinewave is calculated on the GPU. Asm.js, at the time of writing, was strictly only single-threaded and for GPUs, even the low-ended ones have lots of simple threads to spread the floating-point calculations among themselves. See the amount of bytes generated each second below: When the sinewave movement calculation is done on CPU, a total of 1,536,000 bytes has to be sent to the GPU on every second.

1 float = 4 bytes
1 vertex = 3 floats = 12bytes
1 quad = 4 vertex = 12 floats = 64bytes
1 quad = 64bytes/2 = 32bytes
800 quad = 800*32 = 25600 bytes
60 frames per second = 60 * 25600 = 1536000

Video Link of Sinewave

Compression

Compress your assets/wasm file with gzip compression. Do not compress files that are already compressed, like JPEG and PNG. Images can be appended into one big file and loaded into memory if the image library supports loading from memory with the correct offsets.

In-situ decryption.

To protect the assets from being stolen, use an encryption algorithm that can decrypt file quickly and in place, instead of decrypting into a new destination file. Note simple encryption, though fast, can only keep the casual end-user at bay, it does not totally prevent determined hackers from reverse engineering your source code to find out the encryption key to steal your assets.

Not using STL streams

Do not include headers like iostream and sstream. It bloat your executable size by 100KB in wasm and 400KB in asm.js. Replace calls to std::cout and std::ostringstream with printf and sprintf respectively.

Related Articles