Skip to content

Instantly share code, notes, and snippets.

@nrbnlulu
Last active April 6, 2025 12:01
Show Gist options
  • Save nrbnlulu/9d8861a882f631d08c829f12fe7d8085 to your computer and use it in GitHub Desktop.
Save nrbnlulu/9d8861a882f631d08c829f12fe7d8085 to your computer and use it in GitHub Desktop.
ANGLE DirectX Interop (textures for flutter)

ANGLE's Mechanism for DirectX Interoperability

Introduction: Bridging Graphics APIs with ANGLE

The Almost Native Graphics Layer Engine (ANGLE) stands as a pivotal open-source project developed by Google, designed to facilitate cross-platform graphics rendering.¹ At its core, ANGLE functions as a graphics engine abstraction layer, adept at translating OpenGL ES 2 and 3 API calls into corresponding calls for a variety of native graphics APIs, including DirectX 9, DirectX 11, OpenGL, Vulkan, and Metal.¹ This translation capability is particularly significant for bringing high-performance OpenGL compatibility to the Microsoft Windows operating system and to web browsers like Chromium. By converting OpenGL calls into Direct3D, ANGLE leverages the often superior driver support available for DirectX on Windows systems.¹ Its utility is underscored by its integration into major web browsers such as Google Chrome, Mozilla Firefox, Microsoft Edge, and Apple's Safari, as well as game engines like Godot and numerous other applications.¹ Notably, Microsoft actively contributes to the ANGLE project, maintaining a specific branch to cater to the needs of Windows Store application developers.⁴

The widespread adoption of ANGLE across diverse software platforms emphasizes its critical role in the modern graphics ecosystem, effectively addressing the challenges of cross-platform compatibility, especially within the Windows environment. This broad utilization suggests a substantial demand for functionalities that further enhance the flexibility of graphics development, such as the ability to interoperate with other graphics technologies. One such crucial capability is ANGLE's support for interoperability with DirectX code, achieved through the use of shared Direct3D textures.⁵ This interop feature enables the seamless integration of rendering outputs from different DirectX technologies, including Direct3D 11, Direct2D, Win2D, and even video playback, within an OpenGL ES scene rendered by ANGLE.⁵ A key advantage of this functionality is the power it provides to developers to leverage existing DirectX rendering pipelines or specific, specialized DirectX features within an OpenGL ES environment managed by ANGLE.⁵ This capability extends ANGLE's usefulness beyond mere OpenGL ES compatibility, opening doors to hybrid rendering approaches that can potentially yield performance improvements or allow the utilization of unique features available in different graphics APIs.

Direct3D 9 Direct3D 11 Desktop GL GL ES Vulkan Metal
complete complete complete complete complete complete
complete complete complete complete complete complete

The Foundation: Shared DirectX Textures

A fundamental concept underpinning ANGLE's DirectX interoperability is the notion of shared resources within the DirectX API. DirectX provides mechanisms for creating resources, such as textures, that can be accessed and utilized by multiple devices or rendering contexts.⁶ This ability to share resources is not exclusive to ANGLE but represents a broader feature of DirectX, serving as a cornerstone for various inter-process communication scenarios and for enabling different stages of a rendering pipeline to operate on the same underlying data.⁶ In the specific context of ANGLE, these shared textures act as the essential bridge facilitating communication and data exchange between the OpenGL ES and DirectX rendering environments.⁵

The creation of a DirectX texture as a "shared" resource makes its underlying memory accessible not only from different DirectX devices but also, significantly, from other graphics APIs like OpenGL ES through the intermediary of ANGLE.⁵ ANGLE plays a crucial role in this process by providing the necessary mechanisms to establish EGL surfaces that are directly backed by these shared DirectX textures.⁵ Once this linkage between the shared texture and an EGL surface is established, the OpenGL ES rendering context can then render its output directly into or sample data from this shared memory. Conversely, the DirectX environment retains the ability to access and utilize the contents of the same texture.⁵ This approach of using shared textures provides a common memory space accessible to both APIs, effectively eliminating the need for potentially performance-intensive data copies between them. This direct memory sharing is particularly critical for achieving efficient interoperability, as copying large texture datasets between different graphics contexts can introduce significant overhead and negatively impact performance.

Deep Dive into DirectX 11 Sharing Mechanisms

Within the realm of DirectX 11, the ability to create shareable resources hinges on the utilization of specific flags during resource creation. One such crucial flag is D3D11_RESOURCE_MISC_SHARED. This flag plays a pivotal role in enabling the sharing of texture data between distinct Direct3D devices.⁵ When creating a D3D11 Texture2D, this flag is included within the MiscFlags member of the D3D11_TEXTURE2D_DESC structure, which is passed as a parameter to functions like ID3D11Device::CreateTexture2D.⁵ It is important to note that the sharing capability facilitated by this flag is specifically limited to 2D textures that do not utilize mipmapping.⁶ Furthermore, the D3D11_RESOURCE_MISC_SHARED flag cannot be used in conjunction with the D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX flag, as these two are mutually exclusive.⁶ This exclusivity suggests that different synchronization mechanisms might be appropriate depending on the specific sharing scenario. It's also worth noting that certain types of Direct3D devices, namely WARP and REF devices, do not provide support for shared resources created using this flag. Attempting to create a shared resource on these devices will result in an E_OUTOFMEMORY error.⁶ This restriction might influence the compatibility of ANGLE's interop features across different hardware or software rendering implementations.

While the D3D11_RESOURCE_MISC_SHARED flag is fundamental for enabling texture sharing, it's important to consider certain limitations and recommendations surrounding its use. Current DirectX documentation suggests that for retrieving the handle to a shared resource, particularly in newer applications, it is preferable to use IDXGIResource1::CreateSharedHandle instead of the older GetSharedHandle method.⁹ This recommendation often goes hand-in-hand with creating the texture using the D3D11_RESOURCE_MISC_SHARED_NTHANDLE flag, although this particular flag is not explicitly mentioned within the ANGLE interop documentation. The evolution of DirectX sharing mechanisms, with the emphasis on CreateSharedHandle, indicates potential differences in how ANGLE might interact with varying versions or features of DirectX. Developers should therefore consult the most current ANGLE documentation to ascertain the recommended approach for their specific development environment. Additionally, it's crucial to understand that simply creating a texture with the D3D11_RESOURCE_MISC_SHARED flag does not inherently guarantee hardware-level protection for the underlying memory allocation. Some implementations might require that the Digital Rights Management (DRM) components are initialized before any guarantees of content protection can be made.⁶ Finally, there are specific restrictions on the usage of this flag in combination with certain values for D3D11_USAGE (which defines how the texture will be used) and D3D11_BIND_FLAG (which specifies how the texture will be bound to the pipeline), as well as with any CPU access flags.⁶ These limitations underscore the need for careful consideration of texture properties when aiming for interoperability between ANGLE and DirectX.

Obtaining the Shared Handle

The process of enabling interoperability between ANGLE and DirectX via shared textures necessitates obtaining a shareable handle to the DirectX texture. This is where the IDXGIResource interface plays a crucial role. IDXGIResource is a component of the DirectX Graphics Infrastructure (DXGI) and provides the fundamental capabilities for resource sharing, also serving to identify the memory location of a given resource.¹² A significant aspect of this interface is its broad support across different versions of Direct3D; any Direct3D object that supports either the ID3D10Resource or the ID3D11Resource interface will also inherently support IDXGIResource.¹² To gain access to the IDXGIResource interface from a specific Direct3D texture object, such as an ID3D11Texture2D, the QueryInterface method is employed.⁵ The reliance on the DXGI infrastructure for managing shared resources highlights that this cross-API sharing capability is handled at a foundational level within DirectX, providing a consistent and unified approach irrespective of the specific Direct3D version in use.

Once the IDXGIResource interface pointer is obtained from the shared DirectX texture, the next step involves retrieving the actual shareable handle. This is accomplished by calling the GetSharedHandle method on the IDXGIResource interface.⁵ This method returns a Windows HANDLE, which serves as a unique identifier for the shared texture.⁵ It's important to remember that the texture must have been initially created with the D3D11_RESOURCE_MISC_SHARED flag for GetSharedHandle to successfully return a valid handle.⁹ However, as noted previously, the official DirectX documentation now recommends the use of IDXGIResource1::CreateSharedHandle as the preferred method for obtaining shared handles, especially when dealing with NT handles.⁹ Despite this recommendation, the ANGLE documentation still refers to the use of GetSharedHandle in the context of DirectX interop. This discrepancy suggests a potential area where developers might need to exercise caution and potentially adapt their approach based on the specific versions of ANGLE and DirectX they are targeting. It is possible that ANGLE's internal implementation or its compatibility with different DirectX feature levels dictates the continued use of GetSharedHandle in its examples and documentation.

EGL Integration with ANGLE

EGL serves as a critical intermediary in the realm of cross-platform graphics, acting as an interface between Khronos rendering APIs, such as OpenGL ES, and the underlying native platform's windowing system.¹ Its responsibilities encompass a range of essential tasks, including the creation of graphics contexts, the management of rendering surfaces, and facilitating interoperability between different Khronos Group graphics APIs.¹³ ANGLE itself provides a complete implementation of the EGL 1.5 specification.² The existence of EGL as an abstraction layer is fundamental to ANGLE's ability to interact with platform-specific resources like DirectX shared textures on Windows. It provides a platform-agnostic interface that allows ANGLE, which implements OpenGL ES, to communicate with the underlying DirectX system without needing to have platform-specific code for every possible graphics API it supports. This design significantly enhances ANGLE's maintainability and extensibility.

A key function within EGL that enables the integration of shared DirectX textures with ANGLE is eglCreatePbufferFromClientBuffer. This function is specifically designed to create a pixel buffer surface (pbuffer) based on a client-allocated buffer.⁵ In the context of ANGLE's DirectX interop, the target parameter of this function is set to the specific value EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE.⁵ This value acts as a crucial identifier, informing the EGL implementation within ANGLE that the client buffer being provided is a shareable handle to a DirectX 11 2D texture. The client_buffer parameter then receives the shared HANDLE that was previously obtained from the DirectX texture using the IDXGIResource::GetSharedHandle method.⁵ Finally, the attrib_list parameter is used to specify various attributes of the pbuffer surface, such as its width, height, the texture target (which is typically EGL_TEXTURE_2D), and the texture format (often EGL_TEXTURE_RGBA).⁵ The existence of the dedicated EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE target within eglCreatePbufferFromClientBuffer clearly demonstrates a direct and specific integration point between the EGL API (as implemented by ANGLE) and the underlying DirectX technology. This allows ANGLE to correctly interpret and utilize the provided DirectX shared handle to create an EGL surface that is directly linked to the DirectX texture's memory.

Connecting EGL to OpenGL

To effectively utilize the shared DirectX texture within an OpenGL ES rendering context managed by ANGLE, it is necessary to establish a connection between the EGL surface (created from the shared texture) and a corresponding OpenGL texture object. This process involves several steps, starting with the creation of the OpenGL texture itself. First, an OpenGL texture name is generated using the glGenTextures(1, &glTex) function.⁵ This function returns a unique identifier for the new texture. Following the generation of the texture name, it needs to be bound to a specific texture target. In this case, the glBindTexture(GL_TEXTURE_2D, glTex) function is called, which binds the generated texture name to the GL_TEXTURE_2D target.⁵ Once bound, subsequent OpenGL operations on the GL_TEXTURE_2D target will affect this texture object. At this stage, texture parameters, such as filtering options (e.g., using glTexParameteri), can also be set to control how the texture is sampled during rendering.⁵ This step is crucial as it demonstrates that even though the underlying data is shared via DirectX, OpenGL ES operates with its own distinct texture objects. ANGLE's role here is to manage the intricate connection between these OpenGL constructs and the shared DirectX memory.

The final step in linking the EGL surface to the OpenGL texture involves the use of the eglBindTexImage function. Specifically, the call eglBindTexImage(mEglDisplay, surface, EGL_BACK_BUFFER) serves this purpose.⁵ The surface parameter refers to the EGL surface that was previously created from the shared DirectX texture using eglCreatePbufferFromClientBuffer. The EGL_BACK_BUFFER parameter indicates that it is the back buffer of the EGL surface that should be bound to the currently active OpenGL texture.⁵ This function effectively establishes the crucial link, making the content of the shared DirectX texture accessible as the texture data for the OpenGL texture object. Once this binding is in place, the OpenGL ES rendering pipeline can seamlessly sample data from or render directly into the memory region that is also accessible by the DirectX environment. The eglBindTexImage function thus represents the culmination of the process, bridging the gap between the EGL layer (through which ANGLE interacts with the underlying platform) and the OpenGL ES rendering pipeline, enabling cross-API data sharing and interoperability.

Methods for Utilizing OpenGL ES Content in DirectX

Once the shared DirectX texture is accessible within both the OpenGL ES (via ANGLE) and DirectX environments, there are two primary methods through which content rendered using OpenGL ES can be utilized within a DirectX rendering pipeline.

One approach involves rendering the OpenGL ES scene to a Framebuffer Object (FBO).⁵ This technique begins with the creation of an FBO within the OpenGL ES context. Subsequently, the OpenGL texture that is backed by the shared DirectX texture is attached as a color attachment to this FBO.⁵ Following this attachment, the OpenGL ES scene is rendered, with the FBO set as the rendering target.⁵ The key advantage of this method is that the output of the OpenGL ES rendering process is directly written into the shared texture's memory. This texture, now containing the rendered OpenGL ES content, can then be readily accessed and used within the DirectX rendering pipeline as a texture resource. The use of an FBO provides a well-structured and efficient way to manage off-screen rendering in OpenGL ES. In the context of interop, it allows for a clear separation and execution of the OpenGL ES rendering pass before its results are integrated into the DirectX scene. FBOs are a standard and optimized mechanism for performing off-screen rendering in OpenGL ES, making this a robust approach for ANGLE to render OpenGL ES content into the shared texture in a controlled manner.

The second method for utilizing OpenGL ES content in DirectX involves directly employing the eglMakeCurrent function.⁵ This approach entails setting the EGL surface (which was created from the shared DirectX texture) as the current rendering surface. This is achieved by calling eglMakeCurrent with the appropriate display, surface, and context parameters.⁵ Once this EGL surface is set as the current rendering target, any subsequent OpenGL ES rendering commands will be directed to the default framebuffer associated with this surface. Importantly, this default framebuffer is backed by the shared DirectX texture. Consequently, the OpenGL ES content is rendered directly into the memory of the shared texture.⁵ After the OpenGL ES rendering operations are complete, the DirectX code can then directly access the rendered content from the shared texture. This method offers a more direct rendering pathway compared to using an FBO. It might be simpler to implement for certain scenarios where complex off-screen rendering setups are not required. However, it's important to note that using eglMakeCurrent changes the current rendering context, which could have implications for the overall management of the rendering pipeline, especially in more complex applications involving multiple rendering passes or contexts.

Synchronization is Key: Flushing Rendering Commands

When sharing texture data between different graphics APIs like OpenGL ES (via ANGLE) and DirectX, ensuring data consistency is paramount. Without proper synchronization, one API might attempt to access or utilize data that has not been fully rendered or updated by the other, leading to visual inconsistencies, artifacts, or incorrect rendering outcomes.⁵ The necessity for synchronization arises from the inherent asynchronous nature of GPU rendering. Commands submitted to the GPU are not necessarily executed immediately, and different graphics APIs may maintain their own independent command queues and execution models.²⁶

To manage this asynchronicity and ensure that rendering operations are completed before the shared texture is accessed by the other API, both OpenGL ES and DirectX provide mechanisms for flushing rendering commands. In OpenGL ES, the glFlush() function serves to push all currently buffered OpenGL commands to the driver for execution. However, it's crucial to understand that glFlush() returns immediately and does not block the CPU while the GPU processes these commands.⁵ A more forceful synchronization mechanism in OpenGL is glFinish(), which does block the CPU until all previously issued OpenGL commands have been fully executed by the GPU.²⁷ In the DirectX 11 API, the equivalent function to glFlush() is the ID3D11DeviceContext::Flush method. This method also sends any queued commands to the GPU for processing.²⁸ Similar to glFlush(), ID3D11DeviceContext::Flush operates asynchronously and returns control to the CPU before the GPU has necessarily completed the submitted commands. For scenarios demanding stricter synchronization, such as when one API's rendering output directly feeds into the other's processing, more explicit synchronization primitives like fences or events, which are available in both OpenGL ES and DirectX, might be required.²⁹ These primitives allow for signaling and waiting on the completion of specific rendering tasks. For instance, Direct2D also provides a ID2D1RenderTarget::Flush method, although it's important to note that this might not necessarily flush the underlying Direct3D device context.³² While ANGLE's documentation often emphasizes the use of flushing commands like glFlush() to ensure that rendering is complete before the shared texture is accessed, developers should be mindful of the asynchronous nature of these commands and consider more robust synchronization techniques in complex interop scenarios to prevent potential race conditions and ensure data integrity.

Conclusion: Best Practices and Considerations for ANGLE DirectX Interop

Achieving seamless interoperability between OpenGL ES (via ANGLE) and DirectX involves a carefully orchestrated sequence of steps. The cornerstone of this process is the creation of shared DirectX textures, which are enabled by setting the D3D11_RESOURCE_MISC_SHARED flag during texture creation. These shared textures then serve as the common ground for data exchange between the two graphics APIs. EGL plays a vital role in facilitating this sharing within the ANGLE ecosystem. The eglCreatePbufferFromClientBuffer function, when used with the EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE target and the shared DirectX texture handle, allows for the creation of an EGL surface that is directly backed by the DirectX texture's memory. Subsequently, the eglBindTexImage function is used to associate this EGL surface with an OpenGL texture object, making the shared DirectX texture accessible within the OpenGL ES rendering context.

There are two primary methods for utilizing OpenGL ES rendered content within a DirectX pipeline. The first involves rendering the OpenGL ES scene to a Framebuffer Object (FBO), with the FBO's color attachment being the OpenGL texture that is linked to the shared DirectX texture. This approach provides a structured way to manage off-screen rendering. The second method involves using eglMakeCurrent to set the EGL surface (backed by the shared DirectX texture) as the current rendering target for OpenGL ES, allowing for direct rendering into the shared memory.

Crucially, ensuring data consistency between the two APIs necessitates proper synchronization. While OpenGL ES provides glFlush() and DirectX offers ID3D11DeviceContext::Flush to push rendering commands, these functions are often asynchronous. Developers should be aware of this and might need to employ more explicit synchronization mechanisms, such as fences or events, especially in complex scenarios where one API's output directly influences the other's rendering.

It is also important to note that DirectX's mechanisms for sharing resources are continually evolving. While ANGLE's current documentation often refers to using IDXGIResource::GetSharedHandle, the latest DirectX recommendations suggest utilizing IDXGIResource1::CreateSharedHandle, particularly when working with NT handles. Developers should stay informed about the latest ANGLE and DirectX documentation to ensure they are employing the most up-to-date and recommended practices for achieving optimal interoperability and performance. Careful consideration of the specific requirements of the application and the trade-offs between different interop methods will be essential for successful integration.

Function Reference Table

Function Name API Description
D3D11_RESOURCE_MISC_SHARED DirectX 11 Flag used during texture creation to enable sharing between Direct3D devices.
IDXGIResource::GetSharedHandle DirectX/DXGI Method to retrieve the shareable handle of a DirectX resource.
eglCreatePbufferFromClientBuffer EGL Function to create an EGL pbuffer surface from a client-allocated buffer, used with EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE for DirectX.
EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE EGL Target enum value for eglCreatePbufferFromClientBuffer indicating a shared DirectX 11 texture handle.
glGenTextures OpenGL ES Function to generate OpenGL texture names.
glBindTexture OpenGL ES Function to bind an OpenGL texture name to a texture target (e.g., GL_TEXTURE_2D).
eglBindTexImage EGL Function to bind the image from an EGL surface to the currently bound OpenGL texture.
glFlush() OpenGL ES Function to push buffered OpenGL commands to the driver for execution (asynchronous).
ID3D11DeviceContext::Flush DirectX 11 Method to send queued Direct3D commands to the GPU for processing (asynchronous).
eglMakeCurrent EGL Function to set the current rendering context and surfaces.
glBindFramebuffer OpenGL ES Function to bind a framebuffer object.
glFramebufferTexture2D OpenGL ES Function to attach a texture to a framebuffer object.

example

//
// Copyright 2015 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//

#include "test_utils/ANGLETest.h"

#include <d3d11.h>
#include <cstdint>

#include "util/OSWindow.h"
#include "util/com_utils.h"

using namespace angle;

class EGLPresentPathD3D11 : public ANGLETest<>
{
  protected:
    EGLPresentPathD3D11()
        : mDisplay(EGL_NO_DISPLAY),
          mContext(EGL_NO_CONTEXT),
          mSurface(EGL_NO_SURFACE),
          mOffscreenSurfaceD3D11Texture(nullptr),
          mConfig(0),
          mOSWindow(nullptr),
          mWindowWidth(0)
    {}

    void testSetUp() override
    {
        mOSWindow    = OSWindow::New();
        mWindowWidth = 64;
        mOSWindow->initialize("EGLPresentPathD3D11", mWindowWidth, mWindowWidth);
    }

    void initializeEGL(bool usePresentPathFast)
    {
        int clientVersion = GetParam().majorVersion;

        // Set up EGL Display
        EGLint displayAttribs[] = {EGL_PLATFORM_ANGLE_TYPE_ANGLE,
                                   GetParam().getRenderer(),
                                   EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE,
                                   GetParam().eglParameters.majorVersion,
                                   EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE,
                                   GetParam().eglParameters.majorVersion,
                                   EGL_EXPERIMENTAL_PRESENT_PATH_ANGLE,
                                   usePresentPathFast ? EGL_EXPERIMENTAL_PRESENT_PATH_FAST_ANGLE
                                                      : EGL_EXPERIMENTAL_PRESENT_PATH_COPY_ANGLE,
                                   EGL_NONE};
        mDisplay =
            eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, EGL_DEFAULT_DISPLAY, displayAttribs);
        ASSERT_TRUE(EGL_NO_DISPLAY != mDisplay);
        ASSERT_EGL_TRUE(eglInitialize(mDisplay, nullptr, nullptr));

        // Choose the EGL config
        EGLint numConfigs;
        EGLint configAttribs[] = {EGL_RED_SIZE,
                                  8,
                                  EGL_GREEN_SIZE,
                                  8,
                                  EGL_BLUE_SIZE,
                                  8,
                                  EGL_ALPHA_SIZE,
                                  8,
                                  EGL_RENDERABLE_TYPE,
                                  clientVersion == 3 ? EGL_OPENGL_ES3_BIT : EGL_OPENGL_ES2_BIT,
                                  EGL_SURFACE_TYPE,
                                  EGL_PBUFFER_BIT,
                                  EGL_NONE};
        ASSERT_EGL_TRUE(eglChooseConfig(mDisplay, configAttribs, &mConfig, 1, &numConfigs));
        ASSERT_EQ(1, numConfigs);

        // Set up the EGL context
        EGLint contextAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, clientVersion, EGL_NONE};
        mContext                = eglCreateContext(mDisplay, mConfig, nullptr, contextAttribs);
        ASSERT_TRUE(EGL_NO_CONTEXT != mContext);
    }

    void createWindowSurface()
    {
        mSurface = eglCreateWindowSurface(mDisplay, mConfig, mOSWindow->getNativeWindow(), nullptr);
    }

    void createPbufferFromClientBufferSurface()
    {
        EGLAttrib device      = 0;
        EGLAttrib angleDevice = 0;

        EXPECT_TRUE(IsEGLClientExtensionEnabled("EGL_EXT_device_query"));

        ASSERT_EGL_TRUE(eglQueryDisplayAttribEXT(mDisplay, EGL_DEVICE_EXT, &angleDevice));
        ASSERT_EGL_TRUE(eglQueryDeviceAttribEXT(reinterpret_cast<EGLDeviceEXT>(angleDevice),
                                                EGL_D3D11_DEVICE_ANGLE, &device));
        ID3D11Device *d3d11Device = reinterpret_cast<ID3D11Device *>(device);

        D3D11_TEXTURE2D_DESC textureDesc = {0};
        textureDesc.Width                = mWindowWidth;
        textureDesc.Height               = mWindowWidth;
        textureDesc.Format               = DXGI_FORMAT_B8G8R8A8_UNORM;
        textureDesc.MipLevels            = 1;
        textureDesc.ArraySize            = 1;
        textureDesc.SampleDesc.Count     = 1;
        textureDesc.SampleDesc.Quality   = 0;
        textureDesc.Usage                = D3D11_USAGE_DEFAULT;
        textureDesc.BindFlags            = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
        textureDesc.CPUAccessFlags       = 0;
        textureDesc.MiscFlags            = D3D11_RESOURCE_MISC_SHARED;

        ASSERT_TRUE(SUCCEEDED(
            d3d11Device->CreateTexture2D(&textureDesc, nullptr, &mOffscreenSurfaceD3D11Texture)));

        IDXGIResource *dxgiResource =
            DynamicCastComObject<IDXGIResource>(mOffscreenSurfaceD3D11Texture);
        ASSERT_NE(nullptr, dxgiResource);

        HANDLE sharedHandle = 0;
        ASSERT_TRUE(SUCCEEDED(dxgiResource->GetSharedHandle(&sharedHandle)));
        SafeRelease(dxgiResource);

        EGLint pBufferAttributes[] = {EGL_WIDTH,          mWindowWidth,       EGL_HEIGHT,
                                      mWindowWidth,       EGL_TEXTURE_TARGET, EGL_TEXTURE_2D,
                                      EGL_TEXTURE_FORMAT, EGL_TEXTURE_RGBA,   EGL_NONE};

        mSurface = eglCreatePbufferFromClientBuffer(mDisplay, EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE,
                                                    sharedHandle, mConfig, pBufferAttributes);
        ASSERT_TRUE(EGL_NO_SURFACE != mSurface);
    }

    void makeCurrent() { ASSERT_EGL_TRUE(eglMakeCurrent(mDisplay, mSurface, mSurface, mContext)); }

    void testTearDown() override
    {
        SafeRelease(mOffscreenSurfaceD3D11Texture);

        if (mDisplay != EGL_NO_DISPLAY)
        {
            eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);

            if (mSurface != EGL_NO_SURFACE)
            {
                eglDestroySurface(mDisplay, mSurface);
                mSurface = EGL_NO_SURFACE;
            }

            if (mContext != EGL_NO_CONTEXT)
            {
                ASSERT_EGL_TRUE(
                    eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
                eglDestroyContext(mDisplay, mContext);
                mContext = EGL_NO_CONTEXT;
            }

            eglTerminate(mDisplay);
            mDisplay = EGL_NO_DISPLAY;
        }

        mOSWindow->destroy();
        OSWindow::Delete(&mOSWindow);
    }

    void drawQuadUsingGL()
    {
        GLuint m2DProgram;
        GLint mTexture2DUniformLocation;

        constexpr char kVS[] =
            R"(precision highp float;
            attribute vec4 position;
            varying vec2 texcoord;

            void main()
            {
                gl_Position = vec4(position.xy, 0.0, 1.0);
                texcoord = (position.xy * 0.5) + 0.5;
            })";

        constexpr char kFS[] =
            R"(precision highp float;
            uniform sampler2D tex;
            varying vec2 texcoord;

            void main()
            {
                gl_FragColor = texture2D(tex, texcoord);
            })";

        m2DProgram                = CompileProgram(kVS, kFS);
        mTexture2DUniformLocation = glGetUniformLocation(m2DProgram, "tex");

        uint8_t textureInitData[16] = {
            255, 0,   0,   255,  // Red
            0,   255, 0,   255,  // Green
            0,   0,   255, 255,  // Blue
            255, 255, 0,   255   // Red + Green
        };

        // Create a simple RGBA texture
        GLuint tex = 0;
        glGenTextures(1, &tex);
        glBindTexture(GL_TEXTURE_2D, tex);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     textureInitData);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        ASSERT_GL_NO_ERROR();

        // Draw a quad using the texture
        glClear(GL_COLOR_BUFFER_BIT);
        glUseProgram(m2DProgram);
        glUniform1i(mTexture2DUniformLocation, 0);

        GLint positionLocation = glGetAttribLocation(m2DProgram, "position");
        glUseProgram(m2DProgram);
        const GLfloat vertices[] = {
            -1.0f, 1.0f, 0.5f, -1.0f, -1.0f, 0.5f, 1.0f, -1.0f, 0.5f,
            -1.0f, 1.0f, 0.5f, 1.0f,  -1.0f, 0.5f, 1.0f, 1.0f,  0.5f,
        };

        glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, vertices);
        glEnableVertexAttribArray(positionLocation);

        glDrawArrays(GL_TRIANGLES, 0, 6);
        ASSERT_GL_NO_ERROR();

        glDeleteProgram(m2DProgram);
    }

    void checkPixelsUsingGL()
    {
        // Note that the texture is in BGRA format
        EXPECT_PIXEL_EQ(0, 0, 255, 0, 0, 255);                                  // Red
        EXPECT_PIXEL_EQ(mWindowWidth - 1, 0, 0, 255, 0, 255);                   // Green
        EXPECT_PIXEL_EQ(0, mWindowWidth - 1, 0, 0, 255, 255);                   // Blue
        EXPECT_PIXEL_EQ(mWindowWidth - 1, mWindowWidth - 1, 255, 255, 0, 255);  // Red + green
    }

    void checkPixelsUsingD3D(bool usingPresentPathFast)
    {
        ASSERT_NE(nullptr, mOffscreenSurfaceD3D11Texture);

        D3D11_TEXTURE2D_DESC textureDesc = {0};
        ID3D11Device *device;
        ID3D11DeviceContext *context;
        mOffscreenSurfaceD3D11Texture->GetDesc(&textureDesc);
        mOffscreenSurfaceD3D11Texture->GetDevice(&device);
        device->GetImmediateContext(&context);
        ASSERT_NE(nullptr, device);
        ASSERT_NE(nullptr, context);

        textureDesc.CPUAccessFlags  = D3D11_CPU_ACCESS_READ;
        textureDesc.Usage           = D3D11_USAGE_STAGING;
        textureDesc.BindFlags       = 0;
        textureDesc.MiscFlags       = 0;
        ID3D11Texture2D *cpuTexture = nullptr;
        ASSERT_TRUE(SUCCEEDED(device->CreateTexture2D(&textureDesc, nullptr, &cpuTexture)));

        context->CopyResource(cpuTexture, mOffscreenSurfaceD3D11Texture);

        D3D11_MAPPED_SUBRESOURCE mappedSubresource;
        context->Map(cpuTexture, 0, D3D11_MAP_READ, 0, &mappedSubresource);
        ASSERT_EQ(static_cast<UINT>(mWindowWidth * 4), mappedSubresource.RowPitch);
        ASSERT_EQ(static_cast<UINT>(mWindowWidth * mWindowWidth * 4), mappedSubresource.DepthPitch);

        angle::GLColor *byteData = reinterpret_cast<angle::GLColor *>(mappedSubresource.pData);

        // Note that the texture is in BGRA format, although the GLColor struct is RGBA
        GLColor expectedTopLeftPixel     = GLColor(0, 0, 255, 255);    // Red
        GLColor expectedTopRightPixel    = GLColor(0, 255, 0, 255);    // Green
        GLColor expectedBottomLeftPixel  = GLColor(255, 0, 0, 255);    // Blue
        GLColor expectedBottomRightPixel = GLColor(0, 255, 255, 255);  // Red + Green

        if (usingPresentPathFast)
        {
            // Invert the expected values
            GLColor tempTopLeft      = expectedTopLeftPixel;
            GLColor tempTopRight     = expectedTopRightPixel;
            expectedTopLeftPixel     = expectedBottomLeftPixel;
            expectedTopRightPixel    = expectedBottomRightPixel;
            expectedBottomLeftPixel  = tempTopLeft;
            expectedBottomRightPixel = tempTopRight;
        }

        EXPECT_EQ(expectedTopLeftPixel, byteData[0]);
        EXPECT_EQ(expectedTopRightPixel, byteData[(mWindowWidth - 1)]);
        EXPECT_EQ(expectedBottomLeftPixel, byteData[(mWindowWidth - 1) * mWindowWidth]);
        EXPECT_EQ(expectedBottomRightPixel,
                  byteData[(mWindowWidth - 1) * mWindowWidth + (mWindowWidth - 1)]);

        context->Unmap(cpuTexture, 0);
        SafeRelease(cpuTexture);
        SafeRelease(device);
        SafeRelease(context);
    }

    EGLDisplay mDisplay;
    EGLContext mContext;
    EGLSurface mSurface;
    ID3D11Texture2D *mOffscreenSurfaceD3D11Texture;
    EGLConfig mConfig;
    OSWindow *mOSWindow;
    GLint mWindowWidth;
};

// Test that rendering basic content onto a window surface when present path fast
// is enabled works as expected
TEST_P(EGLPresentPathD3D11, WindowPresentPathFast)
{
    initializeEGL(true);
    createWindowSurface();
    makeCurrent();

    drawQuadUsingGL();

    checkPixelsUsingGL();
}

// Test that rendering basic content onto a client buffer surface when present path fast
// works as expected, and is also oriented the correct way around
TEST_P(EGLPresentPathD3D11, ClientBufferPresentPathFast)
{
    initializeEGL(true);
    createPbufferFromClientBufferSurface();
    makeCurrent();

    drawQuadUsingGL();

    checkPixelsUsingGL();
    checkPixelsUsingD3D(true);
}

// Test that rendering basic content onto a window surface when present path fast
// is disabled works as expected
TEST_P(EGLPresentPathD3D11, WindowPresentPathCopy)
{
    initializeEGL(false);
    createWindowSurface();
    makeCurrent();

    drawQuadUsingGL();

    checkPixelsUsingGL();
}

// Test that rendering basic content onto a client buffer surface when present path
// fast is disabled works as expected, and is also oriented the correct way around
TEST_P(EGLPresentPathD3D11, ClientBufferPresentPathCopy)
{
    initializeEGL(false);
    createPbufferFromClientBufferSurface();
    makeCurrent();

    drawQuadUsingGL();

    checkPixelsUsingGL();
    checkPixelsUsingD3D(false);
}

ANGLE_INSTANTIATE_TEST(EGLPresentPathD3D11, WithNoFixture(ES2_D3D11()));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment