Commit 4f77c29b authored by H. Verbeet's avatar H. Verbeet Committed by Alexandre Julliard

wined3d: Track depth stencil location per-surface.

This gets rid of depth_copy_state in the device, and instead tracks the most up to date location per-surface. This makes things a lot easier to follow, and allows us to make a copy when switching depth stencils in SetDepthStencilSurface().
parent e7d0ef72
......@@ -1011,9 +1011,6 @@ static inline WineD3DContext *FindContext(IWineD3DDeviceImpl *This, IWineD3DSurf
This->isInDraw = oldInDraw;
}
if(oldRenderOffscreen != This->render_offscreen && This->depth_copy_state != WINED3D_DCS_NO_COPY) {
This->depth_copy_state = WINED3D_DCS_COPY;
}
return context;
}
......
......@@ -5019,31 +5019,29 @@ HRESULT IWineD3DDeviceImpl_ClearSurface(IWineD3DDeviceImpl *This, IWineD3DSurfa
}
if (Flags & WINED3DCLEAR_ZBUFFER) {
DWORD location = This->render_offscreen ? SFLAG_DS_OFFSCREEN : SFLAG_DS_ONSCREEN;
glDepthMask(GL_TRUE);
glClearDepth(Z);
checkGLcall("glClearDepth");
glMask = glMask | GL_DEPTH_BUFFER_BIT;
IWineD3DDeviceImpl_MarkStateDirty(This, STATE_RENDER(WINED3DRS_ZWRITEENABLE));
if(This->depth_copy_state == WINED3D_DCS_COPY) {
if(vp->X != 0 || vp->Y != 0 ||
vp->Width < depth_stencil->currentDesc.Width || vp->Height < depth_stencil->currentDesc.Height) {
depth_copy((IWineD3DDevice *) This);
}
else if(This->stateBlock->renderState[WINED3DRS_SCISSORTESTENABLE] && (
This->stateBlock->scissorRect.left > 0 || This->stateBlock->scissorRect.top > 0 ||
This->stateBlock->scissorRect.right < depth_stencil->currentDesc.Width ||
This->stateBlock->scissorRect.bottom < depth_stencil->currentDesc.Height)) {
depth_copy((IWineD3DDevice *) This);
}
else if(Count > 0 && pRects && (
pRects[0].x1 > 0 || pRects[0].y1 > 0 ||
pRects[0].x2 < depth_stencil->currentDesc.Width ||
pRects[0].y2 < depth_stencil->currentDesc.Height)) {
depth_copy((IWineD3DDevice *) This);
}
if (vp->X != 0 || vp->Y != 0 ||
vp->Width < depth_stencil->currentDesc.Width || vp->Height < depth_stencil->currentDesc.Height) {
surface_load_ds_location(This->stencilBufferTarget, location);
}
else if (This->stateBlock->renderState[WINED3DRS_SCISSORTESTENABLE] && (
This->stateBlock->scissorRect.left > 0 || This->stateBlock->scissorRect.top > 0 ||
This->stateBlock->scissorRect.right < depth_stencil->currentDesc.Width ||
This->stateBlock->scissorRect.bottom < depth_stencil->currentDesc.Height)) {
surface_load_ds_location(This->stencilBufferTarget, location);
}
else if (Count > 0 && pRects && (
pRects[0].x1 > 0 || pRects[0].y1 > 0 ||
pRects[0].x2 < depth_stencil->currentDesc.Width ||
pRects[0].y2 < depth_stencil->currentDesc.Height)) {
surface_load_ds_location(This->stencilBufferTarget, location);
}
This->depth_copy_state = WINED3D_DCS_INITIAL;
}
if (Flags & WINED3DCLEAR_TARGET) {
......@@ -5133,6 +5131,12 @@ HRESULT IWineD3DDeviceImpl_ClearSurface(IWineD3DDeviceImpl *This, IWineD3DSurfa
target->Flags |= SFLAG_INTEXTURE;
}
}
if (Flags & WINED3DCLEAR_ZBUFFER) {
/* Note that WINED3DCLEAR_ZBUFFER implies a depth stencil exists on the device */
DWORD location = This->render_offscreen ? SFLAG_DS_OFFSCREEN : SFLAG_DS_ONSCREEN;
surface_modify_ds_location(This->stencilBufferTarget, location);
}
LEAVE_GL();
return WINED3D_OK;
......@@ -6759,9 +6763,14 @@ static HRESULT WINAPI IWineD3DDeviceImpl_SetDepthStencilSurface(IWineD3DDevice *
* stencil buffer and incur an extra memory overhead
******************************************************/
if (This->stencilBufferTarget) {
ActivateContext(This, This->render_targets[0], CTXUSAGE_RESOURCELOAD);
surface_load_ds_location(This->stencilBufferTarget, SFLAG_DS_OFFSCREEN);
surface_modify_ds_location(This->stencilBufferTarget, SFLAG_DS_OFFSCREEN);
}
tmp = This->stencilBufferTarget;
This->stencilBufferTarget = pNewZStencil;
This->depth_copy_state = WINED3D_DCS_NO_COPY;
/* should we be calling the parent or the wined3d surface? */
if (NULL != This->stencilBufferTarget) IWineD3DSurface_AddRef(This->stencilBufferTarget);
if (NULL != tmp) IWineD3DSurface_Release(tmp);
......
......@@ -719,7 +719,7 @@ static void drawStridedSlowVs(IWineD3DDevice *iface, WineDirect3DVertexStridedDa
glEnd();
}
static void depth_blt(IWineD3DDevice *iface, GLuint texture) {
void depth_blt(IWineD3DDevice *iface, GLuint texture) {
IWineD3DDeviceImpl *This = (IWineD3DDeviceImpl *)iface;
GLint old_binding = 0;
......@@ -760,83 +760,6 @@ static void depth_blt(IWineD3DDevice *iface, GLuint texture) {
This->shader_backend->shader_select(iface, use_ps(This), use_vs(This));
}
void depth_copy(IWineD3DDevice *iface) {
IWineD3DDeviceImpl *This = (IWineD3DDeviceImpl *)iface;
IWineD3DSurfaceImpl *depth_stencil = (IWineD3DSurfaceImpl *)This->auto_depth_stencil_buffer;
/* Only copy the depth buffer if there is one. */
if (!depth_stencil) return;
/* TODO: Make this work for modes other than FBO */
if (wined3d_settings.offscreen_rendering_mode != ORM_FBO) return;
if (depth_stencil->current_renderbuffer) {
FIXME("Not supported with fixed up depth stencil\n");
return;
}
if (This->render_offscreen) {
GLint old_binding = 0;
TRACE("Copying onscreen depth buffer to offscreen surface\n");
if (!This->depth_blt_texture) {
glGenTextures(1, &This->depth_blt_texture);
}
/* Note that we use depth_blt here as well, rather than glCopyTexImage2D
* directly on the FBO texture. That's because we need to flip. */
GL_EXTCALL(glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0));
glGetIntegerv(GL_TEXTURE_BINDING_2D, &old_binding);
glBindTexture(GL_TEXTURE_2D, This->depth_blt_texture);
glCopyTexImage2D(depth_stencil->glDescription.target,
depth_stencil->glDescription.level,
depth_stencil->glDescription.glFormatInternal,
0,
0,
depth_stencil->currentDesc.Width,
depth_stencil->currentDesc.Height,
0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE_ARB, GL_LUMINANCE);
glBindTexture(GL_TEXTURE_2D, old_binding);
/* Setup the destination */
if (!This->depth_blt_rb) {
GL_EXTCALL(glGenRenderbuffersEXT(1, &This->depth_blt_rb));
checkGLcall("glGenRenderbuffersEXT");
}
if (This->depth_blt_rb_w != depth_stencil->currentDesc.Width
|| This->depth_blt_rb_h != depth_stencil->currentDesc.Height) {
GL_EXTCALL(glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, This->depth_blt_rb));
checkGLcall("glBindRenderbufferEXT");
GL_EXTCALL(glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_RGBA8, depth_stencil->currentDesc.Width, depth_stencil->currentDesc.Height));
checkGLcall("glRenderbufferStorageEXT");
This->depth_blt_rb_w = depth_stencil->currentDesc.Width;
This->depth_blt_rb_h = depth_stencil->currentDesc.Height;
}
bind_fbo(iface, GL_FRAMEBUFFER_EXT, &This->dst_fbo);
GL_EXTCALL(glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER_EXT, This->depth_blt_rb));
checkGLcall("glFramebufferRenderbufferEXT");
attach_depth_stencil_fbo(This, GL_FRAMEBUFFER_EXT, (IWineD3DSurface *)depth_stencil, FALSE);
/* Do the actual blit */
depth_blt(iface, This->depth_blt_texture);
checkGLcall("depth_blt");
bind_fbo(iface, GL_FRAMEBUFFER_EXT, &This->fbo);
} else {
TRACE("Copying offscreen surface to onscreen depth buffer\n");
GL_EXTCALL(glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0));
checkGLcall("glBindFramebuffer()");
depth_blt(iface, depth_stencil->glDescription.textureName);
checkGLcall("depth_blt");
}
}
static inline void drawStridedInstanced(IWineD3DDevice *iface, WineDirect3DVertexStridedData *sd, UINT numberOfVertices,
GLenum glPrimitiveType, const void *idxData, short idxSize, ULONG minIndex,
ULONG startIdx, ULONG startVertex) {
......@@ -1067,10 +990,11 @@ void drawPrimitive(IWineD3DDevice *iface,
ActivateContext(This, This->render_targets[0], CTXUSAGE_DRAWPRIM);
ENTER_GL();
if (This->depth_copy_state == WINED3D_DCS_COPY) {
depth_copy(iface);
if (This->stencilBufferTarget) {
DWORD location = This->render_offscreen ? SFLAG_DS_OFFSCREEN : SFLAG_DS_ONSCREEN;
surface_load_ds_location(This->stencilBufferTarget, location);
surface_modify_ds_location(This->stencilBufferTarget, location);
}
This->depth_copy_state = WINED3D_DCS_INITIAL;
{
GLenum glPrimType;
......
......@@ -3831,6 +3831,130 @@ static HRESULT WINAPI IWineD3DSurfaceImpl_PrivateSetup(IWineD3DSurface *iface) {
return WINED3D_OK;
}
void surface_modify_ds_location(IWineD3DSurface *iface, DWORD location) {
IWineD3DSurfaceImpl *This = (IWineD3DSurfaceImpl *)iface;
TRACE("(%p) New location %#x\n", This, location);
if (location & ~SFLAG_DS_LOCATIONS) {
FIXME("(%p) Invalid location (%#x) specified\n", This, location);
}
This->Flags &= ~SFLAG_DS_LOCATIONS;
This->Flags |= location;
}
void surface_load_ds_location(IWineD3DSurface *iface, DWORD location) {
IWineD3DSurfaceImpl *This = (IWineD3DSurfaceImpl *)iface;
IWineD3DDeviceImpl *device = This->resource.wineD3DDevice;
TRACE("(%p) New location %#x\n", This, location);
/* TODO: Make this work for modes other than FBO */
if (wined3d_settings.offscreen_rendering_mode != ORM_FBO) return;
if (This->Flags & location) {
TRACE("(%p) Location (%#x) is already up to date\n", This, location);
return;
}
if (This->current_renderbuffer) {
FIXME("(%p) Not supported with fixed up depth stencil\n", This);
return;
}
if (location == SFLAG_DS_OFFSCREEN) {
if (This->Flags & SFLAG_DS_ONSCREEN) {
GLint old_binding = 0;
TRACE("(%p) Copying onscreen depth buffer to depth texture\n", This);
ENTER_GL();
if (!device->depth_blt_texture) {
glGenTextures(1, &device->depth_blt_texture);
}
/* Note that we use depth_blt here as well, rather than glCopyTexImage2D
* directly on the FBO texture. That's because we need to flip. */
GL_EXTCALL(glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0));
glGetIntegerv(GL_TEXTURE_BINDING_2D, &old_binding);
glBindTexture(GL_TEXTURE_2D, device->depth_blt_texture);
glCopyTexImage2D(This->glDescription.target,
This->glDescription.level,
This->glDescription.glFormatInternal,
0,
0,
This->currentDesc.Width,
This->currentDesc.Height,
0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE_ARB, GL_LUMINANCE);
glBindTexture(GL_TEXTURE_2D, old_binding);
/* Setup the destination */
if (!device->depth_blt_rb) {
GL_EXTCALL(glGenRenderbuffersEXT(1, &device->depth_blt_rb));
checkGLcall("glGenRenderbuffersEXT");
}
if (device->depth_blt_rb_w != This->currentDesc.Width
|| device->depth_blt_rb_h != This->currentDesc.Height) {
GL_EXTCALL(glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, device->depth_blt_rb));
checkGLcall("glBindRenderbufferEXT");
GL_EXTCALL(glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_RGBA8, This->currentDesc.Width, This->currentDesc.Height));
checkGLcall("glRenderbufferStorageEXT");
device->depth_blt_rb_w = This->currentDesc.Width;
device->depth_blt_rb_h = This->currentDesc.Height;
}
bind_fbo((IWineD3DDevice *)device, GL_FRAMEBUFFER_EXT, &device->dst_fbo);
GL_EXTCALL(glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER_EXT, device->depth_blt_rb));
checkGLcall("glFramebufferRenderbufferEXT");
attach_depth_stencil_fbo(device, GL_FRAMEBUFFER_EXT, iface, FALSE);
/* Do the actual blit */
depth_blt((IWineD3DDevice *)device, device->depth_blt_texture);
checkGLcall("depth_blt");
if (device->render_offscreen) {
bind_fbo((IWineD3DDevice *)device, GL_FRAMEBUFFER_EXT, &device->fbo);
} else {
GL_EXTCALL(glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0));
checkGLcall("glBindFramebuffer()");
}
LEAVE_GL();
} else {
FIXME("No up to date depth stencil location\n");
}
} else if (location == SFLAG_DS_ONSCREEN) {
if (This->Flags & SFLAG_DS_OFFSCREEN) {
TRACE("(%p) Copying depth texture to onscreen depth buffer\n", This);
ENTER_GL();
GL_EXTCALL(glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0));
checkGLcall("glBindFramebuffer()");
depth_blt((IWineD3DDevice *)device, This->glDescription.textureName);
checkGLcall("depth_blt");
if (device->render_offscreen) {
GL_EXTCALL(glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, device->fbo));
checkGLcall("glBindFramebuffer()");
}
LEAVE_GL();
} else {
FIXME("No up to date depth stencil location\n");
}
} else {
ERR("(%p) Invalid location (%#x) specified\n", This, location);
}
This->Flags |= location;
}
static void WINAPI IWineD3DSurfaceImpl_ModifyLocation(IWineD3DSurface *iface, DWORD flag, BOOL persistent) {
IWineD3DSurfaceImpl *This = (IWineD3DSurfaceImpl *) iface;
IWineD3DBaseTexture *texture;
......
......@@ -857,7 +857,6 @@ struct IWineD3DDeviceImpl
/* For rendering to a texture using glCopyTexImage */
BOOL render_offscreen;
WINED3D_DEPTHCOPYSTATE depth_copy_state;
GLuint fbo;
GLuint src_fbo;
GLuint dst_fbo;
......@@ -1355,26 +1354,28 @@ void get_drawable_size_pbuffer(IWineD3DSurfaceImpl *This, UINT *width, UINT *hei
void get_drawable_size_fbo(IWineD3DSurfaceImpl *This, UINT *width, UINT *height);
/* Surface flags: */
#define SFLAG_OVERSIZE 0x00000001 /* Surface is bigger than gl size, blts only */
#define SFLAG_CONVERTED 0x00000002 /* Converted for color keying or Palettized */
#define SFLAG_DIBSECTION 0x00000004 /* Has a DIB section attached for GetDC */
#define SFLAG_LOCKABLE 0x00000008 /* Surface can be locked */
#define SFLAG_DISCARD 0x00000010 /* ??? */
#define SFLAG_LOCKED 0x00000020 /* Surface is locked atm */
#define SFLAG_INTEXTURE 0x00000040 /* The GL texture contains the newest surface content */
#define SFLAG_INDRAWABLE 0x00000080 /* The gl drawable contains the most up to date data */
#define SFLAG_INSYSMEM 0x00000100 /* The system memory copy is most up to date */
#define SFLAG_NONPOW2 0x00000200 /* Surface sizes are not a power of 2 */
#define SFLAG_DYNLOCK 0x00000400 /* Surface is often locked by the app */
#define SFLAG_DYNCHANGE 0x00000C00 /* Surface contents are changed very often, implies DYNLOCK */
#define SFLAG_DCINUSE 0x00001000 /* Set between GetDC and ReleaseDC calls */
#define SFLAG_LOST 0x00002000 /* Surface lost flag for DDraw */
#define SFLAG_USERPTR 0x00004000 /* The application allocated the memory for this surface */
#define SFLAG_GLCKEY 0x00008000 /* The gl texture was created with a color key */
#define SFLAG_CLIENT 0x00010000 /* GL_APPLE_client_storage is used on that texture */
#define SFLAG_ALLOCATED 0x00020000 /* A gl texture is allocated for this surface */
#define SFLAG_PBO 0x00040000 /* Has a PBO attached for speeding up data transfers for dynamically locked surfaces */
#define SFLAG_NORMCOORD 0x00080000 /* Set if the GL texture coords are normalized(non-texture rectangle) */
#define SFLAG_OVERSIZE 0x00000001 /* Surface is bigger than gl size, blts only */
#define SFLAG_CONVERTED 0x00000002 /* Converted for color keying or Palettized */
#define SFLAG_DIBSECTION 0x00000004 /* Has a DIB section attached for GetDC */
#define SFLAG_LOCKABLE 0x00000008 /* Surface can be locked */
#define SFLAG_DISCARD 0x00000010 /* ??? */
#define SFLAG_LOCKED 0x00000020 /* Surface is locked atm */
#define SFLAG_INTEXTURE 0x00000040 /* The GL texture contains the newest surface content */
#define SFLAG_INDRAWABLE 0x00000080 /* The gl drawable contains the most up to date data */
#define SFLAG_INSYSMEM 0x00000100 /* The system memory copy is most up to date */
#define SFLAG_NONPOW2 0x00000200 /* Surface sizes are not a power of 2 */
#define SFLAG_DYNLOCK 0x00000400 /* Surface is often locked by the app */
#define SFLAG_DYNCHANGE 0x00000C00 /* Surface contents are changed very often, implies DYNLOCK */
#define SFLAG_DCINUSE 0x00001000 /* Set between GetDC and ReleaseDC calls */
#define SFLAG_LOST 0x00002000 /* Surface lost flag for DDraw */
#define SFLAG_USERPTR 0x00004000 /* The application allocated the memory for this surface */
#define SFLAG_GLCKEY 0x00008000 /* The gl texture was created with a color key */
#define SFLAG_CLIENT 0x00010000 /* GL_APPLE_client_storage is used on that texture */
#define SFLAG_ALLOCATED 0x00020000 /* A gl texture is allocated for this surface */
#define SFLAG_PBO 0x00040000 /* Has a PBO attached for speeding up data transfers for dynamically locked surfaces */
#define SFLAG_NORMCOORD 0x00080000 /* Set if the GL texture coords are normalized(non-texture rectangle) */
#define SFLAG_DS_ONSCREEN 0x00100000 /* Is a depth stencil, last modified onscreen */
#define SFLAG_DS_OFFSCREEN 0x00200000 /* Is a depth stencil, last modified offscreen */
/* In some conditions the surface memory must not be freed:
* SFLAG_OVERSIZE: Not all data can be kept in GL
......@@ -1386,19 +1387,23 @@ void get_drawable_size_fbo(IWineD3DSurfaceImpl *This, UINT *width, UINT *height)
* SFLAG_PBO: PBOs don't use 'normal' memory. It is either allocated by the driver or must be NULL.
* SFLAG_CLIENT: OpenGL uses our memory as backup
*/
#define SFLAG_DONOTFREE (SFLAG_OVERSIZE | \
SFLAG_CONVERTED | \
SFLAG_DIBSECTION | \
SFLAG_LOCKED | \
SFLAG_DYNLOCK | \
SFLAG_DYNCHANGE | \
SFLAG_USERPTR | \
SFLAG_PBO | \
SFLAG_CLIENT)
#define SFLAG_LOCATIONS (SFLAG_INSYSMEM | \
SFLAG_INTEXTURE | \
SFLAG_INDRAWABLE)
#define SFLAG_DONOTFREE (SFLAG_OVERSIZE | \
SFLAG_CONVERTED | \
SFLAG_DIBSECTION | \
SFLAG_LOCKED | \
SFLAG_DYNLOCK | \
SFLAG_DYNCHANGE | \
SFLAG_USERPTR | \
SFLAG_PBO | \
SFLAG_CLIENT)
#define SFLAG_LOCATIONS (SFLAG_INSYSMEM | \
SFLAG_INTEXTURE | \
SFLAG_INDRAWABLE)
#define SFLAG_DS_LOCATIONS (SFLAG_DS_ONSCREEN | \
SFLAG_DS_OFFSCREEN)
BOOL CalculateTexRect(IWineD3DSurfaceImpl *This, RECT *Rect, float glTexCoord[4]);
typedef enum {
......@@ -1751,6 +1756,8 @@ void texture_activate_dimensions(DWORD stage, IWineD3DStateBlockImpl *stateblock
void surface_set_compatible_renderbuffer(IWineD3DSurface *iface, unsigned int width, unsigned int height);
GLenum surface_get_gl_buffer(IWineD3DSurface *iface, IWineD3DSwapChain *swapchain);
void surface_modify_ds_location(IWineD3DSurface *iface, DWORD location);
void surface_load_ds_location(IWineD3DSurface *iface, DWORD location);
BOOL getColorBits(WINED3DFORMAT fmt, short *redSize, short *greenSize, short *blueSize, short *alphaSize, short *totalSize);
BOOL getDepthStencilBits(WINED3DFORMAT fmt, short *depthSize, short *stencilSize);
......@@ -2442,5 +2449,6 @@ void stretch_rect_fbo(IWineD3DDevice *iface, IWineD3DSurface *src_surface, WINED
IWineD3DSurface *dst_surface, WINED3DRECT *dst_rect, const WINED3DTEXTUREFILTERTYPE filter, BOOL flip);
void bind_fbo(IWineD3DDevice *iface, GLenum target, GLuint *fbo);
void attach_depth_stencil_fbo(IWineD3DDeviceImpl *This, GLenum fbo_target, IWineD3DSurface *depth_stencil, BOOL use_render_buffer);
void depth_blt(IWineD3DDevice *iface, GLuint texture);
#endif
......@@ -23,13 +23,6 @@
#ifndef __WINE_WINED3D_TYPES_INTERNAL_H
#define __WINE_WINED3D_TYPES_INTERNAL_H
/* Depth copy state */
typedef enum {
WINED3D_DCS_INITIAL = 0,
WINED3D_DCS_COPY = 1,
WINED3D_DCS_NO_COPY = 2
} WINED3D_DEPTHCOPYSTATE;
/* WineD3D pixel format flags */
#define WINED3DFMT_FLAG_POSTPIXELSHADER_BLENDING 0x1
#define WINED3DFMT_FLAG_FILTERING 0x2
......
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