Commit 7339308e authored by Alexandre Julliard's avatar Alexandre Julliard

gdi32: Add faster implementation of the pattern_rects primitive when we don't need an AND mask.

parent 2aff4696
...@@ -108,6 +108,13 @@ static inline void calc_rop_masks(INT rop, DWORD color, rop_mask *masks) ...@@ -108,6 +108,13 @@ static inline void calc_rop_masks(INT rop, DWORD color, rop_mask *masks)
calc_and_xor_masks( rop, color, &masks->and, &masks->xor ); calc_and_xor_masks( rop, color, &masks->and, &masks->xor );
} }
static inline BOOL rop_needs_and_mask( INT rop )
{
struct rop_codes codes;
get_rop_codes( rop, &codes );
return codes.a1 || codes.a2;
}
static inline RGBQUAD rgbquad_from_colorref(COLORREF c) static inline RGBQUAD rgbquad_from_colorref(COLORREF c)
{ {
RGBQUAD ret; RGBQUAD ret;
...@@ -1768,8 +1775,8 @@ static BOOL alloc_brush_mask_bits( dib_brush *brush ) ...@@ -1768,8 +1775,8 @@ static BOOL alloc_brush_mask_bits( dib_brush *brush )
assert(brush->masks.xor == NULL); assert(brush->masks.xor == NULL);
assert(brush->dib.stride > 0); assert(brush->dib.stride > 0);
if (!(brush->masks.and = HeapAlloc(GetProcessHeap(), 0, 2 * size))) return FALSE; if (!(brush->masks.xor = HeapAlloc(GetProcessHeap(), 0, 2 * size))) return FALSE;
brush->masks.xor = (char *)brush->masks.and + size; brush->masks.and = (char *)brush->masks.xor + size;
return TRUE; return TRUE;
} }
...@@ -1980,7 +1987,7 @@ static BOOL pattern_brush(dibdrv_physdev *pdev, dib_brush *brush, dib_info *dib, ...@@ -1980,7 +1987,7 @@ static BOOL pattern_brush(dibdrv_physdev *pdev, dib_brush *brush, dib_info *dib,
brush->rop = rop; brush->rop = rop;
} }
if(brush->masks.and == NULL) if(brush->masks.xor == NULL)
{ {
switch(brush->style) switch(brush->style)
{ {
...@@ -2005,6 +2012,7 @@ static BOOL pattern_brush(dibdrv_physdev *pdev, dib_brush *brush, dib_info *dib, ...@@ -2005,6 +2012,7 @@ static BOOL pattern_brush(dibdrv_physdev *pdev, dib_brush *brush, dib_info *dib,
ERR("Unexpected brush style %d\n", brush->style); ERR("Unexpected brush style %d\n", brush->style);
return FALSE; return FALSE;
} }
if (!rop_needs_and_mask( brush->rop )) brush->masks.and = NULL; /* ignore the and mask */
} }
GetBrushOrgEx(pdev->dev.hdc, &origin); GetBrushOrgEx(pdev->dev.hdc, &origin);
......
...@@ -836,43 +836,66 @@ static void pattern_rects_32(const dib_info *dib, int num, const RECT *rc, const ...@@ -836,43 +836,66 @@ static void pattern_rects_32(const dib_info *dib, int num, const RECT *rc, const
const dib_info *brush, const rop_mask_bits *bits) const dib_info *brush, const rop_mask_bits *bits)
{ {
DWORD *ptr, *start, *start_and, *and_ptr, *start_xor, *xor_ptr; DWORD *ptr, *start, *start_and, *and_ptr, *start_xor, *xor_ptr;
int x, y, i; int x, y, i, len, brush_x;
POINT offset; POINT offset;
for(i = 0; i < num; i++, rc++) for(i = 0; i < num; i++, rc++)
{ {
offset = calc_brush_offset(rc, brush, origin); offset = calc_brush_offset(rc, brush, origin);
start = get_pixel_ptr_32(dib, rc->left, rc->top); start = get_pixel_ptr_32(dib, rc->left, rc->top);
start_and = (DWORD*)bits->and + offset.y * brush->stride / 4;
start_xor = (DWORD*)bits->xor + offset.y * brush->stride / 4; start_xor = (DWORD*)bits->xor + offset.y * brush->stride / 4;
for(y = rc->top; y < rc->bottom; y++, start += dib->stride / 4) if (bits->and)
{ {
and_ptr = start_and + offset.x; start_and = (DWORD*)bits->and + offset.y * brush->stride / 4;
xor_ptr = start_xor + offset.x;
for(x = rc->left, ptr = start; x < rc->right; x++) for(y = rc->top; y < rc->bottom; y++, start += dib->stride / 4)
{ {
do_rop_32(ptr++, *and_ptr++, *xor_ptr++); and_ptr = start_and + offset.x;
if(and_ptr == start_and + brush->width) xor_ptr = start_xor + offset.x;
for(x = rc->left, ptr = start; x < rc->right; x++)
{ {
and_ptr = start_and; do_rop_32(ptr++, *and_ptr++, *xor_ptr++);
xor_ptr = start_xor; if(and_ptr == start_and + brush->width)
{
and_ptr = start_and;
xor_ptr = start_xor;
}
} }
}
offset.y++; offset.y++;
if(offset.y == brush->height) if(offset.y == brush->height)
{ {
start_and = bits->and; start_and = bits->and;
start_xor = bits->xor; start_xor = bits->xor;
offset.y = 0; offset.y = 0;
}
else
{
start_and += brush->stride / 4;
start_xor += brush->stride / 4;
}
} }
else }
else
{
for(y = rc->top; y < rc->bottom; y++, start += dib->stride / 4)
{ {
start_and += brush->stride / 4; for (x = rc->left, brush_x = offset.x; x < rc->right; x += len)
{
len = min( rc->right - x, brush->width - brush_x );
memcpy( start + x - rc->left, start_xor + brush_x, len * 4 );
brush_x = 0;
}
start_xor += brush->stride / 4; start_xor += brush->stride / 4;
offset.y++;
if(offset.y == brush->height)
{
start_xor = bits->xor;
offset.y = 0;
}
} }
} }
} }
...@@ -882,7 +905,7 @@ static void pattern_rects_24(const dib_info *dib, int num, const RECT *rc, const ...@@ -882,7 +905,7 @@ static void pattern_rects_24(const dib_info *dib, int num, const RECT *rc, const
const dib_info *brush, const rop_mask_bits *bits) const dib_info *brush, const rop_mask_bits *bits)
{ {
BYTE *ptr, *start, *start_and, *and_ptr, *start_xor, *xor_ptr; BYTE *ptr, *start, *start_and, *and_ptr, *start_xor, *xor_ptr;
int x, y, i; int x, y, i, len, brush_x;
POINT offset; POINT offset;
for(i = 0; i < num; i++, rc++) for(i = 0; i < num; i++, rc++)
...@@ -890,37 +913,60 @@ static void pattern_rects_24(const dib_info *dib, int num, const RECT *rc, const ...@@ -890,37 +913,60 @@ static void pattern_rects_24(const dib_info *dib, int num, const RECT *rc, const
offset = calc_brush_offset(rc, brush, origin); offset = calc_brush_offset(rc, brush, origin);
start = get_pixel_ptr_24(dib, rc->left, rc->top); start = get_pixel_ptr_24(dib, rc->left, rc->top);
start_and = (BYTE*)bits->and + offset.y * brush->stride;
start_xor = (BYTE*)bits->xor + offset.y * brush->stride; start_xor = (BYTE*)bits->xor + offset.y * brush->stride;
for(y = rc->top; y < rc->bottom; y++, start += dib->stride) if (bits->and)
{ {
and_ptr = start_and + offset.x * 3; start_and = (BYTE*)bits->and + offset.y * brush->stride;
xor_ptr = start_xor + offset.x * 3; for(y = rc->top; y < rc->bottom; y++, start += dib->stride)
for(x = rc->left, ptr = start; x < rc->right; x++)
{ {
do_rop_8(ptr++, *and_ptr++, *xor_ptr++); and_ptr = start_and + offset.x * 3;
do_rop_8(ptr++, *and_ptr++, *xor_ptr++); xor_ptr = start_xor + offset.x * 3;
do_rop_8(ptr++, *and_ptr++, *xor_ptr++);
if(and_ptr == start_and + brush->width * 3) for(x = rc->left, ptr = start; x < rc->right; x++)
{ {
and_ptr = start_and; do_rop_8(ptr++, *and_ptr++, *xor_ptr++);
xor_ptr = start_xor; do_rop_8(ptr++, *and_ptr++, *xor_ptr++);
do_rop_8(ptr++, *and_ptr++, *xor_ptr++);
if(and_ptr == start_and + brush->width * 3)
{
and_ptr = start_and;
xor_ptr = start_xor;
}
} }
}
offset.y++; offset.y++;
if(offset.y == brush->height) if(offset.y == brush->height)
{ {
start_and = bits->and; start_and = bits->and;
start_xor = bits->xor; start_xor = bits->xor;
offset.y = 0; offset.y = 0;
}
else
{
start_and += brush->stride;
start_xor += brush->stride;
}
} }
else }
else
{
for(y = rc->top; y < rc->bottom; y++, start += dib->stride)
{ {
start_and += brush->stride; for (x = rc->left, brush_x = offset.x; x < rc->right; x += len)
{
len = min( rc->right - x, brush->width - brush_x );
memcpy( start + (x - rc->left) * 3, start_xor + brush_x * 3, len * 3 );
brush_x = 0;
}
start_xor += brush->stride; start_xor += brush->stride;
offset.y++;
if(offset.y == brush->height)
{
start_xor = bits->xor;
offset.y = 0;
}
} }
} }
} }
...@@ -930,7 +976,7 @@ static void pattern_rects_16(const dib_info *dib, int num, const RECT *rc, const ...@@ -930,7 +976,7 @@ static void pattern_rects_16(const dib_info *dib, int num, const RECT *rc, const
const dib_info *brush, const rop_mask_bits *bits) const dib_info *brush, const rop_mask_bits *bits)
{ {
WORD *ptr, *start, *start_and, *and_ptr, *start_xor, *xor_ptr; WORD *ptr, *start, *start_and, *and_ptr, *start_xor, *xor_ptr;
int x, y, i; int x, y, i, len, brush_x;
POINT offset; POINT offset;
for(i = 0; i < num; i++, rc++) for(i = 0; i < num; i++, rc++)
...@@ -938,35 +984,58 @@ static void pattern_rects_16(const dib_info *dib, int num, const RECT *rc, const ...@@ -938,35 +984,58 @@ static void pattern_rects_16(const dib_info *dib, int num, const RECT *rc, const
offset = calc_brush_offset(rc, brush, origin); offset = calc_brush_offset(rc, brush, origin);
start = get_pixel_ptr_16(dib, rc->left, rc->top); start = get_pixel_ptr_16(dib, rc->left, rc->top);
start_and = (WORD*)bits->and + offset.y * brush->stride / 2;
start_xor = (WORD*)bits->xor + offset.y * brush->stride / 2; start_xor = (WORD*)bits->xor + offset.y * brush->stride / 2;
for(y = rc->top; y < rc->bottom; y++, start += dib->stride / 2) if (bits->and)
{ {
and_ptr = start_and + offset.x; start_and = (WORD*)bits->and + offset.y * brush->stride / 2;
xor_ptr = start_xor + offset.x; for(y = rc->top; y < rc->bottom; y++, start += dib->stride / 2)
for(x = rc->left, ptr = start; x < rc->right; x++)
{ {
do_rop_16(ptr++, *and_ptr++, *xor_ptr++); and_ptr = start_and + offset.x;
if(and_ptr == start_and + brush->width) xor_ptr = start_xor + offset.x;
for(x = rc->left, ptr = start; x < rc->right; x++)
{ {
and_ptr = start_and; do_rop_16(ptr++, *and_ptr++, *xor_ptr++);
xor_ptr = start_xor; if(and_ptr == start_and + brush->width)
{
and_ptr = start_and;
xor_ptr = start_xor;
}
} }
}
offset.y++; offset.y++;
if(offset.y == brush->height) if(offset.y == brush->height)
{ {
start_and = bits->and; start_and = bits->and;
start_xor = bits->xor; start_xor = bits->xor;
offset.y = 0; offset.y = 0;
}
else
{
start_and += brush->stride / 2;
start_xor += brush->stride / 2;
}
} }
else }
else
{
for(y = rc->top; y < rc->bottom; y++, start += dib->stride / 2)
{ {
start_and += brush->stride / 2; for (x = rc->left, brush_x = offset.x; x < rc->right; x += len)
{
len = min( rc->right - x, brush->width - brush_x );
memcpy( start + x - rc->left, start_xor + brush_x, len * 2 );
brush_x = 0;
}
start_xor += brush->stride / 2; start_xor += brush->stride / 2;
offset.y++;
if(offset.y == brush->height)
{
start_xor = bits->xor;
offset.y = 0;
}
} }
} }
} }
...@@ -976,7 +1045,7 @@ static void pattern_rects_8(const dib_info *dib, int num, const RECT *rc, const ...@@ -976,7 +1045,7 @@ static void pattern_rects_8(const dib_info *dib, int num, const RECT *rc, const
const dib_info *brush, const rop_mask_bits *bits) const dib_info *brush, const rop_mask_bits *bits)
{ {
BYTE *ptr, *start, *start_and, *and_ptr, *start_xor, *xor_ptr; BYTE *ptr, *start, *start_and, *and_ptr, *start_xor, *xor_ptr;
int x, y, i; int x, y, i, len, brush_x;
POINT offset; POINT offset;
for(i = 0; i < num; i++, rc++) for(i = 0; i < num; i++, rc++)
...@@ -984,35 +1053,58 @@ static void pattern_rects_8(const dib_info *dib, int num, const RECT *rc, const ...@@ -984,35 +1053,58 @@ static void pattern_rects_8(const dib_info *dib, int num, const RECT *rc, const
offset = calc_brush_offset(rc, brush, origin); offset = calc_brush_offset(rc, brush, origin);
start = get_pixel_ptr_8(dib, rc->left, rc->top); start = get_pixel_ptr_8(dib, rc->left, rc->top);
start_and = (BYTE*)bits->and + offset.y * brush->stride;
start_xor = (BYTE*)bits->xor + offset.y * brush->stride; start_xor = (BYTE*)bits->xor + offset.y * brush->stride;
for(y = rc->top; y < rc->bottom; y++, start += dib->stride) if (bits->and)
{ {
and_ptr = start_and + offset.x; start_and = (BYTE*)bits->and + offset.y * brush->stride;
xor_ptr = start_xor + offset.x; for(y = rc->top; y < rc->bottom; y++, start += dib->stride)
for(x = rc->left, ptr = start; x < rc->right; x++)
{ {
do_rop_8(ptr++, *and_ptr++, *xor_ptr++); and_ptr = start_and + offset.x;
if(and_ptr == start_and + brush->width) xor_ptr = start_xor + offset.x;
for(x = rc->left, ptr = start; x < rc->right; x++)
{ {
and_ptr = start_and; do_rop_8(ptr++, *and_ptr++, *xor_ptr++);
xor_ptr = start_xor; if(and_ptr == start_and + brush->width)
{
and_ptr = start_and;
xor_ptr = start_xor;
}
} }
}
offset.y++; offset.y++;
if(offset.y == brush->height) if(offset.y == brush->height)
{ {
start_and = bits->and; start_and = bits->and;
start_xor = bits->xor; start_xor = bits->xor;
offset.y = 0; offset.y = 0;
}
else
{
start_and += brush->stride;
start_xor += brush->stride;
}
} }
else }
else
{
for(y = rc->top; y < rc->bottom; y++, start += dib->stride)
{ {
start_and += brush->stride; for (x = rc->left, brush_x = offset.x; x < rc->right; x += len)
{
len = min( rc->right - x, brush->width - brush_x );
memcpy( start + x - rc->left, start_xor + brush_x, len );
brush_x = 0;
}
start_xor += brush->stride; start_xor += brush->stride;
offset.y++;
if(offset.y == brush->height)
{
start_xor = bits->xor;
offset.y = 0;
}
} }
} }
} }
...@@ -1032,69 +1124,119 @@ static void pattern_rects_4(const dib_info *dib, int num, const RECT *rc, const ...@@ -1032,69 +1124,119 @@ static void pattern_rects_4(const dib_info *dib, int num, const RECT *rc, const
right = dib->rect.left + rc->right; right = dib->rect.left + rc->right;
start = get_pixel_ptr_4(dib, rc->left, rc->top); start = get_pixel_ptr_4(dib, rc->left, rc->top);
start_and = (BYTE*)bits->and + offset.y * brush->stride;
start_xor = (BYTE*)bits->xor + offset.y * brush->stride; start_xor = (BYTE*)bits->xor + offset.y * brush->stride;
for(y = rc->top; y < rc->bottom; y++, start += dib->stride) if (bits->and)
{ {
INT brush_x = offset.x; start_and = (BYTE*)bits->and + offset.y * brush->stride;
BYTE byte_and, byte_xor; for(y = rc->top; y < rc->bottom; y++, start += dib->stride)
{
INT brush_x = offset.x;
BYTE byte_and, byte_xor;
and_ptr = start_and + brush_x / 2; and_ptr = start_and + brush_x / 2;
xor_ptr = start_xor + brush_x / 2; xor_ptr = start_xor + brush_x / 2;
for(x = left, ptr = start; x < right; x++) for(x = left, ptr = start; x < right; x++)
{
/* FIXME: Two pixels at a time */
if(x & 1) /* lower dst nibble */
{ {
if(brush_x & 1) /* lower pat nibble */ /* FIXME: Two pixels at a time */
if(x & 1) /* lower dst nibble */
{
if(brush_x & 1) /* lower pat nibble */
{
byte_and = *and_ptr++ | 0xf0;
byte_xor = *xor_ptr++ & 0x0f;
}
else /* upper pat nibble */
{
byte_and = (*and_ptr >> 4) | 0xf0;
byte_xor = (*xor_ptr >> 4) & 0x0f;
}
}
else /* upper dst nibble */
{ {
byte_and = *and_ptr++ | 0xf0; if(brush_x & 1) /* lower pat nibble */
byte_xor = *xor_ptr++ & 0x0f; {
byte_and = (*and_ptr++ << 4) | 0x0f;
byte_xor = (*xor_ptr++ << 4) & 0xf0;
}
else /* upper pat nibble */
{
byte_and = *and_ptr | 0x0f;
byte_xor = *xor_ptr & 0xf0;
}
} }
else /* upper pat nibble */ do_rop_8(ptr, byte_and, byte_xor);
if(x & 1) ptr++;
if(++brush_x == brush->width)
{ {
byte_and = (*and_ptr >> 4) | 0xf0; brush_x = 0;
byte_xor = (*xor_ptr >> 4) & 0x0f; and_ptr = start_and;
xor_ptr = start_xor;
} }
} }
else /* upper dst nibble */
offset.y++;
if(offset.y == brush->height)
{
start_and = bits->and;
start_xor = bits->xor;
offset.y = 0;
}
else
{ {
if(brush_x & 1) /* lower pat nibble */ start_and += brush->stride;
start_xor += brush->stride;
}
}
}
else
{
for(y = rc->top; y < rc->bottom; y++, start += dib->stride)
{
INT brush_x = offset.x;
BYTE byte_xor;
xor_ptr = start_xor + brush_x / 2;
for(x = left, ptr = start; x < right; x++)
{
/* FIXME: Two pixels at a time */
if(x & 1) /* lower dst nibble */
{ {
byte_and = (*and_ptr++ << 4) | 0x0f; if(brush_x & 1) /* lower pat nibble */
byte_xor = (*xor_ptr++ << 4) & 0xf0; byte_xor = *xor_ptr++ & 0x0f;
else /* upper pat nibble */
byte_xor = (*xor_ptr >> 4) & 0x0f;
do_rop_8(ptr, 0xf0, byte_xor);
} }
else /* upper pat nibble */ else /* upper dst nibble */
{ {
byte_and = *and_ptr | 0x0f; if(brush_x & 1) /* lower pat nibble */
byte_xor = *xor_ptr & 0xf0; byte_xor = (*xor_ptr++ << 4) & 0xf0;
else /* upper pat nibble */
byte_xor = *xor_ptr & 0xf0;
do_rop_8(ptr, 0x0f, byte_xor);
} }
}
do_rop_8(ptr, byte_and, byte_xor);
if(x & 1) ptr++; if(x & 1) ptr++;
if(++brush_x == brush->width) if(++brush_x == brush->width)
{ {
brush_x = 0; brush_x = 0;
and_ptr = start_and; xor_ptr = start_xor;
xor_ptr = start_xor; }
} }
}
offset.y++;
if(offset.y == brush->height)
{
start_and = bits->and;
start_xor = bits->xor;
offset.y = 0;
}
else
{
start_and += brush->stride;
start_xor += brush->stride; start_xor += brush->stride;
offset.y++;
if(offset.y == brush->height)
{
start_xor = bits->xor;
offset.y = 0;
}
} }
} }
} }
...@@ -1114,53 +1256,90 @@ static void pattern_rects_1(const dib_info *dib, int num, const RECT *rc, const ...@@ -1114,53 +1256,90 @@ static void pattern_rects_1(const dib_info *dib, int num, const RECT *rc, const
right = dib->rect.left + rc->right; right = dib->rect.left + rc->right;
start = get_pixel_ptr_1(dib, rc->left, rc->top); start = get_pixel_ptr_1(dib, rc->left, rc->top);
start_and = (BYTE*)bits->and + offset.y * brush->stride;
start_xor = (BYTE*)bits->xor + offset.y * brush->stride; start_xor = (BYTE*)bits->xor + offset.y * brush->stride;
for(y = rc->top; y < rc->bottom; y++, start += dib->stride) if (bits->and)
{ {
INT brush_x = offset.x; start_and = (BYTE*)bits->and + offset.y * brush->stride;
BYTE byte_and, byte_xor; for(y = rc->top; y < rc->bottom; y++, start += dib->stride)
{
INT brush_x = offset.x;
BYTE byte_and, byte_xor;
and_ptr = start_and + brush_x / 8; and_ptr = start_and + brush_x / 8;
xor_ptr = start_xor + brush_x / 8; xor_ptr = start_xor + brush_x / 8;
for(x = left, ptr = start; x < right; x++) for(x = left, ptr = start; x < right; x++)
{ {
byte_and = (*and_ptr & pixel_masks_1[brush_x % 8]) ? 0xff : 0; byte_and = (*and_ptr & pixel_masks_1[brush_x % 8]) ? 0xff : 0;
byte_and |= ~pixel_masks_1[x % 8]; byte_and |= ~pixel_masks_1[x % 8];
byte_xor = (*xor_ptr & pixel_masks_1[brush_x % 8]) ? 0xff : 0; byte_xor = (*xor_ptr & pixel_masks_1[brush_x % 8]) ? 0xff : 0;
byte_xor &= pixel_masks_1[x % 8]; byte_xor &= pixel_masks_1[x % 8];
do_rop_8(ptr, byte_and, byte_xor); do_rop_8(ptr, byte_and, byte_xor);
if((x & 7) == 7) ptr++; if((x & 7) == 7) ptr++;
if((brush_x & 7) == 7) if((brush_x & 7) == 7)
{ {
and_ptr++; and_ptr++;
xor_ptr++; xor_ptr++;
}
if(++brush_x == brush->width)
{
brush_x = 0;
and_ptr = start_and;
xor_ptr = start_xor;
}
} }
if(++brush_x == brush->width) offset.y++;
if(offset.y == brush->height)
{ {
brush_x = 0; start_and = bits->and;
and_ptr = start_and; start_xor = bits->xor;
xor_ptr = start_xor; offset.y = 0;
}
else
{
start_and += brush->stride;
start_xor += brush->stride;
} }
} }
}
offset.y++; else
if(offset.y == brush->height) {
{ for(y = rc->top; y < rc->bottom; y++, start += dib->stride)
start_and = bits->and;
start_xor = bits->xor;
offset.y = 0;
}
else
{ {
start_and += brush->stride; INT brush_x = offset.x;
xor_ptr = start_xor + brush_x / 8;
for(x = left, ptr = start; x < right; x++)
{
BYTE byte_xor = (*xor_ptr & pixel_masks_1[brush_x % 8]) ? 0xff : 0;
byte_xor &= pixel_masks_1[x % 8];
do_rop_8(ptr, ~pixel_masks_1[x % 8], byte_xor);
if((x & 7) == 7) ptr++;
if((brush_x & 7) == 7) xor_ptr++;
if(++brush_x == brush->width)
{
brush_x = 0;
xor_ptr = start_xor;
}
}
start_xor += brush->stride; start_xor += brush->stride;
offset.y++;
if(offset.y == brush->height)
{
start_xor = bits->xor;
offset.y = 0;
}
} }
} }
} }
......
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