Commit d9671226 by Jiajia Qin Committed by Commit Bot

Implement ES3.1 glDraw*Indirect entry points for OpenGL

BUG=angleproject:1595 TEST=dEQP-GLES31.functional.draw_indirect.* Change-Id: I82f5d0864e70d6e7abdccf5f10330ddfa099ec62 Reviewed-on: https://chromium-review.googlesource.com/417250 Commit-Queue: Jamie Madill <jmadill@chromium.org> Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org> Reviewed-by: 's avatarCorentin Wallez <cwallez@chromium.org>
parent 89c21ea8
......@@ -1670,6 +1670,18 @@ Error Context::drawRangeElements(GLenum mode,
return mImplementation->drawRangeElements(mode, start, end, count, type, indices, indexRange);
}
void Context::drawArraysIndirect(GLenum mode, const GLvoid *indirect)
{
syncRendererState();
handleError(mImplementation->drawArraysIndirect(mode, indirect));
}
void Context::drawElementsIndirect(GLenum mode, GLenum type, const GLvoid *indirect)
{
syncRendererState();
handleError(mImplementation->drawElementsIndirect(mode, type, indirect));
}
Error Context::flush()
{
return mImplementation->flush();
......
......@@ -310,6 +310,8 @@ class Context final : public ValidationContext
GLenum type,
const GLvoid *indices,
const IndexRange &indexRange);
void drawArraysIndirect(GLenum mode, const GLvoid *indirect);
void drawElementsIndirect(GLenum mode, GLenum type, const GLvoid *indirect);
void blitFramebuffer(GLint srcX0,
GLint srcY0,
......
......@@ -61,6 +61,9 @@ class ContextImpl : public GLImplFactory
const GLvoid *indices,
const gl::IndexRange &indexRange) = 0;
virtual gl::Error drawArraysIndirect(GLenum mode, const GLvoid *indirect) = 0;
virtual gl::Error drawElementsIndirect(GLenum mode, GLenum type, const GLvoid *indirect) = 0;
// CHROMIUM_path_rendering path drawing methods.
virtual void stencilFillPath(const gl::Path *path, GLenum fillMode, GLuint mask);
virtual void stencilStrokePath(const gl::Path *path, GLint reference, GLuint mask);
......
......@@ -192,6 +192,18 @@ gl::Error Context11::drawRangeElements(GLenum mode,
return mRenderer->genericDrawElements(this, mode, count, type, indices, 0, indexRange);
}
gl::Error Context11::drawArraysIndirect(GLenum mode, const GLvoid *indirect)
{
UNIMPLEMENTED();
return gl::InternalError() << "DrawArraysIndirect hasn't been implemented for D3D11 backend.";
}
gl::Error Context11::drawElementsIndirect(GLenum mode, GLenum type, const GLvoid *indirect)
{
UNIMPLEMENTED();
return gl::InternalError() << "DrawElementsIndirect hasn't been implemented for D3D11 backend.";
}
GLenum Context11::getResetStatus()
{
return mRenderer->getResetStatus();
......
......@@ -88,6 +88,8 @@ class Context11 : public ContextImpl
GLenum type,
const GLvoid *indices,
const gl::IndexRange &indexRange) override;
gl::Error drawArraysIndirect(GLenum mode, const GLvoid *indirect) override;
gl::Error drawElementsIndirect(GLenum mode, GLenum type, const GLvoid *indirect) override;
// Device loss
GLenum getResetStatus() override;
......
......@@ -178,6 +178,18 @@ gl::Error Context9::drawRangeElements(GLenum mode,
return mRenderer->genericDrawElements(this, mode, count, type, indices, 0, indexRange);
}
gl::Error Context9::drawArraysIndirect(GLenum mode, const GLvoid *indirect)
{
UNREACHABLE();
return gl::InternalError() << "D3D9 doesn't support ES 3.1 DrawArraysIndirect API";
}
gl::Error Context9::drawElementsIndirect(GLenum mode, GLenum type, const GLvoid *indirect)
{
UNREACHABLE();
return gl::InternalError() << "D3D9 doesn't support ES 3.1 DrawElementsIndirect API";
}
GLenum Context9::getResetStatus()
{
return mRenderer->getResetStatus();
......
......@@ -88,6 +88,8 @@ class Context9 : public ContextImpl
GLenum type,
const GLvoid *indices,
const gl::IndexRange &indexRange) override;
gl::Error drawArraysIndirect(GLenum mode, const GLvoid *indirect) override;
gl::Error drawElementsIndirect(GLenum mode, GLenum type, const GLvoid *indirect) override;
// Device loss
GLenum getResetStatus() override;
......
......@@ -194,6 +194,16 @@ gl::Error ContextGL::drawRangeElements(GLenum mode,
return mRenderer->drawRangeElements(mState, mode, start, end, count, type, indices, indexRange);
}
gl::Error ContextGL::drawArraysIndirect(GLenum mode, const GLvoid *indirect)
{
return mRenderer->drawArraysIndirect(mState, mode, indirect);
}
gl::Error ContextGL::drawElementsIndirect(GLenum mode, GLenum type, const GLvoid *indirect)
{
return mRenderer->drawElementsIndirect(mState, mode, type, indirect);
}
void ContextGL::stencilFillPath(const gl::Path *path, GLenum fillMode, GLuint mask)
{
mRenderer->stencilFillPath(mState, path, fillMode, mask);
......
......@@ -96,6 +96,8 @@ class ContextGL : public ContextImpl
GLenum type,
const GLvoid *indices,
const gl::IndexRange &indexRange) override;
gl::Error drawArraysIndirect(GLenum mode, const GLvoid *indirect) override;
gl::Error drawElementsIndirect(GLenum mode, GLenum type, const GLvoid *indirect) override;
// CHROMIUM_path_rendering implementation
void stencilFillPath(const gl::Path *path, GLenum fillMode, GLuint mask) override;
......
......@@ -274,6 +274,33 @@ gl::Error RendererGL::drawRangeElements(const gl::ContextState &data,
return gl::Error(GL_NO_ERROR);
}
gl::Error RendererGL::drawArraysIndirect(const gl::ContextState &data,
GLenum mode,
const GLvoid *indirect)
{
ANGLE_TRY(mStateManager->setDrawIndirectState(data, GL_NONE));
if (!mSkipDrawCalls)
{
mFunctions->drawArraysIndirect(mode, indirect);
}
return gl::NoError();
}
gl::Error RendererGL::drawElementsIndirect(const gl::ContextState &data,
GLenum mode,
GLenum type,
const GLvoid *indirect)
{
ANGLE_TRY(mStateManager->setDrawIndirectState(data, type));
if (!mSkipDrawCalls)
{
mFunctions->drawElementsIndirect(mode, type, indirect);
}
return gl::NoError();
}
void RendererGL::stencilFillPath(const gl::ContextState &state,
const gl::Path *path,
GLenum fillMode,
......
......@@ -77,6 +77,11 @@ class RendererGL : angle::NonCopyable
GLenum type,
const GLvoid *indices,
const gl::IndexRange &indexRange);
gl::Error drawArraysIndirect(const gl::ContextState &data, GLenum mode, const GLvoid *indirect);
gl::Error drawElementsIndirect(const gl::ContextState &data,
GLenum mode,
GLenum type,
const GLvoid *indirect);
// CHROMIUM_path_rendering implementation
void stencilFillPath(const gl::ContextState &state,
......
......@@ -662,6 +662,25 @@ gl::Error StateManagerGL::setDrawElementsState(const gl::ContextState &data,
return setGenericDrawState(data);
}
gl::Error StateManagerGL::setDrawIndirectState(const gl::ContextState &data, GLenum type)
{
const gl::State &state = data.getState();
if (type != GL_NONE)
{
const gl::VertexArray *vao = state.getVertexArray();
const VertexArrayGL *vaoGL = GetImplAs<VertexArrayGL>(vao);
ANGLE_TRY(vaoGL->syncElementArrayState());
}
gl::Buffer *drawIndirectBuffer = state.getDrawIndirectBuffer();
ASSERT(drawIndirectBuffer);
const BufferGL *bufferGL = GetImplAs<BufferGL>(drawIndirectBuffer);
bindBuffer(GL_DRAW_INDIRECT_BUFFER, bufferGL->getBufferID());
return setGenericDrawState(data);
}
void StateManagerGL::pauseTransformFeedback()
{
if (mPrevDrawTransformFeedback != nullptr)
......
......@@ -149,6 +149,7 @@ class StateManagerGL final : angle::NonCopyable
const GLvoid *indices,
GLsizei instanceCount,
const GLvoid **outIndices);
gl::Error setDrawIndirectState(const gl::ContextState &data, GLenum type);
void pauseTransformFeedback();
void pauseAllQueries();
......
......@@ -97,6 +97,20 @@ gl::Error VertexArrayGL::syncDrawElementsState(const gl::AttributesMask &activeA
primitiveRestartEnabled, outIndices);
}
gl::Error VertexArrayGL::syncElementArrayState() const
{
gl::Buffer *elementArrayBuffer = mData.getElementArrayBuffer().get();
ASSERT(elementArrayBuffer);
if (elementArrayBuffer != mAppliedElementArrayBuffer.get())
{
const BufferGL *bufferGL = GetImplAs<BufferGL>(elementArrayBuffer);
mStateManager->bindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufferGL->getBufferID());
mAppliedElementArrayBuffer.set(elementArrayBuffer);
}
return gl::NoError();
}
gl::Error VertexArrayGL::syncDrawState(const gl::AttributesMask &activeAttributesMask,
GLint first,
GLsizei count,
......@@ -141,13 +155,13 @@ gl::Error VertexArrayGL::syncDrawState(const gl::AttributesMask &activeAttribute
return Error(GL_NO_ERROR);
}
Error VertexArrayGL::syncIndexData(GLsizei count,
GLenum type,
const GLvoid *indices,
bool primitiveRestartEnabled,
bool attributesNeedStreaming,
IndexRange *outIndexRange,
const GLvoid **outIndices) const
gl::Error VertexArrayGL::syncIndexData(GLsizei count,
GLenum type,
const GLvoid *indices,
bool primitiveRestartEnabled,
bool attributesNeedStreaming,
IndexRange *outIndexRange,
const GLvoid **outIndices) const
{
ASSERT(outIndices);
......
......@@ -36,6 +36,7 @@ class VertexArrayGL : public VertexArrayImpl
GLsizei instanceCount,
bool primitiveRestartEnabled,
const GLvoid **outIndices) const;
gl::Error syncElementArrayState() const;
GLuint getVertexArrayID() const;
GLuint getAppliedElementArrayBufferID() const;
......
......@@ -101,6 +101,16 @@ gl::Error ContextNULL::drawRangeElements(GLenum mode,
return gl::NoError();
}
gl::Error ContextNULL::drawArraysIndirect(GLenum mode, const GLvoid *indirect)
{
return gl::NoError();
}
gl::Error ContextNULL::drawElementsIndirect(GLenum mode, GLenum type, const GLvoid *indirect)
{
return gl::NoError();
}
void ContextNULL::stencilFillPath(const gl::Path *path, GLenum fillMode, GLuint mask)
{
}
......
......@@ -52,6 +52,8 @@ class ContextNULL : public ContextImpl
GLenum type,
const GLvoid *indices,
const gl::IndexRange &indexRange) override;
gl::Error drawArraysIndirect(GLenum mode, const GLvoid *indirect) override;
gl::Error drawElementsIndirect(GLenum mode, GLenum type, const GLvoid *indirect) override;
// CHROMIUM_path_rendering path drawing methods.
void stencilFillPath(const gl::Path *path, GLenum fillMode, GLuint mask) override;
......
......@@ -106,6 +106,19 @@ gl::Error ContextVk::drawRangeElements(GLenum mode,
return gl::Error(GL_INVALID_OPERATION);
}
gl::Error ContextVk::drawArraysIndirect(GLenum mode, const GLvoid *indirect)
{
UNIMPLEMENTED();
return gl::InternalError() << "DrawArraysIndirect hasn't been implemented for vulkan backend.";
}
gl::Error ContextVk::drawElementsIndirect(GLenum mode, GLenum type, const GLvoid *indirect)
{
UNIMPLEMENTED();
return gl::InternalError()
<< "DrawElementsIndirect hasn't been implemented for vulkan backend.";
}
GLenum ContextVk::getResetStatus()
{
UNIMPLEMENTED();
......
......@@ -53,6 +53,8 @@ class ContextVk : public ContextImpl
GLenum type,
const GLvoid *indices,
const gl::IndexRange &indexRange) override;
gl::Error drawArraysIndirect(GLenum mode, const GLvoid *indirect) override;
gl::Error drawElementsIndirect(GLenum mode, GLenum type, const GLvoid *indirect) override;
// Device loss
GLenum getResetStatus() override;
......
......@@ -3051,10 +3051,7 @@ bool ValidateCopyTexImageParametersBase(ValidationContext *context,
return true;
}
static bool ValidateDrawBase(ValidationContext *context,
GLenum mode,
GLsizei count,
GLsizei primcount)
bool ValidateDrawBase(ValidationContext *context, GLenum mode, GLsizei count)
{
switch (mode)
{
......@@ -3191,7 +3188,7 @@ bool ValidateDrawArrays(ValidationContext *context,
return false;
}
if (!ValidateDrawBase(context, mode, count, primcount))
if (!ValidateDrawBase(context, mode, count))
{
return false;
}
......@@ -3272,13 +3269,7 @@ bool ValidateDrawArraysInstancedANGLE(Context *context,
return ValidateDrawArraysInstanced(context, mode, first, count, primcount);
}
bool ValidateDrawElements(ValidationContext *context,
GLenum mode,
GLsizei count,
GLenum type,
const GLvoid *indices,
GLsizei primcount,
IndexRange *indexRangeOut)
bool ValidateDrawElementsBase(ValidationContext *context, GLenum type)
{
switch (type)
{
......@@ -3310,6 +3301,22 @@ bool ValidateDrawElements(ValidationContext *context,
return false;
}
return true;
}
bool ValidateDrawElements(ValidationContext *context,
GLenum mode,
GLsizei count,
GLenum type,
const GLvoid *indices,
GLsizei primcount,
IndexRange *indexRangeOut)
{
if (!ValidateDrawElementsBase(context, type))
return false;
const State &state = context->getGLState();
// Check for mapped buffers
if (state.hasMappedBuffer(GL_ELEMENT_ARRAY_BUFFER))
{
......@@ -3361,7 +3368,7 @@ bool ValidateDrawElements(ValidationContext *context,
return false;
}
if (!ValidateDrawBase(context, mode, count, primcount))
if (!ValidateDrawBase(context, mode, count))
{
return false;
}
......
......@@ -221,6 +221,7 @@ bool ValidateCopyTexImageParametersBase(ValidationContext *context,
GLint border,
Format *textureFormatOut);
bool ValidateDrawBase(ValidationContext *context, GLenum mode, GLsizei count);
bool ValidateDrawArrays(ValidationContext *context,
GLenum mode,
GLint first,
......@@ -229,6 +230,7 @@ bool ValidateDrawArrays(ValidationContext *context,
bool ValidateDrawArraysInstanced(Context *context, GLenum mode, GLint first, GLsizei count, GLsizei primcount);
bool ValidateDrawArraysInstancedANGLE(Context *context, GLenum mode, GLint first, GLsizei count, GLsizei primcount);
bool ValidateDrawElementsBase(ValidationContext *context, GLenum type);
bool ValidateDrawElements(ValidationContext *context,
GLenum mode,
GLsizei count,
......
......@@ -11,6 +11,7 @@
#include "libANGLE/Context.h"
#include "libANGLE/validationES.h"
#include "libANGLE/validationES3.h"
#include "libANGLE/VertexArray.h"
using namespace angle;
......@@ -64,4 +65,119 @@ bool ValidateGetBooleani_vRobustANGLE(Context *context,
return true;
}
bool ValidateDrawIndirectBase(Context *context, GLenum mode, const GLvoid *indirect)
{
if (context->getClientVersion() < ES_3_1)
{
context->handleError(Error(GL_INVALID_OPERATION, "Context does not support GLES3.1"));
return false;
}
// Here the third parameter 1 is only to pass the count validation.
if (!ValidateDrawBase(context, mode, 1))
{
return false;
}
const State &state = context->getGLState();
// An INVALID_OPERATION error is generated if zero is bound to VERTEX_ARRAY_BINDING,
// DRAW_INDIRECT_BUFFER or to any enabled vertex array.
if (!state.getVertexArrayId())
{
context->handleError(Error(GL_INVALID_OPERATION, "zero is bound to VERTEX_ARRAY_BINDING"));
return false;
}
gl::Buffer *drawIndirectBuffer = state.getDrawIndirectBuffer();
if (!drawIndirectBuffer)
{
context->handleError(Error(GL_INVALID_OPERATION, "zero is bound to DRAW_INDIRECT_BUFFER"));
return false;
}
// An INVALID_VALUE error is generated if indirect is not a multiple of the size, in basic
// machine units, of uint.
GLint64 offset = reinterpret_cast<GLint64>(indirect);
if ((static_cast<GLuint>(offset) % sizeof(GLuint)) != 0)
{
context->handleError(
Error(GL_INVALID_VALUE,
"indirect is not a multiple of the size, in basic machine units, of uint"));
return false;
}
return true;
}
bool ValidateDrawArraysIndirect(Context *context, GLenum mode, const GLvoid *indirect)
{
const State &state = context->getGLState();
gl::TransformFeedback *curTransformFeedback = state.getCurrentTransformFeedback();
if (curTransformFeedback && curTransformFeedback->isActive() &&
!curTransformFeedback->isPaused())
{
// An INVALID_OPERATION error is generated if transform feedback is active and not paused.
context->handleError(
Error(GL_INVALID_OPERATION, "transform feedback is active and not paused."));
return false;
}
if (!ValidateDrawIndirectBase(context, mode, indirect))
return false;
gl::Buffer *drawIndirectBuffer = state.getDrawIndirectBuffer();
CheckedNumeric<size_t> checkedOffset(reinterpret_cast<size_t>(indirect));
// In OpenGL ES3.1 spec, session 10.5, it defines the struct of DrawArraysIndirectCommand
// which's size is 4 * sizeof(uint).
auto checkedSum = checkedOffset + 4 * sizeof(GLuint);
if (!checkedSum.IsValid() ||
checkedSum.ValueOrDie() > static_cast<size_t>(drawIndirectBuffer->getSize()))
{
context->handleError(
Error(GL_INVALID_OPERATION,
"the command would source data beyond the end of the buffer object."));
return false;
}
return true;
}
bool ValidateDrawElementsIndirect(Context *context,
GLenum mode,
GLenum type,
const GLvoid *indirect)
{
if (!ValidateDrawElementsBase(context, type))
return false;
const State &state = context->getGLState();
const VertexArray *vao = state.getVertexArray();
gl::Buffer *elementArrayBuffer = vao->getElementArrayBuffer().get();
if (!elementArrayBuffer)
{
context->handleError(Error(GL_INVALID_OPERATION, "zero is bound to ELEMENT_ARRAY_BUFFER"));
return false;
}
if (!ValidateDrawIndirectBase(context, mode, indirect))
return false;
gl::Buffer *drawIndirectBuffer = state.getDrawIndirectBuffer();
CheckedNumeric<size_t> checkedOffset(reinterpret_cast<size_t>(indirect));
// In OpenGL ES3.1 spec, session 10.5, it defines the struct of DrawElementsIndirectCommand
// which's size is 5 * sizeof(uint).
auto checkedSum = checkedOffset + 5 * sizeof(GLuint);
if (!checkedSum.IsValid() ||
checkedSum.ValueOrDie() > static_cast<size_t>(drawIndirectBuffer->getSize()))
{
context->handleError(
Error(GL_INVALID_OPERATION,
"the command would source data beyond the end of the buffer object."));
return false;
}
return true;
}
} // namespace gl
......@@ -23,6 +23,13 @@ bool ValidateGetBooleani_vRobustANGLE(Context *context,
GLsizei *length,
GLboolean *data);
bool ValidateDrawIndirectBase(Context *context, GLenum mode, const GLvoid *indirect);
bool ValidateDrawArraysIndirect(Context *context, GLenum mode, const GLvoid *indirect);
bool ValidateDrawElementsIndirect(Context *context,
GLenum mode,
GLenum type,
const GLvoid *indirect);
} // namespace gl
#endif // LIBANGLE_VALIDATION_ES31_H_
......@@ -55,11 +55,12 @@ void GL_APIENTRY DrawArraysIndirect(GLenum mode, const void *indirect)
Context *context = GetValidGlobalContext();
if (context)
{
if (!context->skipValidation())
if (!context->skipValidation() && !ValidateDrawArraysIndirect(context, mode, indirect))
{
context->handleError(Error(GL_INVALID_OPERATION, "Entry point not implemented"));
return;
}
UNIMPLEMENTED();
context->drawArraysIndirect(mode, indirect);
}
}
......@@ -69,11 +70,13 @@ void GL_APIENTRY DrawElementsIndirect(GLenum mode, GLenum type, const void *indi
Context *context = GetValidGlobalContext();
if (context)
{
if (!context->skipValidation())
if (!context->skipValidation() &&
!ValidateDrawElementsIndirect(context, mode, type, indirect))
{
context->handleError(Error(GL_INVALID_OPERATION, "Entry point not implemented"));
return;
}
UNIMPLEMENTED();
context->drawElementsIndirect(mode, type, indirect);
}
}
......
......@@ -24,6 +24,10 @@
// 91532 MAC NVIDIA 0x0640 : tex_image_and_sub_image_2d_with_video = PASS FAIL
// Crashing Tests
1659 NVIDIA OPENGL : dEQP-GLES31.functional.draw_indirect.random.0 = SKIP
1659 NVIDIA OPENGL : dEQP-GLES31.functional.draw_indirect.random.10 = SKIP
1659 NVIDIA OPENGL : dEQP-GLES31.functional.draw_indirect.random.15 = SKIP
1659 NVIDIA OPENGL : dEQP-GLES31.functional.draw_indirect.random.18 = SKIP
1442 OPENGL D3D11 : dEQP-GLES31.functional.layout_binding.image.image2d.vertex_binding_array = SKIP
1442 OPENGL D3D11 : dEQP-GLES31.functional.layout_binding.image.image2d.vertex_binding_max_array = SKIP
1442 OPENGL D3D11 : dEQP-GLES31.functional.layout_binding.image.image2d.fragment_binding_array = SKIP
......@@ -46,6 +50,7 @@
1442 D3D11 : dEQP-GLES31.functional.stencil_texturing.* = SKIP
// D3D11 Failing Tests
1442 D3D11 : dEQP-GLES31.functional.draw_indirect.* = FAIL
1442 D3D11 : dEQP-GLES31.functional.state_query.integer.max_color_texture_samples_* = FAIL
1442 D3D11 : dEQP-GLES31.functional.state_query.integer.max_depth_texture_samples_* = FAIL
1442 D3D11 : dEQP-GLES31.functional.state_query.integer.max_integer_samples_* = FAIL
......@@ -99,6 +104,10 @@
1442 D3D11 : dEQP-GLES31.functional.debug.error_groups.case_9 = SKIP
1442 D3D11 : dEQP-GLES31.functional.debug.error_groups.case_10 = SKIP
// OPENGL Failing Tests
1663 OPENGL : dEQP-GLES31.functional.draw_indirect.compute_interop.* = FAIL
1665 WIN NVIDIA OPENGL : dEQP-GLES31.functional.draw_indirect.negative.command_offset_not_in_buffer_unsigned32_wrap = FAIL
// OpenGL and D3D11 Failing Tests
1442 OPENGL D3D11 : dEQP-GLES31.functional.shaders.builtin_var.compute.num_work_groups = FAIL
1442 OPENGL D3D11 : dEQP-GLES31.functional.shaders.builtin_var.compute.work_group_size = FAIL
......@@ -1119,8 +1128,6 @@
1442 OPENGL D3D11 : dEQP-GLES31.functional.compute.basic.atomic_counter_multiple_groups = FAIL
1442 OPENGL D3D11 : dEQP-GLES31.functional.compute.shared_var.* = FAIL
1442 OPENGL D3D11 : dEQP-GLES31.functional.compute.indirect_dispatch.* = FAIL
1442 OPENGL D3D11 : dEQP-GLES31.functional.draw_indirect.* = FAIL
1442 OPENGL D3D11 : dEQP-GLES31.functional.draw_indirect.negative.* = FAIL
1442 OPENGL D3D11 : dEQP-GLES31.functional.ssbo.* = FAIL
1442 OPENGL D3D11 : dEQP-GLES31.functional.ubo.2_level_array.* = FAIL
1442 OPENGL D3D11 : dEQP-GLES31.functional.ubo.3_level_array.* = FAIL
......
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