Commit 8e7d9d6c by Kenneth Russell Committed by Commit Bot

Add EGL_ANGLE_device_cgl extension.

Supports querying the CGLContextObj and CGLPixelFormatObj associated with ANGLE's underlying OpenGL context on macOS. Minor refactorings to implementation of device attribute queries on all platforms. Bug: angleproject:3973 Change-Id: I24341668be4cbfed0b7f2df4c1402df1effe275e Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1846733 Commit-Queue: Kenneth Russell <kbr@chromium.org> Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org>
parent 7de9f95d
Name
ANGLE_device_cgl
Name Strings
EGL_ANGLE_device_cgl
Contributors
Ken Russell (kbr 'at' google.com)
Geoff Lang (geofflang 'at' google.com)
Contact
Ken Russell (kbr 'at' google.com)
Status
Draft
Version
Version 1, October 4, 2019
Number
EGL Extension #XXX
Extension Type
EGL device extension
Dependencies
This extension is written against the language of EGL 1.5 as
modified by EGL_EXT_device_query.
EGL_EXT_device_query is required.
Overview
ANGLE on macOS internally uses an OpenGL context allocated via CGL.
This extension defines a mapping from an EGL device to the underlying
CGLContextObj and its associated CGLPixelFormatObj, after it's been
queried from an EGL display.
IP Status
No known claims.
New Types
None.
New Procedures and Functions
None.
New Tokens
Accepted as a queried <attribute> in eglQueryDeviceAttribEXT:
EGL_CGL_CONTEXT_ANGLE 0x3485
EGL_CGL_PIXEL_FORMAT_ANGLE 0x3486
Add a new section 2.1.3 (CGL Devices) after 2.1.2 (Devices)
On macOS the underlying CGLContextObj and CGLPixelFormatObj can be queried
from the EGL device. The intented purpose is to allow applications to create
new CGL contexts which share resources with this one.
Changes to section 3.2 (Devices)
Replace the paragraph immediately following the prototype for
eglQueryDeviceAttribEXT:
<attribute> may be either EGL_CGLCONTEXT_DEVICE_ANGLE or
EGL_CGLPIXELFORMAT_DEVICE_ANGLE. On success, EGL_TRUE is returned, and a
valid CGLContextObj or CGLPixelFormatObj corresponding to the EGL device is
returned in <value>. These objects are compatible with OpenGL and CGL API
functions. If the EGL device is not currently associated with a CGL context,
EGL_BAD_ATTRIBUTE is returned, and <value> is left unchanged.
Issues
None
Revision History
Version 1, October 4, 2019 (Ken Russell)
- Initial Draft
Version 2, October 8, 2019 (Ken Russell)
- Address feedback from Geoff Lang
...@@ -243,6 +243,12 @@ EGLAPI EGLBoolean EGLAPIENTRY eglQueryDisplayAttribANGLE(EGLDisplay dpy, EGLint ...@@ -243,6 +243,12 @@ EGLAPI EGLBoolean EGLAPIENTRY eglQueryDisplayAttribANGLE(EGLDisplay dpy, EGLint
#define EGL_CONTEXT_OPENGL_BACKWARDS_COMPATIBLE_ANGLE 0x3483 #define EGL_CONTEXT_OPENGL_BACKWARDS_COMPATIBLE_ANGLE 0x3483
#endif /* EGL_ANGLE_create_context_backwards_compatible */ #endif /* EGL_ANGLE_create_context_backwards_compatible */
#ifndef EGL_ANGLE_device_cgl
#define EGL_ANGLE_device_cgl 1
#define EGL_CGL_CONTEXT_ANGLE 0x3485
#define EGL_CGL_PIXEL_FORMAT_ANGLE 0x3486
#endif
// clang-format on // clang-format on
#endif // INCLUDE_EGL_EGLEXT_ANGLE_ #endif // INCLUDE_EGL_EGLEXT_ANGLE_
...@@ -1234,6 +1234,7 @@ std::vector<std::string> DeviceExtensions::getStrings() const ...@@ -1234,6 +1234,7 @@ std::vector<std::string> DeviceExtensions::getStrings() const
// clang-format off // clang-format off
// | Extension name | Supported flag | Output vector | // | Extension name | Supported flag | Output vector |
InsertExtensionString("EGL_ANGLE_device_d3d", deviceD3D, &extensionStrings); InsertExtensionString("EGL_ANGLE_device_d3d", deviceD3D, &extensionStrings);
InsertExtensionString("EGL_ANGLE_device_cgl", deviceCGL, &extensionStrings);
// clang-format on // clang-format on
return extensionStrings; return extensionStrings;
......
...@@ -942,6 +942,9 @@ struct DeviceExtensions ...@@ -942,6 +942,9 @@ struct DeviceExtensions
// EGL_ANGLE_device_d3d // EGL_ANGLE_device_d3d
bool deviceD3D = false; bool deviceD3D = false;
// EGL_ANGLE_device_cgl
bool deviceCGL = false;
}; };
struct ClientExtensions struct ClientExtensions
......
...@@ -101,11 +101,12 @@ EGLLabelKHR Device::getLabel() const ...@@ -101,11 +101,12 @@ EGLLabelKHR Device::getLabel() const
return mLabel; return mLabel;
} }
Error Device::getDevice(EGLAttrib *value) Error Device::getAttribute(EGLint attribute, EGLAttrib *value)
{ {
void *nativeDevice = nullptr; void *nativeAttribute = nullptr;
egl::Error error = getImplementation()->getDevice(&nativeDevice); egl::Error error =
*value = reinterpret_cast<EGLAttrib>(nativeDevice); getImplementation()->getAttribute(getOwningDisplay(), attribute, &nativeAttribute);
*value = reinterpret_cast<EGLAttrib>(nativeAttribute);
return error; return error;
} }
......
...@@ -32,7 +32,7 @@ class Device final : public LabeledObject, angle::NonCopyable ...@@ -32,7 +32,7 @@ class Device final : public LabeledObject, angle::NonCopyable
void setLabel(EGLLabelKHR label) override; void setLabel(EGLLabelKHR label) override;
EGLLabelKHR getLabel() const override; EGLLabelKHR getLabel() const override;
Error getDevice(EGLAttrib *value); Error getAttribute(EGLint attribute, EGLAttrib *value);
Display *getOwningDisplay() { return mOwningDisplay; } Display *getOwningDisplay() { return mOwningDisplay; }
EGLint getType(); EGLint getType();
......
...@@ -15,11 +15,13 @@ ...@@ -15,11 +15,13 @@
namespace egl namespace egl
{ {
class Device; class Display;
} }
namespace rx namespace rx
{ {
class DisplayImpl;
class DeviceImpl : angle::NonCopyable class DeviceImpl : angle::NonCopyable
{ {
public: public:
...@@ -28,7 +30,9 @@ class DeviceImpl : angle::NonCopyable ...@@ -28,7 +30,9 @@ class DeviceImpl : angle::NonCopyable
virtual egl::Error initialize() = 0; virtual egl::Error initialize() = 0;
virtual egl::Error getDevice(void **outValue) = 0; virtual egl::Error getAttribute(const egl::Display *display,
EGLint attribute,
void **outValue) = 0;
virtual EGLint getType() = 0; virtual EGLint getType() = 0;
virtual void generateExtensions(egl::DeviceExtensions *outExtensions) const = 0; virtual void generateExtensions(egl::DeviceExtensions *outExtensions) const = 0;
}; };
......
...@@ -7,7 +7,6 @@ ...@@ -7,7 +7,6 @@
// DeviceD3D.cpp: D3D implementation of egl::Device // DeviceD3D.cpp: D3D implementation of egl::Device
#include "libANGLE/renderer/d3d/DeviceD3D.h" #include "libANGLE/renderer/d3d/DeviceD3D.h"
#include "libANGLE/renderer/d3d/RendererD3D.h"
#include "libANGLE/Device.h" #include "libANGLE/Device.h"
#include "libANGLE/Display.h" #include "libANGLE/Display.h"
...@@ -33,9 +32,12 @@ DeviceD3D::~DeviceD3D() ...@@ -33,9 +32,12 @@ DeviceD3D::~DeviceD3D()
#endif #endif
} }
egl::Error DeviceD3D::getDevice(void **outValue) egl::Error DeviceD3D::getAttribute(const egl::Display *display, EGLint attribute, void **outValue)
{ {
ASSERT(mIsInitialized); ASSERT(mIsInitialized);
ANGLE_UNUSED_VARIABLE(display);
// Validated at higher levels.
ASSERT(getType() == attribute);
*outValue = mDevice; *outValue = mDevice;
return egl::NoError(); return egl::NoError();
} }
......
...@@ -22,7 +22,9 @@ class DeviceD3D : public DeviceImpl ...@@ -22,7 +22,9 @@ class DeviceD3D : public DeviceImpl
~DeviceD3D() override; ~DeviceD3D() override;
egl::Error initialize() override; egl::Error initialize() override;
egl::Error getDevice(void **outValue) override; egl::Error getAttribute(const egl::Display *display,
EGLint attribute,
void **outValue) override;
EGLint getType() override; EGLint getType() override;
void generateExtensions(egl::DeviceExtensions *outExtensions) const override; void generateExtensions(egl::DeviceExtensions *outExtensions) const override;
......
...@@ -755,7 +755,7 @@ egl::Error Renderer11::initializeD3DDevice() ...@@ -755,7 +755,7 @@ egl::Error Renderer11::initializeD3DDevice()
// We should use the inputted D3D11 device instead // We should use the inputted D3D11 device instead
void *device = nullptr; void *device = nullptr;
ANGLE_TRY(deviceD3D->getDevice(&device)); ANGLE_TRY(deviceD3D->getAttribute(mDisplay, EGL_D3D11_DEVICE_ANGLE, &device));
ID3D11Device *d3dDevice = static_cast<ID3D11Device *>(device); ID3D11Device *d3dDevice = static_cast<ID3D11Device *>(device);
if (FAILED(d3dDevice->GetDeviceRemovedReason())) if (FAILED(d3dDevice->GetDeviceRemovedReason()))
......
//
// Copyright 2019 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.
//
// DeviceCGL.cpp: CGL implementation of egl::Device
#include "libANGLE/renderer/gl/cgl/DeviceCGL.h"
#include "libANGLE/renderer/gl/cgl/DisplayCGL.h"
#include <EGL/eglext.h>
namespace rx
{
DeviceCGL::DeviceCGL() {}
DeviceCGL::~DeviceCGL() {}
egl::Error DeviceCGL::initialize()
{
return egl::NoError();
}
egl::Error DeviceCGL::getAttribute(const egl::Display *display, EGLint attribute, void **outValue)
{
DisplayCGL *displayImpl = GetImplAs<DisplayCGL>(display);
switch (attribute)
{
case EGL_CGL_CONTEXT_ANGLE:
*outValue = displayImpl->getCGLContext();
break;
case EGL_CGL_PIXEL_FORMAT_ANGLE:
*outValue = displayImpl->getCGLPixelFormat();
break;
default:
return egl::EglBadAttribute();
}
return egl::NoError();
}
EGLint DeviceCGL::getType()
{
return 0;
}
void DeviceCGL::generateExtensions(egl::DeviceExtensions *outExtensions) const
{
outExtensions->deviceCGL = true;
}
} // namespace rx
//
// Copyright 2019 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.
//
// DeviceCGL.h: CGL implementation of egl::Device
#ifndef LIBANGLE_RENDERER_GL_CGL_DEVICECGL_H_
#define LIBANGLE_RENDERER_GL_CGL_DEVICECGL_H_
#include "libANGLE/Device.h"
#include "libANGLE/renderer/DeviceImpl.h"
namespace rx
{
class DeviceCGL : public DeviceImpl
{
public:
DeviceCGL();
~DeviceCGL() override;
egl::Error initialize() override;
egl::Error getAttribute(const egl::Display *display,
EGLint attribute,
void **outValue) override;
EGLint getType() override;
void generateExtensions(egl::DeviceExtensions *outExtensions) const override;
};
} // namespace rx
#endif // LIBANGLE_RENDERER_GL_CGL_DEVICECGL_H_
...@@ -71,6 +71,7 @@ class DisplayCGL : public DisplayGL ...@@ -71,6 +71,7 @@ class DisplayCGL : public DisplayGL
gl::Version getMaxSupportedESVersion() const override; gl::Version getMaxSupportedESVersion() const override;
CGLContextObj getCGLContext() const; CGLContextObj getCGLContext() const;
CGLPixelFormatObj getCGLPixelFormat() const;
WorkerContext *createWorkerContext(std::string *infoLog); WorkerContext *createWorkerContext(std::string *infoLog);
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
# include "gpu_info_util/SystemInfo.h" # include "gpu_info_util/SystemInfo.h"
# include "libANGLE/Display.h" # include "libANGLE/Display.h"
# include "libANGLE/renderer/gl/cgl/ContextCGL.h" # include "libANGLE/renderer/gl/cgl/ContextCGL.h"
# include "libANGLE/renderer/gl/cgl/DeviceCGL.h"
# include "libANGLE/renderer/gl/cgl/IOSurfaceSurfaceCGL.h" # include "libANGLE/renderer/gl/cgl/IOSurfaceSurfaceCGL.h"
# include "libANGLE/renderer/gl/cgl/PbufferSurfaceCGL.h" # include "libANGLE/renderer/gl/cgl/PbufferSurfaceCGL.h"
# include "libANGLE/renderer/gl/cgl/RendererCGL.h" # include "libANGLE/renderer/gl/cgl/RendererCGL.h"
...@@ -209,8 +210,7 @@ ContextImpl *DisplayCGL::createContext(const gl::State &state, ...@@ -209,8 +210,7 @@ ContextImpl *DisplayCGL::createContext(const gl::State &state,
DeviceImpl *DisplayCGL::createDevice() DeviceImpl *DisplayCGL::createDevice()
{ {
UNIMPLEMENTED(); return new DeviceCGL();
return nullptr;
} }
egl::ConfigSet DisplayCGL::generateConfigs() egl::ConfigSet DisplayCGL::generateConfigs()
...@@ -323,10 +323,16 @@ CGLContextObj DisplayCGL::getCGLContext() const ...@@ -323,10 +323,16 @@ CGLContextObj DisplayCGL::getCGLContext() const
return mContext; return mContext;
} }
CGLPixelFormatObj DisplayCGL::getCGLPixelFormat() const
{
return mPixelFormat;
}
void DisplayCGL::generateExtensions(egl::DisplayExtensions *outExtensions) const void DisplayCGL::generateExtensions(egl::DisplayExtensions *outExtensions) const
{ {
outExtensions->iosurfaceClientBuffer = true; outExtensions->iosurfaceClientBuffer = true;
outExtensions->surfacelessContext = true; outExtensions->surfacelessContext = true;
outExtensions->deviceQuery = true;
// Contexts are virtualized so textures can be shared globally // Contexts are virtualized so textures can be shared globally
outExtensions->displayTextureShareGroup = true; outExtensions->displayTextureShareGroup = true;
......
...@@ -23,7 +23,7 @@ egl::Error DeviceNULL::initialize() ...@@ -23,7 +23,7 @@ egl::Error DeviceNULL::initialize()
return egl::NoError(); return egl::NoError();
} }
egl::Error DeviceNULL::getDevice(void **outValue) egl::Error DeviceNULL::getAttribute(const egl::Display *display, EGLint attribute, void **outValue)
{ {
UNIMPLEMENTED(); UNIMPLEMENTED();
return egl::EglBadAccess(); return egl::EglBadAccess();
......
...@@ -22,7 +22,9 @@ class DeviceNULL : public DeviceImpl ...@@ -22,7 +22,9 @@ class DeviceNULL : public DeviceImpl
~DeviceNULL() override; ~DeviceNULL() override;
egl::Error initialize() override; egl::Error initialize() override;
egl::Error getDevice(void **outValue) override; egl::Error getAttribute(const egl::Display *display,
EGLint attribute,
void **outValue) override;
EGLint getType() override; EGLint getType() override;
void generateExtensions(egl::DeviceExtensions *outExtensions) const override; void generateExtensions(egl::DeviceExtensions *outExtensions) const override;
}; };
......
...@@ -24,7 +24,7 @@ egl::Error DeviceVk::initialize() ...@@ -24,7 +24,7 @@ egl::Error DeviceVk::initialize()
return egl::NoError(); return egl::NoError();
} }
egl::Error DeviceVk::getDevice(void **outValue) egl::Error DeviceVk::getAttribute(const egl::Display *display, EGLint attribute, void **outValue)
{ {
UNIMPLEMENTED(); UNIMPLEMENTED();
return egl::EglBadAccess(); return egl::EglBadAccess();
......
...@@ -22,7 +22,9 @@ class DeviceVk : public DeviceImpl ...@@ -22,7 +22,9 @@ class DeviceVk : public DeviceImpl
~DeviceVk() override; ~DeviceVk() override;
egl::Error initialize() override; egl::Error initialize() override;
egl::Error getDevice(void **outValue) override; egl::Error getAttribute(const egl::Display *display,
EGLint attribute,
void **outValue) override;
EGLint getType() override; EGLint getType() override;
void generateExtensions(egl::DeviceExtensions *outExtensions) const override; void generateExtensions(egl::DeviceExtensions *outExtensions) const override;
}; };
......
...@@ -842,6 +842,8 @@ libangle_gl_egl_android_sources = [ ...@@ -842,6 +842,8 @@ libangle_gl_egl_android_sources = [
libangle_gl_cgl_sources = [ libangle_gl_cgl_sources = [
"src/libANGLE/renderer/gl/cgl/ContextCGL.cpp", "src/libANGLE/renderer/gl/cgl/ContextCGL.cpp",
"src/libANGLE/renderer/gl/cgl/ContextCGL.h", "src/libANGLE/renderer/gl/cgl/ContextCGL.h",
"src/libANGLE/renderer/gl/cgl/DeviceCGL.cpp",
"src/libANGLE/renderer/gl/cgl/DeviceCGL.h",
"src/libANGLE/renderer/gl/cgl/DisplayCGL.mm", "src/libANGLE/renderer/gl/cgl/DisplayCGL.mm",
"src/libANGLE/renderer/gl/cgl/DisplayCGL.h", "src/libANGLE/renderer/gl/cgl/DisplayCGL.h",
"src/libANGLE/renderer/gl/cgl/IOSurfaceSurfaceCGL.mm", "src/libANGLE/renderer/gl/cgl/IOSurfaceSurfaceCGL.mm",
......
...@@ -299,13 +299,17 @@ EGLBoolean EGLAPIENTRY EGL_QueryDeviceAttribEXT(EGLDeviceEXT device, ...@@ -299,13 +299,17 @@ EGLBoolean EGLAPIENTRY EGL_QueryDeviceAttribEXT(EGLDeviceEXT device,
GetDeviceIfValid(dev)); GetDeviceIfValid(dev));
return EGL_FALSE; return EGL_FALSE;
} }
error = dev->getDevice(value); error = dev->getAttribute(attribute, value);
if (error.isError()) break;
case EGL_CGL_CONTEXT_ANGLE:
case EGL_CGL_PIXEL_FORMAT_ANGLE:
if (!dev->getExtensions().deviceCGL)
{ {
thread->setError(error, GetDebug(), "eglQueryDeviceAttribEXT", thread->setError(EglBadAttribute(), GetDebug(), "eglQueryDeviceAttribEXT",
GetDeviceIfValid(dev)); GetDeviceIfValid(dev));
return EGL_FALSE; return EGL_FALSE;
} }
error = dev->getAttribute(attribute, value);
break; break;
default: default:
thread->setError(EglBadAttribute(), GetDebug(), "eglQueryDeviceAttribEXT", thread->setError(EglBadAttribute(), GetDebug(), "eglQueryDeviceAttribEXT",
...@@ -313,6 +317,11 @@ EGLBoolean EGLAPIENTRY EGL_QueryDeviceAttribEXT(EGLDeviceEXT device, ...@@ -313,6 +317,11 @@ EGLBoolean EGLAPIENTRY EGL_QueryDeviceAttribEXT(EGLDeviceEXT device,
return EGL_FALSE; return EGL_FALSE;
} }
if (error.isError())
{
thread->setError(error, GetDebug(), "eglQueryDeviceAttribEXT", GetDeviceIfValid(dev));
return EGL_FALSE;
}
thread->setSuccess(); thread->setSuccess();
return EGL_TRUE; return EGL_TRUE;
} }
......
...@@ -158,8 +158,10 @@ angle_end2end_tests_sources = [ ...@@ -158,8 +158,10 @@ angle_end2end_tests_sources = [
"test_utils/angle_test_instantiate.h", "test_utils/angle_test_instantiate.h",
"test_utils/gl_raii.h", "test_utils/gl_raii.h",
] ]
angle_end2end_tests_mac_sources = angle_end2end_tests_mac_sources = [
[ "egl_tests/EGLIOSurfaceClientBufferTest.cpp" ] "egl_tests/EGLDeviceCGLTest.cpp",
"egl_tests/EGLIOSurfaceClientBufferTest.cpp",
]
angle_end2end_tests_win_sources = [ angle_end2end_tests_win_sources = [
"gl_tests/D3DImageFormatConversionTest.cpp", "gl_tests/D3DImageFormatConversionTest.cpp",
"egl_tests/EGLDeviceTest.cpp", "egl_tests/EGLDeviceTest.cpp",
......
//
// Copyright 2019 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.
//
// EGLDeviceCGLTest.cpp: tests for the EGL_ANGLE_device_cgl extension.
//
#include "test_utils/ANGLETest.h"
#include "util/EGLWindow.h"
#include "util/OSWindow.h"
#include "util/gles_loader_autogen.h"
using namespace angle;
class EGLDeviceCGLQueryTest : public ANGLETest
{
protected:
EGLDeviceCGLQueryTest() {}
void testSetUp() override
{
const char *extensionString =
static_cast<const char *>(eglQueryString(getEGLWindow()->getDisplay(), EGL_EXTENSIONS));
if (!eglQueryDeviceStringEXT)
{
FAIL() << "ANGLE extension EGL_EXT_device_query export eglQueryDeviceStringEXT was not "
"found";
}
if (!eglQueryDisplayAttribEXT)
{
FAIL() << "ANGLE extension EGL_EXT_device_query export eglQueryDisplayAttribEXT was "
"not found";
}
if (!eglQueryDeviceAttribEXT)
{
FAIL() << "ANGLE extension EGL_EXT_device_query export eglQueryDeviceAttribEXT was not "
"found";
}
EGLAttrib angleDevice = 0;
EXPECT_EGL_TRUE(
eglQueryDisplayAttribEXT(getEGLWindow()->getDisplay(), EGL_DEVICE_EXT, &angleDevice));
extensionString = static_cast<const char *>(
eglQueryDeviceStringEXT(reinterpret_cast<EGLDeviceEXT>(angleDevice), EGL_EXTENSIONS));
if (strstr(extensionString, "EGL_ANGLE_device_cgl") == nullptr)
{
FAIL() << "ANGLE extension EGL_ANGLE_device_cgl was not found";
}
}
};
// This test attempts to query the CGLContextObj and CGLPixelFormatObj from the
// EGLDevice associated with the display.
TEST_P(EGLDeviceCGLQueryTest, QueryDevice)
{
EGLAttrib angleDevice = 0;
EXPECT_EGL_TRUE(
eglQueryDisplayAttribEXT(getEGLWindow()->getDisplay(), EGL_DEVICE_EXT, &angleDevice));
EGLAttrib contextAttrib = 0;
EGLAttrib pixelFormatAttrib = 0;
EXPECT_EGL_TRUE(eglQueryDeviceAttribEXT(reinterpret_cast<EGLDeviceEXT>(angleDevice),
EGL_CGL_CONTEXT_ANGLE, &contextAttrib));
EXPECT_TRUE(contextAttrib != 0);
EXPECT_EGL_TRUE(eglQueryDeviceAttribEXT(reinterpret_cast<EGLDeviceEXT>(angleDevice),
EGL_CGL_PIXEL_FORMAT_ANGLE, &pixelFormatAttrib));
EXPECT_TRUE(pixelFormatAttrib != 0);
}
// Use this to select which configurations (e.g. which renderer, which GLES major version) these
// tests should be run against.
ANGLE_INSTANTIATE_TEST(EGLDeviceCGLQueryTest, ES2_OPENGL(), ES3_OPENGL());
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment