Commit f546e7df by Jiajia Qin Committed by Geoff Lang

ES31: Add GL_SHADER_STORAGE_BUFFER_BINDING binding point

The affected APIs are below: getIntegeri_v getInteger64i_v BindBuffer BindBufferBase BindBufferRange BUG=angleproject:1951 TEST=dEQP-GLES31.functional.state_query.integer.shader_storage_buffer_binding_* dEQP-GLES31.functional.state_query.indexed.shader_storage_buffer_* Change-Id: Ief7186b2ebe305f14e620c31841bc244f84429a5 Reviewed-on: https://chromium-review.googlesource.com/459093Reviewed-by: 's avatarYunchao He <yunchao.he@intel.com> Reviewed-by: 's avatarGeoff Lang <geofflang@chromium.org> Reviewed-by: 's avatarCorentin Wallez <cwallez@chromium.org>
parent fd7c3b52
...@@ -321,6 +321,12 @@ Context::Context(rx::EGLImplFactory *implFactory, ...@@ -321,6 +321,12 @@ Context::Context(rx::EGLImplFactory *implFactory,
{ {
bindIndexedAtomicCounterBuffer(0, i, 0, 0); bindIndexedAtomicCounterBuffer(0, i, 0, 0);
} }
bindGenericShaderStorageBuffer(0);
for (unsigned int i = 0; i < mCaps.maxShaderStorageBufferBindings; i++)
{
bindIndexedShaderStorageBuffer(0, i, 0, 0);
}
} }
if (mExtensions.eglImageExternal || mExtensions.eglStreamConsumerExternal) if (mExtensions.eglImageExternal || mExtensions.eglStreamConsumerExternal)
...@@ -1096,6 +1102,21 @@ void Context::bindIndexedAtomicCounterBuffer(GLuint bufferHandle, ...@@ -1096,6 +1102,21 @@ void Context::bindIndexedAtomicCounterBuffer(GLuint bufferHandle,
mGLState.setIndexedAtomicCounterBufferBinding(index, buffer, offset, size); mGLState.setIndexedAtomicCounterBufferBinding(index, buffer, offset, size);
} }
void Context::bindGenericShaderStorageBuffer(GLuint bufferHandle)
{
Buffer *buffer = mState.mBuffers->checkBufferAllocation(mImplementation.get(), bufferHandle);
mGLState.setGenericShaderStorageBufferBinding(buffer);
}
void Context::bindIndexedShaderStorageBuffer(GLuint bufferHandle,
GLuint index,
GLintptr offset,
GLsizeiptr size)
{
Buffer *buffer = mState.mBuffers->checkBufferAllocation(mImplementation.get(), bufferHandle);
mGLState.setIndexedShaderStorageBufferBinding(index, buffer, offset, size);
}
void Context::bindCopyReadBuffer(GLuint bufferHandle) void Context::bindCopyReadBuffer(GLuint bufferHandle)
{ {
Buffer *buffer = mState.mBuffers->checkBufferAllocation(mImplementation.get(), bufferHandle); Buffer *buffer = mState.mBuffers->checkBufferAllocation(mImplementation.get(), bufferHandle);
...@@ -3851,11 +3872,7 @@ void Context::bindBuffer(GLenum target, GLuint buffer) ...@@ -3851,11 +3872,7 @@ void Context::bindBuffer(GLenum target, GLuint buffer)
bindGenericAtomicCounterBuffer(buffer); bindGenericAtomicCounterBuffer(buffer);
break; break;
case GL_SHADER_STORAGE_BUFFER: case GL_SHADER_STORAGE_BUFFER:
if (buffer != 0) bindGenericShaderStorageBuffer(buffer);
{
// Binding buffers to this binding point is not implemented yet.
UNIMPLEMENTED();
}
break; break;
case GL_DRAW_INDIRECT_BUFFER: case GL_DRAW_INDIRECT_BUFFER:
bindDrawIndirectBuffer(buffer); bindDrawIndirectBuffer(buffer);
...@@ -3900,11 +3917,8 @@ void Context::bindBufferRange(GLenum target, ...@@ -3900,11 +3917,8 @@ void Context::bindBufferRange(GLenum target,
bindGenericAtomicCounterBuffer(buffer); bindGenericAtomicCounterBuffer(buffer);
break; break;
case GL_SHADER_STORAGE_BUFFER: case GL_SHADER_STORAGE_BUFFER:
if (buffer != 0) bindIndexedShaderStorageBuffer(buffer, index, offset, size);
{ bindGenericShaderStorageBuffer(buffer);
// Binding buffers to this binding point is not implemented yet.
UNIMPLEMENTED();
}
break; break;
default: default:
UNREACHABLE(); UNREACHABLE();
......
...@@ -149,6 +149,11 @@ class Context final : public ValidationContext ...@@ -149,6 +149,11 @@ class Context final : public ValidationContext
GLuint index, GLuint index,
GLintptr offset, GLintptr offset,
GLsizeiptr size); GLsizeiptr size);
void bindGenericShaderStorageBuffer(GLuint bufferHandle);
void bindIndexedShaderStorageBuffer(GLuint bufferHandle,
GLuint index,
GLintptr offset,
GLsizeiptr size);
void bindCopyReadBuffer(GLuint bufferHandle); void bindCopyReadBuffer(GLuint bufferHandle);
void bindCopyWriteBuffer(GLuint bufferHandle); void bindCopyWriteBuffer(GLuint bufferHandle);
void bindPixelPackBuffer(GLuint bufferHandle); void bindPixelPackBuffer(GLuint bufferHandle);
......
...@@ -635,6 +635,7 @@ bool ValidationContext::getQueryParameterInfo(GLenum pname, GLenum *type, unsign ...@@ -635,6 +635,7 @@ bool ValidationContext::getQueryParameterInfo(GLenum pname, GLenum *type, unsign
case GL_MAX_COMBINED_IMAGE_UNIFORMS: case GL_MAX_COMBINED_IMAGE_UNIFORMS:
case GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS: case GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS:
case GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS: case GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS:
case GL_SHADER_STORAGE_BUFFER_BINDING:
case GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT: case GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT:
*type = GL_INT; *type = GL_INT;
*numParams = 1; *numParams = 1;
...@@ -687,6 +688,7 @@ bool ValidationContext::getIndexedQueryParameterInfo(GLenum target, ...@@ -687,6 +688,7 @@ bool ValidationContext::getIndexedQueryParameterInfo(GLenum target,
case GL_MAX_COMPUTE_WORK_GROUP_COUNT: case GL_MAX_COMPUTE_WORK_GROUP_COUNT:
case GL_MAX_COMPUTE_WORK_GROUP_SIZE: case GL_MAX_COMPUTE_WORK_GROUP_SIZE:
case GL_ATOMIC_COUNTER_BUFFER_BINDING: case GL_ATOMIC_COUNTER_BUFFER_BINDING:
case GL_SHADER_STORAGE_BUFFER_BINDING:
case GL_VERTEX_BINDING_BUFFER: case GL_VERTEX_BINDING_BUFFER:
case GL_VERTEX_BINDING_DIVISOR: case GL_VERTEX_BINDING_DIVISOR:
case GL_VERTEX_BINDING_OFFSET: case GL_VERTEX_BINDING_OFFSET:
...@@ -698,6 +700,8 @@ bool ValidationContext::getIndexedQueryParameterInfo(GLenum target, ...@@ -698,6 +700,8 @@ bool ValidationContext::getIndexedQueryParameterInfo(GLenum target,
} }
case GL_ATOMIC_COUNTER_BUFFER_START: case GL_ATOMIC_COUNTER_BUFFER_START:
case GL_ATOMIC_COUNTER_BUFFER_SIZE: case GL_ATOMIC_COUNTER_BUFFER_SIZE:
case GL_SHADER_STORAGE_BUFFER_START:
case GL_SHADER_STORAGE_BUFFER_SIZE:
{ {
*type = GL_INT_64_ANGLEX; *type = GL_INT_64_ANGLEX;
*numParams = 1; *numParams = 1;
......
...@@ -179,6 +179,7 @@ void State::initialize(const Caps &caps, ...@@ -179,6 +179,7 @@ void State::initialize(const Caps &caps,
mSamplerTextures[GL_TEXTURE_2D_MULTISAMPLE].resize(caps.maxCombinedTextureImageUnits); mSamplerTextures[GL_TEXTURE_2D_MULTISAMPLE].resize(caps.maxCombinedTextureImageUnits);
mAtomicCounterBuffers.resize(caps.maxAtomicCounterBufferBindings); mAtomicCounterBuffers.resize(caps.maxAtomicCounterBufferBindings);
mShaderStorageBuffers.resize(caps.maxShaderStorageBufferBindings);
} }
if (extensions.eglImageExternal || extensions.eglStreamConsumerExternal) if (extensions.eglImageExternal || extensions.eglStreamConsumerExternal)
{ {
...@@ -270,6 +271,12 @@ void State::reset(const Context *context) ...@@ -270,6 +271,12 @@ void State::reset(const Context *context)
buf.set(nullptr); buf.set(nullptr);
} }
mGenericShaderStorageBuffer.set(nullptr);
for (auto &buf : mShaderStorageBuffers)
{
buf.set(nullptr);
}
mProgram = NULL; mProgram = NULL;
angle::Matrix<GLfloat>::setToIdentity(mPathMatrixProj); angle::Matrix<GLfloat>::setToIdentity(mPathMatrixProj);
...@@ -1230,6 +1237,26 @@ const OffsetBindingPointer<Buffer> &State::getIndexedAtomicCounterBuffer(size_t ...@@ -1230,6 +1237,26 @@ const OffsetBindingPointer<Buffer> &State::getIndexedAtomicCounterBuffer(size_t
return mAtomicCounterBuffers[index]; return mAtomicCounterBuffers[index];
} }
void State::setGenericShaderStorageBufferBinding(Buffer *buffer)
{
mGenericShaderStorageBuffer.set(buffer);
}
void State::setIndexedShaderStorageBufferBinding(GLuint index,
Buffer *buffer,
GLintptr offset,
GLsizeiptr size)
{
ASSERT(static_cast<size_t>(index) < mShaderStorageBuffers.size());
mShaderStorageBuffers[index].set(buffer, offset, size);
}
const OffsetBindingPointer<Buffer> &State::getIndexedShaderStorageBuffer(size_t index) const
{
ASSERT(static_cast<size_t>(index) < mShaderStorageBuffers.size());
return mShaderStorageBuffers[index];
}
void State::setCopyReadBufferBinding(Buffer *buffer) void State::setCopyReadBufferBinding(Buffer *buffer)
{ {
mCopyReadBuffer.set(buffer); mCopyReadBuffer.set(buffer);
...@@ -1267,8 +1294,7 @@ Buffer *State::getTargetBuffer(GLenum target) const ...@@ -1267,8 +1294,7 @@ Buffer *State::getTargetBuffer(GLenum target) const
case GL_ATOMIC_COUNTER_BUFFER: case GL_ATOMIC_COUNTER_BUFFER:
return mGenericAtomicCounterBuffer.get(); return mGenericAtomicCounterBuffer.get();
case GL_SHADER_STORAGE_BUFFER: case GL_SHADER_STORAGE_BUFFER:
UNIMPLEMENTED(); return mGenericShaderStorageBuffer.get();
return nullptr;
case GL_DRAW_INDIRECT_BUFFER: case GL_DRAW_INDIRECT_BUFFER:
return mDrawIndirectBuffer.get(); return mDrawIndirectBuffer.get();
default: UNREACHABLE(); return NULL; default: UNREACHABLE(); return NULL;
...@@ -1277,10 +1303,10 @@ Buffer *State::getTargetBuffer(GLenum target) const ...@@ -1277,10 +1303,10 @@ Buffer *State::getTargetBuffer(GLenum target) const
void State::detachBuffer(GLuint bufferName) void State::detachBuffer(GLuint bufferName)
{ {
BindingPointer<Buffer> *buffers[] = {&mArrayBuffer, &mGenericAtomicCounterBuffer, BindingPointer<Buffer> *buffers[] = {
&mCopyReadBuffer, &mCopyWriteBuffer, &mArrayBuffer, &mGenericAtomicCounterBuffer, &mCopyReadBuffer,
&mDrawIndirectBuffer, &mPack.pixelBuffer, &mCopyWriteBuffer, &mDrawIndirectBuffer, &mPack.pixelBuffer,
&mUnpack.pixelBuffer, &mGenericUniformBuffer}; &mUnpack.pixelBuffer, &mGenericUniformBuffer, &mGenericShaderStorageBuffer};
for (auto buffer : buffers) for (auto buffer : buffers)
{ {
if (buffer->id() == bufferName) if (buffer->id() == bufferName)
...@@ -1937,6 +1963,9 @@ void State::getIntegerv(const ContextState &data, GLenum pname, GLint *params) ...@@ -1937,6 +1963,9 @@ void State::getIntegerv(const ContextState &data, GLenum pname, GLint *params)
case GL_ATOMIC_COUNTER_BUFFER_BINDING: case GL_ATOMIC_COUNTER_BUFFER_BINDING:
*params = mGenericAtomicCounterBuffer.id(); *params = mGenericAtomicCounterBuffer.id();
break; break;
case GL_SHADER_STORAGE_BUFFER_BINDING:
*params = mGenericShaderStorageBuffer.id();
break;
default: default:
UNREACHABLE(); UNREACHABLE();
break; break;
...@@ -1975,6 +2004,10 @@ void State::getIntegeri_v(GLenum target, GLuint index, GLint *data) ...@@ -1975,6 +2004,10 @@ void State::getIntegeri_v(GLenum target, GLuint index, GLint *data)
ASSERT(static_cast<size_t>(index) < mAtomicCounterBuffers.size()); ASSERT(static_cast<size_t>(index) < mAtomicCounterBuffers.size());
*data = mAtomicCounterBuffers[index].id(); *data = mAtomicCounterBuffers[index].id();
break; break;
case GL_SHADER_STORAGE_BUFFER_BINDING:
ASSERT(static_cast<size_t>(index) < mShaderStorageBuffers.size());
*data = mShaderStorageBuffers[index].id();
break;
case GL_VERTEX_BINDING_BUFFER: case GL_VERTEX_BINDING_BUFFER:
ASSERT(static_cast<size_t>(index) < mVertexArray->getMaxBindings()); ASSERT(static_cast<size_t>(index) < mVertexArray->getMaxBindings());
*data = mVertexArray->getVertexBinding(index).buffer.id(); *data = mVertexArray->getVertexBinding(index).buffer.id();
...@@ -2025,6 +2058,14 @@ void State::getInteger64i_v(GLenum target, GLuint index, GLint64 *data) ...@@ -2025,6 +2058,14 @@ void State::getInteger64i_v(GLenum target, GLuint index, GLint64 *data)
ASSERT(static_cast<size_t>(index) < mAtomicCounterBuffers.size()); ASSERT(static_cast<size_t>(index) < mAtomicCounterBuffers.size());
*data = mAtomicCounterBuffers[index].getSize(); *data = mAtomicCounterBuffers[index].getSize();
break; break;
case GL_SHADER_STORAGE_BUFFER_START:
ASSERT(static_cast<size_t>(index) < mShaderStorageBuffers.size());
*data = mShaderStorageBuffers[index].getOffset();
break;
case GL_SHADER_STORAGE_BUFFER_SIZE:
ASSERT(static_cast<size_t>(index) < mShaderStorageBuffers.size());
*data = mShaderStorageBuffers[index].getSize();
break;
default: default:
UNREACHABLE(); UNREACHABLE();
break; break;
......
...@@ -236,6 +236,14 @@ class State : angle::NonCopyable ...@@ -236,6 +236,14 @@ class State : angle::NonCopyable
GLsizeiptr size); GLsizeiptr size);
const OffsetBindingPointer<Buffer> &getIndexedAtomicCounterBuffer(size_t index) const; const OffsetBindingPointer<Buffer> &getIndexedAtomicCounterBuffer(size_t index) const;
// GL_SHADER_STORAGE_BUFFER - Both indexed and generic targets
void setGenericShaderStorageBufferBinding(Buffer *buffer);
void setIndexedShaderStorageBufferBinding(GLuint index,
Buffer *buffer,
GLintptr offset,
GLsizeiptr size);
const OffsetBindingPointer<Buffer> &getIndexedShaderStorageBuffer(size_t index) const;
// GL_COPY_[READ/WRITE]_BUFFER // GL_COPY_[READ/WRITE]_BUFFER
void setCopyReadBufferBinding(Buffer *buffer); void setCopyReadBufferBinding(Buffer *buffer);
void setCopyWriteBufferBinding(Buffer *buffer); void setCopyWriteBufferBinding(Buffer *buffer);
...@@ -499,6 +507,9 @@ class State : angle::NonCopyable ...@@ -499,6 +507,9 @@ class State : angle::NonCopyable
BindingPointer<Buffer> mGenericAtomicCounterBuffer; BindingPointer<Buffer> mGenericAtomicCounterBuffer;
BufferVector mAtomicCounterBuffers; BufferVector mAtomicCounterBuffers;
BindingPointer<Buffer> mGenericShaderStorageBuffer;
BufferVector mShaderStorageBuffers;
BindingPointer<Buffer> mCopyReadBuffer; BindingPointer<Buffer> mCopyReadBuffer;
BindingPointer<Buffer> mCopyWriteBuffer; BindingPointer<Buffer> mCopyWriteBuffer;
......
...@@ -1331,8 +1331,22 @@ static bool ValidateBindBufferCommon(Context *context, ...@@ -1331,8 +1331,22 @@ static bool ValidateBindBufferCommon(Context *context,
{ {
if (context->getClientVersion() < ES_3_1) if (context->getClientVersion() < ES_3_1)
{ {
context->handleError(
Error(GL_INVALID_ENUM, "SHADER_STORAGE_BUFFER is not supported in GLES3."));
return false;
}
if (index >= caps.maxShaderStorageBufferBindings)
{
context->handleError(Error(GL_INVALID_VALUE,
"index is greater than or equal to the number of "
"SHADER_STORAGE_BUFFER indexed binding points."));
return false;
}
if (buffer != 0 && (offset % caps.shaderStorageBufferOffsetAlignment) != 0)
{
context->handleError(Error( context->handleError(Error(
GL_INVALID_ENUM, "ATOMIC_COUNTER_BUFFER is not supported before GLES 3.1")); GL_INVALID_VALUE,
"offset must be multiple of value of SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT."));
return false; return false;
} }
break; break;
...@@ -2043,6 +2057,25 @@ bool ValidateIndexedStateQuery(ValidationContext *context, ...@@ -2043,6 +2057,25 @@ bool ValidateIndexedStateQuery(ValidationContext *context,
} }
break; break;
case GL_SHADER_STORAGE_BUFFER_START:
case GL_SHADER_STORAGE_BUFFER_SIZE:
case GL_SHADER_STORAGE_BUFFER_BINDING:
if (context->getClientVersion() < ES_3_1)
{
context->handleError(
Error(GL_INVALID_ENUM,
"Shader storage buffers are not supported in this version of GL"));
return false;
}
if (index >= caps.maxShaderStorageBufferBindings)
{
context->handleError(
Error(GL_INVALID_VALUE,
"index is outside the valid range for GL_SHADER_STORAGE_BUFFER_BINDING"));
return false;
}
break;
case GL_VERTEX_BINDING_BUFFER: case GL_VERTEX_BINDING_BUFFER:
case GL_VERTEX_BINDING_DIVISOR: case GL_VERTEX_BINDING_DIVISOR:
case GL_VERTEX_BINDING_OFFSET: case GL_VERTEX_BINDING_OFFSET:
...@@ -2062,7 +2095,6 @@ bool ValidateIndexedStateQuery(ValidationContext *context, ...@@ -2062,7 +2095,6 @@ bool ValidateIndexedStateQuery(ValidationContext *context,
return false; return false;
} }
break; break;
default: default:
context->handleError(Error(GL_INVALID_ENUM)); context->handleError(Error(GL_INVALID_ENUM));
return false; return false;
......
...@@ -131,6 +131,7 @@ ...@@ -131,6 +131,7 @@
1442 D3D11 : dEQP-GLES31.functional.state_query.integer.max_uniform_buffer_bindings_* = FAIL 1442 D3D11 : dEQP-GLES31.functional.state_query.integer.max_uniform_buffer_bindings_* = FAIL
1442 D3D11 : dEQP-GLES31.functional.state_query.integer.max_combined_texture_image_units_* = FAIL 1442 D3D11 : dEQP-GLES31.functional.state_query.integer.max_combined_texture_image_units_* = FAIL
1729 D3D11 : dEQP-GLES31.functional.state_query.indexed.atomic_counter_buffer_* = FAIL 1729 D3D11 : dEQP-GLES31.functional.state_query.indexed.atomic_counter_buffer_* = FAIL
1951 D3D11 : dEQP-GLES31.functional.state_query.indexed.shader_storage_buffer_* = FAIL
1442 D3D11 : dEQP-GLES31.functional.state_query.internal_format.renderbuffer.* = FAIL 1442 D3D11 : dEQP-GLES31.functional.state_query.internal_format.renderbuffer.* = FAIL
1679 D3D11 : dEQP-GLES31.functional.state_query.texture_level.texture_2d_multisample.* = FAIL 1679 D3D11 : dEQP-GLES31.functional.state_query.texture_level.texture_2d_multisample.* = FAIL
1442 D3D11 : dEQP-GLES31.functional.texture.multisample.* = FAIL 1442 D3D11 : dEQP-GLES31.functional.texture.multisample.* = FAIL
...@@ -1316,12 +1317,10 @@ ...@@ -1316,12 +1317,10 @@
1442 OPENGL D3D11 : dEQP-GLES31.functional.texture.gather.* = FAIL 1442 OPENGL D3D11 : dEQP-GLES31.functional.texture.gather.* = FAIL
1442 OPENGL D3D11 : dEQP-GLES31.functional.state_query.boolean.sample_mask_* = FAIL 1442 OPENGL D3D11 : dEQP-GLES31.functional.state_query.boolean.sample_mask_* = FAIL
1442 OPENGL D3D11 : dEQP-GLES31.functional.state_query.integer.texture_binding_2d_multisample_* = FAIL 1442 OPENGL D3D11 : dEQP-GLES31.functional.state_query.integer.texture_binding_2d_multisample_* = FAIL
1442 OPENGL D3D11 : dEQP-GLES31.functional.state_query.integer.shader_storage_buffer_binding_* = FAIL
1442 OPENGL D3D11 : dEQP-GLES31.functional.state_query.integer.dispatch_indirect_buffer_binding_* = FAIL 1442 OPENGL D3D11 : dEQP-GLES31.functional.state_query.integer.dispatch_indirect_buffer_binding_* = FAIL
1442 OPENGL D3D11 : dEQP-GLES31.functional.state_query.integer.program_pipeline_binding_* = FAIL 1442 OPENGL D3D11 : dEQP-GLES31.functional.state_query.integer.program_pipeline_binding_* = FAIL
1442 OPENGL D3D11 : dEQP-GLES31.functional.state_query.indexed.sample_mask_value_* = FAIL 1442 OPENGL D3D11 : dEQP-GLES31.functional.state_query.indexed.sample_mask_value_* = FAIL
1442 OPENGL D3D11 : dEQP-GLES31.functional.state_query.indexed.image_binding* = FAIL 1442 OPENGL D3D11 : dEQP-GLES31.functional.state_query.indexed.image_binding* = FAIL
1442 OPENGL D3D11 : dEQP-GLES31.functional.state_query.indexed.shader_storage_buffer_* = FAIL
1442 OPENGL D3D11 : dEQP-GLES31.functional.state_query.texture.texture_2d.depth_stencil_mode_* = FAIL 1442 OPENGL D3D11 : dEQP-GLES31.functional.state_query.texture.texture_2d.depth_stencil_mode_* = FAIL
1442 OPENGL D3D11 : dEQP-GLES31.functional.state_query.texture.texture_cube_map.depth_stencil_mode_* = FAIL 1442 OPENGL D3D11 : dEQP-GLES31.functional.state_query.texture.texture_cube_map.depth_stencil_mode_* = FAIL
1442 OPENGL D3D11 : dEQP-GLES31.functional.state_query.texture.texture_2d_array.depth_stencil_mode_* = FAIL 1442 OPENGL D3D11 : dEQP-GLES31.functional.state_query.texture.texture_2d_array.depth_stencil_mode_* = 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