XRender: CompositeGlyphs* requests misbehave under certain circumstances (GLAMOR?)
I observed the following strange behavior of the CompositeGlyphs32 request: It seems to completely ignore the source position (for the actual source picture) and instead align the source picture origin to the destination picture origin, and also repeat the source picture in both directions although not requested. This only happens on some systems, and so far, my suspicion is that GLAMOR triggers it, although I don't have proof for that.
Example system exposing the buggy rendering:
FreeBSD, xserver 1.21.1.16, radeon_drv 19.1.0 giving the following output:
RADEON(0): glamor X acceleration enabled on AMD SUMO (DRM 2.50.0 / 14.2-RELEASE-p2, LLVM 19.1.7)
Here's a little tool probing for the bug:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <xcb/render.h>
#include <xcb/xcb_image.h>
#include <xcb/xcb.h>
typedef struct GlpyhRenderInfo
{
uint8_t count;
uint8_t pad[3];
int16_t dx;
int16_t dy;
uint32_t glyphid;
} GlyphRenderInfo;
static int probeGlyphRenderSourcePositionGlitch(
xcb_connection_t *c, xcb_screen_t *s,
xcb_render_pictformat_t a8format)
{
/* Create a dummy alpha mask glyph set */
xcb_render_glyphset_t probeset = xcb_generate_id(c);
xcb_render_create_glyph_set(c, probeset, a8format);
/* Add one single-pixel fully opaque glyph */
xcb_render_glyphinfo_t probeglyph = { 1, 1, 0, 0, 0, 0 };
uint8_t probebitmap[] = { 0xff, 0xff, 0xff, 0xff };
uint32_t probegid = 0;
xcb_render_add_glyphs(c, probeset, 1, &probegid, &probeglyph,
4, probebitmap);
/* Create a dummy single-pixel alpha mask XRender picture
* for composite source, fill it fully opaque */
xcb_pixmap_t probepixmap = xcb_generate_id(c);
xcb_create_pixmap(c, 8, probepixmap, s->root, 1, 1);
xcb_render_picture_t probesrc = xcb_generate_id(c);
xcb_render_create_picture(c, probesrc, probepixmap, a8format, 0, 0);
xcb_free_pixmap(c, probepixmap);
xcb_rectangle_t proberect = { 0, 0, 1, 1 };
xcb_render_color_t probecolor = {0xffff, 0xffff, 0xffff, 0xffff};
xcb_render_fill_rectangles(c, XCB_RENDER_PICT_OP_SRC, probesrc,
probecolor, 1, &proberect);
/* Create another dummy single-pixel alpha mask XRender picture
* for composite destination, fill it fully transparent */
probepixmap = xcb_generate_id(c);
xcb_create_pixmap(c, 8, probepixmap, s->root, 1, 1);
xcb_render_picture_t probedst = xcb_generate_id(c);
xcb_render_create_picture(c, probedst, probepixmap, a8format, 0, 0);
memset(&probecolor, 0, sizeof probecolor);
xcb_render_fill_rectangles(c, XCB_RENDER_PICT_OP_SRC, probedst,
probecolor, 1, &proberect);
/* Composite the dummy source to the dummy destination using the
* dummy glyph as the alpha mask, with a source y coordinate of 1.
* This *should* be a no-op, because it's outside our 1-pixel
* source picture. */
GlyphRenderInfo proberender = { 1, { 0, 0, 0 }, 0, 0, 0 };
xcb_render_composite_glyphs_32(c, XCB_RENDER_PICT_OP_OVER, probesrc,
probedst, 0, probeset, 0, 1, sizeof proberender,
(const uint8_t *)&proberender);
xcb_render_free_picture(c, probedst);
xcb_render_free_picture(c, probesrc);
xcb_render_free_glyph_set(c, probeset);
/* Read the underlying pixmap of the destination picture from the
* X server */
xcb_image_t *probeimg = xcb_image_get(c, probepixmap, 0, 0, 1, 1,
0xffffffff, XCB_IMAGE_FORMAT_Z_PIXMAP);
xcb_free_pixmap(c, probepixmap);
/* If it isn't fully transparent any more, we detected our glitch. */
int haveGlitch = !!probeimg->data[0];
xcb_image_destroy(probeimg);
return haveGlitch;
}
static xcb_connection_t *setup(xcb_render_pictformat_t *a8format)
{
xcb_connection_t *c = xcb_connect(0, 0);
if (xcb_connection_has_error(c)) goto error;
xcb_render_query_version_cookie_t versioncookie =
xcb_render_query_version(c,
XCB_RENDER_MAJOR_VERSION, XCB_RENDER_MINOR_VERSION);
xcb_render_query_pict_formats_cookie_t formatscookie =
xcb_render_query_pict_formats(c);
xcb_render_query_version_reply_t *version =
xcb_render_query_version_reply(c, versioncookie, 0);
if (!version) goto error;
free(version);
xcb_render_query_pict_formats_reply_t *formats =
xcb_render_query_pict_formats_reply(c, formatscookie, 0);
if (!formats) goto error;
*a8format = 0;
for (xcb_render_pictforminfo_iterator_t i =
xcb_render_query_pict_formats_formats_iterator(formats);
i.rem;
xcb_render_pictforminfo_next(&i))
{
if (i.data->type == XCB_RENDER_PICT_TYPE_DIRECT
&& i.data->depth == 8
&& i.data->direct.red_mask == 0
&& i.data->direct.green_mask == 0
&& i.data->direct.blue_mask == 0
&& i.data->direct.alpha_mask == 255)
{
*a8format = i.data->id;
break;
}
}
free(formats);
if (!*a8format) goto error;
return c;
error:
xcb_disconnect(c);
return 0;
}
int main(void)
{
xcb_render_pictformat_t a8format = 0;
xcb_connection_t *c = setup(&a8format);
if (!c)
{
fputs("Cannot setup X11 connection with XRender.\n", stderr);
return EXIT_FAILURE;
}
xcb_screen_t *s = xcb_setup_roots_iterator(xcb_get_setup(c)).data;
if (probeGlyphRenderSourcePositionGlitch(c, s, a8format))
{
puts("Bug detected");
}
else
{
puts("Bug NOT detected");
}
xcb_disconnect(c);
return EXIT_SUCCESS;
}
compile for example with cc -obugdemo -I/usr/local/include main.c -L/usr/local/lib -lxcb -lxcb-render -lxcb-image
I don't know whether my description of the issue is correct and understandable, for seeing the actual result, you could look at https://github.com/Zirias/xmoji and make the probeGlyphRenderSourcePositionGlitch()
function in x11adapter.c
always return 0
, then run it on an affected system.