/*
 * Copyright (c) 2014 Intel Corporation
 *
 * 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 (including the next
 * paragraph) 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.
 *
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xlibint.h>
#include <X11/extensions/Xrender.h>
#include <X11/extensions/XShm.h>
#if HAVE_X11_EXTENSIONS_SHMPROTO_H
#include <X11/extensions/shmproto.h>
#elif HAVE_X11_EXTENSIONS_SHMSTR_H
#include <X11/extensions/shmstr.h>
#else
#error Failed to find the right header for X11 MIT-SHM protocol definitions
#endif
#include <xf86drm.h>
#include <i915_drm.h>

#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <assert.h>
#include <errno.h>

#include <sys/mman.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <pciaccess.h>

#include "dri3.h"
#include "../src/i915_pciids.h"

#define ALIGN(x, y) (((x) + (y) - 1) & -(y))
#define PAGE_ALIGN(x) ALIGN(x, 4096)

#define GTT I915_GEM_DOMAIN_GTT
#define CPU I915_GEM_DOMAIN_CPU

static int _x_error_occurred;

static const struct pci_id_match ids[] = {
	INTEL_I830_IDS(020),
	INTEL_I845G_IDS(021),
	INTEL_I85X_IDS(022),
	INTEL_I865G_IDS(023),

	INTEL_I915G_IDS(030),
	INTEL_I915GM_IDS(030),
	INTEL_I945G_IDS(031),
	INTEL_I945GM_IDS(031),

	INTEL_G33_IDS(033),
	INTEL_PINEVIEW_IDS(033),

	INTEL_I965G_IDS(040),
	INTEL_I965GM_IDS(040),

	INTEL_G45_IDS(045),
	INTEL_GM45_IDS(045),

	INTEL_IRONLAKE_D_IDS(050),
	INTEL_IRONLAKE_M_IDS(050),

	INTEL_SNB_D_IDS(060),
	INTEL_SNB_M_IDS(060),

	INTEL_IVB_D_IDS(070),
	INTEL_IVB_M_IDS(070),

	INTEL_HSW_D_IDS(075),
	INTEL_HSW_M_IDS(075),

	INTEL_VLV_D_IDS(071),
	INTEL_VLV_M_IDS(071),

	INTEL_BDW_D_IDS(0100),
	INTEL_BDW_M_IDS(0100),
};

static int i915_gen(int device)
{
	struct drm_i915_getparam gp;
	int devid = 0;
	int n;

	gp.param = I915_PARAM_CHIPSET_ID;
	gp.value = &devid;

	if (drmIoctl(device, DRM_IOCTL_I915_GETPARAM, &gp))
		return 0;

	for (n = 0; n < sizeof(ids)/sizeof(ids[0]); n++) {
		if (devid == ids[n].device_id)
			return ids[n].match_data;
	}

	return 0;
}

static int is_i915_device(int fd)
{
	drm_version_t version;
	char name[5] = "";

	memset(&version, 0, sizeof(version));
	version.name_len = 4;
	version.name = name;

	if (drmIoctl(fd, DRM_IOCTL_VERSION, &version))
		return 0;

	return strcmp("i915", name) == 0;
}

static int is_intel(int fd)
{
	struct drm_i915_getparam gp;
	int ret;

	/* Confirm that this is a i915.ko device with GEM/KMS enabled */
	ret = is_i915_device(fd);
	if (ret) {
		gp.param = I915_PARAM_HAS_GEM;
		gp.value = &ret;
		if (drmIoctl(fd, DRM_IOCTL_I915_GETPARAM, &gp))
			ret = 0;
	}
	return ret;
}

static uint32_t gem_create(int fd, int size)
{
	struct drm_i915_gem_create create;

	create.handle = 0;
	create.size = size;
	(void)drmIoctl(fd, DRM_IOCTL_I915_GEM_CREATE, &create);

	return create.handle;
}

struct local_i915_gem_caching {
	uint32_t handle;
	uint32_t caching;
};

#define LOCAL_I915_GEM_SET_CACHING	0x2f
#define LOCAL_IOCTL_I915_GEM_SET_CACHING DRM_IOW(DRM_COMMAND_BASE + LOCAL_I915_GEM_SET_CACHING, struct local_i915_gem_caching)

static int gem_set_caching(int fd, uint32_t handle, int caching)
{
	struct local_i915_gem_caching arg;

	arg.handle = handle;
	arg.caching = caching;

	return drmIoctl(fd, LOCAL_IOCTL_I915_GEM_SET_CACHING, &arg) == 0;
}

static int gem_export(int fd, uint32_t handle)
{
	struct drm_prime_handle args;

	args.handle = handle;
	args.flags = O_CLOEXEC;

	if (drmIoctl(fd, DRM_IOCTL_PRIME_HANDLE_TO_FD, &args))
		return -1;

	return args.fd;
}

static uint32_t gem_import(int fd, int name)
{
	struct drm_prime_handle args;

	args.fd = name;
	args.flags = 0;
	if (drmIoctl(fd, DRM_IOCTL_PRIME_FD_TO_HANDLE, &args))
		return 0;

	return args.handle;
}

static int gem_write(int fd, uint32_t handle, int offset, void *data, int len)
{
	struct drm_i915_gem_pwrite gem_pwrite;

	gem_pwrite.handle = handle;
	gem_pwrite.offset = offset;
	gem_pwrite.size = len;
	gem_pwrite.data_ptr = (uintptr_t)data;
	return drmIoctl(fd, DRM_IOCTL_I915_GEM_PWRITE, &gem_pwrite);
}

static void *gem_mmap(int fd, uint32_t handle, int size, unsigned prot, int domain)
{
	struct drm_i915_gem_set_domain set_domain;
	void *ptr;

	if (domain == CPU) {
		struct drm_i915_gem_mmap mmap_arg;

		mmap_arg.handle = handle;
		mmap_arg.offset = 0;
		mmap_arg.size = size;
		if (drmIoctl(fd, DRM_IOCTL_I915_GEM_MMAP, &mmap_arg))
			return NULL;

		ptr = (void *)(uintptr_t)mmap_arg.addr_ptr;
	} else {
		struct drm_i915_gem_mmap_gtt mmap_arg;

		mmap_arg.handle = handle;
		if (drmIoctl(fd, DRM_IOCTL_I915_GEM_MMAP_GTT, &mmap_arg))
			return NULL;

		ptr = mmap(0, size, prot, MAP_SHARED, fd, mmap_arg.offset);
		if (ptr == MAP_FAILED)
			return NULL;
	}

	set_domain.handle = handle;
	set_domain.read_domains = domain;
	set_domain.write_domain = prot & PROT_WRITE ? set_domain.read_domains : 0;
	if (drmIoctl(fd, DRM_IOCTL_I915_GEM_SET_DOMAIN, &set_domain)) {
		munmap(ptr, size);
		return NULL;
	}

	return ptr;
}

static void gem_sync(int fd, uint32_t handle, int read)
{
	struct drm_i915_gem_set_domain set_domain;

	set_domain.handle = handle;
	set_domain.read_domains = read;
	set_domain.write_domain = 0;
	drmIoctl(fd, DRM_IOCTL_I915_GEM_SET_DOMAIN, &set_domain);
}

static int gem_get_tiling(int fd, uint32_t handle)
{
	struct drm_i915_gem_get_tiling tiling;

	tiling.handle = handle;
	tiling.tiling_mode = -1;
	(void)drmIoctl(fd, DRM_IOCTL_I915_GEM_GET_TILING, &tiling);
	return tiling.tiling_mode;
}

static void gem_close(int fd, uint32_t handle)
{
	struct drm_gem_close close;

	close.handle = handle;
	(void)drmIoctl(fd, DRM_IOCTL_GEM_CLOSE, &close);
}

static void gem_fill(int fd, uint32_t handle, uint32_t pixel, uint32_t size, int domain)
{
	uint32_t *ptr, s;

	ptr = gem_mmap(fd, handle, size, PROT_READ | PROT_WRITE, domain);
	if (ptr == NULL)
		return;

	for (s = 0; s < size; s += 4)
		ptr[s/4] = pixel;
	munmap(ptr, size);
}

static int check_pixmap(Display *dpy, Pixmap pix,
			int x, int y, uint32_t expected, int bpp)
{
	XImage *image;
	int w = 32 / bpp;

	image = XGetImage(dpy, pix, x - (x % w), y, w, 1, AllPlanes, ZPixmap);
	if (image == NULL)
		return 0;

	if (*(uint32_t *)image->data != expected) {
		printf("pixmap[%d, %d]:%d = %08x\n", x, y, bpp, *(uint32_t *)image->data);
		return 0;
	}
	XDestroyImage(image);

	return 1;
}

static int check_pixel(int fd, uint32_t handle, uint32_t stride, uint32_t size,
		       int x, int y, uint32_t expected, int bpp, int domain)
{
	uint32_t *ptr;
	int w = 32 / bpp;

	assert((stride & 3) == 0);

	ptr = gem_mmap(fd, handle, size, PROT_READ, domain);
	if (ptr == NULL)
		return 0;

	if (ptr[(y*stride + x - (x % w))/4] != expected) {
		printf("pixel[%d, %d]:%d = %08x\n", x, y, bpp, ptr[(y * stride + x)/4]);
		return 0;
	}
	munmap(ptr, size);

	return 1;
}

static GC get_gc(Display *dpy, Drawable d, int depth)
{
	static GC gc[33];
	if (gc[depth] == NULL) {
		XGCValues gcv;

		gcv.graphics_exposures = False;
		gc[depth] = XCreateGC(dpy, d, GCGraphicsExposures, &gcv);
	}
	return gc[depth];
}

static int
can_use_shm(Display *dpy)
{
	int major, minor, has_pixmap;

	if (!XShmQueryExtension(dpy))
		return 0;

	XShmQueryVersion(dpy, &major, &minor, &has_pixmap);
	return has_pixmap;
}

static int gpu_fill(int device, int handle, int width, int height, int pitch, int bpp, int tiling, uint32_t pixel)
{
	struct drm_i915_gem_execbuffer2 execbuf;
	struct drm_i915_gem_relocation_entry gem_reloc[2];
	struct drm_i915_gem_exec_object2 gem_exec[2];
	uint32_t batch[10];
	int gen = i915_gen(device);
	int len = 0;
	int ret;

	if (gen == 0)
		return -ENODEV;

	batch[0] = 2 << 29 | 0x50 << 22;
	batch[0] |= (gen >= 0100 ? 5 : 4);
	batch[1] = pitch;
	if (gen >= 040 && tiling) {
		batch[0] |= 1 << 11;
		batch[1] >>= 2;
	}

	batch[1] |= 0xf0 << 16;
	switch (bpp) {
	default: assert(0);
	case 32: batch[0] |= 1 << 21 | 1 << 20;
		 batch[1] |= 1 << 25; /* RGB8888 */
	case 16: batch[1] |= 1 << 24; /* RGB565 */
	case 8: break;
	}

	batch[2] = 0;
	batch[3] = height << 16 | width;
	batch[4] = 0;
	len = 5;
	if (gen >= 0100)
		batch[len++] = 0;
	batch[len++] = pixel;
	batch[len++] = 0xA << 23;
	if (len & 1)
		len++;

	gem_reloc[0].offset = 4 * sizeof(uint32_t);
	gem_reloc[0].delta = 0;
	gem_reloc[0].target_handle = handle;
	gem_reloc[0].read_domains = I915_GEM_DOMAIN_RENDER;
	gem_reloc[0].write_domain = I915_GEM_DOMAIN_RENDER;
	gem_reloc[0].presumed_offset = 0;

	memset(gem_exec, 0, sizeof(gem_exec));
	gem_exec[0].handle = handle;
	gem_exec[1].handle = gem_create(device, 4096);
	gem_exec[1].relocation_count = 1;
	gem_exec[1].relocs_ptr = (uintptr_t)gem_reloc;

	memset(&execbuf, 0, sizeof(execbuf));
	execbuf.buffers_ptr = (uintptr_t)gem_exec;
	execbuf.buffer_count = 2;
	execbuf.batch_len = len * sizeof(uint32_t);
	execbuf.flags = gen >= 060 ? I915_EXEC_BLT : 0;

	ret = gem_write(device, gem_exec[1].handle, 0, batch, execbuf.batch_len);
	if (ret == 0)
		ret = drmIoctl(device, DRM_IOCTL_I915_GEM_EXECBUFFER2, &execbuf);
	if (ret < 0)
		ret = -errno;

	gem_close(device, gem_exec[1].handle);
	return ret;
}

static int test_shm(Display *dpy, int device,
		    int width, int height)
{
	const int x_loc[] = {0, width/2, width-1};
	const int y_loc[] = {0, height/2, height-1};
	uint32_t pixel = 0xffff00ff;
	XShmSegmentInfo shm;
	Pixmap pixmap;
	uint32_t handle = 0;
	uint32_t *ptr;
	int stride, fd;
	int x, y;
	int line;

	if (!can_use_shm(dpy))
		return 0;

	printf("Creating %dx%d SHM pixmap\n", width, height);
	_x_error_occurred = 0;

	shm.shmid = shmget(IPC_PRIVATE, height * 4*width, IPC_CREAT | 0666);
	if (shm.shmid == -1)
		return 0;

	shm.shmaddr = shmat(shm.shmid, 0, 0);
	if (shm.shmaddr == (char *) -1) {
		shmctl(shm.shmid, IPC_RMID, NULL);
		return 0;
	}

	shm.readOnly = False;
	XShmAttach(dpy, &shm);

	pixmap = XShmCreatePixmap(dpy, DefaultRootWindow(dpy),
				  shm.shmaddr, &shm, width, height, 24);
	XSync(dpy, False);
	shmctl(shm.shmid, IPC_RMID, NULL);

	if (_x_error_occurred) {
		XShmDetach(dpy, &shm);
		shmdt(shm.shmaddr);
		return 0;
	}

	printf("Testing write of %dx%d SHM pixmap via DRI3 fd\n", width, height);

	fd = dri3_create_fd(dpy, pixmap, &stride);
	if (fd < 0) {
		line = __LINE__;
		goto fail;
	}

	handle = gem_import(device, fd);
	close(fd);
	if (handle == 0) {
		line = __LINE__;
		goto fail;
	}

	if (gpu_fill(device, handle, width, height, stride, 32, I915_TILING_NONE, pixel)) {
		line = __LINE__;
		goto fail;
	}

	gem_sync(device, handle, CPU);
	ptr = (uint32_t *)shm.shmaddr;
	for (x = 0; x < sizeof(x_loc)/sizeof(x_loc[0]); x++)
		for (y = 0; y < sizeof(y_loc)/sizeof(y_loc[0]); y++)
			if (ptr[y_loc[y]*width + x_loc[x]] != pixel) {
				printf("pixel[%d, %d]:%d = %08x\n", x, y, 32, ptr[y_loc[y] * width + x_loc[x]]);
				line = __LINE__;
				goto fail;
			}

	for (x = 0; x < sizeof(x_loc)/sizeof(x_loc[0]); x++)
		for (y = 0; y < sizeof(y_loc)/sizeof(y_loc[0]); y++)
			if (!check_pixmap(dpy, pixmap,
					  x_loc[x], y_loc[y],
					  pixel, 32)) {
				line = __LINE__;
				goto fail;
			}

	if (_x_error_occurred) {
		line = __LINE__;
		goto fail;
	}

out:
	gem_close(device, handle);
	XFreePixmap(dpy, pixmap);
	XShmDetach(dpy, &shm);
	shmdt(shm.shmaddr);
	return fd != -1;

fail:
	printf("%s failed at (%dx%d), line %d\n",
	       __func__, width, height, line);
	fd = -1;
	goto out;
}

static int test_read_after_write(Display *dpy, int device,
				 int width, int height, int depth,
				 int domain)
{
	const uint32_t pixel = 0xffff00ff;
	const int x_loc[] = {0, width/2, width-1};
	const int y_loc[] = {0, height/2, height-1};
	Window root = RootWindow(dpy, DefaultScreen(dpy));
	uint32_t src, dst;
	int src_fd, dst_fd;
	int src_stride, src_size;
	int dst_stride, dst_size;
	Pixmap src_pix, dst_pix;
	struct dri3_fence fence;
	int x, y, bpp;

	_x_error_occurred = 0;

	switch (depth) {
	case 8: bpp = 8; break;
	case 16: bpp = 16; break;
	case 24: bpp = 32; break;
	case 32: bpp = 32; break;
	default: return 0;
	}

	src_stride = width * bpp/8;
	src_size = PAGE_ALIGN(src_stride * height);
	printf("Creating %dx%d (source stride=%d, size=%d, domain=%d)\n",
	       width, height, src_stride, src_size, domain);

	src = gem_create(device, src_size);
	if (!src)
		goto fail;

	if (domain == CPU)
		gem_set_caching(device, src, 1);

	gem_fill(device, src, pixel, src_size, domain);

	src_fd = gem_export(device, src);
	if (src_fd < 0)
		goto fail;

	src_pix = dri3_create_pixmap(dpy, root,
				     width, height, depth,
				     src_fd, bpp, src_stride, src_size);

	for (x = 0; x < sizeof(x_loc)/sizeof(x_loc[0]); x++)
		for (y = 0; y < sizeof(y_loc)/sizeof(y_loc[0]); y++)
			if (!check_pixmap(dpy, src_pix,
					  x_loc[x], y_loc[y],
					  pixel, bpp))
				goto fail;
	close(src_fd);

	dst_pix = XCreatePixmap(dpy, root, width, height, depth);
	if (dri3_create_fence(dpy, dst_pix, &fence))
		goto fail;

	dst_fd = dri3_create_fd(dpy, dst_pix, &dst_stride);
	if (dst_fd < 0)
		goto fail;
	dst_size = lseek(dst_fd, 0, SEEK_END);
	printf("Comparing %dx%d (destination stride=%d, size=%d)\n",
	       width, height, dst_stride, dst_size);
	dst = gem_import(device, dst_fd);
	if (dst == 0)
		goto fail;
	close(dst_fd);

	XCopyArea(dpy, src_pix, dst_pix,
		  get_gc(dpy, dst_pix, depth),
		  0, 0, width, height, 0, 0);
	dri3_fence_sync(dpy, &fence);
	dri3_fence_free(dpy, &fence);

	for (x = 0; x < sizeof(x_loc)/sizeof(x_loc[0]); x++)
		for (y = 0; y < sizeof(y_loc)/sizeof(y_loc[0]); y++)
			if (!check_pixel(device, dst, dst_stride, dst_size,
					 x_loc[x], y_loc[y],
					 pixel, bpp, GTT))
				goto fail;

	XFreePixmap(dpy, dst_pix);
	XFreePixmap(dpy, src_pix);

	gem_close(device, src);
	gem_close(device, dst);

	if (_x_error_occurred)
		goto fail;

	return 0;

fail:
	printf("%s failed at (%dx%d), depth=%d, domain=%d\n",
	       __func__, width, height, depth, domain);
	return 1;
}

static XRenderPictFormat *format_for_depth(Display *dpy, int depth)
{
	switch (depth) {
	case 8: return XRenderFindStandardFormat(dpy, PictStandardA8);
	case 24: return XRenderFindStandardFormat(dpy, PictStandardRGB24);
	case 32: return XRenderFindStandardFormat(dpy, PictStandardARGB32);
	default: assert(0); return NULL;
	}
}

static int test_read(Display *dpy, int device,
		     int width, int height,
		     int domain)
{
	const uint32_t pixel = 0xffff00ff;
	const XRenderColor color = { 0xffff, 0x0000, 0xffff, 0xffff };
	const int x_loc[] = {0, width/2, width-1};
	const int y_loc[] = {0, height/2, height-1};
	Window root = RootWindow(dpy, DefaultScreen(dpy));
	uint32_t dst;
	int dst_stride, dst_size, dst_fd;
	Pixmap src_pix, dst_pix;
	Picture src_pic;
	struct dri3_fence fence;
	int depth = 32, bpp = 32;
	int x, y;

	_x_error_occurred = 0;

	dst_stride = width * bpp/8;
	dst_size = PAGE_ALIGN(dst_stride * height);
	printf("Creating %dx%d (destination stride=%d, size=%d, domain=%d)\n",
	       width, height, dst_stride, dst_size, domain);

	dst = gem_create(device, dst_size);
	if (!dst)
		goto fail;

	if (domain == CPU)
		gem_set_caching(device, dst, 1);

	gem_fill(device, dst, ~pixel, dst_size, domain);

	dst_fd = gem_export(device, dst);
	if (dst_fd < 0)
		goto fail;

	dst_pix = dri3_create_pixmap(dpy, root,
				     width, height, depth,
				     dst_fd, bpp, dst_stride, dst_size);
	XSync(dpy, True);
	if (_x_error_occurred)
		goto fail;
	if (dri3_create_fence(dpy, dst_pix, &fence))
		goto fail;

	src_pix = XCreatePixmap(dpy, root, width, height, depth);
	src_pic = XRenderCreatePicture(dpy, src_pix, format_for_depth(dpy, depth), 0, NULL);
	XRenderFillRectangle(dpy, PictOpSrc, src_pic, &color, 0, 0, width, height);
	XCopyArea(dpy, src_pix, dst_pix,
		  get_gc(dpy, dst_pix, depth),
		  0, 0, width, height, 0, 0);
	dri3_fence_sync(dpy, &fence);
	dri3_fence_free(dpy, &fence);

	for (x = 0; x < sizeof(x_loc)/sizeof(x_loc[0]); x++)
		for (y = 0; y < sizeof(y_loc)/sizeof(y_loc[0]); y++)
			if (!check_pixel(device, dst, dst_stride, dst_size,
					 x_loc[x], y_loc[y],
					 pixel, bpp, domain))
				goto fail;

	XFreePixmap(dpy, dst_pix);
	XRenderFreePicture(dpy, src_pic);
	XFreePixmap(dpy, src_pix);

	gem_close(device, dst);

	if (_x_error_occurred)
		goto fail;

	return 0;

fail:
	printf("%s failed at (%dx%d), depth=%d, domain=%d\n",
	       __func__, width, height, depth, domain);
	return 1;
}

static int test_dup_pixmap(Display *dpy, int device)
{
	const uint32_t pixel = 0xffff00ff;
	const XRenderColor color = { 0xffff, 0x0000, 0xffff, 0xffff };
	const XRenderColor inverse = { 0, 0xffff, 0, 0 };
	int width = 400, height = 400;
	const int x_loc[] = {0, width/2, width-1};
	const int y_loc[] = {0, height/2, height-1};
	Window root = RootWindow(dpy, DefaultScreen(dpy));
	uint32_t handle;
	int stride, size, fd;
	Pixmap src_pix, dst_pix;
	Picture src_pic, dst_pic;
	struct dri3_fence fence;
	int depth = 32, bpp = 32;
	int x, y;

	_x_error_occurred = 0;

	printf("%s: Creating %dx%d pixmap\n", __func__, width, height);
	src_pix = XCreatePixmap(dpy, root, width, height, depth);
	src_pic = XRenderCreatePicture(dpy, src_pix, format_for_depth(dpy, depth), 0, NULL);
	fd = dri3_create_fd(dpy, src_pix, &stride);
	if (fd < 0)
		goto fail;

	size = lseek(fd, 0, SEEK_END);
	handle = gem_import(device, fd);

	printf("%s: Creating duplicate from pixmap exported fd\n", __func__);
	dst_pix = dri3_create_pixmap(dpy, root,
				     width, height, depth,
				     fd, bpp, stride, size);
	dst_pic = XRenderCreatePicture(dpy, dst_pix, format_for_depth(dpy, depth), 0, NULL);
	XSync(dpy, True);
	if (_x_error_occurred)
		goto fail;

	printf("%s: Filling src with %08x, reading dst\n", __func__, pixel);
	XRenderFillRectangle(dpy, PictOpSrc, src_pic, &color, 0, 0, width, height);
	for (x = 0; x < sizeof(x_loc)/sizeof(x_loc[0]); x++)
		for (y = 0; y < sizeof(y_loc)/sizeof(y_loc[0]); y++)
			if (!check_pixmap(dpy, dst_pix,
					  x_loc[x], y_loc[y],
					  pixel, 32))
				goto fail;

	printf("%s: Filling dst with %08x, reading src\n", __func__, ~pixel);
	XRenderFillRectangle(dpy, PictOpSrc, dst_pic, &inverse, 0, 0, width, height);
	for (x = 0; x < sizeof(x_loc)/sizeof(x_loc[0]); x++)
		for (y = 0; y < sizeof(y_loc)/sizeof(y_loc[0]); y++)
			if (!check_pixmap(dpy, dst_pix,
					  x_loc[x], y_loc[y],
					  ~pixel, 32))
				goto fail;

	if (dri3_create_fence(dpy, src_pix, &fence))
		goto fail;

	printf("%s: Filling src with %08x, reading fd\n", __func__, pixel);
	XRenderFillRectangle(dpy, PictOpSrc, src_pic, &color, 0, 0, width, height);
	dri3_fence_sync(dpy, &fence);
	dri3_fence_free(dpy, &fence);
	for (x = 0; x < sizeof(x_loc)/sizeof(x_loc[0]); x++)
		for (y = 0; y < sizeof(y_loc)/sizeof(y_loc[0]); y++)
			if (!check_pixel(device, handle, stride, size,
					 x_loc[x], y_loc[y],
					 pixel, bpp, GTT))
				goto fail;

	printf("%s: Filling fd with %08x, reading src\n", __func__, ~pixel);
	if (gpu_fill(device, handle, width, height, stride, 32, gem_get_tiling(device, handle), ~pixel))
		goto fail;
	for (x = 0; x < sizeof(x_loc)/sizeof(x_loc[0]); x++)
		for (y = 0; y < sizeof(y_loc)/sizeof(y_loc[0]); y++)
			if (!check_pixmap(dpy, src_pix,
					  x_loc[x], y_loc[y],
					  ~pixel, 32))
				goto fail;

	if (dri3_create_fence(dpy, dst_pix, &fence))
		goto fail;

	printf("%s: Filling dst with %08x, reading fd\n", __func__, pixel);
	XRenderFillRectangle(dpy, PictOpSrc, dst_pic, &color, 0, 0, width, height);
	dri3_fence_sync(dpy, &fence);
	dri3_fence_free(dpy, &fence);
	for (x = 0; x < sizeof(x_loc)/sizeof(x_loc[0]); x++)
		for (y = 0; y < sizeof(y_loc)/sizeof(y_loc[0]); y++)
			if (!check_pixel(device, handle, stride, size,
					 x_loc[x], y_loc[y],
					 pixel, bpp, GTT))
				goto fail;

	printf("%s: Filling fd with %08x, reading dst\n", __func__, ~pixel);
	if (gpu_fill(device, handle, width, height, stride, 32, gem_get_tiling(device, handle), ~pixel))
		goto fail;
	for (x = 0; x < sizeof(x_loc)/sizeof(x_loc[0]); x++)
		for (y = 0; y < sizeof(y_loc)/sizeof(y_loc[0]); y++)
			if (!check_pixmap(dpy, dst_pix,
					  x_loc[x], y_loc[y],
					  ~pixel, 32))
				goto fail;

	XRenderFreePicture(dpy, src_pic);
	XFreePixmap(dpy, src_pix);

	if (dri3_create_fence(dpy, dst_pix, &fence))
		goto fail;

	printf("%s: Closed original src, filling dst with %08x, reading fd\n", __func__, pixel);
	XRenderFillRectangle(dpy, PictOpSrc, dst_pic, &color, 0, 0, width, height);
	dri3_fence_sync(dpy, &fence);
	dri3_fence_free(dpy, &fence);
	for (x = 0; x < sizeof(x_loc)/sizeof(x_loc[0]); x++)
		for (y = 0; y < sizeof(y_loc)/sizeof(y_loc[0]); y++)
			if (!check_pixel(device, handle, stride, size,
					 x_loc[x], y_loc[y],
					 pixel, bpp, GTT))
				goto fail;

	XRenderFreePicture(dpy, dst_pic);
	XFreePixmap(dpy, dst_pix);

	gem_close(device, handle);

	if (_x_error_occurred)
		goto fail;

	return 0;

fail:
	printf("%s failed at (%dx%d), depth=%d\n",
	       __func__, width, height, depth);
	return 1;
}

static int test_bad_size(Display *dpy, int device)
{
	Window root = RootWindow(dpy, DefaultScreen(dpy));
	uint32_t src;
	int src_fd;
	Pixmap src_pix;
	int line = -1;

	_x_error_occurred = 0;

	src = gem_create(device, 4096);
	if (!src)
		goto fail;

	src_fd = gem_export(device, src);
	if (src_fd < 0)
		goto fail;

	src_pix = dri3_create_pixmap(dpy, root,
				     16, 16, 32,
				     dup(src_fd), 32, 16*4, 4096);
	line = __LINE__;
	XSync(dpy, True);
	if (_x_error_occurred)
		goto fail;
	XFreePixmap(dpy, src_pix);
	_x_error_occurred = 0;

	src_pix = dri3_create_pixmap(dpy, root,
				     32, 32, 32,
				     dup(src_fd), 32, 32*4, 4096);
	line = __LINE__;
	XSync(dpy, True);
	if (_x_error_occurred)
		goto fail;
	XFreePixmap(dpy, src_pix);
	_x_error_occurred = 0;

	src_pix = dri3_create_pixmap(dpy, root,
				     64, 64, 32,
				     dup(src_fd), 32, 64*4, 4096);
	line = __LINE__;
	XSync(dpy, True);
	if (!_x_error_occurred)
		goto fail;
	_x_error_occurred = 0;

	src_pix = dri3_create_pixmap(dpy, root,
				     64, 64, 32,
				     dup(src_fd), 32, 64*4, 64*64*4);
	line = __LINE__;
	XSync(dpy, True);
	if (!_x_error_occurred)
		goto fail;
	_x_error_occurred = 0;

	src_pix = dri3_create_pixmap(dpy, root,
				     INT16_MAX, INT16_MAX, 8,
				     dup(src_fd), 8, INT16_MAX, UINT32_MAX);
	line = __LINE__;
	XSync(dpy, True);
	if (!_x_error_occurred)
		goto fail;
	_x_error_occurred = 0;

	close(src_fd);
	gem_close(device, src);

	return 0;

fail:
	printf("%s failed at line %d\n", __func__, line);
	return 1;
}

static int test_bad_pitch(Display *dpy, int device)
{
	Window root = RootWindow(dpy, DefaultScreen(dpy));
	uint32_t src;
	int src_fd;
	Pixmap src_pix;
	int line = -1;

	_x_error_occurred = 0;

	src = gem_create(device, 4096);
	if (!src)
		goto fail;

	src_fd = gem_export(device, src);
	if (src_fd < 0)
		goto fail;

	src_pix = dri3_create_pixmap(dpy, root,
				     16, 16, 32,
				     dup(src_fd), 32, 16*4, 4096);
	line = __LINE__;
	XSync(dpy, True);
	if (_x_error_occurred)
		goto fail;
	XFreePixmap(dpy, src_pix);
	_x_error_occurred = 0;

	src_pix = dri3_create_pixmap(dpy, root,
				     256, 2, 32,
				     dup(src_fd), 32, 256*4, 4096);
	line = __LINE__;
	XSync(dpy, True);
	if (_x_error_occurred)
		goto fail;
	XFreePixmap(dpy, src_pix);
	_x_error_occurred = 0;

	src_pix = dri3_create_pixmap(dpy, root,
				     256, 2, 32,
				     dup(src_fd), 32, 256, 4096);
	line = __LINE__;
	XSync(dpy, True);
	if (!_x_error_occurred)
		goto fail;
	_x_error_occurred = 0;

	src_pix = dri3_create_pixmap(dpy, root,
				     256, 2, 32,
				     dup(src_fd), 32, 16384, 4096);
	line = __LINE__;
	XSync(dpy, True);
	if (!_x_error_occurred)
		goto fail;
	_x_error_occurred = 0;

	src_pix = dri3_create_pixmap(dpy, root,
				     256, 2, 32,
				     dup(src_fd), 32, 1023, 4096);
	line = __LINE__;
	XSync(dpy, True);
	if (!_x_error_occurred)
		goto fail;
	_x_error_occurred = 0;

	src_pix = dri3_create_pixmap(dpy, root,
				     256, 2, 32,
				     dup(src_fd), 32, 1025, 4096);
	line = __LINE__;
	XSync(dpy, True);
	if (!_x_error_occurred)
		goto fail;
	_x_error_occurred = 0;

	close(src_fd);
	gem_close(device, src);

	return 0;

fail:
	printf("%s failed at line %d\n", __func__, line);
	return 1;
}

static int gem_set_tiling(int fd, uint32_t handle, int tiling, int stride)
{
	struct drm_i915_gem_set_tiling set_tiling;

	set_tiling.handle = handle;
	set_tiling.tiling_mode = tiling;
	set_tiling.stride = stride;

	return drmIoctl(fd, DRM_IOCTL_I915_GEM_SET_TILING, &set_tiling) == 0;
}

static int test_tiling(Display *dpy, int device)
{
	Window root = RootWindow(dpy, DefaultScreen(dpy));
	const int tiling[] = { I915_TILING_NONE, I915_TILING_X, I915_TILING_Y };
	int line = -1;
	int t;

	_x_error_occurred = 0;

	for (t = 0; t < sizeof(tiling)/sizeof(tiling[0]); t++) {
		uint32_t src;
		int src_fd;
		Pixmap src_pix;

		src = gem_create(device, 4*4096);
		if (!src) {
			line = __LINE__;
			goto fail;
		}

		gem_set_tiling(device, src, tiling[t], 512);

		src_fd = gem_export(device, src);
		if (src_fd < 0) {
			line = __LINE__;
			goto fail;
		}

		src_pix = dri3_create_pixmap(dpy, root,
					     128, 32, 32,
					     src_fd, 32, 512, 4*4096);
		XSync(dpy, True);
		if (_x_error_occurred) {
			line = __LINE__;
			goto fail;
		}
		XFreePixmap(dpy, src_pix);
		_x_error_occurred = 0;

		close(src_fd);
		gem_close(device, src);
	}

	return 0;

fail:
	printf("%s failed with tiling %d, line %d\n", __func__, tiling[t], line);
	return 1;
}

static int
_check_error_handler(Display     *display,
		     XErrorEvent *event)
{
	printf("X11 error from display %s, serial=%ld, error=%d, req=%d.%d\n",
	       DisplayString(display),
	       event->serial,
	       event->error_code,
	       event->request_code,
	       event->minor_code);
	_x_error_occurred++;
	return False; /* ignored */
}

int main(void)
{
	Display *dpy;
	int device;
	int error = 0;

	dpy = XOpenDisplay(NULL);
	if (dpy == NULL)
		return 77;

	if (DefaultDepth(dpy, DefaultScreen(dpy)) != 24)
		return 77;

	XSetErrorHandler(_check_error_handler);

	device = dri3_open(dpy);
	if (device < 0)
		return 127;

	if (!is_intel(device))
		return 77;

	printf("Opened Intel DRI3 device\n");

	error += test_bad_size(dpy, device);
	error += test_bad_pitch(dpy, device);
	error += test_tiling(dpy, device);

	error += test_shm(dpy, device, 400, 300);
	error += test_shm(dpy, device, 300, 400);

	error += test_read(dpy, device, 400, 200, GTT);
	error += test_read(dpy, device, 4000, 20, GTT);
	error += test_read(dpy, device, 16000, 10, GTT);
	error += test_read(dpy, device, 30000, 10, GTT);

	error += test_read(dpy, device, 200, 400, GTT);
	error += test_read(dpy, device, 20, 4000, GTT);
	error += test_read(dpy, device, 16, 16000, GTT);
	error += test_read(dpy, device, 16, 30000, GTT);

	error += test_read(dpy, device, 400, 200, CPU);
	error += test_read(dpy, device, 4000, 20, CPU);
	error += test_read(dpy, device, 16000, 10, CPU);
	error += test_read(dpy, device, 30000, 10, CPU);

	error += test_read(dpy, device, 200, 400, CPU);
	error += test_read(dpy, device, 20, 4000, CPU);
	error += test_read(dpy, device, 16, 16000, CPU);
	error += test_read(dpy, device, 16, 30000, CPU);

	error += test_read_after_write(dpy, device, 400, 200, 24, GTT);
	error += test_read_after_write(dpy, device, 4000, 20, 24, GTT);
	error += test_read_after_write(dpy, device, 16000, 10, 24, GTT);
	error += test_read_after_write(dpy, device, 30000, 10, 24, GTT);
	error += test_read_after_write(dpy, device, 30000, 10, 8, GTT);

	error += test_read_after_write(dpy, device, 200, 400, 24, GTT);
	error += test_read_after_write(dpy, device, 20, 4000, 24, GTT);
	error += test_read_after_write(dpy, device, 16, 16000, 24, GTT);
	error += test_read_after_write(dpy, device, 16, 30000, 24, GTT);

	error += test_read_after_write(dpy, device, 400, 200, 24, CPU);
	error += test_read_after_write(dpy, device, 4000, 20, 24, CPU);
	error += test_read_after_write(dpy, device, 16000, 10, 24, CPU);
	error += test_read_after_write(dpy, device, 30000, 10, 24, CPU);
	error += test_read_after_write(dpy, device, 30000, 10, 8, CPU);

	error += test_read_after_write(dpy, device, 200, 400, 24, CPU);
	error += test_read_after_write(dpy, device, 20, 4000, 24, CPU);
	error += test_read_after_write(dpy, device, 16, 16000, 24, CPU);
	error += test_read_after_write(dpy, device, 16, 30000, 24, CPU);

	error += test_dup_pixmap(dpy, device);

	return !!error;
}
