Commit 56663dbf by Jamie Madill Committed by Commit Bot

EGL: Expose device query as a client extension.

This matches the extension spec. Previously we were exposing the ext as a normal display extension. The extension should work without needing a display. Because the extension requires a non-null device for every display we also add a MockDevice class to handle back-ends which don't implement any attribute query extensions. By default the device query ext does not expose any way to use devices so this works fine. Bug: angleproject:5372 Change-Id: I474310a86aff6a83bd6f9a6b21c8a07c649f306d Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2551543Reviewed-by: 's avatarJonah Ryan-Davis <jonahr@google.com> Reviewed-by: 's avatarCody Northrop <cnorthrop@google.com> Commit-Queue: Jamie Madill <jmadill@chromium.org>
parent c2a74cbb
...@@ -1375,7 +1375,6 @@ std::vector<std::string> DisplayExtensions::getStrings() const ...@@ -1375,7 +1375,6 @@ std::vector<std::string> DisplayExtensions::getStrings() const
InsertExtensionString("EGL_ANGLE_windows_ui_composition", windowsUIComposition, &extensionStrings); InsertExtensionString("EGL_ANGLE_windows_ui_composition", windowsUIComposition, &extensionStrings);
InsertExtensionString("EGL_NV_post_sub_buffer", postSubBuffer, &extensionStrings); InsertExtensionString("EGL_NV_post_sub_buffer", postSubBuffer, &extensionStrings);
InsertExtensionString("EGL_KHR_create_context", createContext, &extensionStrings); InsertExtensionString("EGL_KHR_create_context", createContext, &extensionStrings);
InsertExtensionString("EGL_EXT_device_query", deviceQuery, &extensionStrings);
InsertExtensionString("EGL_KHR_image", image, &extensionStrings); InsertExtensionString("EGL_KHR_image", image, &extensionStrings);
InsertExtensionString("EGL_KHR_image_base", imageBase, &extensionStrings); InsertExtensionString("EGL_KHR_image_base", imageBase, &extensionStrings);
InsertExtensionString("EGL_KHR_image_pixmap", imagePixmap, &extensionStrings); InsertExtensionString("EGL_KHR_image_pixmap", imagePixmap, &extensionStrings);
...@@ -1464,6 +1463,7 @@ std::vector<std::string> ClientExtensions::getStrings() const ...@@ -1464,6 +1463,7 @@ std::vector<std::string> ClientExtensions::getStrings() const
// clang-format off // clang-format off
// | Extension name | Supported flag | Output vector | // | Extension name | Supported flag | Output vector |
InsertExtensionString("EGL_EXT_client_extensions", clientExtensions, &extensionStrings); InsertExtensionString("EGL_EXT_client_extensions", clientExtensions, &extensionStrings);
InsertExtensionString("EGL_EXT_device_query", deviceQueryEXT, &extensionStrings);
InsertExtensionString("EGL_EXT_platform_base", platformBase, &extensionStrings); InsertExtensionString("EGL_EXT_platform_base", platformBase, &extensionStrings);
InsertExtensionString("EGL_EXT_platform_device", platformDevice, &extensionStrings); InsertExtensionString("EGL_EXT_platform_device", platformDevice, &extensionStrings);
InsertExtensionString("EGL_ANGLE_platform_angle", platformANGLE, &extensionStrings); InsertExtensionString("EGL_ANGLE_platform_angle", platformANGLE, &extensionStrings);
......
...@@ -989,9 +989,6 @@ struct DisplayExtensions ...@@ -989,9 +989,6 @@ struct DisplayExtensions
// EGL_KHR_create_context // EGL_KHR_create_context
bool createContext = false; bool createContext = false;
// EGL_EXT_device_query
bool deviceQuery = false;
// EGL_KHR_image // EGL_KHR_image
bool image = false; bool image = false;
...@@ -1266,6 +1263,9 @@ struct ClientExtensions ...@@ -1266,6 +1263,9 @@ struct ClientExtensions
// EGL_ANGLE_platform_angle_device_type_egl_angle // EGL_ANGLE_platform_angle_device_type_egl_angle
bool platformANGLEDeviceTypeEGLANGLE = false; bool platformANGLEDeviceTypeEGLANGLE = false;
// EGL_EXT_device_query
bool deviceQueryEXT = false;
}; };
} // namespace egl } // namespace egl
......
...@@ -864,32 +864,29 @@ Error Display::initialize() ...@@ -864,32 +864,29 @@ Error Display::initialize()
initVendorString(); initVendorString();
// Populate the Display's EGLDeviceEXT if the Display wasn't created using one // Populate the Display's EGLDeviceEXT if the Display wasn't created using one
if (mPlatform != EGL_PLATFORM_DEVICE_EXT) if (mPlatform == EGL_PLATFORM_DEVICE_EXT)
{ {
if (mDisplayExtensions.deviceQuery) // For EGL_PLATFORM_DEVICE_EXT, mDevice should always be populated using
{ // an external device
std::unique_ptr<rx::DeviceImpl> impl(mImplementation->createDevice()); ASSERT(mDevice != nullptr);
ASSERT(impl != nullptr); }
error = impl->initialize(); else if (GetClientExtensions().deviceQueryEXT)
if (error.isError()) {
{ std::unique_ptr<rx::DeviceImpl> impl(mImplementation->createDevice());
ERR() << "Failed to initialize display because device creation failed: " ASSERT(impl);
<< error.getMessage(); error = impl->initialize();
mImplementation->terminate(); if (error.isError())
return error;
}
mDevice = new Device(this, impl.release());
}
else
{ {
mDevice = nullptr; ERR() << "Failed to initialize display because device creation failed: "
<< error.getMessage();
mImplementation->terminate();
return error;
} }
mDevice = new Device(this, impl.release());
} }
else else
{ {
// For EGL_PLATFORM_DEVICE_EXT, mDevice should always be populated using mDevice = nullptr;
// an external device
ASSERT(mDevice != nullptr);
} }
mInitialized = true; mInitialized = true;
...@@ -1666,6 +1663,7 @@ static ClientExtensions GenerateClientExtensions() ...@@ -1666,6 +1663,7 @@ static ClientExtensions GenerateClientExtensions()
extensions.debug = true; extensions.debug = true;
extensions.explicitContext = true; extensions.explicitContext = true;
extensions.featureControlANGLE = true; extensions.featureControlANGLE = true;
extensions.deviceQueryEXT = true;
return extensions; return extensions;
} }
......
...@@ -10,9 +10,34 @@ ...@@ -10,9 +10,34 @@
#include "libANGLE/Display.h" #include "libANGLE/Display.h"
#include "libANGLE/Surface.h" #include "libANGLE/Surface.h"
#include "libANGLE/renderer/DeviceImpl.h"
namespace rx namespace rx
{ {
namespace
{
// For back-ends that do not implement EGLDevice.
class MockDevice : public DeviceImpl
{
public:
MockDevice() = default;
egl::Error initialize() override { return egl::NoError(); }
egl::Error getAttribute(const egl::Display *display, EGLint attribute, void **outValue) override
{
UNREACHABLE();
return egl::EglBadAttribute();
}
EGLint getType() override
{
UNREACHABLE();
return EGL_NONE;
}
void generateExtensions(egl::DeviceExtensions *outExtensions) const override
{
*outExtensions = egl::DeviceExtensions();
}
};
} // anonymous namespace
DisplayImpl::DisplayImpl(const egl::DisplayState &state) DisplayImpl::DisplayImpl(const egl::DisplayState &state)
: mState(state), mExtensionsInitialized(false), mCapsInitialized(false), mBlobCache(nullptr) : mState(state), mExtensionsInitialized(false), mCapsInitialized(false), mBlobCache(nullptr)
...@@ -86,4 +111,8 @@ const egl::Caps &DisplayImpl::getCaps() const ...@@ -86,4 +111,8 @@ const egl::Caps &DisplayImpl::getCaps() const
return mCaps; return mCaps;
} }
DeviceImpl *DisplayImpl::createDevice()
{
return new MockDevice();
}
} // namespace rx } // namespace rx
...@@ -96,7 +96,7 @@ class DisplayImpl : public EGLImplFactory, public angle::Subject ...@@ -96,7 +96,7 @@ class DisplayImpl : public EGLImplFactory, public angle::Subject
virtual std::string getVendorString() const = 0; virtual std::string getVendorString() const = 0;
virtual DeviceImpl *createDevice() = 0; virtual DeviceImpl *createDevice();
virtual egl::Error waitClient(const gl::Context *context) = 0; virtual egl::Error waitClient(const gl::Context *context) = 0;
virtual egl::Error waitNative(const gl::Context *context, EGLint engine) = 0; virtual egl::Error waitNative(const gl::Context *context, EGLint engine) = 0;
......
...@@ -1287,8 +1287,6 @@ void Renderer11::generateDisplayExtensions(egl::DisplayExtensions *outExtensions ...@@ -1287,8 +1287,6 @@ void Renderer11::generateDisplayExtensions(egl::DisplayExtensions *outExtensions
// D3D11 does not support present with dirty rectangles until DXGI 1.2. // D3D11 does not support present with dirty rectangles until DXGI 1.2.
outExtensions->postSubBuffer = mRenderer11DeviceCaps.supportsDXGI1_2; outExtensions->postSubBuffer = mRenderer11DeviceCaps.supportsDXGI1_2;
outExtensions->deviceQuery = true;
outExtensions->image = true; outExtensions->image = true;
outExtensions->imageBase = true; outExtensions->imageBase = true;
outExtensions->glTexture2DImage = true; outExtensions->glTexture2DImage = true;
......
...@@ -588,7 +588,6 @@ void Renderer9::generateDisplayExtensions(egl::DisplayExtensions *outExtensions) ...@@ -588,7 +588,6 @@ void Renderer9::generateDisplayExtensions(egl::DisplayExtensions *outExtensions)
outExtensions->querySurfacePointer = true; outExtensions->querySurfacePointer = true;
outExtensions->windowFixedSize = true; outExtensions->windowFixedSize = true;
outExtensions->postSubBuffer = true; outExtensions->postSubBuffer = true;
outExtensions->deviceQuery = true;
outExtensions->image = true; outExtensions->image = true;
outExtensions->imageBase = true; outExtensions->imageBase = true;
......
...@@ -479,7 +479,6 @@ void DisplayCGL::generateExtensions(egl::DisplayExtensions *outExtensions) const ...@@ -479,7 +479,6 @@ 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 and semaphores can be shared globally // Contexts are virtualized so textures and semaphores can be shared globally
outExtensions->displayTextureShareGroup = true; outExtensions->displayTextureShareGroup = true;
......
...@@ -566,12 +566,6 @@ bool DisplayEGL::isValidNativeWindow(EGLNativeWindowType window) const ...@@ -566,12 +566,6 @@ bool DisplayEGL::isValidNativeWindow(EGLNativeWindowType window) const
return true; return true;
} }
DeviceImpl *DisplayEGL::createDevice()
{
UNIMPLEMENTED();
return nullptr;
}
egl::Error DisplayEGL::waitClient(const gl::Context *context) egl::Error DisplayEGL::waitClient(const gl::Context *context)
{ {
UNIMPLEMENTED(); UNIMPLEMENTED();
......
...@@ -76,8 +76,6 @@ class DisplayEGL : public DisplayGL ...@@ -76,8 +76,6 @@ class DisplayEGL : public DisplayGL
bool isValidNativeWindow(EGLNativeWindowType window) const override; bool isValidNativeWindow(EGLNativeWindowType window) const override;
DeviceImpl *createDevice() override;
egl::Error waitClient(const gl::Context *context) override; egl::Error waitClient(const gl::Context *context) override;
egl::Error waitNative(const gl::Context *context, EGLint engine) override; egl::Error waitNative(const gl::Context *context, EGLint engine) override;
......
...@@ -495,12 +495,6 @@ ContextImpl *DisplayGLX::createContext(const gl::State &state, ...@@ -495,12 +495,6 @@ ContextImpl *DisplayGLX::createContext(const gl::State &state,
return new ContextGL(state, errorSet, mRenderer, robustnessVideoMemoryPurgeStatus); return new ContextGL(state, errorSet, mRenderer, robustnessVideoMemoryPurgeStatus);
} }
DeviceImpl *DisplayGLX::createDevice()
{
UNIMPLEMENTED();
return nullptr;
}
egl::Error DisplayGLX::initializeContext(glx::FBConfig config, egl::Error DisplayGLX::initializeContext(glx::FBConfig config,
const egl::AttributeMap &eglAttributes, const egl::AttributeMap &eglAttributes,
glx::Context *context) glx::Context *context)
......
...@@ -70,8 +70,6 @@ class DisplayGLX : public DisplayGL ...@@ -70,8 +70,6 @@ class DisplayGLX : public DisplayGL
bool isValidNativeWindow(EGLNativeWindowType window) const override; bool isValidNativeWindow(EGLNativeWindowType window) const override;
DeviceImpl *createDevice() override;
std::string getVendorString() const override; std::string getVendorString() const override;
egl::Error waitClient(const gl::Context *context) override; egl::Error waitClient(const gl::Context *context) override;
......
...@@ -472,12 +472,6 @@ rx::ContextImpl *DisplayWGL::createContext(const gl::State &state, ...@@ -472,12 +472,6 @@ rx::ContextImpl *DisplayWGL::createContext(const gl::State &state,
return new ContextWGL(state, errorSet, mRenderer); return new ContextWGL(state, errorSet, mRenderer);
} }
DeviceImpl *DisplayWGL::createDevice()
{
UNREACHABLE();
return nullptr;
}
egl::ConfigSet DisplayWGL::generateConfigs() egl::ConfigSet DisplayWGL::generateConfigs()
{ {
egl::ConfigSet configs; egl::ConfigSet configs;
......
...@@ -63,8 +63,6 @@ class DisplayWGL : public DisplayGL ...@@ -63,8 +63,6 @@ class DisplayWGL : public DisplayGL
EGLClientBuffer clientBuffer, EGLClientBuffer clientBuffer,
const egl::AttributeMap &attribs) const override; const egl::AttributeMap &attribs) const override;
DeviceImpl *createDevice() override;
std::string getVendorString() const override; std::string getVendorString() const override;
egl::Error waitClient(const gl::Context *context) override; egl::Error waitClient(const gl::Context *context) override;
......
...@@ -48,8 +48,6 @@ class DisplayMtl : public DisplayImpl ...@@ -48,8 +48,6 @@ class DisplayMtl : public DisplayImpl
std::string getVendorString() const override; std::string getVendorString() const override;
DeviceImpl *createDevice() override;
egl::Error waitClient(const gl::Context *context) override; egl::Error waitClient(const gl::Context *context) override;
egl::Error waitNative(const gl::Context *context, EGLint engine) override; egl::Error waitNative(const gl::Context *context, EGLint engine) override;
......
...@@ -24,7 +24,6 @@ ...@@ -24,7 +24,6 @@
namespace rx namespace rx
{ {
bool IsMetalDisplayAvailable() bool IsMetalDisplayAvailable()
{ {
// We only support macos 10.13+ and 11 for now. Since they are requirements for Metal 2.0. // We only support macos 10.13+ and 11 for now. Since they are requirements for Metal 2.0.
...@@ -153,12 +152,6 @@ std::string DisplayMtl::getVendorString() const ...@@ -153,12 +152,6 @@ std::string DisplayMtl::getVendorString() const
} }
} }
DeviceImpl *DisplayMtl::createDevice()
{
UNIMPLEMENTED();
return nullptr;
}
egl::Error DisplayMtl::waitClient(const gl::Context *context) egl::Error DisplayMtl::waitClient(const gl::Context *context)
{ {
auto contextMtl = GetImplAs<ContextMtl>(context); auto contextMtl = GetImplAs<ContextMtl>(context);
......
...@@ -200,7 +200,6 @@ void DisplayNULL::generateExtensions(egl::DisplayExtensions *outExtensions) cons ...@@ -200,7 +200,6 @@ void DisplayNULL::generateExtensions(egl::DisplayExtensions *outExtensions) cons
outExtensions->createContextRobustness = true; outExtensions->createContextRobustness = true;
outExtensions->postSubBuffer = true; outExtensions->postSubBuffer = true;
outExtensions->createContext = true; outExtensions->createContext = true;
outExtensions->deviceQuery = true;
outExtensions->image = true; outExtensions->image = true;
outExtensions->imageBase = true; outExtensions->imageBase = true;
outExtensions->glTexture2DImage = true; outExtensions->glTexture2DImage = true;
......
...@@ -85,12 +85,6 @@ std::string DisplayVk::getVendorString() const ...@@ -85,12 +85,6 @@ std::string DisplayVk::getVendorString() const
return vendorString; return vendorString;
} }
DeviceImpl *DisplayVk::createDevice()
{
UNIMPLEMENTED();
return nullptr;
}
egl::Error DisplayVk::waitClient(const gl::Context *context) egl::Error DisplayVk::waitClient(const gl::Context *context)
{ {
ANGLE_TRACE_EVENT0("gpu.angle", "DisplayVk::waitClient"); ANGLE_TRACE_EVENT0("gpu.angle", "DisplayVk::waitClient");
......
...@@ -64,8 +64,6 @@ class DisplayVk : public DisplayImpl, public vk::Context ...@@ -64,8 +64,6 @@ class DisplayVk : public DisplayImpl, public vk::Context
std::string getVendorString() const override; std::string getVendorString() const override;
DeviceImpl *createDevice() override;
egl::Error waitClient(const gl::Context *context) override; egl::Error waitClient(const gl::Context *context) override;
egl::Error waitNative(const gl::Context *context, EGLint engine) override; egl::Error waitNative(const gl::Context *context, EGLint engine) override;
......
...@@ -4545,7 +4545,7 @@ Error ValidateQueryDisplayAttribBase(const Display *display, const EGLint attrib ...@@ -4545,7 +4545,7 @@ Error ValidateQueryDisplayAttribBase(const Display *display, const EGLint attrib
switch (attribute) switch (attribute)
{ {
case EGL_DEVICE_EXT: case EGL_DEVICE_EXT:
if (!display->getExtensions().deviceQuery) if (!Display::GetClientExtensions().deviceQueryEXT)
{ {
return EglBadDisplay() << "EGL_EXT_device_query extension is not available."; return EglBadDisplay() << "EGL_EXT_device_query extension is not available.";
} }
......
...@@ -279,20 +279,10 @@ EGLBoolean EGLAPIENTRY EGL_QueryDeviceAttribEXT(EGLDeviceEXT device, ...@@ -279,20 +279,10 @@ EGLBoolean EGLAPIENTRY EGL_QueryDeviceAttribEXT(EGLDeviceEXT device,
thread->setError(error, GetDebug(), "eglQueryDeviceAttribEXT", GetDeviceIfValid(dev)); thread->setError(error, GetDebug(), "eglQueryDeviceAttribEXT", GetDeviceIfValid(dev));
return EGL_FALSE; return EGL_FALSE;
} }
egl::Display *owningDisplay = dev->getOwningDisplay(); if (!Display::GetClientExtensions().deviceQueryEXT)
if (owningDisplay)
{
ANGLE_EGL_TRY_RETURN(thread, owningDisplay->prepareForCall(), "eglQueryDeviceAttribEXT",
GetDisplayIfValid(owningDisplay), EGL_FALSE);
}
// If the device was created by (and is owned by) a display, and that display doesn't support
// device querying, then this call should fail
if (owningDisplay != nullptr && !owningDisplay->getExtensions().deviceQuery)
{ {
thread->setError(EglBadAccess() << "Device wasn't created using eglCreateDeviceANGLE, " thread->setError(EglBadAccess() << "EGL_EXT_device_query not supported.", GetDebug(),
"and the egl::Display that created it doesn't support " "eglQueryDeviceAttribEXT", GetDeviceIfValid(dev));
"device querying",
GetDebug(), "eglQueryDeviceAttribEXT", GetDeviceIfValid(dev));
return EGL_FALSE; return EGL_FALSE;
} }
......
...@@ -88,9 +88,7 @@ class EGLPresentPathD3D11 : public ANGLETest ...@@ -88,9 +88,7 @@ class EGLPresentPathD3D11 : public ANGLETest
EGLAttrib device = 0; EGLAttrib device = 0;
EGLAttrib angleDevice = 0; EGLAttrib angleDevice = 0;
const char *extensionString = EXPECT_TRUE(IsEGLClientExtensionEnabled("EGL_EXT_device_query"));
static_cast<const char *>(eglQueryString(mDisplay, EGL_EXTENSIONS));
EXPECT_TRUE(strstr(extensionString, "EGL_EXT_device_query"));
ASSERT_EGL_TRUE(eglQueryDisplayAttribEXT(mDisplay, EGL_DEVICE_EXT, &angleDevice)); ASSERT_EGL_TRUE(eglQueryDisplayAttribEXT(mDisplay, EGL_DEVICE_EXT, &angleDevice));
ASSERT_EGL_TRUE(eglQueryDeviceAttribEXT(reinterpret_cast<EGLDeviceEXT>(angleDevice), ASSERT_EGL_TRUE(eglQueryDeviceAttribEXT(reinterpret_cast<EGLDeviceEXT>(angleDevice),
......
...@@ -80,37 +80,30 @@ class D3DTextureTest : public ANGLETest ...@@ -80,37 +80,30 @@ class D3DTextureTest : public ANGLETest
PFN_D3D11_CREATE_DEVICE createDeviceFunc = reinterpret_cast<PFN_D3D11_CREATE_DEVICE>( PFN_D3D11_CREATE_DEVICE createDeviceFunc = reinterpret_cast<PFN_D3D11_CREATE_DEVICE>(
GetProcAddress(mD3D11Module, "D3D11CreateDevice")); GetProcAddress(mD3D11Module, "D3D11CreateDevice"));
EGLWindow *window = getEGLWindow(); EGLWindow *window = getEGLWindow();
EGLDisplay display = window->getDisplay(); EGLDisplay display = window->getDisplay();
if (IsEGLDisplayExtensionEnabled(display, "EGL_EXT_device_query")) EGLDeviceEXT device = EGL_NO_DEVICE_EXT;
if (IsEGLClientExtensionEnabled("EGL_EXT_device_query"))
{ {
PFNEGLQUERYDISPLAYATTRIBEXTPROC eglQueryDisplayAttribEXT = EGLAttrib result = 0;
reinterpret_cast<PFNEGLQUERYDISPLAYATTRIBEXTPROC>( EXPECT_EGL_TRUE(eglQueryDisplayAttribEXT(display, EGL_DEVICE_EXT, &result));
eglGetProcAddress("eglQueryDisplayAttribEXT")); device = reinterpret_cast<EGLDeviceEXT>(result);
PFNEGLQUERYDEVICEATTRIBEXTPROC eglQueryDeviceAttribEXT = }
reinterpret_cast<PFNEGLQUERYDEVICEATTRIBEXTPROC>(
eglGetProcAddress("eglQueryDeviceAttribEXT")); ASSERT_NE(EGL_NO_DEVICE_EXT, device);
EGLDeviceEXT device = 0; if (IsEGLDeviceExtensionEnabled(device, "EGL_ANGLE_device_d3d"))
{
EGLAttrib result = 0;
if (eglQueryDeviceAttribEXT(device, EGL_D3D11_DEVICE_ANGLE, &result))
{ {
EGLAttrib result = 0; mD3D11Device = reinterpret_cast<ID3D11Device *>(result);
EXPECT_EGL_TRUE(eglQueryDisplayAttribEXT(display, EGL_DEVICE_EXT, &result)); mD3D11Device->AddRef();
device = reinterpret_cast<EGLDeviceEXT>(result);
} }
else if (eglQueryDeviceAttribEXT(device, EGL_D3D9_DEVICE_ANGLE, &result))
if (IsEGLDeviceExtensionEnabled(device, "EGL_ANGLE_device_d3d"))
{ {
EGLAttrib result = 0; mD3D9Device = reinterpret_cast<IDirect3DDevice9 *>(result);
if (eglQueryDeviceAttribEXT(device, EGL_D3D11_DEVICE_ANGLE, &result)) mD3D9Device->AddRef();
{
mD3D11Device = reinterpret_cast<ID3D11Device *>(result);
mD3D11Device->AddRef();
}
else if (eglQueryDeviceAttribEXT(device, EGL_D3D9_DEVICE_ANGLE, &result))
{
mD3D9Device = reinterpret_cast<IDirect3DDevice9 *>(result);
mD3D9Device->AddRef();
}
} }
} }
else else
......
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