blob: fc063028d6b3fb66d6b4177916fc43e3753e08b6 [file] [log] [blame]
//
// Copyright 2018 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.
//
// EGLDirectCompositionTest.cpp:
// Tests pertaining to DirectComposition and WindowsUIComposition.
#include <d3d11.h>
#include "test_utils/ANGLETest.h"
#include <DispatcherQueue.h>
#include <VersionHelpers.h>
#include <Windows.Foundation.h>
#include <windows.ui.composition.Desktop.h>
#include <windows.ui.composition.h>
#include <windows.ui.composition.interop.h>
#include <wrl.h>
#include <memory>
#include "libANGLE/renderer/d3d/d3d11/converged/CompositorNativeWindow11.h"
#include "util/OSWindow.h"
#include "util/com_utils.h"
#include "util/test_utils.h"
using namespace angle;
using namespace ABI::Windows::System;
using namespace ABI::Windows::UI::Composition;
using namespace ABI::Windows::UI::Composition::Desktop;
using namespace ABI::Windows::Foundation;
using namespace Microsoft::WRL;
using namespace Microsoft::WRL::Wrappers;
const int WINDOWWIDTH = 200, WINDOWHEIGHT = 200;
class EGLDirectCompositionTest : public ANGLETest
{
protected:
EGLDirectCompositionTest() : mOSWindow(nullptr) {}
void testSetUp() override
{
if (!mRoHelper.SupportedWindowsRelease())
{
return;
}
// Create an OS Window
mOSWindow = OSWindow::New();
mOSWindow->initialize("EGLDirectCompositionTest", WINDOWWIDTH, WINDOWHEIGHT);
auto nativeWindow = mOSWindow->getNativeWindow();
mOSWindow->setVisible(true);
// Create DispatcherQueue for window to process compositor callbacks
CreateDispatcherQueue(mDispatcherController);
HSTRING act;
HSTRING_HEADER header;
auto hr = mRoHelper.GetStringReference(RuntimeClass_Windows_UI_Composition_Compositor, &act,
&header);
ASSERT_TRUE(SUCCEEDED(hr));
void *fac = nullptr;
hr = mRoHelper.GetActivationFactory(act, __uuidof(IActivationFactory), &fac);
ASSERT_TRUE(SUCCEEDED(hr));
ComPtr<IActivationFactory> compositorFactory;
compositorFactory.Attach((IActivationFactory *)fac);
hr = compositorFactory->ActivateInstance(&mCompositor);
ASSERT_TRUE(SUCCEEDED(hr));
// Create a DesktopWindowTarget against native window (HWND)
CreateDesktopWindowTarget(mCompositor, static_cast<HWND>(nativeWindow), mDesktopTarget);
ASSERT_TRUE(SUCCEEDED(mCompositor->CreateSpriteVisual(mAngleHost.GetAddressOf())));
ComPtr<IVisual> angleVis;
ASSERT_TRUE(SUCCEEDED(mAngleHost.As(&angleVis)));
ASSERT_TRUE(SUCCEEDED(angleVis->put_Size(
{static_cast<FLOAT>(WINDOWWIDTH), static_cast<FLOAT>(WINDOWHEIGHT)})));
ASSERT_TRUE(SUCCEEDED(angleVis->put_Offset({0, 0, 0})));
ComPtr<ICompositionTarget> compTarget;
ASSERT_TRUE(SUCCEEDED(mDesktopTarget.As(&compTarget)));
ASSERT_TRUE(SUCCEEDED(compTarget->put_Root(angleVis.Get())));
Init();
}
void CreateDispatcherQueue(ComPtr<IDispatcherQueueController> &controller)
{
DispatcherQueueOptions options{sizeof(DispatcherQueueOptions), DQTYPE_THREAD_CURRENT,
DQTAT_COM_STA};
auto hr = mRoHelper.CreateDispatcherQueueController(options, controller.GetAddressOf());
ASSERT_TRUE(SUCCEEDED(hr));
}
void CreateDesktopWindowTarget(ComPtr<ICompositor> const &compositor,
const HWND window,
ComPtr<IDesktopWindowTarget> &target)
{
namespace abi = ABI::Windows::UI::Composition::Desktop;
ComPtr<ICompositorDesktopInterop> interop;
ASSERT_TRUE(SUCCEEDED(compositor.As(&interop)));
ASSERT_TRUE(SUCCEEDED(interop->CreateDesktopWindowTarget(
window, true, reinterpret_cast<abi::IDesktopWindowTarget **>(target.GetAddressOf()))));
}
void Init()
{
if (!mRoHelper.SupportedWindowsRelease())
{
return;
}
DPI_AWARENESS_CONTEXT
WINAPI
SetThreadDpiAwarenessContext(_In_ DPI_AWARENESS_CONTEXT dpiContext);
auto userModule = LoadLibraryA("user32.dll");
if (userModule == nullptr)
{
return;
}
auto temp = GetProcAddress(userModule, "SetThreadDpiAwarenessContext");
mFpSetThreadDpiAwarenessContext = reinterpret_cast<_SetThreadDpiAwarenessContext *>(temp);
const EGLint configAttributes[] = {
EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8,
EGL_DEPTH_SIZE, 8, EGL_STENCIL_SIZE, 8, EGL_NONE};
const EGLint defaultDisplayAttributes[] = {
EGL_PLATFORM_ANGLE_TYPE_ANGLE,
EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE,
EGL_NONE,
};
PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT =
reinterpret_cast<PFNEGLGETPLATFORMDISPLAYEXTPROC>(
eglGetProcAddress("eglGetPlatformDisplayEXT"));
ASSERT_TRUE(eglGetPlatformDisplayEXT != nullptr);
mEglDisplay = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE,
reinterpret_cast<void *>(EGL_DEFAULT_DISPLAY),
defaultDisplayAttributes);
ASSERT_TRUE(mEglDisplay != EGL_NO_DISPLAY);
ASSERT_EGL_TRUE(eglInitialize(mEglDisplay, nullptr, nullptr));
EGLint nConfigs = 0;
ASSERT_EGL_TRUE(eglGetConfigs(mEglDisplay, nullptr, 0, &nConfigs));
ASSERT_TRUE(nConfigs != 0);
ASSERT_EGL_TRUE(eglChooseConfig(mEglDisplay, configAttributes, &mEglConfig, 1, &nConfigs));
}
void CreateSurface(ComPtr<ABI::Windows::UI::Composition::ISpriteVisual> visual,
EGLSurface &surface)
{
auto displayExtensions = eglQueryString(mEglDisplay, EGL_EXTENSIONS);
// Check that the EGL_ANGLE_windows_ui_composition display extension is available
ASSERT_TRUE(strstr(displayExtensions, "EGL_ANGLE_windows_ui_composition") != nullptr);
const EGLint contextAttributes[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
// Use a spritevisual as the nativewindowtype
surface =
eglCreateWindowSurface(mEglDisplay, mEglConfig,
static_cast<EGLNativeWindowType>((void *)visual.Get()), nullptr);
ASSERT_TRUE(surface != EGL_NO_SURFACE);
mEglContext = eglCreateContext(mEglDisplay, mEglConfig, EGL_NO_CONTEXT, contextAttributes);
ASSERT_TRUE(mEglContext != EGL_NO_CONTEXT);
ASSERT_TRUE(eglMakeCurrent(mEglDisplay, surface, surface, mEglContext) != EGL_FALSE);
}
void testTearDown() override
{
if (!mRoHelper.SupportedWindowsRelease())
{
return;
}
ASSERT_EGL_TRUE(eglTerminate(mEglDisplay));
OSWindow::Delete(&mOSWindow);
}
OSWindow *mOSWindow;
ComPtr<ICompositor> mCompositor;
ComPtr<IDispatcherQueueController> mDispatcherController;
ComPtr<ICompositionColorBrush> mColorBrush;
ComPtr<IDesktopWindowTarget> mDesktopTarget;
ComPtr<ISpriteVisual> mAngleHost;
EGLDisplay mEglDisplay;
EGLContext mEglContext;
EGLConfig mEglConfig;
rx::RoHelper mRoHelper;
using _SetThreadDpiAwarenessContext =
DPI_AWARENESS_CONTEXT WINAPI(DPI_AWARENESS_CONTEXT dpiContext);
_SetThreadDpiAwarenessContext *mFpSetThreadDpiAwarenessContext;
};
// This tests that a surface created using a SpriteVisual as container has the expected dimensions
// which should match the dimensions of the SpriteVisual passed in
TEST_P(EGLDirectCompositionTest, SurfaceSizeFromSpriteSize)
{
// Only attempt this test when on Windows 10 1803+
ANGLE_SKIP_TEST_IF(!mRoHelper.SupportedWindowsRelease());
EGLSurface s{nullptr};
CreateSurface(mAngleHost, s);
EGLint surfacewidth = 0, surfaceheight = 0;
eglQuerySurface(mEglDisplay, s, EGL_WIDTH, &surfacewidth);
eglQuerySurface(mEglDisplay, s, EGL_HEIGHT, &surfaceheight);
ComPtr<IVisual> angleVis;
ASSERT_TRUE(SUCCEEDED(mAngleHost.As(&angleVis)));
ABI::Windows::Foundation::Numerics::Vector2 visualsize{0, 0};
ASSERT_TRUE(SUCCEEDED(angleVis->get_Size(&visualsize)));
ASSERT_TRUE(surfacewidth == static_cast<int>(visualsize.X));
ASSERT_TRUE(surfaceheight == static_cast<int>(visualsize.Y));
ASSERT_EGL_TRUE(eglDestroySurface(mEglDisplay, s));
ASSERT_EGL_TRUE(eglDestroyContext(mEglDisplay, mEglContext));
}
// This tests that a WindowSurface can be created using a SpriteVisual as the containing window
// and that pixels can be successfully rendered into the resulting WindowSurface
TEST_P(EGLDirectCompositionTest, RenderSolidColor)
{
// Only attempt this test when on Windows 10 1803+
ANGLE_SKIP_TEST_IF(!mRoHelper.SupportedWindowsRelease());
EGLSurface s{nullptr};
CreateSurface(mAngleHost, s);
glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
glViewport(0, 0, WINDOWWIDTH, WINDOWHEIGHT);
glClear(GL_COLOR_BUFFER_BIT);
ASSERT_EGL_TRUE(eglSwapBuffers(mEglDisplay, s));
// ensure user/DWM have a chance to paint the window and kick it to the top of the desktop
// zorder before we attempt to sample
angle::Sleep(200);
mOSWindow->messageLoop();
uint8_t *pixelBuffer = static_cast<uint8_t *>(malloc(WINDOWWIDTH * WINDOWHEIGHT * 4));
ZeroMemory(pixelBuffer, WINDOWWIDTH * WINDOWHEIGHT * 4);
// In order to accurately capture a bitmap, we need to temporarily shift into per-monitor DPI
// mode in order to get the window offset from desktop correct
auto previous = mFpSetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE);
bool success = mOSWindow->takeScreenshot(pixelBuffer);
mFpSetThreadDpiAwarenessContext(previous);
ASSERT_EGL_TRUE(success);
ASSERT_EGL_TRUE(pixelBuffer[(50 * 50 * 4)] == 255);
ASSERT_EGL_TRUE(pixelBuffer[(50 * 50 * 4) + 1] == 0);
ASSERT_EGL_TRUE(pixelBuffer[(50 * 50 * 4) + 2] == 0);
ASSERT_EGL_TRUE(pixelBuffer[(50 * 50 * 4) + 3] == 255);
ASSERT_EGL_TRUE(eglDestroySurface(mEglDisplay, s));
ASSERT_EGL_TRUE(eglDestroyContext(mEglDisplay, mEglContext));
}
ANGLE_INSTANTIATE_TEST(EGLDirectCompositionTest, WithNoFixture(ES2_D3D11()));