Commit 5c39f688 by Geoff Lang

Have the DisplayImpl create the Renderer and Contexts.

BUG=angle:658 Change-Id: I726d87b90be8382c5dd8964e4d8686711e404ffe Reviewed-on: https://chromium-review.googlesource.com/238861Reviewed-by: 's avatarGeoff Lang <geofflang@chromium.org> Tested-by: 's avatarGeoff Lang <geofflang@chromium.org>
parent 821f40a4
...@@ -433,6 +433,11 @@ Caps::Caps() ...@@ -433,6 +433,11 @@ Caps::Caps()
namespace egl namespace egl
{ {
Caps::Caps()
: textureNPOT(false)
{
}
DisplayExtensions::DisplayExtensions() DisplayExtensions::DisplayExtensions()
: createContextRobustness(false), : createContextRobustness(false),
d3dShareHandleClientBuffer(false), d3dShareHandleClientBuffer(false),
......
...@@ -283,6 +283,14 @@ struct Caps ...@@ -283,6 +283,14 @@ struct Caps
namespace egl namespace egl
{ {
struct Caps
{
Caps();
// Support for NPOT surfaces
bool textureNPOT;
};
struct DisplayExtensions struct DisplayExtensions
{ {
DisplayExtensions(); DisplayExtensions();
......
...@@ -102,8 +102,6 @@ class ConfigSet ...@@ -102,8 +102,6 @@ class ConfigSet
const egl::Config *get(EGLConfig configHandle); const egl::Config *get(EGLConfig configHandle);
private: private:
DISALLOW_COPY_AND_ASSIGN(ConfigSet);
typedef std::set<Config, SortConfig> Set; typedef std::set<Config, SortConfig> Set;
typedef Set::iterator Iterator; typedef Set::iterator Iterator;
Set mSet; Set mSet;
......
...@@ -24,97 +24,13 @@ ...@@ -24,97 +24,13 @@
#include <EGL/eglext.h> #include <EGL/eglext.h>
#if defined (ANGLE_ENABLE_D3D9) #if defined(ANGLE_ENABLE_D3D9) || defined(ANGLE_ENABLE_D3D11)
# include "libANGLE/renderer/d3d/d3d9/Renderer9.h" # include "libANGLE/renderer/d3d/DisplayD3D.h"
#endif // ANGLE_ENABLE_D3D9
#if defined (ANGLE_ENABLE_D3D11)
# include "libANGLE/renderer/d3d/d3d11/Renderer11.h"
#endif // ANGLE_ENABLE_D3D11
#if defined (ANGLE_TEST_CONFIG)
# define ANGLE_DEFAULT_D3D11 1
#endif
#if !defined(ANGLE_DEFAULT_D3D11)
// Enables use of the Direct3D 11 API for a default display, when available
# define ANGLE_DEFAULT_D3D11 0
#endif #endif
namespace egl namespace egl
{ {
typedef rx::Renderer *(*CreateRendererFunction)(Display*, EGLNativeDisplayType, const AttributeMap &);
template <typename RendererType>
static rx::Renderer *CreateTypedRenderer(Display *display, EGLNativeDisplayType nativeDisplay, const AttributeMap &attributes)
{
return new RendererType(display, nativeDisplay, attributes);
}
rx::Renderer *CreateRenderer(Display *display, EGLNativeDisplayType nativeDisplay, const AttributeMap &attribMap)
{
std::vector<CreateRendererFunction> rendererCreationFunctions;
EGLint requestedDisplayType = attribMap.get(EGL_PLATFORM_ANGLE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE);
# if defined(ANGLE_ENABLE_D3D11)
if (nativeDisplay == EGL_D3D11_ELSE_D3D9_DISPLAY_ANGLE ||
nativeDisplay == EGL_D3D11_ONLY_DISPLAY_ANGLE ||
requestedDisplayType == EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE)
{
rendererCreationFunctions.push_back(CreateTypedRenderer<rx::Renderer11>);
}
# endif
# if defined(ANGLE_ENABLE_D3D9)
if (nativeDisplay == EGL_D3D11_ELSE_D3D9_DISPLAY_ANGLE ||
requestedDisplayType == EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE)
{
rendererCreationFunctions.push_back(CreateTypedRenderer<rx::Renderer9>);
}
# endif
if (nativeDisplay != EGL_D3D11_ELSE_D3D9_DISPLAY_ANGLE &&
nativeDisplay != EGL_D3D11_ONLY_DISPLAY_ANGLE &&
requestedDisplayType == EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE)
{
// The default display is requested, try the D3D9 and D3D11 renderers, order them using
// the definition of ANGLE_DEFAULT_D3D11
# if ANGLE_DEFAULT_D3D11
# if defined(ANGLE_ENABLE_D3D11)
rendererCreationFunctions.push_back(CreateTypedRenderer<rx::Renderer11>);
# endif
# if defined(ANGLE_ENABLE_D3D9)
rendererCreationFunctions.push_back(CreateTypedRenderer<rx::Renderer9>);
# endif
# else
# if defined(ANGLE_ENABLE_D3D9)
rendererCreationFunctions.push_back(CreateTypedRenderer<rx::Renderer9>);
# endif
# if defined(ANGLE_ENABLE_D3D11)
rendererCreationFunctions.push_back(CreateTypedRenderer<rx::Renderer11>);
# endif
# endif
}
for (size_t i = 0; i < rendererCreationFunctions.size(); i++)
{
rx::Renderer *renderer = rendererCreationFunctions[i](display, nativeDisplay, attribMap);
if (renderer->initialize() == EGL_SUCCESS)
{
return renderer;
}
else
{
// Failed to create the renderer, try the next
SafeDelete(renderer);
}
}
return NULL;
}
typedef std::map<EGLNativeDisplayType, Display*> DisplayMap; typedef std::map<EGLNativeDisplayType, Display*> DisplayMap;
static DisplayMap *GetDisplayMap() static DisplayMap *GetDisplayMap()
{ {
...@@ -134,7 +50,39 @@ Display *Display::getDisplay(EGLNativeDisplayType displayId, const AttributeMap ...@@ -134,7 +50,39 @@ Display *Display::getDisplay(EGLNativeDisplayType displayId, const AttributeMap
} }
else else
{ {
display = new Display(displayId); rx::DisplayImpl *impl = nullptr;
EGLint displayType = attribMap.get(EGL_PLATFORM_ANGLE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE);
switch (displayType)
{
case EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE:
#if defined(ANGLE_ENABLE_D3D9) || defined(ANGLE_ENABLE_D3D11)
// Default to D3D displays
impl = new rx::DisplayD3D();
#else
// No display available
UNREACHABLE();
#endif
break;
case EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE:
case EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE:
#if defined(ANGLE_ENABLE_D3D9) || defined(ANGLE_ENABLE_D3D11)
impl = new rx::DisplayD3D();
#else
// A D3D display was requested on a platform that doesn't support it
UNREACHABLE();
#endif
break;
default:
UNREACHABLE();
break;
}
ASSERT(impl != nullptr);
display = new Display(impl, displayId);
// Validate the native display // Validate the native display
if (!display->isValidNativeDisplay(displayId)) if (!display->isValidNativeDisplay(displayId))
...@@ -156,12 +104,13 @@ Display *Display::getDisplay(EGLNativeDisplayType displayId, const AttributeMap ...@@ -156,12 +104,13 @@ Display *Display::getDisplay(EGLNativeDisplayType displayId, const AttributeMap
return display; return display;
} }
Display::Display(EGLNativeDisplayType displayId) Display::Display(rx::DisplayImpl *impl, EGLNativeDisplayType displayId)
: mImplementation(NULL), : mImplementation(impl),
mDisplayId(displayId), mDisplayId(displayId),
mAttributeMap(), mAttributeMap(),
mRenderer(NULL) mInitialized(false)
{ {
ASSERT(mImplementation != nullptr);
} }
Display::~Display() Display::~Display()
...@@ -174,6 +123,8 @@ Display::~Display() ...@@ -174,6 +123,8 @@ Display::~Display()
{ {
displays->erase(iter); displays->erase(iter);
} }
SafeDelete(mImplementation);
} }
void Display::setAttributes(const AttributeMap &attribMap) void Display::setAttributes(const AttributeMap &attribMap)
...@@ -188,29 +139,15 @@ Error Display::initialize() ...@@ -188,29 +139,15 @@ Error Display::initialize()
return Error(EGL_SUCCESS); return Error(EGL_SUCCESS);
} }
mRenderer = CreateRenderer(this, mDisplayId, mAttributeMap); Error error = mImplementation->initialize(this, mDisplayId, mAttributeMap);
if (error.isError())
if (!mRenderer)
{ {
terminate(); return error;
return Error(EGL_NOT_INITIALIZED);
} }
mImplementation = mRenderer->createDisplay(); mCaps = mImplementation->getCaps();
ASSERT(mImplementation);
//TODO(jmadill): should be part of caps?
EGLint minSwapInterval = mRenderer->getMinSwapInterval();
EGLint maxSwapInterval = mRenderer->getMaxSwapInterval();
EGLint maxTextureSize = mRenderer->getRendererCaps().max2DTextureSize;
std::vector<rx::ConfigDesc> descList = mImplementation->generateConfigs();
ConfigSet configSet; ConfigSet configSet = mImplementation->generateConfigs();
for (size_t i = 0; i < descList.size(); ++i)
{
configSet.add(descList[i], minSwapInterval, maxSwapInterval, maxTextureSize, maxTextureSize);
}
// Give the sorted configs a unique ID and store them internally // Give the sorted configs a unique ID and store them internally
EGLint index = 1; EGLint index = 1;
...@@ -223,15 +160,16 @@ Error Display::initialize() ...@@ -223,15 +160,16 @@ Error Display::initialize()
mConfigSet.mSet.insert(configuration); mConfigSet.mSet.insert(configuration);
} }
if (!isInitialized()) if (mConfigSet.size() == 0)
{ {
terminate(); mImplementation->terminate();
return Error(EGL_NOT_INITIALIZED); return Error(EGL_NOT_INITIALIZED);
} }
initDisplayExtensions(); initDisplayExtensions();
initVendorString(); initVendorString();
mInitialized = true;
return Error(EGL_SUCCESS); return Error(EGL_SUCCESS);
} }
...@@ -242,9 +180,10 @@ void Display::terminate() ...@@ -242,9 +180,10 @@ void Display::terminate()
destroyContext(*mContextSet.begin()); destroyContext(*mContextSet.begin());
} }
SafeDelete(mRenderer);
mConfigSet.mSet.clear(); mConfigSet.mSet.clear();
mImplementation->terminate();
mInitialized = false;
} }
bool Display::getConfigs(EGLConfig *configs, const EGLint *attribList, EGLint configSize, EGLint *numConfig) bool Display::getConfigs(EGLConfig *configs, const EGLint *attribList, EGLint configSize, EGLint *numConfig)
...@@ -365,7 +304,7 @@ Error Display::createWindowSurface(EGLNativeWindowType window, EGLConfig config, ...@@ -365,7 +304,7 @@ Error Display::createWindowSurface(EGLNativeWindowType window, EGLConfig config,
return Error(EGL_BAD_ALLOC); return Error(EGL_BAD_ALLOC);
} }
if (mRenderer->testDeviceLost()) if (mImplementation->testDeviceLost())
{ {
Error error = restoreLostDevice(); Error error = restoreLostDevice();
if (error.isError()) if (error.isError())
...@@ -465,7 +404,7 @@ Error Display::createOffscreenSurface(EGLConfig config, EGLClientBuffer shareHan ...@@ -465,7 +404,7 @@ Error Display::createOffscreenSurface(EGLConfig config, EGLClientBuffer shareHan
return Error(EGL_BAD_ATTRIBUTE); return Error(EGL_BAD_ATTRIBUTE);
} }
if (textureFormat != EGL_NO_TEXTURE && !mRenderer->getRendererExtensions().textureNPOT && (!gl::isPow2(width) || !gl::isPow2(height))) if (textureFormat != EGL_NO_TEXTURE && !mCaps.textureNPOT && (!gl::isPow2(width) || !gl::isPow2(height)))
{ {
return Error(EGL_BAD_MATCH); return Error(EGL_BAD_MATCH);
} }
...@@ -487,7 +426,7 @@ Error Display::createOffscreenSurface(EGLConfig config, EGLClientBuffer shareHan ...@@ -487,7 +426,7 @@ Error Display::createOffscreenSurface(EGLConfig config, EGLClientBuffer shareHan
return Error(EGL_BAD_ATTRIBUTE); return Error(EGL_BAD_ATTRIBUTE);
} }
if (mRenderer->testDeviceLost()) if (mImplementation->testDeviceLost())
{ {
Error error = restoreLostDevice(); Error error = restoreLostDevice();
if (error.isError()) if (error.isError())
...@@ -513,17 +452,12 @@ Error Display::createOffscreenSurface(EGLConfig config, EGLClientBuffer shareHan ...@@ -513,17 +452,12 @@ Error Display::createOffscreenSurface(EGLConfig config, EGLClientBuffer shareHan
return Error(EGL_SUCCESS); return Error(EGL_SUCCESS);
} }
Error Display::createContext(EGLConfig configHandle, EGLint clientVersion, const gl::Context *shareContext, bool notifyResets, Error Display::createContext(EGLConfig configHandle, EGLContext shareContext, const egl::AttributeMap &attribs,
bool robustAccess, EGLContext *outContext) EGLContext *outContext)
{ {
const Config *configuration = mConfigSet.get(configHandle); ASSERT(isInitialized());
if (!mRenderer) if (mImplementation->testDeviceLost())
{
*outContext = EGL_NO_CONTEXT;
return Error(EGL_SUCCESS);
}
else if (mRenderer->testDeviceLost()) // Lost device
{ {
Error error = restoreLostDevice(); Error error = restoreLostDevice();
if (error.isError()) if (error.isError())
...@@ -532,12 +466,21 @@ Error Display::createContext(EGLConfig configHandle, EGLint clientVersion, const ...@@ -532,12 +466,21 @@ Error Display::createContext(EGLConfig configHandle, EGLint clientVersion, const
} }
} }
if (clientVersion == 3 && !(configuration->mConformant & EGL_OPENGL_ES3_BIT_KHR)) const Config *configuration = mConfigSet.get(configHandle);
if (attribs.get(EGL_CONTEXT_CLIENT_VERSION, 1) == 3 && !(configuration->mConformant & EGL_OPENGL_ES3_BIT_KHR))
{ {
return Error(EGL_BAD_CONFIG); return Error(EGL_BAD_CONFIG);
} }
gl::Context *context = new gl::Context(clientVersion, shareContext, mRenderer, notifyResets, robustAccess); gl::Context *context = nullptr;
Error error = mImplementation->createContext(configuration, reinterpret_cast<gl::Context*>(shareContext),
attribs, &context);
if (error.isError())
{
return error;
}
ASSERT(context != nullptr);
mContextSet.insert(context); mContextSet.insert(context);
*outContext = context; *outContext = context;
...@@ -569,6 +512,18 @@ void Display::destroyContext(gl::Context *context) ...@@ -569,6 +512,18 @@ void Display::destroyContext(gl::Context *context)
SafeDelete(context); SafeDelete(context);
} }
bool Display::isDeviceLost() const
{
ASSERT(isInitialized());
return mImplementation->isDeviceLost();
}
bool Display::testDeviceLost()
{
ASSERT(isInitialized());
return mImplementation->testDeviceLost();
}
void Display::notifyDeviceLost() void Display::notifyDeviceLost()
{ {
for (ContextSet::iterator context = mContextSet.begin(); context != mContextSet.end(); context++) for (ContextSet::iterator context = mContextSet.begin(); context != mContextSet.end(); context++)
...@@ -577,9 +532,14 @@ void Display::notifyDeviceLost() ...@@ -577,9 +532,14 @@ void Display::notifyDeviceLost()
} }
} }
const Caps &Display::getCaps() const
{
return mCaps;
}
bool Display::isInitialized() const bool Display::isInitialized() const
{ {
return mRenderer != NULL && mConfigSet.size() > 0; return mInitialized;
} }
bool Display::isValidConfig(EGLConfig config) bool Display::isValidConfig(EGLConfig config)
...@@ -685,13 +645,7 @@ bool Display::isValidNativeDisplay(EGLNativeDisplayType display) const ...@@ -685,13 +645,7 @@ bool Display::isValidNativeDisplay(EGLNativeDisplayType display) const
void Display::initVendorString() void Display::initVendorString()
{ {
mVendorString = "Google Inc."; mVendorString = mImplementation->getVendorString();
// TODO(jmadill): clean this up
if (mRenderer)
{
mVendorString += " " + mRenderer->getVendorString();
}
} }
const DisplayExtensions &Display::getExtensions() const const DisplayExtensions &Display::getExtensions() const
......
...@@ -50,8 +50,7 @@ class Display final ...@@ -50,8 +50,7 @@ class Display final
Error createWindowSurface(EGLNativeWindowType window, EGLConfig config, const EGLint *attribList, EGLSurface *outSurface); Error createWindowSurface(EGLNativeWindowType window, EGLConfig config, const EGLint *attribList, EGLSurface *outSurface);
Error createOffscreenSurface(EGLConfig config, EGLClientBuffer shareHandle, const EGLint *attribList, EGLSurface *outSurface); Error createOffscreenSurface(EGLConfig config, EGLClientBuffer shareHandle, const EGLint *attribList, EGLSurface *outSurface);
Error createContext(EGLConfig configHandle, EGLint clientVersion, const gl::Context *shareContext, bool notifyResets, Error createContext(EGLConfig configHandle, EGLContext shareContext, const egl::AttributeMap &attribs, EGLContext *outContext);
bool robustAccess, EGLContext *outContext);
void destroySurface(egl::Surface *surface); void destroySurface(egl::Surface *surface);
void destroyContext(gl::Context *context); void destroyContext(gl::Context *context);
...@@ -64,10 +63,12 @@ class Display final ...@@ -64,10 +63,12 @@ class Display final
bool isValidNativeWindow(EGLNativeWindowType window) const; bool isValidNativeWindow(EGLNativeWindowType window) const;
bool isValidNativeDisplay(EGLNativeDisplayType display) const; bool isValidNativeDisplay(EGLNativeDisplayType display) const;
rx::Renderer *getRenderer() { return mRenderer; }; bool isDeviceLost() const;
bool testDeviceLost();
void notifyDeviceLost(); void notifyDeviceLost();
const Caps &getCaps() const;
const DisplayExtensions &getExtensions() const; const DisplayExtensions &getExtensions() const;
const std::string &getExtensionString() const; const std::string &getExtensionString() const;
const std::string &getVendorString() const; const std::string &getVendorString() const;
...@@ -75,7 +76,7 @@ class Display final ...@@ -75,7 +76,7 @@ class Display final
private: private:
DISALLOW_COPY_AND_ASSIGN(Display); DISALLOW_COPY_AND_ASSIGN(Display);
Display(EGLNativeDisplayType displayId); Display(rx::DisplayImpl *impl, EGLNativeDisplayType displayId);
void setAttributes(const AttributeMap &attribMap); void setAttributes(const AttributeMap &attribMap);
...@@ -94,7 +95,9 @@ class Display final ...@@ -94,7 +95,9 @@ class Display final
typedef std::set<gl::Context*> ContextSet; typedef std::set<gl::Context*> ContextSet;
ContextSet mContextSet; ContextSet mContextSet;
rx::Renderer *mRenderer; bool mInitialized;
Caps mCaps;
DisplayExtensions mDisplayExtensions; DisplayExtensions mDisplayExtensions;
std::string mDisplayExtensionString; std::string mDisplayExtensionString;
......
...@@ -14,7 +14,8 @@ namespace rx ...@@ -14,7 +14,8 @@ namespace rx
{ {
DisplayImpl::DisplayImpl() DisplayImpl::DisplayImpl()
: mExtensionsInitialized(false) : mExtensionsInitialized(false),
mCapsInitialized(false)
{ {
} }
...@@ -43,4 +44,15 @@ const egl::DisplayExtensions &DisplayImpl::getExtensions() const ...@@ -43,4 +44,15 @@ const egl::DisplayExtensions &DisplayImpl::getExtensions() const
return mExtensions; return mExtensions;
} }
const egl::Caps &DisplayImpl::getCaps() const
{
if (!mCapsInitialized)
{
generateCaps(&mCaps);
mCapsInitialized = true;
}
return mCaps;
}
} }
...@@ -18,11 +18,18 @@ ...@@ -18,11 +18,18 @@
namespace egl namespace egl
{ {
class AttributeMap;
class Display; class Display;
class Config; class Config;
class ConfigSet;
class Surface; class Surface;
} }
namespace gl
{
class Context;
}
namespace rx namespace rx
{ {
class SurfaceImpl; class SurfaceImpl;
...@@ -34,19 +41,30 @@ class DisplayImpl ...@@ -34,19 +41,30 @@ class DisplayImpl
DisplayImpl(); DisplayImpl();
virtual ~DisplayImpl(); virtual ~DisplayImpl();
virtual egl::Error initialize(egl::Display *display, EGLNativeDisplayType nativeDisplay, const egl::AttributeMap &attribMap) = 0;
virtual void terminate() = 0;
virtual SurfaceImpl *createWindowSurface(egl::Display *display, const egl::Config *config, virtual SurfaceImpl *createWindowSurface(egl::Display *display, const egl::Config *config,
EGLNativeWindowType window, EGLint fixedSize, EGLNativeWindowType window, EGLint fixedSize,
EGLint width, EGLint height, EGLint postSubBufferSupported) = 0; EGLint width, EGLint height, EGLint postSubBufferSupported) = 0;
virtual SurfaceImpl *createOffscreenSurface(egl::Display *display, const egl::Config *config, virtual SurfaceImpl *createOffscreenSurface(egl::Display *display, const egl::Config *config,
EGLClientBuffer shareHandle, EGLint width, EGLint height, EGLClientBuffer shareHandle, EGLint width, EGLint height,
EGLenum textureFormat, EGLenum textureTarget) = 0; EGLenum textureFormat, EGLenum textureTarget) = 0;
virtual egl::Error createContext(const egl::Config *config, const gl::Context *shareContext, const egl::AttributeMap &attribs,
gl::Context **outContext) = 0;
virtual std::vector<ConfigDesc> generateConfigs() const = 0; virtual egl::ConfigSet generateConfigs() const = 0;
virtual bool isDeviceLost() const = 0;
virtual bool testDeviceLost() = 0;
virtual egl::Error restoreLostDevice() = 0; virtual egl::Error restoreLostDevice() = 0;
virtual bool isValidNativeWindow(EGLNativeWindowType window) const = 0; virtual bool isValidNativeWindow(EGLNativeWindowType window) const = 0;
const egl::Caps &getCaps() const;
virtual std::string getVendorString() const = 0;
typedef std::set<egl::Surface*> SurfaceSet; typedef std::set<egl::Surface*> SurfaceSet;
const SurfaceSet &getSurfaceSet() const { return mSurfaceSet; } const SurfaceSet &getSurfaceSet() const { return mSurfaceSet; }
SurfaceSet &getSurfaceSet() { return mSurfaceSet; } SurfaceSet &getSurfaceSet() { return mSurfaceSet; }
...@@ -64,9 +82,13 @@ class DisplayImpl ...@@ -64,9 +82,13 @@ class DisplayImpl
DISALLOW_COPY_AND_ASSIGN(DisplayImpl); DISALLOW_COPY_AND_ASSIGN(DisplayImpl);
virtual void generateExtensions(egl::DisplayExtensions *outExtensions) const = 0; virtual void generateExtensions(egl::DisplayExtensions *outExtensions) const = 0;
virtual void generateCaps(egl::Caps *outCaps) const = 0;
mutable bool mExtensionsInitialized; mutable bool mExtensionsInitialized;
mutable egl::DisplayExtensions mExtensions; mutable egl::DisplayExtensions mExtensions;
mutable bool mCapsInitialized;
mutable egl::Caps mCaps;
}; };
} }
......
...@@ -70,8 +70,6 @@ class Renderer ...@@ -70,8 +70,6 @@ class Renderer
Renderer(); Renderer();
virtual ~Renderer(); virtual ~Renderer();
virtual EGLint initialize() = 0;
virtual gl::Error flush() = 0; virtual gl::Error flush() = 0;
virtual gl::Error finish() = 0; virtual gl::Error finish() = 0;
...@@ -133,8 +131,6 @@ class Renderer ...@@ -133,8 +131,6 @@ class Renderer
virtual int getMinSwapInterval() const = 0; virtual int getMinSwapInterval() const = 0;
virtual int getMaxSwapInterval() const = 0; virtual int getMaxSwapInterval() const = 0;
virtual DisplayImpl *createDisplay() = 0;
private: private:
DISALLOW_COPY_AND_ASSIGN(Renderer); DISALLOW_COPY_AND_ASSIGN(Renderer);
......
...@@ -8,19 +8,123 @@ ...@@ -8,19 +8,123 @@
#include "libANGLE/renderer/d3d/DisplayD3D.h" #include "libANGLE/renderer/d3d/DisplayD3D.h"
#include "libANGLE/Context.h"
#include "libANGLE/Config.h"
#include "libANGLE/Surface.h" #include "libANGLE/Surface.h"
#include "libANGLE/renderer/d3d/RendererD3D.h" #include "libANGLE/renderer/d3d/RendererD3D.h"
#include "libANGLE/renderer/d3d/SurfaceD3D.h" #include "libANGLE/renderer/d3d/SurfaceD3D.h"
#include "libANGLE/renderer/d3d/SwapChainD3D.h" #include "libANGLE/renderer/d3d/SwapChainD3D.h"
#include <EGL/eglext.h>
#if defined (ANGLE_ENABLE_D3D9)
# include "libANGLE/renderer/d3d/d3d9/Renderer9.h"
#endif // ANGLE_ENABLE_D3D9
#if defined (ANGLE_ENABLE_D3D11)
# include "libANGLE/renderer/d3d/d3d11/Renderer11.h"
#endif // ANGLE_ENABLE_D3D11
#if defined (ANGLE_TEST_CONFIG)
# define ANGLE_DEFAULT_D3D11 1
#endif
#if !defined(ANGLE_DEFAULT_D3D11)
// Enables use of the Direct3D 11 API for a default display, when available
# define ANGLE_DEFAULT_D3D11 0
#endif
namespace rx namespace rx
{ {
typedef RendererD3D *(*CreateRendererD3DFunction)(egl::Display*, EGLNativeDisplayType, const egl::AttributeMap &);
template <typename RendererType>
static RendererD3D *CreateTypedRendererD3D(egl::Display *display, EGLNativeDisplayType nativeDisplay, const egl::AttributeMap &attributes)
{
return new RendererType(display, nativeDisplay, attributes);
}
egl::Error CreateRendererD3D(egl::Display *display, EGLNativeDisplayType nativeDisplay, const egl::AttributeMap &attribMap, RendererD3D **outRenderer)
{
ASSERT(outRenderer != nullptr);
std::vector<CreateRendererD3DFunction> rendererCreationFunctions;
EGLint requestedDisplayType = attribMap.get(EGL_PLATFORM_ANGLE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE);
# if defined(ANGLE_ENABLE_D3D11)
if (nativeDisplay == EGL_D3D11_ELSE_D3D9_DISPLAY_ANGLE ||
nativeDisplay == EGL_D3D11_ONLY_DISPLAY_ANGLE ||
requestedDisplayType == EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE)
{
rendererCreationFunctions.push_back(CreateTypedRendererD3D<Renderer11>);
}
# endif
# if defined(ANGLE_ENABLE_D3D9)
if (nativeDisplay == EGL_D3D11_ELSE_D3D9_DISPLAY_ANGLE ||
requestedDisplayType == EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE)
{
rendererCreationFunctions.push_back(CreateTypedRendererD3D<Renderer9>);
}
# endif
if (nativeDisplay != EGL_D3D11_ELSE_D3D9_DISPLAY_ANGLE &&
nativeDisplay != EGL_D3D11_ONLY_DISPLAY_ANGLE &&
requestedDisplayType == EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE)
{
// The default display is requested, try the D3D9 and D3D11 renderers, order them using
// the definition of ANGLE_DEFAULT_D3D11
# if ANGLE_DEFAULT_D3D11
# if defined(ANGLE_ENABLE_D3D11)
rendererCreationFunctions.push_back(CreateTypedRendererD3D<Renderer11>);
# endif
# if defined(ANGLE_ENABLE_D3D9)
rendererCreationFunctions.push_back(CreateTypedRendererD3D<Renderer9>);
# endif
# else
# if defined(ANGLE_ENABLE_D3D9)
rendererCreationFunctions.push_back(CreateTypedRendererD3D<Renderer9>);
# endif
# if defined(ANGLE_ENABLE_D3D11)
rendererCreationFunctions.push_back(CreateTypedRendererD3D<Renderer11>);
# endif
# endif
}
EGLint result = EGL_NOT_INITIALIZED;
for (size_t i = 0; i < rendererCreationFunctions.size(); i++)
{
RendererD3D *renderer = rendererCreationFunctions[i](display, nativeDisplay, attribMap);
result = renderer->initialize();
if (result == EGL_SUCCESS)
{
*outRenderer = renderer;
break;
}
else
{
// Failed to create the renderer, try the next
SafeDelete(renderer);
}
}
return egl::Error(result);
}
DisplayD3D::DisplayD3D()
: mRenderer(nullptr)
{
}
SurfaceImpl *DisplayD3D::createWindowSurface(egl::Display *display, const egl::Config *config, SurfaceImpl *DisplayD3D::createWindowSurface(egl::Display *display, const egl::Config *config,
EGLNativeWindowType window, EGLint fixedSize, EGLNativeWindowType window, EGLint fixedSize,
EGLint width, EGLint height, EGLint postSubBufferSupported) EGLint width, EGLint height, EGLint postSubBufferSupported)
{ {
return SurfaceD3D::createFromWindow(display, config, window, fixedSize, ASSERT(mRenderer != nullptr);
return SurfaceD3D::createFromWindow(mRenderer, display, config, window, fixedSize,
width, height, postSubBufferSupported); width, height, postSubBufferSupported);
} }
...@@ -28,18 +132,64 @@ SurfaceImpl *DisplayD3D::createOffscreenSurface(egl::Display *display, const egl ...@@ -28,18 +132,64 @@ SurfaceImpl *DisplayD3D::createOffscreenSurface(egl::Display *display, const egl
EGLClientBuffer shareHandle, EGLint width, EGLint height, EGLClientBuffer shareHandle, EGLint width, EGLint height,
EGLenum textureFormat, EGLenum textureTarget) EGLenum textureFormat, EGLenum textureTarget)
{ {
return SurfaceD3D::createOffscreen(display, config, shareHandle, ASSERT(mRenderer != nullptr);
return SurfaceD3D::createOffscreen(mRenderer, display, config, shareHandle,
width, height, textureFormat, textureTarget); width, height, textureFormat, textureTarget);
} }
DisplayD3D::DisplayD3D(rx::RendererD3D *renderer) egl::Error DisplayD3D::createContext(const egl::Config *config, const gl::Context *shareContext, const egl::AttributeMap &attribs,
: mRenderer(renderer) gl::Context **outContext)
{
ASSERT(mRenderer != nullptr);
EGLint clientVersion = attribs.get(EGL_CONTEXT_CLIENT_VERSION, 1);
bool notifyResets = (attribs.get(EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT, EGL_NO_RESET_NOTIFICATION_EXT) == EGL_LOSE_CONTEXT_ON_RESET_EXT);
bool robustAccess = (attribs.get(EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT, EGL_FALSE) == EGL_TRUE);
*outContext = new gl::Context(clientVersion, shareContext, mRenderer, notifyResets, robustAccess);
return egl::Error(EGL_SUCCESS);
}
egl::Error DisplayD3D::initialize(egl::Display *display, EGLNativeDisplayType nativeDisplay, const egl::AttributeMap &attribMap)
{ {
ASSERT(mRenderer == nullptr);
return CreateRendererD3D(display, nativeDisplay, attribMap, &mRenderer);
} }
std::vector<ConfigDesc> DisplayD3D::generateConfigs() const void DisplayD3D::terminate()
{ {
return mRenderer->generateConfigs(); SafeDelete(mRenderer);
}
egl::ConfigSet DisplayD3D::generateConfigs() const
{
ASSERT(mRenderer != nullptr);
EGLint minSwapInterval = mRenderer->getMinSwapInterval();
EGLint maxSwapInterval = mRenderer->getMaxSwapInterval();
EGLint maxTextureSize = mRenderer->getRendererCaps().max2DTextureSize;
std::vector<ConfigDesc> descList = mRenderer->generateConfigs();
egl::ConfigSet configSet;
for (size_t i = 0; i < descList.size(); ++i)
{
configSet.add(descList[i], minSwapInterval, maxSwapInterval, maxTextureSize, maxTextureSize);
}
return configSet;
}
bool DisplayD3D::isDeviceLost() const
{
ASSERT(mRenderer != nullptr);
return mRenderer->isDeviceLost();
}
bool DisplayD3D::testDeviceLost()
{
ASSERT(mRenderer != nullptr);
return mRenderer->testDeviceLost();
} }
egl::Error DisplayD3D::restoreLostDevice() egl::Error DisplayD3D::restoreLostDevice()
...@@ -102,4 +252,23 @@ void DisplayD3D::generateExtensions(egl::DisplayExtensions *outExtensions) const ...@@ -102,4 +252,23 @@ void DisplayD3D::generateExtensions(egl::DisplayExtensions *outExtensions) const
outExtensions->createContext = true; outExtensions->createContext = true;
} }
std::string DisplayD3D::getVendorString() const
{
std::string vendorString = "Google Inc.";
if (mRenderer)
{
vendorString += " " + mRenderer->getVendorString();
}
return vendorString;
}
void DisplayD3D::generateCaps(egl::Caps *outCaps) const
{
// Display must be initialized to generate caps
ASSERT(mRenderer != nullptr);
outCaps->textureNPOT = mRenderer->getRendererExtensions().textureNPOT;
}
} }
...@@ -18,7 +18,11 @@ class RendererD3D; ...@@ -18,7 +18,11 @@ class RendererD3D;
class DisplayD3D : public DisplayImpl class DisplayD3D : public DisplayImpl
{ {
public: public:
DisplayD3D(rx::RendererD3D *renderer); DisplayD3D();
egl::Error initialize(egl::Display *display, EGLNativeDisplayType nativeDisplay, const egl::AttributeMap &attribMap) override;
virtual void terminate() override;
SurfaceImpl *createWindowSurface(egl::Display *display, const egl::Config *config, SurfaceImpl *createWindowSurface(egl::Display *display, const egl::Config *config,
EGLNativeWindowType window, EGLint fixedSize, EGLNativeWindowType window, EGLint fixedSize,
EGLint width, EGLint height, EGLint postSubBufferSupported) override; EGLint width, EGLint height, EGLint postSubBufferSupported) override;
...@@ -26,16 +30,24 @@ class DisplayD3D : public DisplayImpl ...@@ -26,16 +30,24 @@ class DisplayD3D : public DisplayImpl
EGLClientBuffer shareHandle, EGLint width, EGLint height, EGLClientBuffer shareHandle, EGLint width, EGLint height,
EGLenum textureFormat, EGLenum textureTarget) override; EGLenum textureFormat, EGLenum textureTarget) override;
std::vector<ConfigDesc> generateConfigs() const override; egl::Error createContext(const egl::Config *config, const gl::Context *shareContext, const egl::AttributeMap &attribs,
gl::Context **outContext) override;
egl::ConfigSet generateConfigs() const override;
bool isDeviceLost() const override;
bool testDeviceLost() override;
egl::Error restoreLostDevice() override; egl::Error restoreLostDevice() override;
bool isValidNativeWindow(EGLNativeWindowType window) const override; bool isValidNativeWindow(EGLNativeWindowType window) const override;
std::string getVendorString() const override;
private: private:
DISALLOW_COPY_AND_ASSIGN(DisplayD3D); DISALLOW_COPY_AND_ASSIGN(DisplayD3D);
void generateExtensions(egl::DisplayExtensions *outExtensions) const override; void generateExtensions(egl::DisplayExtensions *outExtensions) const override;
void generateCaps(egl::Caps *outCaps) const override;
rx::RendererD3D *mRenderer; rx::RendererD3D *mRenderer;
}; };
......
...@@ -627,11 +627,6 @@ std::string RendererD3D::getVendorString() const ...@@ -627,11 +627,6 @@ std::string RendererD3D::getVendorString() const
return std::string(""); return std::string("");
} }
DisplayImpl *RendererD3D::createDisplay()
{
return new DisplayD3D(this);
}
gl::Error RendererD3D::getScratchMemoryBuffer(size_t requestedSize, MemoryBuffer **bufferOut) gl::Error RendererD3D::getScratchMemoryBuffer(size_t requestedSize, MemoryBuffer **bufferOut)
{ {
if (mScratchMemoryBuffer.size() == requestedSize) if (mScratchMemoryBuffer.size() == requestedSize)
......
...@@ -51,6 +51,8 @@ class RendererD3D : public Renderer ...@@ -51,6 +51,8 @@ class RendererD3D : public Renderer
static RendererD3D *makeRendererD3D(Renderer *renderer); static RendererD3D *makeRendererD3D(Renderer *renderer);
virtual EGLint initialize() = 0;
virtual std::vector<ConfigDesc> generateConfigs() const = 0; virtual std::vector<ConfigDesc> generateConfigs() const = 0;
gl::Error drawArrays(const gl::Data &data, gl::Error drawArrays(const gl::Data &data,
...@@ -68,8 +70,6 @@ class RendererD3D : public Renderer ...@@ -68,8 +70,6 @@ class RendererD3D : public Renderer
virtual int getMinorShaderModel() const = 0; virtual int getMinorShaderModel() const = 0;
virtual std::string getShaderModelSuffix() const = 0; virtual std::string getShaderModelSuffix() const = 0;
DisplayImpl *createDisplay() override;
// Direct3D Specific methods // Direct3D Specific methods
virtual GUID getAdapterIdentifier() const = 0; virtual GUID getAdapterIdentifier() const = 0;
......
...@@ -20,25 +20,25 @@ ...@@ -20,25 +20,25 @@
namespace rx namespace rx
{ {
SurfaceD3D *SurfaceD3D::createOffscreen(egl::Display *display, const egl::Config *config, EGLClientBuffer shareHandle, SurfaceD3D *SurfaceD3D::createOffscreen(RendererD3D *renderer, egl::Display *display, const egl::Config *config, EGLClientBuffer shareHandle,
EGLint width, EGLint height, EGLenum textureFormat, EGLenum textureType) EGLint width, EGLint height, EGLenum textureFormat, EGLenum textureType)
{ {
return new SurfaceD3D(display, config, width, height, EGL_TRUE, EGL_FALSE, return new SurfaceD3D(renderer, display, config, width, height, EGL_TRUE, EGL_FALSE,
textureFormat, textureType, shareHandle, NULL); textureFormat, textureType, shareHandle, NULL);
} }
SurfaceD3D *SurfaceD3D::createFromWindow(egl::Display *display, const egl::Config *config, EGLNativeWindowType window, SurfaceD3D *SurfaceD3D::createFromWindow(RendererD3D *renderer, egl::Display *display, const egl::Config *config, EGLNativeWindowType window,
EGLint fixedSize, EGLint width, EGLint height, EGLint postSubBufferSupported) EGLint fixedSize, EGLint width, EGLint height, EGLint postSubBufferSupported)
{ {
return new SurfaceD3D(display, config, width, height, fixedSize, postSubBufferSupported, return new SurfaceD3D(renderer, display, config, width, height, fixedSize, postSubBufferSupported,
EGL_NO_TEXTURE, EGL_NO_TEXTURE, static_cast<EGLClientBuffer>(0), window); EGL_NO_TEXTURE, EGL_NO_TEXTURE, static_cast<EGLClientBuffer>(0), window);
} }
SurfaceD3D::SurfaceD3D(egl::Display *display, const egl::Config *config, EGLint width, EGLint height, SurfaceD3D::SurfaceD3D(RendererD3D *renderer, egl::Display *display, const egl::Config *config, EGLint width, EGLint height,
EGLint fixedSize, EGLint postSubBufferSupported, EGLenum textureFormat, EGLint fixedSize, EGLint postSubBufferSupported, EGLenum textureFormat,
EGLenum textureType, EGLClientBuffer shareHandle, EGLNativeWindowType window) EGLenum textureType, EGLClientBuffer shareHandle, EGLNativeWindowType window)
: SurfaceImpl(display, config, width, height, fixedSize, postSubBufferSupported, textureFormat, textureType, shareHandle), : SurfaceImpl(display, config, width, height, fixedSize, postSubBufferSupported, textureFormat, textureType, shareHandle),
mRenderer(static_cast<rx::RendererD3D*>(mDisplay->getRenderer())), mRenderer(renderer),
mSwapChain(NULL), mSwapChain(NULL),
mWindowSubclassed(false), mWindowSubclassed(false),
mNativeWindow(window) mNativeWindow(window)
......
...@@ -25,10 +25,10 @@ class RendererD3D; ...@@ -25,10 +25,10 @@ class RendererD3D;
class SurfaceD3D : public SurfaceImpl class SurfaceD3D : public SurfaceImpl
{ {
public: public:
static SurfaceD3D *createFromWindow(egl::Display *display, const egl::Config *config, static SurfaceD3D *createFromWindow(RendererD3D *renderer, egl::Display *display, const egl::Config *config,
EGLNativeWindowType window, EGLint fixedSize, EGLNativeWindowType window, EGLint fixedSize,
EGLint width, EGLint height, EGLint postSubBufferSupported); EGLint width, EGLint height, EGLint postSubBufferSupported);
static SurfaceD3D *createOffscreen(egl::Display *display, const egl::Config *config, static SurfaceD3D *createOffscreen(RendererD3D *renderer, egl::Display *display, const egl::Config *config,
EGLClientBuffer shareHandle, EGLint width, EGLint height, EGLClientBuffer shareHandle, EGLint width, EGLint height,
EGLenum textureFormat, EGLenum textureTarget); EGLenum textureFormat, EGLenum textureTarget);
~SurfaceD3D() override; ~SurfaceD3D() override;
...@@ -55,7 +55,7 @@ class SurfaceD3D : public SurfaceImpl ...@@ -55,7 +55,7 @@ class SurfaceD3D : public SurfaceImpl
private: private:
DISALLOW_COPY_AND_ASSIGN(SurfaceD3D); DISALLOW_COPY_AND_ASSIGN(SurfaceD3D);
SurfaceD3D(egl::Display *display, const egl::Config *config, EGLint width, EGLint height, SurfaceD3D(RendererD3D *renderer, egl::Display *display, const egl::Config *config, EGLint width, EGLint height,
EGLint fixedSize, EGLint postSubBufferSupported, EGLenum textureFormat, EGLint fixedSize, EGLint postSubBufferSupported, EGLenum textureFormat,
EGLenum textureType, EGLClientBuffer shareHandle, EGLNativeWindowType window); EGLenum textureType, EGLClientBuffer shareHandle, EGLNativeWindowType window);
egl::Error swapRect(EGLint x, EGLint y, EGLint width, EGLint height); egl::Error swapRect(EGLint x, EGLint y, EGLint width, EGLint height);
......
...@@ -587,22 +587,23 @@ EGLContext EGLAPIENTRY CreateContext(EGLDisplay dpy, EGLConfig config, EGLContex ...@@ -587,22 +587,23 @@ EGLContext EGLAPIENTRY CreateContext(EGLDisplay dpy, EGLConfig config, EGLContex
if (share_context) if (share_context)
{ {
gl::Context* sharedGLContext = static_cast<gl::Context*>(share_context); gl::Context* sharedGLContext = static_cast<gl::Context*>(share_context);
if (sharedGLContext->isResetNotificationEnabled() != resetNotification)
// Shared context is invalid or is owned by another display
if (!display->isValidContext(sharedGLContext))
{ {
SetGlobalError(Error(EGL_BAD_MATCH)); SetGlobalError(Error(EGL_BAD_MATCH));
return EGL_NO_CONTEXT; return EGL_NO_CONTEXT;
} }
if (sharedGLContext->getClientVersion() != clientMajorVersion) if (sharedGLContext->isResetNotificationEnabled() != resetNotification)
{ {
SetGlobalError(Error(EGL_BAD_CONTEXT)); SetGlobalError(Error(EGL_BAD_MATCH));
return EGL_NO_CONTEXT; return EGL_NO_CONTEXT;
} }
// Can not share contexts between displays if (sharedGLContext->getClientVersion() != clientMajorVersion)
if (sharedGLContext->getRenderer() != display->getRenderer())
{ {
SetGlobalError(Error(EGL_BAD_MATCH)); SetGlobalError(Error(EGL_BAD_CONTEXT));
return EGL_NO_CONTEXT; return EGL_NO_CONTEXT;
} }
} }
...@@ -613,8 +614,7 @@ EGLContext EGLAPIENTRY CreateContext(EGLDisplay dpy, EGLConfig config, EGLContex ...@@ -613,8 +614,7 @@ EGLContext EGLAPIENTRY CreateContext(EGLDisplay dpy, EGLConfig config, EGLContex
} }
EGLContext context = EGL_NO_CONTEXT; EGLContext context = EGL_NO_CONTEXT;
Error error = display->createContext(config, clientMajorVersion, static_cast<gl::Context*>(share_context), Error error = display->createContext(config, share_context, egl::AttributeMap(attrib_list), &context);
resetNotification, robustAccess, &context);
if (error.isError()) if (error.isError())
{ {
SetGlobalError(error); SetGlobalError(error);
...@@ -677,14 +677,13 @@ EGLBoolean EGLAPIENTRY MakeCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface r ...@@ -677,14 +677,13 @@ EGLBoolean EGLAPIENTRY MakeCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface r
if (dpy != EGL_NO_DISPLAY && display->isInitialized()) if (dpy != EGL_NO_DISPLAY && display->isInitialized())
{ {
rx::Renderer *renderer = display->getRenderer(); if (display->testDeviceLost())
if (renderer->testDeviceLost())
{ {
display->notifyDeviceLost(); display->notifyDeviceLost();
return EGL_FALSE; return EGL_FALSE;
} }
if (renderer->isDeviceLost()) if (display->isDeviceLost())
{ {
SetGlobalError(Error(EGL_CONTEXT_LOST)); SetGlobalError(Error(EGL_CONTEXT_LOST));
return EGL_FALSE; return EGL_FALSE;
...@@ -801,7 +800,7 @@ EGLBoolean EGLAPIENTRY SwapBuffers(EGLDisplay dpy, EGLSurface surface) ...@@ -801,7 +800,7 @@ EGLBoolean EGLAPIENTRY SwapBuffers(EGLDisplay dpy, EGLSurface surface)
return EGL_FALSE; return EGL_FALSE;
} }
if (display->getRenderer()->isDeviceLost()) if (display->isDeviceLost())
{ {
SetGlobalError(Error(EGL_CONTEXT_LOST)); SetGlobalError(Error(EGL_CONTEXT_LOST));
return EGL_FALSE; return EGL_FALSE;
...@@ -836,7 +835,7 @@ EGLBoolean EGLAPIENTRY CopyBuffers(EGLDisplay dpy, EGLSurface surface, EGLNative ...@@ -836,7 +835,7 @@ EGLBoolean EGLAPIENTRY CopyBuffers(EGLDisplay dpy, EGLSurface surface, EGLNative
return EGL_FALSE; return EGL_FALSE;
} }
if (display->getRenderer()->isDeviceLost()) if (display->isDeviceLost())
{ {
SetGlobalError(Error(EGL_CONTEXT_LOST)); SetGlobalError(Error(EGL_CONTEXT_LOST));
return EGL_FALSE; return EGL_FALSE;
......
...@@ -118,7 +118,7 @@ EGLBoolean EGLAPIENTRY PostSubBufferNV(EGLDisplay dpy, EGLSurface surface, EGLin ...@@ -118,7 +118,7 @@ EGLBoolean EGLAPIENTRY PostSubBufferNV(EGLDisplay dpy, EGLSurface surface, EGLin
return EGL_FALSE; return EGL_FALSE;
} }
if (display->getRenderer()->isDeviceLost()) if (display->isDeviceLost())
{ {
SetGlobalError(Error(EGL_CONTEXT_LOST)); SetGlobalError(Error(EGL_CONTEXT_LOST));
return EGL_FALSE; return EGL_FALSE;
......
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