Modernizing Raytracing In A Weekend

Ray Tracing In A Weekend Repo

My Modernized Ray Tracing In A Weekend Repo

The improvements I made to the original code:

  • Convert all raw allocation to shared_ptr to prevent memory leaks.
  • Replace non-reentrant rand() with a copy of C++11 random number generator in every thread using thread_local to avoid sharing and locking.
  • Replace the text-based image format with stb_image saving to PNG.
  • Parallelize with C++17 parallel for_each, achieving a more than 5X performance on my 6 core CPU.

CreateCertificateContext Failed

CertCreateCertificateContext failure can be fixed with first exporting the certificate to DER Encoded Binary X.509 (cer file) manually.

std::vector<BYTE> vec;

PCCERT_CONTEXT pCertContext = NULL;
if (pCertContext = ::CertCreateCertificateContext(
    PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, 
    vec.data(), 
    vec.size()))
{
    std::cout << "Success!\n";
}
else
{
    std::cout << "Failure!\n";
}

If one want to forgo the manual export option, CryptQueryObject can be a solution. Note: CryptQueryObject is marked deprecated by Microsoft but I still have to support older OS like WinXP, so using a newer API is out of options.

CERT_BLOB blob = {};
blob.pbData = vec.data();
blob.cbData = vec.size();

DWORD dwContentType = 0;
pCertContext = NULL;

if(::CryptQueryObject(
    CERT_QUERY_OBJECT_BLOB, 
    reinterpret_cast<LPCVOID>(&blob), 
    CERT_QUERY_CONTENT_FLAG_ALL, 
    CERT_QUERY_FORMAT_FLAG_ALL, 
    0, 
    NULL, 
    &dwContentType, 
    NULL, 
    NULL, 
    NULL, 
    reinterpret_cast<LPCVOID*>(&pCertContext)))
{
    std::cout << "Success!\n";
}
else
{
    std::cout << "Failure!\n";
}

Last but not least, remember to free the certificate context!

if(pCertContext)
    CertFreeCertificateContext(pCertContext);

Avoid Default Clause in Switch Case for Enum

Do not define a default clause for enum in switch statement! Say in future, if another fruit type is added to the enum, default clause shall cause the C++ compiler not to warn you that you have not handled the case for the new fruit.

enum class Fruit
{
    Apple,
    Orange
};

Fruit fruit = ...;
switch(fruit)
{
    case Fruit::Apple: ... 
        break;
    case Fruit::Orange: ... 
        break;
    default: ... // <-- Do not ever define a default clause for enum!
}

As a point of interest, first item under Bad Coding Practices is CWE-478: Missing Default Case in Switch Statement advises programmers to put default clause in their switch statement.

Private Inheritance

In private inheritance, the base class public access members are still public access in the derived class. Therefore the derived class still can call the base class functions and access its public data. But the user who instantiates the derived class, cannot access the public member of the base class, hence private inheritance.

  • Public inheritance is a “is-a” relationship.
  • Private inheritance is a “implemented-in-terms-of” relationship.

A useful example, is I like .NET string class and like a C++ string class with the same C# methods but I do not want to reimplement from scratch, so I derived from std::wstring with private inheritance to make use of its functionality, so that user of my string class cannot access the base class’s std::wstring functions to avoid the confusion and I also do not want the user to be exposed to underlying std::wstring details.

class MyString : private std::wstring
{
};

There is an excellent blog about this topic: C++ Tutorial: Private Inheritance.

Digitally Signing Without Worrying About Cert Expiry

If you want your executable’s signature to be still valid after certificate validity period, you have to digitally sign it with a timestamp from one of the timestamp websites with /tr switch before its expiry. In my case, I choose timestamp.digicert.com. This command is for Windows only.

signtool.exe sign /tr http://timestamp.digicert.com /f Cert.pfx /p password /n "Your Name" /d "Your Description" YourApp.exe

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)