text: /*
   * kmscon - Pixman Text Renderer Backend
   *
   * Copyright (c) 2012-2013 David Herrmann <dh.herrmann@googlemail.com>
   *
   * Permission is hereby granted, free of charge, to any person obtaining
   * a copy of this software and associated documentation files
   * (the "Software"), to deal in the Software without restriction, including
   * without limitation the rights to use, copy, modify, merge, publish,
   * distribute, sublicense, and/or sell copies of the Software, and to
   * permit persons to whom the Software is furnished to do so, subject to
   * the following conditions:
   *
   * The above copyright notice and this permission notice shall be included
   * in all copies or substantial portions of the Software.
   *
   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
   * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
   * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
   * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
   * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
   * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
   */
  
  /*
   * Pixman based text renderer
   */
  
  #include <errno.h>
  #include <pixman.h>
  #include <stdbool.h>
  #include <stdlib.h>
  #include <string.h>
  #include "shl_hashtable.h"
  #include "shl_log.h"
  #include "text.h"
  #include "uterm_video.h"
  
  #define LOG_SUBSYSTEM "text_pixman"
  
  struct tp_glyph {
  	const struct kmscon_glyph *glyph;
  	pixman_image_t *surf;
  	uint8_t *data;
  };
  
  struct tp_pixman {
  	pixman_image_t *white;
  	struct shl_hashtable *glyphs;
  	struct shl_hashtable *bold_glyphs;
  
  	struct uterm_video_buffer buf[2];
  	pixman_image_t *surf[2];
  	unsigned int format[2];
  
  	bool new_stride;
  	bool use_indirect;
  	uint8_t *data[2];
  	struct uterm_video_buffer vbuf;
  
  	/* cache */
  	unsigned int cur;
  	unsigned int c_bpp;
  	uint32_t *c_data;
  	unsigned int c_stride;
  };
  
  static int tp_init(struct kmscon_text *txt)
  {
  	struct tp_pixman *tp;
  
  	tp = malloc(sizeof(*tp));
  	if (!tp)
  		return -ENOMEM;
  
  	txt->data = tp;
  	return 0;
  }
  
  static void tp_destroy(struct kmscon_text *txt)
  {
  	struct tp_pixman *tp = txt->data;
  
  	free(tp);
  }
  
  static void free_glyph(void *data)
  {
  	struct tp_glyph *glyph = data;
  
  	pixman_image_unref(glyph->surf);
  	free(glyph->data);
  	free(glyph);
  }
  
  static unsigned int format_u2p(unsigned int f)
  {
  	switch (f) {
  	case UTERM_FORMAT_XRGB32:
  		return PIXMAN_x8r8g8b8;
  	case UTERM_FORMAT_RGB16:
  		return PIXMAN_r5g6b5;
  	case UTERM_FORMAT_GREY:
  		return PIXMAN_a8;
  	default:
  		return 0;
  	}
  }
  
  static int alloc_indirect(struct kmscon_text *txt,
  			  unsigned int w, unsigned int h)
  {
  	struct tp_pixman *tp = txt->data;
  	unsigned int s, i, format;
  	int ret;
  
  	log_info("using blitting engine");
  
  	format = format_u2p(UTERM_FORMAT_XRGB32);
  	s = w * 4;
  
  	tp->data[0] = malloc(s * h);
  	tp->data[1] = malloc(s * h);
  	if (!tp->data[0] || !tp->data[1]) {
  		log_error("cannot allocate memory for render-buffer");
  		ret = -ENOMEM;
  		goto err_free;
  	}
  
  	for (i = 0; i < 2; ++i) {
  		tp->format[i] = format;
  		tp->surf[i] = pixman_image_create_bits(format, w, h,
  						       (void*)tp->data[i], s);
  		if (!tp->surf[i]) {
  			log_error("cannot create pixman surfaces");
  			goto err_pixman;
  		}
  	}
  
  	tp->vbuf.width = w;
  	tp->vbuf.height = h;
  	tp->vbuf.stride = s;
  	tp->vbuf.format = UTERM_FORMAT_XRGB32;
  	tp->use_indirect = true;
  	return 0;
  
  err_pixman:
  	if (tp->surf[1])
  		pixman_image_unref(tp->surf[1]);
  	tp->surf[1] = NULL;
  	if (tp->surf[0])
  		pixman_image_unref(tp->surf[0]);
  	tp->surf[0] = NULL;
  err_free:
  	free(tp->data[1]);
  	free(tp->data[0]);
  	tp->data[1] = NULL;
  	tp->data[0] = NULL;
  	return ret;
  }
  
  static int tp_set(struct kmscon_text *txt)
  {
  	struct tp_pixman *tp = txt->data;
  	int ret;
  	unsigned int w, h;
  	struct uterm_mode *m;
  	pixman_color_t white;
  
  	memset(tp, 0, sizeof(*tp));
  	m = uterm_display_get_current(txt->disp);
  	w = uterm_mode_get_width(m);
  	h = uterm_mode_get_height(m);
  
  	white.red = 0xffff;
  	white.green = 0xffff;
  	white.blue = 0xffff;
  	white.red = 0xffff;
  
  	tp->white = pixman_image_create_solid_fill(&white);
  	if (!tp->white) {
  		log_error("cannot create pixman solid color buffer");
  		return -ENOMEM;
  	}
  
  	ret = shl_hashtable_new(&tp->glyphs, shl_direct_hash,
  				shl_direct_equal, free_glyph);
  	if (ret)
  		goto err_white;
  
  	ret = shl_hashtable_new(&tp->bold_glyphs, shl_direct_hash,
  				shl_direct_equal, free_glyph);
  	if (ret)
  		goto err_htable;
  
  	/*
  	 * TODO: It is actually faster to use a local shadow buffer and then
  	 * blit all data to the framebuffer afterwards. Reads seem to be
  	 * horribly slow on some mmap'ed framebuffers. However, that's not true
  	 * for all so we actually don't know which to use here.
  	 */
  	ret = uterm_display_get_buffers(txt->disp, tp->buf,
  					UTERM_FORMAT_XRGB32);
  	if (ret) {
  		log_warning("cannot get buffers for display %p",
  			    txt->disp);
  		ret = alloc_indirect(txt, w, h);
  		if (ret)
  			goto err_htable_bold;
  	} else {
  		tp->format[0] = format_u2p(tp->buf[0].format);
  		tp->surf[0] = pixman_image_create_bits_no_clear(tp->format[0],
  					tp->buf[0].width, tp->buf[0].height,
  					(void*)tp->buf[0].data,
  					tp->buf[0].stride);
  		tp->format[1] = format_u2p(tp->buf[1].format);
  		tp->surf[1] = pixman_image_create_bits_no_clear(tp->format[1],
  					tp->buf[1].width, tp->buf[1].height,
  					(void*)tp->buf[1].data,
  					tp->buf[1].stride);
  		if (!tp->surf[0] || !tp->surf[1]) {
  			log_error("cannot create pixman surfaces");
  			goto err_ctx;
  		}
  	}
  
  	txt->cols = w / txt->font->attr.width;
  	txt->rows = h / txt->font->attr.height;
  
  	return 0;
  
  err_ctx:
  	if (tp->surf[1])
  		pixman_image_unref(tp->surf[1]);
  	if (tp->surf[0])
  		pixman_image_unref(tp->surf[0]);
  	free(tp->data[1]);
  	free(tp->data[0]);
  err_htable_bold:
  	shl_hashtable_free(tp->bold_glyphs);
  err_htable:
  	shl_hashtable_free(tp->glyphs);
  err_white:
  	pixman_image_unref(tp->white);
  	return ret;
  }
  
  static void tp_unset(struct kmscon_text *txt)
  {
  	struct tp_pixman *tp = txt->data;
  
  	pixman_image_unref(tp->surf[1]);
  	pixman_image_unref(tp->surf[0]);
  	free(tp->data[1]);
  	free(tp->data[0]);
  	shl_hashtable_free(tp->bold_glyphs);
  	shl_hashtable_free(tp->glyphs);
  	pixman_image_unref(tp->white);
  }
  
  static int find_glyph(struct kmscon_text *txt, struct tp_glyph **out,
  		      uint64_t id, const uint32_t *ch, size_t len, const struct tsm_screen_attr *attr)
  {
  	struct tp_pixman *tp = txt->data;
  	struct tp_glyph *glyph;
  	struct shl_hashtable *gtable;
  	struct kmscon_font *font;
  	const struct uterm_video_buffer *buf;
  	uint8_t *dst, *src;
  	unsigned int format, i;
  	int ret, stride;
  	bool res;
  
  	if (attr->bold) {
  		gtable = tp->bold_glyphs;
  		font = txt->bold_font;
  	} else {
  		gtable = tp->glyphs;
  		font = txt->font;
  	}
  
  	if (attr->underline)
  		font->attr.underline = true;
  	else
  		font->attr.underline = false;
  
  	if (attr->italic)
  		font->attr.italic = true;
  	else
  		font->attr.italic = false;
  
  	res = shl_hashtable_find(gtable, (void**)&glyph, id);
  	if (res) {
  		*out = glyph;
  		return 0;
  	}
  
  	glyph = malloc(sizeof(*glyph));
  	if (!glyph)
  		return -ENOMEM;
  	memset(glyph, 0, sizeof(*glyph));
  
  	if (!len)
  		ret = kmscon_font_render_empty(font, &glyph->glyph);
  	else
  		ret = kmscon_font_render(font, id, ch, len, &glyph->glyph);
  
  	if (ret) {
  		ret = kmscon_font_render_inval(font, &glyph->glyph);
  		if (ret)
  			goto err_free;
  	}
  
  	buf = &glyph->glyph->buf;
  	stride = buf->stride;
  	format = format_u2p(buf->format);
  	glyph->surf = pixman_image_create_bits_no_clear(format,
  							buf->width,
  							buf->height,
  							(void*)buf->data,
  							buf->stride);
  	if (!glyph->surf) {
  		stride = (buf->stride + 3) & ~0x3;
  		if (!tp->new_stride) {
  			tp->new_stride = true;
  			log_debug("wrong stride, copy buffer (%d => %d)",
  			  buf->stride, stride);
  		}
  
  		glyph->data = malloc(stride * buf->height);
  		if (!glyph->data) {
  			log_error("cannot allocate memory for glyph storage");
  			ret = -ENOMEM;
  			goto err_free;
  		}
  
  		src = buf->data;
  		dst = glyph->data;
  		for (i = 0; i < buf->height; ++i) {
  			memcpy(dst, src, buf->width);
  			dst += stride;
  			src += buf->stride;
  		}
  
  		glyph->surf = pixman_image_create_bits_no_clear(format,
  								buf->width,
  								buf->height,
  								(void*)
  								glyph->data,
  								stride);
  	}
  	if (!glyph->surf) {
  		log_error("cannot create pixman-glyph: %d %p %d %d %d %d",
  			  ret, glyph->data ? glyph->data : buf->data, format,
  			  buf->width, buf->height, stride);
  		ret = -EFAULT;
  		goto err_free;
  	}
  
  	ret = shl_hashtable_insert(gtable, id, glyph);
  	if (ret)
  		goto err_pixman;
  
  	*out = glyph;
  	return 0;
  
  err_pixman:
  	pixman_image_unref(glyph->surf);
  err_free:
  	free(glyph);
  	return ret;
  }
  
  static int tp_prepare(struct kmscon_text *txt)
  {
  	struct tp_pixman *tp = txt->data;
  	int ret;
  	pixman_image_t *img;
  
  	ret = uterm_display_use(txt->disp, NULL);
  	if (ret < 0) {
  		log_error("cannot use display %p", txt->disp);
  		return ret;
  	}
  
  	tp->cur = ret;
  	img = tp->surf[tp->cur];
  	tp->c_bpp = PIXMAN_FORMAT_BPP(tp->format[tp->cur]);
  	tp->c_data = pixman_image_get_data(img);
  	tp->c_stride = pixman_image_get_stride(img);
  
  	return 0;
  }
  
  static int tp_draw(struct kmscon_text *txt,
  		   uint64_t id, const uint32_t *ch, size_t len,
  		   unsigned int width,
  		   unsigned int posx, unsigned int posy,
  		   const struct tsm_screen_attr *attr)
  {
  	struct tp_pixman *tp = txt->data;
  	struct tp_glyph *glyph;
  	int ret;
  	uint32_t bc;
  	pixman_color_t fc;
  	pixman_image_t *col;
  
  	if (!width)
  		return 0;
  
  	ret = find_glyph(txt, &glyph, id, ch, len, attr);
  	if (ret)
  		return ret;
  
  	if (attr->inverse) {
  		bc = (attr->fr << 16) | (attr->fg << 8) | (attr->fb);
  		fc.red = attr->br << 8;
  		fc.green = attr->bg << 8;
  		fc.blue = attr->bb << 8;
  		fc.alpha = 0xffff;
  	} else {
  		bc = (attr->br << 16) | (attr->bg << 8) | (attr->bb);
  		fc.red = attr->fr << 8;
  		fc.green = attr->fg << 8;
  		fc.blue = attr->fb << 8;
  		fc.alpha = 0xffff;
  	}
  
  	/* TODO: We _really_ should fix pixman to allow something like
  	 * pixman_image_set_solid_fill(img, &fc) to avoid allocating a pixman
  	 * image for each glyph here.
  	 * libc malloc() is pretty fast, but this still costs us a lot of
  	 * rendering performance. */
  	if (!fc.red && !fc.green && !fc.blue) {
  		col = tp->white;
  		pixman_image_ref(col);
  	} else {
  		col = pixman_image_create_solid_fill(&fc);
  		if (!col) {
  			log_error("cannot create pixman color image");
  			return -ENOMEM;
  		}
  	}
  
  	if (!bc) {
  		pixman_image_composite(PIXMAN_OP_SRC,
  				       col,
  				       glyph->surf,
  				       tp->surf[tp->cur],
  				       0, 0, 0, 0,
  				       posx * txt->font->attr.width,
  				       posy * txt->font->attr.height,
  				       txt->font->attr.width,
  				       txt->font->attr.height);
  	} else {
  		pixman_fill(tp->c_data, tp->c_stride / 4, tp->c_bpp,
  			    posx * txt->font->attr.width,
  			    posy * txt->font->attr.height,
  			    txt->font->attr.width,
  			    txt->font->attr.height,
  			    bc);
  
  		pixman_image_composite(PIXMAN_OP_OVER,
  				       col,
  				       glyph->surf,
  				       tp->surf[tp->cur],
  				       0, 0, 0, 0,
  				       posx * txt->font->attr.width,
  				       posy * txt->font->attr.height,
  				       txt->font->attr.width,
  				       txt->font->attr.height);
  	}
  
  	pixman_image_unref(col);
  
  	return 0;
  }
  
  static int tp_render(struct kmscon_text *txt)
  {
  	struct tp_pixman *tp = txt->data;
  	int ret;
  
  	if (!tp->use_indirect)
  		return 0;
  
  	tp->vbuf.data = tp->data[tp->cur];
  	ret = uterm_display_blit(txt->disp, &tp->vbuf, 0, 0);
  	if (ret) {
  		log_error("cannot blit back-buffer to display: %d", ret);
  		return ret;
  	}
  
  	return 0;
  }
  
  struct kmscon_text_ops kmscon_text_pixman_ops = {
  	.name = "pixman",
  	.owner = NULL,
  	.init = tp_init,
  	.destroy = tp_destroy,
  	.set = tp_set,
  	.unset = tp_unset,
  	.prepare = tp_prepare,
  	.draw = tp_draw,
  	.render = tp_render,
  	.abort = NULL,
  };
  