Commit 5f9482f4 by Jiawei Shao Committed by Commit Bot

ES31: Implement FramebufferTextureEXT entry point

This patch adds the entry point and related validation for FramebufferTextureEXT defined in OpenGL ES 3.1 extension EXT_geometry_shader. BUG=angleproject:1941 TEST=angle_end2end_tests Change-Id: Id6804e0b3971f52273562ce1a325d8377926a558 Reviewed-on: https://chromium-review.googlesource.com/1069842 Commit-Queue: Corentin Wallez <cwallez@chromium.org> Reviewed-by: 's avatarCorentin Wallez <cwallez@chromium.org> Reviewed-by: 's avatarGeoff Lang <geofflang@chromium.org>
parent 04796cda
...@@ -168,6 +168,7 @@ typedef void (GL_APIENTRYP PFNGLEGLIMAGETARGETTEXTURE2DOESCONTEXTANGLE)(GLeglCon ...@@ -168,6 +168,7 @@ typedef void (GL_APIENTRYP PFNGLEGLIMAGETARGETTEXTURE2DOESCONTEXTANGLE)(GLeglCon
typedef void (GL_APIENTRYP PFNGLENDQUERYEXTCONTEXTANGLE)(GLeglContext ctx, GLenum target); typedef void (GL_APIENTRYP PFNGLENDQUERYEXTCONTEXTANGLE)(GLeglContext ctx, GLenum target);
typedef void (GL_APIENTRYP PFNGLFINISHFENCENVCONTEXTANGLE)(GLeglContext ctx, GLuint fence); typedef void (GL_APIENTRYP PFNGLFINISHFENCENVCONTEXTANGLE)(GLeglContext ctx, GLuint fence);
typedef void (GL_APIENTRYP PFNGLFLUSHMAPPEDBUFFERRANGEEXTCONTEXTANGLE)(GLeglContext ctx, GLenum target, GLintptr offset, GLsizeiptr length); typedef void (GL_APIENTRYP PFNGLFLUSHMAPPEDBUFFERRANGEEXTCONTEXTANGLE)(GLeglContext ctx, GLenum target, GLintptr offset, GLsizeiptr length);
typedef void (GL_APIENTRYP PFNGLFRAMEBUFFERTEXTUREEXTCONTEXTANGLE)(GLeglContext ctx, GLenum target, GLenum attachment, GLuint texture, GLint level);
typedef void (GL_APIENTRYP PFNGLGENFENCESNVCONTEXTANGLE)(GLeglContext ctx, GLsizei n, GLuint *fences); typedef void (GL_APIENTRYP PFNGLGENFENCESNVCONTEXTANGLE)(GLeglContext ctx, GLsizei n, GLuint *fences);
typedef void (GL_APIENTRYP PFNGLGENQUERIESEXTCONTEXTANGLE)(GLeglContext ctx, GLsizei n, GLuint *ids); typedef void (GL_APIENTRYP PFNGLGENQUERIESEXTCONTEXTANGLE)(GLeglContext ctx, GLsizei n, GLuint *ids);
typedef void (GL_APIENTRYP PFNGLGENVERTEXARRAYSOESCONTEXTANGLE)(GLeglContext ctx, GLsizei n, GLuint *arrays); typedef void (GL_APIENTRYP PFNGLGENVERTEXARRAYSOESCONTEXTANGLE)(GLeglContext ctx, GLsizei n, GLuint *arrays);
...@@ -468,6 +469,7 @@ GL_APICALL void GL_APIENTRY glEGLImageTargetTexture2DOESContextANGLE(GLeglContex ...@@ -468,6 +469,7 @@ GL_APICALL void GL_APIENTRY glEGLImageTargetTexture2DOESContextANGLE(GLeglContex
GL_APICALL void GL_APIENTRY glEndQueryEXTContextANGLE(GLeglContext ctx, GLenum target); GL_APICALL void GL_APIENTRY glEndQueryEXTContextANGLE(GLeglContext ctx, GLenum target);
GL_APICALL void GL_APIENTRY glFinishFenceNVContextANGLE(GLeglContext ctx, GLuint fence); GL_APICALL void GL_APIENTRY glFinishFenceNVContextANGLE(GLeglContext ctx, GLuint fence);
GL_APICALL void GL_APIENTRY glFlushMappedBufferRangeEXTContextANGLE(GLeglContext ctx, GLenum target, GLintptr offset, GLsizeiptr length); GL_APICALL void GL_APIENTRY glFlushMappedBufferRangeEXTContextANGLE(GLeglContext ctx, GLenum target, GLintptr offset, GLsizeiptr length);
GL_APICALL void GL_APIENTRY glFramebufferTextureEXTContextANGLE(GLeglContext ctx, GLenum target, GLenum attachment, GLuint texture, GLint level);
GL_APICALL void GL_APIENTRY glGenFencesNVContextANGLE(GLeglContext ctx, GLsizei n, GLuint *fences); GL_APICALL void GL_APIENTRY glGenFencesNVContextANGLE(GLeglContext ctx, GLsizei n, GLuint *fences);
GL_APICALL void GL_APIENTRY glGenQueriesEXTContextANGLE(GLeglContext ctx, GLsizei n, GLuint *ids); GL_APICALL void GL_APIENTRY glGenQueriesEXTContextANGLE(GLeglContext ctx, GLsizei n, GLuint *ids);
GL_APICALL void GL_APIENTRY glGenVertexArraysOESContextANGLE(GLeglContext ctx, GLsizei n, GLuint *arrays); GL_APICALL void GL_APIENTRY glGenVertexArraysOESContextANGLE(GLeglContext ctx, GLsizei n, GLuint *arrays);
......
...@@ -57,6 +57,7 @@ supported_extensions = sorted(angle_extensions + gles1_extensions + [ ...@@ -57,6 +57,7 @@ supported_extensions = sorted(angle_extensions + gles1_extensions + [
"GL_EXT_discard_framebuffer", "GL_EXT_discard_framebuffer",
"GL_EXT_disjoint_timer_query", "GL_EXT_disjoint_timer_query",
"GL_EXT_draw_buffers", "GL_EXT_draw_buffers",
"GL_EXT_geometry_shader",
"GL_EXT_map_buffer_range", "GL_EXT_map_buffer_range",
"GL_EXT_occlusion_query_boolean", "GL_EXT_occlusion_query_boolean",
"GL_EXT_robustness", "GL_EXT_robustness",
......
...@@ -3688,6 +3688,12 @@ void Context::framebufferTextureMultiviewSideBySide(GLenum target, ...@@ -3688,6 +3688,12 @@ void Context::framebufferTextureMultiviewSideBySide(GLenum target,
mGLState.setObjectDirty(target); mGLState.setObjectDirty(target);
} }
// TODO(jiawei.shao@intel.com): implement framebufferTextureEXT
void Context::framebufferTexture(GLenum target, GLenum attachment, GLuint texture, GLint level)
{
UNIMPLEMENTED();
}
void Context::drawBuffers(GLsizei n, const GLenum *bufs) void Context::drawBuffers(GLsizei n, const GLenum *bufs)
{ {
Framebuffer *framebuffer = mGLState.getDrawFramebuffer(); Framebuffer *framebuffer = mGLState.getDrawFramebuffer();
......
...@@ -1374,6 +1374,8 @@ class Context final : angle::NonCopyable ...@@ -1374,6 +1374,8 @@ class Context final : angle::NonCopyable
void memoryBarrier(GLbitfield barriers); void memoryBarrier(GLbitfield barriers);
void memoryBarrierByRegion(GLbitfield barriers); void memoryBarrierByRegion(GLbitfield barriers);
void framebufferTexture(GLenum target, GLenum attachment, GLuint texture, GLint level);
// Consumes the error. // Consumes the error.
void handleError(const Error &error) const; void handleError(const Error &error) const;
......
...@@ -126,6 +126,7 @@ ERRMSG(InvalidStencil, "Invalid stencil."); ...@@ -126,6 +126,7 @@ ERRMSG(InvalidStencil, "Invalid stencil.");
ERRMSG(InvalidStencilBitMask, "Invalid stencil bit mask."); ERRMSG(InvalidStencilBitMask, "Invalid stencil bit mask.");
ERRMSG(InvalidTarget, "Invalid target."); ERRMSG(InvalidTarget, "Invalid target.");
ERRMSG(InvalidTextureFilterParam, "Texture filter not recognized."); ERRMSG(InvalidTextureFilterParam, "Texture filter not recognized.");
ERRMSG(InvalidTextureName, "Not a valid texture object name.");
ERRMSG(InvalidTextureRange, "Cannot be less than 0 or greater than maximum number of textures."); ERRMSG(InvalidTextureRange, "Cannot be less than 0 or greater than maximum number of textures.");
ERRMSG(InvalidTextureTarget, "Invalid or unsupported texture target."); ERRMSG(InvalidTextureTarget, "Invalid or unsupported texture target.");
ERRMSG(InvalidTextureWrap, "Texture wrap mode not recognized."); ERRMSG(InvalidTextureWrap, "Texture wrap mode not recognized.");
......
...@@ -172,6 +172,7 @@ enum class EntryPoint ...@@ -172,6 +172,7 @@ enum class EntryPoint
FramebufferRenderbufferOES, FramebufferRenderbufferOES,
FramebufferTexture2D, FramebufferTexture2D,
FramebufferTexture2DOES, FramebufferTexture2DOES,
FramebufferTextureEXT,
FramebufferTextureLayer, FramebufferTextureLayer,
FramebufferTextureMultiviewLayeredANGLE, FramebufferTextureMultiviewLayeredANGLE,
FramebufferTextureMultiviewSideBySideANGLE, FramebufferTextureMultiviewSideBySideANGLE,
......
...@@ -1965,4 +1965,51 @@ bool ValidateSampleMaski(Context *context, GLuint maskNumber, GLbitfield mask) ...@@ -1965,4 +1965,51 @@ bool ValidateSampleMaski(Context *context, GLuint maskNumber, GLbitfield mask)
return true; return true;
} }
bool ValidateFramebufferTextureEXT(Context *context,
GLenum target,
GLenum attachment,
GLuint texture,
GLint level)
{
if (!context->getExtensions().geometryShader)
{
ANGLE_VALIDATION_ERR(context, InvalidOperation(), GeometryShaderExtensionNotEnabled);
return false;
}
if (texture != 0)
{
gl::Texture *tex = context->getTexture(texture);
// [EXT_geometry_shader] Section 9.2.8 "Attaching Texture Images to a Framebuffer"
// An INVALID_VALUE error is generated if <texture> is not the name of a texture object.
// We put this validation before ValidateFramebufferTextureBase because it is an
// INVALID_OPERATION error for both FramebufferTexture2D and FramebufferTextureLayer:
// [OpenGL ES 3.1] Chapter 9.2.8 (FramebufferTexture2D)
// An INVALID_OPERATION error is generated if texture is not zero, and does not name an
// existing texture object of type matching textarget.
// [OpenGL ES 3.1 Chapter 9.2.8 (FramebufferTextureLayer)
// An INVALID_OPERATION error is generated if texture is non-zero and is not the name of a
// three-dimensional or two-dimensional array texture.
if (tex == nullptr)
{
ANGLE_VALIDATION_ERR(context, InvalidValue(), InvalidTextureName);
return false;
}
if (!ValidMipLevel(context, tex->getType(), level))
{
ANGLE_VALIDATION_ERR(context, InvalidValue(), InvalidMipLevel);
return false;
}
}
if (!ValidateFramebufferTextureBase(context, target, attachment, texture, level))
{
return false;
}
return true;
}
} // namespace gl } // namespace gl
...@@ -355,6 +355,13 @@ bool ValidateMemoryBarrierByRegion(Context *context, GLbitfield barriers); ...@@ -355,6 +355,13 @@ bool ValidateMemoryBarrierByRegion(Context *context, GLbitfield barriers);
bool ValidateSampleMaski(Context *context, GLuint maskNumber, GLbitfield mask); bool ValidateSampleMaski(Context *context, GLuint maskNumber, GLbitfield mask);
// GL_EXT_geometry_shader
bool ValidateFramebufferTextureEXT(Context *context,
GLenum target,
GLenum attachment,
GLuint texture,
GLint level);
} // namespace gl } // namespace gl
#endif // LIBANGLE_VALIDATION_ES31_H_ #endif // LIBANGLE_VALIDATION_ES31_H_
...@@ -2924,6 +2924,29 @@ void GL_APIENTRY DrawBuffersEXT(GLsizei n, const GLenum *bufs) ...@@ -2924,6 +2924,29 @@ void GL_APIENTRY DrawBuffersEXT(GLsizei n, const GLenum *bufs)
} }
} }
// GL_EXT_geometry_shader
void GL_APIENTRY FramebufferTextureEXT(GLenum target,
GLenum attachment,
GLuint texture,
GLint level)
{
EVENT("(GLenum target = 0x%X, GLenum attachment = 0x%X, GLuint texture = %u, GLint level = %d)",
target, attachment, texture, level);
Context *context = GetValidGlobalContext();
if (context)
{
context->gatherParams<EntryPoint::FramebufferTextureEXT>(target, attachment, texture,
level);
if (context->skipValidation() ||
ValidateFramebufferTextureEXT(context, target, attachment, texture, level))
{
context->framebufferTexture(target, attachment, texture, level);
}
}
}
// GL_EXT_map_buffer_range // GL_EXT_map_buffer_range
void GL_APIENTRY FlushMappedBufferRangeEXT(GLenum target, GLintptr offset, GLsizeiptr length) void GL_APIENTRY FlushMappedBufferRangeEXT(GLenum target, GLintptr offset, GLsizeiptr length)
{ {
...@@ -7328,6 +7351,30 @@ void GL_APIENTRY FramebufferTexture2DOESContextANGLE(GLeglContext ctx, ...@@ -7328,6 +7351,30 @@ void GL_APIENTRY FramebufferTexture2DOESContextANGLE(GLeglContext ctx,
} }
} }
void GL_APIENTRY FramebufferTextureEXTContextANGLE(GLeglContext ctx,
GLenum target,
GLenum attachment,
GLuint texture,
GLint level)
{
EVENT("(GLenum target = 0x%X, GLenum attachment = 0x%X, GLuint texture = %u, GLint level = %d)",
target, attachment, texture, level);
Context *context = static_cast<gl::Context *>(ctx);
if (context)
{
ASSERT(context == GetValidGlobalContext());
context->gatherParams<EntryPoint::FramebufferTextureEXT>(target, attachment, texture,
level);
if (context->skipValidation() ||
ValidateFramebufferTextureEXT(context, target, attachment, texture, level))
{
context->framebufferTexture(target, attachment, texture, level);
}
}
}
void GL_APIENTRY FramebufferTextureLayerContextANGLE(GLeglContext ctx, void GL_APIENTRY FramebufferTextureLayerContextANGLE(GLeglContext ctx,
GLenum target, GLenum target,
GLenum attachment, GLenum attachment,
......
...@@ -594,6 +594,12 @@ ANGLE_EXPORT void GL_APIENTRY QueryCounterEXT(GLuint id, GLenum target); ...@@ -594,6 +594,12 @@ ANGLE_EXPORT void GL_APIENTRY QueryCounterEXT(GLuint id, GLenum target);
// GL_EXT_draw_buffers // GL_EXT_draw_buffers
ANGLE_EXPORT void GL_APIENTRY DrawBuffersEXT(GLsizei n, const GLenum *bufs); ANGLE_EXPORT void GL_APIENTRY DrawBuffersEXT(GLsizei n, const GLenum *bufs);
// GL_EXT_geometry_shader
ANGLE_EXPORT void GL_APIENTRY FramebufferTextureEXT(GLenum target,
GLenum attachment,
GLuint texture,
GLint level);
// GL_EXT_map_buffer_range // GL_EXT_map_buffer_range
ANGLE_EXPORT void GL_APIENTRY FlushMappedBufferRangeEXT(GLenum target, ANGLE_EXPORT void GL_APIENTRY FlushMappedBufferRangeEXT(GLenum target,
GLintptr offset, GLintptr offset,
...@@ -1265,6 +1271,11 @@ ANGLE_EXPORT void GL_APIENTRY FramebufferTexture2DOESContextANGLE(GLeglContext c ...@@ -1265,6 +1271,11 @@ ANGLE_EXPORT void GL_APIENTRY FramebufferTexture2DOESContextANGLE(GLeglContext c
GLenum textarget, GLenum textarget,
GLuint texture, GLuint texture,
GLint level); GLint level);
ANGLE_EXPORT void GL_APIENTRY FramebufferTextureEXTContextANGLE(GLeglContext ctx,
GLenum target,
GLenum attachment,
GLuint texture,
GLint level);
ANGLE_EXPORT void GL_APIENTRY FramebufferTextureLayerContextANGLE(GLeglContext ctx, ANGLE_EXPORT void GL_APIENTRY FramebufferTextureLayerContextANGLE(GLeglContext ctx,
GLenum target, GLenum target,
GLenum attachment, GLenum attachment,
......
...@@ -3479,6 +3479,15 @@ void GL_APIENTRY glDrawBuffersEXT(GLsizei n, const GLenum *bufs) ...@@ -3479,6 +3479,15 @@ void GL_APIENTRY glDrawBuffersEXT(GLsizei n, const GLenum *bufs)
return gl::DrawBuffersEXT(n, bufs); return gl::DrawBuffersEXT(n, bufs);
} }
// GL_EXT_geometry_shader
void GL_APIENTRY glFramebufferTextureEXT(GLenum target,
GLenum attachment,
GLuint texture,
GLint level)
{
return gl::FramebufferTextureEXT(target, attachment, texture, level);
}
// GL_EXT_map_buffer_range // GL_EXT_map_buffer_range
void GL_APIENTRY glFlushMappedBufferRangeEXT(GLenum target, GLintptr offset, GLsizeiptr length) void GL_APIENTRY glFlushMappedBufferRangeEXT(GLenum target, GLintptr offset, GLsizeiptr length)
{ {
...@@ -4953,6 +4962,15 @@ void GL_APIENTRY glFramebufferTexture2DOESContextANGLE(GLeglContext ctx, ...@@ -4953,6 +4962,15 @@ void GL_APIENTRY glFramebufferTexture2DOESContextANGLE(GLeglContext ctx,
level); level);
} }
void GL_APIENTRY glFramebufferTextureEXTContextANGLE(GLeglContext ctx,
GLenum target,
GLenum attachment,
GLuint texture,
GLint level)
{
return gl::FramebufferTextureEXTContextANGLE(ctx, target, attachment, texture, level);
}
void GL_APIENTRY glFramebufferTextureLayerContextANGLE(GLeglContext ctx, void GL_APIENTRY glFramebufferTextureLayerContextANGLE(GLeglContext ctx,
GLenum target, GLenum target,
GLenum attachment, GLenum attachment,
......
...@@ -389,6 +389,8 @@ ProcEntry g_procTable[] = { ...@@ -389,6 +389,8 @@ ProcEntry g_procTable[] = {
{"glFramebufferRenderbufferContextANGLE", P(gl::FramebufferRenderbufferContextANGLE)}, {"glFramebufferRenderbufferContextANGLE", P(gl::FramebufferRenderbufferContextANGLE)},
{"glFramebufferTexture2D", P(gl::FramebufferTexture2D)}, {"glFramebufferTexture2D", P(gl::FramebufferTexture2D)},
{"glFramebufferTexture2DContextANGLE", P(gl::FramebufferTexture2DContextANGLE)}, {"glFramebufferTexture2DContextANGLE", P(gl::FramebufferTexture2DContextANGLE)},
{"glFramebufferTextureEXT", P(gl::FramebufferTextureEXT)},
{"glFramebufferTextureEXTContextANGLE", P(gl::FramebufferTextureEXTContextANGLE)},
{"glFramebufferTextureLayer", P(gl::FramebufferTextureLayer)}, {"glFramebufferTextureLayer", P(gl::FramebufferTextureLayer)},
{"glFramebufferTextureLayerContextANGLE", P(gl::FramebufferTextureLayerContextANGLE)}, {"glFramebufferTextureLayerContextANGLE", P(gl::FramebufferTextureLayerContextANGLE)},
{"glFramebufferTextureMultiviewLayeredANGLE", P(gl::FramebufferTextureMultiviewLayeredANGLE)}, {"glFramebufferTextureMultiviewLayeredANGLE", P(gl::FramebufferTextureMultiviewLayeredANGLE)},
...@@ -1229,5 +1231,5 @@ ProcEntry g_procTable[] = { ...@@ -1229,5 +1231,5 @@ ProcEntry g_procTable[] = {
{"glWeightPointerOES", P(gl::WeightPointerOES)}, {"glWeightPointerOES", P(gl::WeightPointerOES)},
{"glWeightPointerOESContextANGLE", P(gl::WeightPointerOESContextANGLE)}}; {"glWeightPointerOESContextANGLE", P(gl::WeightPointerOESContextANGLE)}};
size_t g_numProcs = 1161; size_t g_numProcs = 1163;
} // namespace egl } // namespace egl
...@@ -704,6 +704,10 @@ ...@@ -704,6 +704,10 @@
"glVertexBindingDivisor" "glVertexBindingDivisor"
], ],
"GL_EXT_geometry_shader": [
"glFramebufferTextureEXT"
],
"EGL 1.0": [ "EGL 1.0": [
"eglChooseConfig", "eglChooseConfig",
"eglCopyBuffers", "eglCopyBuffers",
......
...@@ -699,6 +699,52 @@ TEST_P(GeometryShaderTest, ReferencedByGeometryShader) ...@@ -699,6 +699,52 @@ TEST_P(GeometryShaderTest, ReferencedByGeometryShader)
} }
} }
// Verify correct errors can be reported when we use illegal parameters on FramebufferTextureEXT.
TEST_P(GeometryShaderTest, NegativeFramebufferTextureEXT)
{
ANGLE_SKIP_TEST_IF(!extensionEnabled("GL_EXT_geometry_shader"));
GLFramebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
GLTexture tex;
glBindTexture(GL_TEXTURE_3D, tex);
glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA8, 32, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
// [EXT_geometry_shader] Section 9.2.8, "Attaching Texture Images to a Framebuffer"
// An INVALID_ENUM error is generated if <target> is not DRAW_FRAMEBUFFER, READ_FRAMEBUFFER, or
// FRAMEBUFFER.
glFramebufferTextureEXT(GL_TEXTURE_2D, GL_COLOR_ATTACHMENT0, tex, 0);
EXPECT_GL_ERROR(GL_INVALID_ENUM);
// An INVALID_ENUM error is generated if <attachment> is not one of the attachments in Table
// 9.1.
glFramebufferTextureEXT(GL_FRAMEBUFFER, GL_TEXTURE_2D, tex, 0);
EXPECT_GL_ERROR(GL_INVALID_ENUM);
// An INVALID_OPERATION error is generated if zero is bound to <target>.
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glFramebufferTextureEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex, 0);
EXPECT_GL_ERROR(GL_INVALID_OPERATION);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
// An INVALID_VALUE error is generated if <texture> is not the name of a texture object, or if
// <level> is not a supported texture level for <texture>.
GLuint tex2;
glGenTextures(1, &tex2);
glDeleteTextures(1, &tex2);
ASSERT_FALSE(glIsTexture(tex2));
glFramebufferTextureEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2, 0);
EXPECT_GL_ERROR(GL_INVALID_VALUE);
GLint max3DSize;
glGetIntegerv(GL_MAX_3D_TEXTURE_SIZE, &max3DSize);
GLint max3DLevel = std::log2(max3DSize);
glFramebufferTextureEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex, max3DLevel + 1);
EXPECT_GL_ERROR(GL_INVALID_VALUE);
}
ANGLE_INSTANTIATE_TEST(GeometryShaderTestES3, ES3_OPENGL(), ES3_OPENGLES(), ES3_D3D11()); ANGLE_INSTANTIATE_TEST(GeometryShaderTestES3, ES3_OPENGL(), ES3_OPENGLES(), ES3_D3D11());
ANGLE_INSTANTIATE_TEST(GeometryShaderTest, ES31_OPENGL(), ES31_OPENGLES(), ES31_D3D11()); ANGLE_INSTANTIATE_TEST(GeometryShaderTest, ES31_OPENGL(), ES31_OPENGLES(), ES31_D3D11());
} }
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