Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement a DirectX 11 renderer #675

Draft
wants to merge 121 commits into
base: master
Choose a base branch
from

Conversation

hyblocker
Copy link

@hyblocker hyblocker commented Sep 23, 2024

I've implemented a DirectX 11 backend for RmlUI. Currently this has feature parity with the SDL renderer, but I plan on extending it to match the GL3 renderer in the near future (hence I'm marking this as a draft PR).

As for backends, currently this only has an implementation with the Win32 backend. I'll also be providing a GLFW backend at a later point.

I've introduced a pair of functions pointers only in the DX11 RenderInterface for this backend to make it more flexible and easily consumable by end users. As the current backends only support uncompressed targa files for images, and in practice one would likely want to connect it to a texture loader or something more sophisticated such as stb_image, I defined the following functions:

// Use these typedefs to overload the default implementation with another image loader so that you can handle more than uncompressed TGA if you wish.
// Must define both the load and free functions. See RenderInterface_DX11::
typedef void (*pfnLoadTextureRaw)(const Rml::String& filename, int* pWidth, int* pHeight, uint8_t** pData, size_t* pDataSize);
typedef void (*pfnFreeTextureRaw)(uint8_t* pData);

Where a user may override these as follows for example:

// Initialize the window but don't show it yet.
HWND window_handle = InitializeWindow(data->instance_handle, name, width, height, allow_resize);
if (!window_handle)
    return false;

if (!CreateDeviceD3D(window_handle)) {
    ::CloseWindow(window_handle);
    return false;
}

// Assign STB image loader to render interface
data->render_interface.LoadTextureFromFileRaw = &LoadTexture;
data->render_interface.FreeTextureFromFileRaw = &FreeTexture;
stbi_set_unpremultiply_on_load(true);
stbi_convert_iphone_png_to_rgb(true);

data->render_interface.Init(data->d3d_resources.pd3dDevice, data->d3d_resources.pd3dDeviceContext);

data->window_handle = window_handle;
data->system_interface.SetWindow(window_handle);

// Now we are ready to show the window.
::ShowWindow(window_handle, SW_SHOW);
::SetForegroundWindow(window_handle);
::SetFocus(window_handle);

The renderer will fallback to the Targa image loader if the user doesn't override the loader with their own one.

For completeness, below is the entire STB image loader I've been testing with. I have converted the sample assets to PNG to verify that it works correctly.

// Add somewhere at the top of the backend
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#include <RmlUi/Core/FileInterface.h>

// STB image based loaders
void LoadTexture(const Rml::String& filename, int* pWidth, int* pHeight, uint8_t** pData, size_t* pDataSize);
void FreeTexture(uint8_t* pData);

// STB loader implementation

void LoadTexture(const Rml::String& filename, int* pWidth, int* pHeight, uint8_t** pData, size_t* pDataSize)
{
    Rml::FileInterface* file_interface = Rml::GetFileInterface();
    Rml::FileHandle file_handle = file_interface->Open(filename);
    if (!file_handle)
    {
        *pData = nullptr;
        return;
    }

    int texture_width, texture_height, num_channels;
    *pData = stbi_load_from_file((FILE*)file_handle, &texture_width, &texture_height, &num_channels, 0);

    if (*pData != nullptr)
    {
        *pWidth = texture_width;
        *pHeight = texture_height;

        // Compute number of elements in texture
        *pDataSize = texture_width * texture_height * num_channels;

        uint8_t* dataView = *pData;

        // Pre-multiply the data, otherwise it will be loaded incorrectly
        for (int i = 0; i < *pDataSize; i += 4)
        {
            dataView[i + 0] = (uint8_t)((dataView[i + 0] * dataView[i + 3]) / 255);
            dataView[i + 1] = (uint8_t)((dataView[i + 1] * dataView[i + 3]) / 255);
            dataView[i + 2] = (uint8_t)((dataView[i + 2] * dataView[i + 3]) / 255);
        }
    }
}

void FreeTexture(uint8_t* pData)
{
    stbi_image_free(pData);
}

Not usable in current state.
This allows users to render to a render target which may either be the swapchain (in most cases it will be) or a render texture which may then be used elsewhere (eg in a 3D scene).
We should provide a image loader (in this case the uncompressed TGA loader), but should also allow users using this renderer backend to overload it with their own one. E.g. a user may have libpng in their project and they want to hook libpng into the load image function to load PNGs.
@igorsegallafa
Copy link
Contributor

Ohh nice PR @hyblocker, I was also trying to implement DX11 renderer by myself.
Do you know exactly what is missing to finish this PR or is there anything I could help with? Thank you!

@hyblocker
Copy link
Author

The PR is practically ready for merging, I'm waiting for the weekend to find the time to implement the GLFW backend and then I'll move this from draft status.

Transient buffers should still be investigated if possible but my schedule has been getting busier lately, so I think it makes more sense to merge it as a separate PR later.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backends Platforms and renderers
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants