Commit 6f1dc51b by Shahbaz Youssefi Committed by Commit Bot

Vulkan: Use a specialized macro for wrapped vulkan calls

Vulkan wrapper functions are fairly straightforward. They call the Vulkan function and return immediately. The specialized macros introduced in this commit take care of the final return for brevity of the warpper. Additionally, this commit gets rid of ANGLE_EMPTY_STATEMENT in favor of the more robust macro definition using do {} while (0). Bug: none Change-Id: Ia7c68962d1aeee2c06ef210122b345b1a2e54f08 Reviewed-on: https://chromium-review.googlesource.com/c/1262020 Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org> Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org>
parent 957e9b82
......@@ -214,7 +214,6 @@ std::ostream &FmtHex(std::ostream &os, T value)
#define ANGLE_TRACE_ENABLED
#endif
#define ANGLE_EMPTY_STATEMENT for (;;) break
#if !defined(NDEBUG) || defined(ANGLE_ENABLE_RELEASE_ASSERTS)
#define ANGLE_ENABLE_ASSERTS
#endif
......@@ -281,34 +280,34 @@ std::ostream &FmtHex(std::ostream &os, T value)
#if defined(ANGLE_TRACE_ENABLED) || defined(ANGLE_ENABLE_ASSERTS)
#define UNIMPLEMENTED() \
do \
{ \
WARN() << "\t! Unimplemented: " << __FUNCTION__ << "(" << __FILE__ << ":" << __LINE__ \
<< ")"; \
ASSERT(NOASSERT_UNIMPLEMENTED); \
} \
ANGLE_EMPTY_STATEMENT
} while (0)
// A macro for code which is not expected to be reached under valid assumptions
#define UNREACHABLE() \
do \
{ \
ERR() << "\t! Unreachable reached: " << __FUNCTION__ << "(" << __FILE__ << ":" << __LINE__ \
<< ")"; \
ASSERT(false); \
} \
ANGLE_EMPTY_STATEMENT
} while (0)
#else
#define UNIMPLEMENTED() \
do \
{ \
ASSERT(NOASSERT_UNIMPLEMENTED); \
} \
ANGLE_EMPTY_STATEMENT
} while (0)
// A macro for code which is not expected to be reached under valid assumptions
#define UNREACHABLE() \
do \
{ \
ASSERT(false); \
} \
ANGLE_EMPTY_STATEMENT
} while (0)
#endif // defined(ANGLE_TRACE_ENABLED) || defined(ANGLE_ENABLE_ASSERTS)
#if defined(ANGLE_PLATFORM_WINDOWS)
......
......@@ -191,20 +191,21 @@ inline Error NoError()
#define ANGLE_LOCAL_VAR ANGLE_CONCAT2(_localVar, __LINE__)
#define ANGLE_TRY_TEMPLATE(EXPR, FUNC) \
do \
{ \
auto ANGLE_LOCAL_VAR = EXPR; \
if (ANGLE_UNLIKELY(ANGLE_LOCAL_VAR.isError())) \
{ \
FUNC(ANGLE_LOCAL_VAR); \
} \
} \
ANGLE_EMPTY_STATEMENT
} while (0)
#define ANGLE_RETURN(X) return X;
#define ANGLE_TRY(EXPR) ANGLE_TRY_TEMPLATE(EXPR, ANGLE_RETURN);
// TODO(jmadill): Remove this once refactor is complete. http://anglebug.com/2491
#define ANGLE_TRY_HANDLE(CONTEXT, EXPR) \
do \
{ \
auto ANGLE_LOCAL_VAR = (EXPR); \
if (ANGLE_UNLIKELY(ANGLE_LOCAL_VAR.isError())) \
......@@ -212,19 +213,18 @@ inline Error NoError()
CONTEXT->handleError(ANGLE_LOCAL_VAR); \
return angle::Result::Stop(); \
} \
} \
ANGLE_EMPTY_STATEMENT
} while (0)
// TODO(jmadill): Introduce way to store errors to a const Context. http://anglebug.com/2491
#define ANGLE_SWALLOW_ERR(EXPR) \
do \
{ \
auto ANGLE_LOCAL_VAR = EXPR; \
if (ANGLE_UNLIKELY(ANGLE_LOCAL_VAR.isError())) \
{ \
ERR() << "Unhandled internal error: " << ANGLE_LOCAL_VAR; \
} \
} \
ANGLE_EMPTY_STATEMENT
} while (0)
#undef ANGLE_LOCAL_VAR
#undef ANGLE_CONCAT2
......
......@@ -316,9 +316,8 @@ void CommandPool::destroy(VkDevice device)
angle::Result CommandPool::init(Context *context, const VkCommandPoolCreateInfo &createInfo)
{
ASSERT(!valid());
ANGLE_VK_TRY(context,
vkCreateCommandPool(context->getDevice(), &createInfo, nullptr, &mHandle));
return angle::Result::Continue();
ANGLE_VK_TRY_RETURN(context,
vkCreateCommandPool(context->getDevice(), &createInfo, nullptr, &mHandle));
}
// CommandBuffer implementation.
......@@ -336,8 +335,8 @@ VkCommandBuffer CommandBuffer::releaseHandle()
angle::Result CommandBuffer::init(Context *context, const VkCommandBufferAllocateInfo &createInfo)
{
ASSERT(!valid());
ANGLE_VK_TRY(context, vkAllocateCommandBuffers(context->getDevice(), &createInfo, &mHandle));
return angle::Result::Continue();
ANGLE_VK_TRY_RETURN(context,
vkAllocateCommandBuffers(context->getDevice(), &createInfo, &mHandle));
}
void CommandBuffer::blitImage(const Image &srcImage,
......@@ -356,22 +355,19 @@ void CommandBuffer::blitImage(const Image &srcImage,
angle::Result CommandBuffer::begin(Context *context, const VkCommandBufferBeginInfo &info)
{
ASSERT(valid());
ANGLE_VK_TRY(context, vkBeginCommandBuffer(mHandle, &info));
return angle::Result::Continue();
ANGLE_VK_TRY_RETURN(context, vkBeginCommandBuffer(mHandle, &info));
}
angle::Result CommandBuffer::end(Context *context)
{
ASSERT(valid());
ANGLE_VK_TRY(context, vkEndCommandBuffer(mHandle));
return angle::Result::Continue();
ANGLE_VK_TRY_RETURN(context, vkEndCommandBuffer(mHandle));
}
angle::Result CommandBuffer::reset(Context *context)
{
ASSERT(valid());
ANGLE_VK_TRY(context, vkResetCommandBuffer(mHandle, 0));
return angle::Result::Continue();
ANGLE_VK_TRY_RETURN(context, vkResetCommandBuffer(mHandle, 0));
}
void CommandBuffer::pipelineBarrier(VkPipelineStageFlags srcStageMask,
......@@ -614,8 +610,8 @@ void Image::destroy(VkDevice device)
angle::Result Image::init(Context *context, const VkImageCreateInfo &createInfo)
{
ASSERT(!valid());
ANGLE_VK_TRY(context, vkCreateImage(context->getDevice(), &createInfo, nullptr, &mHandle));
return angle::Result::Continue();
ANGLE_VK_TRY_RETURN(context,
vkCreateImage(context->getDevice(), &createInfo, nullptr, &mHandle));
}
void Image::getMemoryRequirements(VkDevice device, VkMemoryRequirements *requirementsOut) const
......@@ -627,9 +623,8 @@ void Image::getMemoryRequirements(VkDevice device, VkMemoryRequirements *require
angle::Result Image::bindMemory(Context *context, const vk::DeviceMemory &deviceMemory)
{
ASSERT(valid() && deviceMemory.valid());
ANGLE_VK_TRY(context,
vkBindImageMemory(context->getDevice(), mHandle, deviceMemory.getHandle(), 0));
return angle::Result::Continue();
ANGLE_VK_TRY_RETURN(
context, vkBindImageMemory(context->getDevice(), mHandle, deviceMemory.getHandle(), 0));
}
void Image::getSubresourceLayout(VkDevice device,
......@@ -662,8 +657,8 @@ void ImageView::destroy(VkDevice device)
angle::Result ImageView::init(Context *context, const VkImageViewCreateInfo &createInfo)
{
ANGLE_VK_TRY(context, vkCreateImageView(context->getDevice(), &createInfo, nullptr, &mHandle));
return angle::Result::Continue();
ANGLE_VK_TRY_RETURN(context,
vkCreateImageView(context->getDevice(), &createInfo, nullptr, &mHandle));
}
// Semaphore implementation.
......@@ -688,10 +683,8 @@ angle::Result Semaphore::init(Context *context)
semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
semaphoreInfo.flags = 0;
ANGLE_VK_TRY(context,
vkCreateSemaphore(context->getDevice(), &semaphoreInfo, nullptr, &mHandle));
return angle::Result::Continue();
ANGLE_VK_TRY_RETURN(context,
vkCreateSemaphore(context->getDevice(), &semaphoreInfo, nullptr, &mHandle));
}
// Framebuffer implementation.
......@@ -711,9 +704,8 @@ void Framebuffer::destroy(VkDevice device)
angle::Result Framebuffer::init(Context *context, const VkFramebufferCreateInfo &createInfo)
{
ASSERT(!valid());
ANGLE_VK_TRY(context,
vkCreateFramebuffer(context->getDevice(), &createInfo, nullptr, &mHandle));
return angle::Result::Continue();
ANGLE_VK_TRY_RETURN(context,
vkCreateFramebuffer(context->getDevice(), &createInfo, nullptr, &mHandle));
}
void Framebuffer::setHandle(VkFramebuffer handle)
......@@ -738,8 +730,8 @@ void DeviceMemory::destroy(VkDevice device)
angle::Result DeviceMemory::allocate(Context *context, const VkMemoryAllocateInfo &allocInfo)
{
ASSERT(!valid());
ANGLE_VK_TRY(context, vkAllocateMemory(context->getDevice(), &allocInfo, nullptr, &mHandle));
return angle::Result::Continue();
ANGLE_VK_TRY_RETURN(context,
vkAllocateMemory(context->getDevice(), &allocInfo, nullptr, &mHandle));
}
angle::Result DeviceMemory::map(Context *context,
......@@ -749,9 +741,8 @@ angle::Result DeviceMemory::map(Context *context,
uint8_t **mapPointer) const
{
ASSERT(valid());
ANGLE_VK_TRY(context, vkMapMemory(context->getDevice(), mHandle, offset, size, flags,
reinterpret_cast<void **>(mapPointer)));
return angle::Result::Continue();
ANGLE_VK_TRY_RETURN(context, vkMapMemory(context->getDevice(), mHandle, offset, size, flags,
reinterpret_cast<void **>(mapPointer)));
}
void DeviceMemory::unmap(VkDevice device) const
......@@ -777,8 +768,8 @@ void RenderPass::destroy(VkDevice device)
angle::Result RenderPass::init(Context *context, const VkRenderPassCreateInfo &createInfo)
{
ASSERT(!valid());
ANGLE_VK_TRY(context, vkCreateRenderPass(context->getDevice(), &createInfo, nullptr, &mHandle));
return angle::Result::Continue();
ANGLE_VK_TRY_RETURN(context,
vkCreateRenderPass(context->getDevice(), &createInfo, nullptr, &mHandle));
}
// Buffer implementation.
......@@ -798,16 +789,15 @@ void Buffer::destroy(VkDevice device)
angle::Result Buffer::init(Context *context, const VkBufferCreateInfo &createInfo)
{
ASSERT(!valid());
ANGLE_VK_TRY(context, vkCreateBuffer(context->getDevice(), &createInfo, nullptr, &mHandle));
return angle::Result::Continue();
ANGLE_VK_TRY_RETURN(context,
vkCreateBuffer(context->getDevice(), &createInfo, nullptr, &mHandle));
}
angle::Result Buffer::bindMemory(Context *context, const DeviceMemory &deviceMemory)
{
ASSERT(valid() && deviceMemory.valid());
ANGLE_VK_TRY(context,
vkBindBufferMemory(context->getDevice(), mHandle, deviceMemory.getHandle(), 0));
return angle::Result::Continue();
ANGLE_VK_TRY_RETURN(
context, vkBindBufferMemory(context->getDevice(), mHandle, deviceMemory.getHandle(), 0));
}
void Buffer::getMemoryRequirements(VkDevice device, VkMemoryRequirements *memoryRequirementsOut)
......@@ -833,9 +823,8 @@ void ShaderModule::destroy(VkDevice device)
angle::Result ShaderModule::init(Context *context, const VkShaderModuleCreateInfo &createInfo)
{
ASSERT(!valid());
ANGLE_VK_TRY(context,
vkCreateShaderModule(context->getDevice(), &createInfo, nullptr, &mHandle));
return angle::Result::Continue();
ANGLE_VK_TRY_RETURN(context,
vkCreateShaderModule(context->getDevice(), &createInfo, nullptr, &mHandle));
}
// PipelineLayout implementation.
......@@ -855,9 +844,8 @@ void PipelineLayout::destroy(VkDevice device)
angle::Result PipelineLayout::init(Context *context, const VkPipelineLayoutCreateInfo &createInfo)
{
ASSERT(!valid());
ANGLE_VK_TRY(context,
vkCreatePipelineLayout(context->getDevice(), &createInfo, nullptr, &mHandle));
return angle::Result::Continue();
ANGLE_VK_TRY_RETURN(
context, vkCreatePipelineLayout(context->getDevice(), &createInfo, nullptr, &mHandle));
}
// PipelineCache implementation.
......@@ -879,9 +867,8 @@ angle::Result PipelineCache::init(Context *context, const VkPipelineCacheCreateI
ASSERT(!valid());
// Note: if we are concerned with memory usage of this cache, we should give it custom
// allocators. Also, failure of this function is of little importance.
ANGLE_VK_TRY(context,
vkCreatePipelineCache(context->getDevice(), &createInfo, nullptr, &mHandle));
return angle::Result::Continue();
ANGLE_VK_TRY_RETURN(
context, vkCreatePipelineCache(context->getDevice(), &createInfo, nullptr, &mHandle));
}
angle::Result PipelineCache::getCacheData(Context *context, size_t *cacheSize, void *cacheData)
......@@ -894,12 +881,8 @@ angle::Result PipelineCache::getCacheData(Context *context, size_t *cacheSize, v
// VK_INCOMPLETE in the first case is an expected output. In the second case, VK_INCOMPLETE is
// also acceptable and the resulting buffer will contain valid value by spec. Angle currently
// ensures *cacheSize to be either 0 or of enough size, therefore VK_INCOMPLETE is not expected.
angle::Result result = angle::Result::Stop();
ANGLE_VK_TRY_ALLOW_INCOMPLETE(
context, vkGetPipelineCacheData(context->getDevice(), mHandle, cacheSize, cacheData),
result);
return result;
ANGLE_VK_TRY_RETURN_ALLOW_INCOMPLETE(
context, vkGetPipelineCacheData(context->getDevice(), mHandle, cacheSize, cacheData));
}
// Pipeline implementation.
......@@ -921,10 +904,9 @@ angle::Result Pipeline::initGraphics(Context *context,
const PipelineCache &pipelineCacheVk)
{
ASSERT(!valid());
ANGLE_VK_TRY(context,
vkCreateGraphicsPipelines(context->getDevice(), pipelineCacheVk.getHandle(), 1,
ANGLE_VK_TRY_RETURN(
context, vkCreateGraphicsPipelines(context->getDevice(), pipelineCacheVk.getHandle(), 1,
&createInfo, nullptr, &mHandle));
return angle::Result::Continue();
}
// DescriptorSetLayout implementation.
......@@ -945,9 +927,8 @@ angle::Result DescriptorSetLayout::init(Context *context,
const VkDescriptorSetLayoutCreateInfo &createInfo)
{
ASSERT(!valid());
ANGLE_VK_TRY(context,
vkCreateDescriptorSetLayout(context->getDevice(), &createInfo, nullptr, &mHandle));
return angle::Result::Continue();
ANGLE_VK_TRY_RETURN(
context, vkCreateDescriptorSetLayout(context->getDevice(), &createInfo, nullptr, &mHandle));
}
// DescriptorPool implementation.
......@@ -967,9 +948,8 @@ void DescriptorPool::destroy(VkDevice device)
angle::Result DescriptorPool::init(Context *context, const VkDescriptorPoolCreateInfo &createInfo)
{
ASSERT(!valid());
ANGLE_VK_TRY(context,
vkCreateDescriptorPool(context->getDevice(), &createInfo, nullptr, &mHandle));
return angle::Result::Continue();
ANGLE_VK_TRY_RETURN(
context, vkCreateDescriptorPool(context->getDevice(), &createInfo, nullptr, &mHandle));
}
angle::Result DescriptorPool::allocateDescriptorSets(Context *context,
......@@ -977,9 +957,8 @@ angle::Result DescriptorPool::allocateDescriptorSets(Context *context,
VkDescriptorSet *descriptorSetsOut)
{
ASSERT(valid());
ANGLE_VK_TRY(context,
vkAllocateDescriptorSets(context->getDevice(), &allocInfo, descriptorSetsOut));
return angle::Result::Continue();
ANGLE_VK_TRY_RETURN(
context, vkAllocateDescriptorSets(context->getDevice(), &allocInfo, descriptorSetsOut));
}
angle::Result DescriptorPool::freeDescriptorSets(Context *context,
......@@ -988,9 +967,8 @@ angle::Result DescriptorPool::freeDescriptorSets(Context *context,
{
ASSERT(valid());
ASSERT(descriptorSetCount > 0);
ANGLE_VK_TRY(context, vkFreeDescriptorSets(context->getDevice(), mHandle, descriptorSetCount,
descriptorSets));
return angle::Result::Continue();
ANGLE_VK_TRY_RETURN(context, vkFreeDescriptorSets(context->getDevice(), mHandle,
descriptorSetCount, descriptorSets));
}
// Sampler implementation.
......@@ -1010,8 +988,8 @@ void Sampler::destroy(VkDevice device)
angle::Result Sampler::init(Context *context, const VkSamplerCreateInfo &createInfo)
{
ASSERT(!valid());
ANGLE_VK_TRY(context, vkCreateSampler(context->getDevice(), &createInfo, nullptr, &mHandle));
return angle::Result::Continue();
ANGLE_VK_TRY_RETURN(context,
vkCreateSampler(context->getDevice(), &createInfo, nullptr, &mHandle));
}
// Fence implementation.
......@@ -1031,8 +1009,8 @@ void Fence::destroy(VkDevice device)
angle::Result Fence::init(Context *context, const VkFenceCreateInfo &createInfo)
{
ASSERT(!valid());
ANGLE_VK_TRY(context, vkCreateFence(context->getDevice(), &createInfo, nullptr, &mHandle));
return angle::Result::Continue();
ANGLE_VK_TRY_RETURN(context,
vkCreateFence(context->getDevice(), &createInfo, nullptr, &mHandle));
}
VkResult Fence::getStatus(VkDevice device) const
......@@ -1144,8 +1122,8 @@ void QueryPool::destroy(VkDevice device)
angle::Result QueryPool::init(Context *context, const VkQueryPoolCreateInfo &createInfo)
{
ASSERT(!valid());
ANGLE_VK_TRY(context, vkCreateQueryPool(context->getDevice(), &createInfo, nullptr, &mHandle));
return angle::Result::Continue();
ANGLE_VK_TRY_RETURN(context,
vkCreateQueryPool(context->getDevice(), &createInfo, nullptr, &mHandle));
}
angle::Result QueryPool::getResults(Context *context,
......@@ -1156,13 +1134,9 @@ angle::Result QueryPool::getResults(Context *context,
VkDeviceSize stride,
VkQueryResultFlags flags) const
{
angle::Result result = angle::Result::Stop();
ANGLE_VK_TRY_ALLOW_NOT_READY(context,
vkGetQueryPoolResults(context->getDevice(), mHandle, firstQuery,
queryCount, dataSize, data, stride, flags),
result);
return result;
ANGLE_VK_TRY_RETURN_ALLOW_NOT_READY(
context, vkGetQueryPoolResults(context->getDevice(), mHandle, firstQuery, queryCount,
dataSize, data, stride, flags));
}
angle::Result AllocateBufferMemory(vk::Context *context,
......
......@@ -860,6 +860,7 @@ VkColorComponentFlags GetColorComponentFlags(bool red, bool green, bool blue, bo
} // namespace rx
#define ANGLE_VK_TRY(context, command) \
do \
{ \
auto ANGLE_LOCAL_VAR = command; \
if (ANGLE_UNLIKELY(ANGLE_LOCAL_VAR != VK_SUCCESS)) \
......@@ -867,10 +868,27 @@ VkColorComponentFlags GetColorComponentFlags(bool red, bool green, bool blue, bo
context->handleError(ANGLE_LOCAL_VAR, __FILE__, __LINE__); \
return angle::Result::Stop(); \
} \
} \
ANGLE_EMPTY_STATEMENT
} while (0)
#define ANGLE_VK_TRY_ALLOW_OTHER(context, command, acceptable, result) \
#define ANGLE_VK_CHECK(context, test, error) ANGLE_VK_TRY(context, test ? VK_SUCCESS : error)
#define ANGLE_VK_CHECK_MATH(context, result) \
ANGLE_VK_CHECK(context, result, VK_ERROR_VALIDATION_FAILED_EXT)
#define ANGLE_VK_CHECK_ALLOC(context, result) \
ANGLE_VK_CHECK(context, result, VK_ERROR_OUT_OF_HOST_MEMORY)
// Macros specifically made for vulkan wrappers (in vk_utils.h) that execute the call and return
// appropriately.
#define ANGLE_VK_TRY_RETURN(context, command) \
do \
{ \
ANGLE_VK_TRY(context, command); \
return angle::Result::Continue(); \
} while (0)
#define ANGLE_VK_TRY_RETURN_ALLOW_OTHER(context, command, acceptable) \
do \
{ \
auto ANGLE_LOCAL_VAR = command; \
if (ANGLE_UNLIKELY(ANGLE_LOCAL_VAR != VK_SUCCESS && ANGLE_LOCAL_VAR != acceptable)) \
......@@ -878,23 +896,14 @@ VkColorComponentFlags GetColorComponentFlags(bool red, bool green, bool blue, bo
context->handleError(ANGLE_LOCAL_VAR, __FILE__, __LINE__); \
return angle::Result::Stop(); \
} \
result = ANGLE_LOCAL_VAR == VK_SUCCESS ? angle::Result::Continue() \
: angle::Result::Incomplete(); \
} \
ANGLE_EMPTY_STATEMENT
return ANGLE_LOCAL_VAR == VK_SUCCESS ? angle::Result::Continue() \
: angle::Result::Incomplete(); \
} while (0)
#define ANGLE_VK_TRY_ALLOW_INCOMPLETE(context, command, result) \
ANGLE_VK_TRY_ALLOW_OTHER(context, command, VK_INCOMPLETE, result)
#define ANGLE_VK_TRY_RETURN_ALLOW_INCOMPLETE(context, command) \
ANGLE_VK_TRY_RETURN_ALLOW_OTHER(context, command, VK_INCOMPLETE)
#define ANGLE_VK_TRY_ALLOW_NOT_READY(context, command, result) \
ANGLE_VK_TRY_ALLOW_OTHER(context, command, VK_NOT_READY, result)
#define ANGLE_VK_CHECK(context, test, error) ANGLE_VK_TRY(context, test ? VK_SUCCESS : error)
#define ANGLE_VK_CHECK_MATH(context, result) \
ANGLE_VK_CHECK(context, result, VK_ERROR_VALIDATION_FAILED_EXT)
#define ANGLE_VK_CHECK_ALLOC(context, result) \
ANGLE_VK_CHECK(context, result, VK_ERROR_OUT_OF_HOST_MEMORY)
#define ANGLE_VK_TRY_RETURN_ALLOW_NOT_READY(context, command) \
ANGLE_VK_TRY_RETURN_ALLOW_OTHER(context, command, VK_NOT_READY)
#endif // LIBANGLE_RENDERER_VULKAN_VK_UTILS_H_
......@@ -470,14 +470,15 @@ bool IsDebug();
bool IsRelease();
// Note: git cl format messes up this formatting.
#define ANGLE_SKIP_TEST_IF(COND) \
\
if (COND) \
\
{ \
std::cout << "Test skipped: " #COND "." << std::endl; \
return; \
} \
ANGLE_EMPTY_STATEMENT
#define ANGLE_SKIP_TEST_IF(COND) \
do \
{ \
if (COND) \
\
{ \
std::cout << "Test skipped: " #COND "." << std::endl; \
return; \
} \
} while (0)
#endif // ANGLE_TESTS_ANGLE_TEST_H_
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