diff --git a/Makefile.target b/Makefile.target
index c14d496..60211a3 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -309,6 +309,24 @@ obj-y += $(addprefix ../libdis/, $(libdis-y))
 obj-y += $(libobj-y)
 obj-y += $(addprefix $(HWDIR)/, $(hw-obj-y))
 
+ifeq ($(TARGET_BASE_ARCH), arm)
+ifdef CONFIG_GLES2
+CFLAGS+=-I$(DGLES2)/include
+LIBS+=-L$(DGLES2)/lib
+ifdef CONFIG_GLES2_STATIC
+LIBS+=-Wl,-Bstatic -lEGL -lGLESv2 -lOSMesa -Wl,-Bdynamic
+else
+LIBS+=-lEGL -lGLESv2
+endif
+ifndef CONFIG_WIN32
+LIBS+=-LX11 -ldl
+endif
+gles2_calls.o: gles2_calls.c gles2.h gles2_calls.h
+gles2.o: gles2.c gles2.h gles2_calls.h
+obj-arm-y += gles2.o gles2_calls.o
+endif # CONFIG_GLES2
+endif # TARGET_BASE_ARCH==arm
+
 endif # CONFIG_SOFTMMU
 
 obj-$(CONFIG_GDBSTUB_XML) += gdbstub-xml.o
diff --git a/configure b/configure
index 6bc40a3..70f5c61 100755
--- a/configure
+++ b/configure
@@ -295,6 +295,8 @@ blobs="yes"
 pkgversion=""
 check_utests="no"
 user_pie="no"
+gles2="no"
+gles2_static="no"
 zero_malloc=""
 
 # OS specific
@@ -651,6 +653,12 @@ for opt do
   ;;
   --enable-docs) docs="yes"
   ;;
+  --enable-gles2) gles2="yes"; gles2dir="/usr";
+  ;;
+  --enable-gles2-static) gles2="yes"; gles2_static="yes" gles2dir="/usr";
+  ;;
+  --gles2dir=*) gles2dir="$optarg"
+  ;;
   *) echo "ERROR: unknown option $opt"; show_help="yes"
   ;;
   esac
@@ -806,6 +814,9 @@ echo "  --disable-blobs          disable installing provided firmware blobs"
 echo "  --kerneldir=PATH         look for kernel includes in PATH"
 echo "  --enable-docs            enable documentation build"
 echo "  --disable-docs           disable documentation build"
+echo "  --enable-gles2           ARM target: Enable OpenGL ES 2.0 hardware accelerator"
+echo "  --enable-gles2-static    ARM target: Link OpenGL ES 2.0 hardware accelerator statically"
+echo "  --gles2dir=PATH          prefix where dgles2 is installed"
 echo ""
 echo "NOTE: The object files are built at the place where configure is launched"
 exit 1
@@ -1629,6 +1640,21 @@ if compile_prog "" "" ; then
   inotify=yes
 fi
 
+inotify1=no
+cat > $TMPC << EOF
+#include <sys/inotify.h>
+
+int
+main(void)
+{
+	/* try to start inotify */
+	return inotify_init1(0);
+}
+EOF
+if compile_prog "" "" ; then
+  inotify1=yes
+fi
+
 # check if utimensat and futimens are supported
 utimens=no
 cat > $TMPC << EOF
@@ -1966,8 +1992,9 @@ echo "Install blobs     $blobs"
 echo "KVM support       $kvm"
 echo "fdt support       $fdt"
 echo "preadv support    $preadv"
-echo "fdatasync         $fdatasync"
-echo "uuid support      $uuid"
+echo "gles2 support     $gles2"
+echo "gles2 static      $gles2_static"
+echo "dgles2 prefix     $gles2dir"
 
 if test $sdl_too_old = "yes"; then
 echo "-> Your SDL version is too old - please upgrade to have SDL support"
@@ -2136,6 +2163,9 @@ fi
 if test "$inotify" = "yes" ; then
   echo "CONFIG_INOTIFY=y" >> $config_host_mak
 fi
+if test "$inotify1" = "yes" ; then
+  echo "CONFIG_INOTIFY1=y" >> $config_host_mak
+fi
 if test "$byteswap_h" = "yes" ; then
   echo "CONFIG_BYTESWAP_H=y" >> $config_host_mak
 fi
@@ -2183,6 +2213,13 @@ fi
 if test "$fdatasync" = "yes" ; then
   echo "CONFIG_FDATASYNC=y" >> $config_host_mak
 fi
+if test "$gles2" = "yes" ; then
+  echo "CONFIG_GLES2=y" >> $config_host_mak
+if test "$gles2_static" = "yes" ; then
+  echo "CONFIG_GLES2_STATIC=y" >> $config_host_mak
+fi
+  echo "DGLES2=$gles2dir" >> $config_host_mak
+fi
 
 # XXX: suppress that
 if [ "$bsd" = "yes" ] ; then
diff --git a/hw/gles2.c b/hw/gles2.c
new file mode 100644
index 0000000..51583e2
--- /dev/null
+++ b/hw/gles2.c
@@ -0,0 +1,764 @@
+/* Copyright (c) 2009-2010 Nokia Corporation
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) any later version of the License.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gles2.h"
+#include <pthread.h>
+
+// From target-arm/helper.c.
+extern int get_phys_addr(CPUState *env, uint32_t address,
+                         int access_type, int is_user,
+                         uint32_t *phys_ptr, int *prot,
+                         target_ulong *page_size);
+
+
+// Information holder for guest->host call.
+struct gles2_Call
+{
+#ifndef NDEBUG
+    char const* name;
+#endif //!NDEBUG
+    gles2_Callback* callback;
+};
+
+// List of calls to used by the kernel module (page 0).
+static gles2_Call const gles2_kcalls[];
+// List of calls used by clients (page 1->15).
+static gles2_Call const gles2_calls[];
+int gles2_quality = 100;
+
+// Translate a target virtual address to physical address.
+static target_ulong gles2_pa(gles2_State *s, target_ulong va,
+    int access_type)
+{
+    target_ulong pa, ps;
+    int prot;
+
+    if (get_phys_addr(s->env, va, access_type, 1, &pa, &prot, &ps)) {
+        GLES2_PRINT("ERROR: Page fault on guest!\n");
+        return 0;
+    }
+    
+    return pa;
+}
+
+int gles2_transfer_compile(gles2_CompiledTransfer* tfr, gles2_State *s,
+    target_ulong va, target_ulong len)
+{
+    tfr->nsections = 0;
+    tfr->sections = 0;
+
+#if (GLES2_DEBUG == 1)
+    target_ulong first_page = TARGET_PAGE(va);
+#endif // GLES2_DEBUG == 1
+
+    target_ulong last_page = TARGET_PAGE(va + len - 1);
+    target_ulong start_addr = va;
+
+    GLES2_PRINT("DEBUG: Compiling transfer of %d bytes at 0x%x (0x%x->0x%x).\n",
+        len, va, first_page, last_page);
+
+    // Loop through the pages.
+    while (len) {
+        target_ulong start_page = TARGET_PAGE(start_addr);
+        target_ulong start_pa = gles2_pa(s, start_page, 0);
+        target_ulong end_pa = start_pa;
+
+        // Solve length of continuous section.
+        target_ulong end_page = start_page;
+        while(end_page < last_page) {
+            target_ulong next_page = end_page + TARGET_PAGE_SIZE;
+            target_ulong next_pa = gles2_pa(s, next_page, 0);
+
+            // If the target pages are not linearly spaced, stop..
+            if((next_pa < start_pa) ||
+                (next_pa - start_pa > next_page - start_page)) {
+                break;
+            }
+
+            end_page = next_page;
+            end_pa = next_pa;
+        }
+
+        unsigned id = tfr->nsections++;
+
+        GLES2_PRINT("\tContinuous from 0x%x to 0x%x (0x%x to 0x%x) #%d.\n",
+	        start_page, end_page, start_pa, end_pa, id);
+        tfr->sections = realloc(tfr->sections,
+            tfr->nsections*sizeof(*(tfr->sections)));
+
+        target_phys_addr_t pages_len = end_page + TARGET_PAGE_SIZE - start_page;
+        void* target_pages = cpu_physical_memory_map(start_pa, &pages_len, 0);
+
+        if (!target_pages || !pages_len) {
+            fprintf(stderr, "ERROR: Failed to map memory to host!\n");
+            return 0;
+        }
+
+        target_ulong section_len = end_page + TARGET_PAGE_SIZE - start_addr;
+
+        if (section_len > len) {
+            section_len = len;
+        }
+
+        target_ulong offset = TARGET_OFFSET(start_addr);
+        void* target_data = target_pages + offset;
+
+        GLES2_PRINT("\tSlice of %d bytes at %p (offset = %x).\n",
+            section_len, target_data, offset);
+
+        tfr->sections[id].base = target_data;
+        tfr->sections[id].len = section_len;
+
+        cpu_physical_memory_unmap(target_pages, pages_len, 0, pages_len);
+        len -= section_len;
+        start_addr += section_len;
+        GLES2_PRINT("\t%d bytes remain...\n", len);
+    }
+    return 1;
+}
+
+void gles2_transfer_exec(gles2_CompiledTransfer* tfr, gles2_State *s,
+    void* data, int access_type)
+{
+    unsigned i;
+
+    for (i = 0; i < tfr->nsections; ++i) {
+        void* target_data = tfr->sections[i].base;
+        target_ulong len = tfr->sections[i].len;
+        if (access_type == 0) {
+            memcpy(data, target_data, len);
+        } else {
+            memcpy(target_data, data, len);
+        }
+        data += len;
+    }
+}
+
+void gles2_transfer_free(gles2_CompiledTransfer* tfr)
+{
+    free(tfr->sections);
+    tfr->sections = 0;
+    tfr->nsections = 0;
+}
+
+int gles2_transfer(gles2_State *s, target_ulong va, target_ulong len,
+    void* data, int access_type)
+{
+#if (GLES2_DEBUG == 1)
+    target_ulong first_page = TARGET_PAGE(va);
+#endif // GLES2_DEBUG == 1
+
+    target_ulong last_page = TARGET_PAGE(va + len - 1);
+    target_ulong start_addr = va;
+
+    GLES2_PRINT("DEBUG: Request transfer of %d bytes at 0x%x (0x%x->0x%x) (access=%d).\n",
+        len, va, first_page, last_page, access_type);
+
+    // Loop through the pages.
+    while (len) {
+        target_ulong start_page = TARGET_PAGE(start_addr);
+        target_ulong start_pa = gles2_pa(s, start_page, access_type);
+        target_ulong end_pa = start_pa;
+
+        // Solve length of continuous section.
+        target_ulong end_page = start_page;
+        while(end_page < last_page) {
+            target_ulong next_page = end_page + TARGET_PAGE_SIZE;
+            target_ulong next_pa = gles2_pa(s, next_page, access_type);
+
+            // If the target pages are not linearly spaced, stop..
+            if ((next_pa < start_pa) ||
+                (next_pa - start_pa > next_page - start_page)) {
+                break;
+            }
+
+            end_page = next_page;
+            end_pa = next_pa;
+        }
+
+        GLES2_PRINT("\tContinuous from 0x%x to 0x%x (0x%x to 0x%x).\n",
+            start_page, end_page, start_pa, end_pa);
+
+        target_phys_addr_t pages_len = end_page + TARGET_PAGE_SIZE - start_page;
+        void* target_pages = cpu_physical_memory_map(start_pa, &pages_len, access_type);
+        if (!target_pages || !pages_len) {
+            GLES2_PRINT("ERROR: Failed to map memory to host!\n");
+            return 0;
+        }
+
+        target_ulong section_len = end_page + TARGET_PAGE_SIZE - start_addr;
+        target_ulong offset = TARGET_OFFSET(start_addr);
+        void* target_data = target_pages + offset;
+
+        if (section_len > len) {
+            section_len = len;
+        }
+
+        GLES2_PRINT("\tTransfering %d bytes at %p (offset = %x).\n",
+            section_len, target_data, offset);
+
+        if (access_type == 0) {
+            memcpy(data, target_data, section_len);
+        } else {
+            memcpy(target_data, data, section_len);
+        }
+
+        cpu_physical_memory_unmap(target_pages, pages_len, access_type, pages_len);
+        len -= section_len;
+        start_addr += section_len;
+        data += section_len;
+        
+        GLES2_PRINT("\t%d bytes remain...\n", len);
+    }
+    return 1;
+}
+
+void gles2_put_byte(gles2_State *s, target_ulong va, uint8_t byte)
+{
+    int prot;
+    target_ulong pa, ps;
+
+    if (get_phys_addr(s->env, va, 1, 1, &pa, &prot, &ps)) {
+        GLES2_PRINT("ERROR: Memory mapping failed for 0x%x!\n", va);
+        return;
+    }
+
+    GLES2_PRINT("DEBUG: Written 0x%x to 0x%x(0x%x)\n", byte, va, pa);
+    stb_phys(pa, byte);
+}
+
+uint8_t gles2_get_byte(gles2_State *s, target_ulong va)
+{
+    uint8_t byte;
+    int prot;
+    target_ulong pa, ps;
+
+    if (get_phys_addr(s->env, va, 0, 1, &pa, &prot, &ps)) {
+        GLES2_PRINT("ERROR: Memory mapping failed for 0x%x!\n", va);
+        return 0xDE;
+    }
+
+    byte = ldub_phys(pa);
+
+    GLES2_PRINT("DEBUG: Read 0x%x from 0x%x(0x%x)\n", byte, va, pa);
+
+    return byte;
+}
+
+void gles2_put_word(gles2_State *s, target_ulong va, uint16_t word)
+{
+    int prot;
+    target_ulong pa, ps;
+
+    if (get_phys_addr(s->env, va, 1, 1, &pa, &prot, &ps)) {
+        GLES2_PRINT("ERROR: Memory mapping failed for 0x%x!\n", va);
+        return;
+    }
+
+    GLES2_PRINT("DEBUG: Written 0x%x to 0x%x(0x%x)\n", word, va, pa);
+    stw_phys(pa, word);
+}
+
+uint16_t gles2_get_word(gles2_State *s, target_ulong va)
+{
+    uint16_t word;
+
+    int prot;
+    target_ulong pa, ps;
+    if (get_phys_addr(s->env, va, 0, 1, &pa, &prot, &ps)) {
+        GLES2_PRINT("ERROR: Memory mapping failed for 0x%x!\n", va);
+        return 0xDEAD;
+    }
+
+    word = lduw_phys(pa);
+
+    GLES2_PRINT("DEBUG: Read 0x%x from 0x%x(0x%x)\n", word, va, pa);
+
+    return word;
+}
+
+uint32_t gles2_get_dword(gles2_State *s, target_ulong va)
+{
+    uint32_t dword;
+    int prot;
+    target_ulong pa, ps;
+
+    if (get_phys_addr(s->env, va, 0, 1, &pa, &prot, &ps)) {
+        GLES2_PRINT("ERROR: Memory mapping failed for 0x%x!\n", va);
+        return 0xDEAD000;
+    }
+
+    dword = ldl_phys(pa);
+
+    GLES2_PRINT("DEBUG: Read 0x%x from 0x%x(0x%x)\n", dword, va, pa);
+
+    return dword;
+}
+
+void gles2_put_dword(gles2_State *s, target_ulong va, uint32_t dword)
+{
+    int prot;
+    target_ulong pa, ps;
+
+    if (get_phys_addr(s->env, va, 1, 1, &pa, &prot, &ps)) {
+        GLES2_PRINT("ERROR: Memory mapping failed for 0x%x!\n", va);
+        return;
+    }
+
+    GLES2_PRINT("DEBUG: Written 0x%x to 0x%x(0x%x)\n", dword, va, pa);
+    stl_phys(pa, dword);
+}
+
+float gles2_get_float(gles2_State *s, target_ulong va)
+{
+    float flt;
+    int prot;
+    target_ulong pa, ps;
+
+    if (get_phys_addr(s->env, va, 0, 1, &pa, &prot, &ps)) {
+        GLES2_PRINT("ERROR: Memory mapping failed for 0x%x!\n", va);
+        return -123456789.f;
+    }
+
+    cpu_physical_memory_read(pa, (unsigned char*)&flt, 4);
+    //	flt = ldfl_p(pa);
+
+    GLES2_PRINT("DEBUG: Read %f from 0x%x(0x%x)\n", flt, va, pa);
+
+    return flt;
+}
+
+void gles2_put_float(gles2_State *s, target_ulong va, float flt)
+{
+    int prot;
+    target_ulong pa, ps;
+
+    if (get_phys_addr(s->env, va, 1, 1, &pa, &prot, &ps)) {
+        GLES2_PRINT("ERROR: Memory mapping failed for 0x%x!\n", va);
+        return;
+    }
+
+    GLES2_PRINT("DEBUG: Written %f to 0x%x(0x%x)\n", flt, va, pa);
+    cpu_physical_memory_write(pa, (unsigned char*)&flt, 4);
+    //	stfl_p(pa, flt);
+}
+
+uint32_t gles2_handle_create(gles2_State *s, void* data)
+{
+    uint32_t i = 0;
+
+    if (data) {
+        for (i = 0; i < GLES2_NHANDLES; ++i) {
+            if (!s->handles[i]) {
+                break;
+            }
+        }
+
+        if (i == GLES2_NHANDLES) {
+            fprintf(stderr, "ERROR: No free handles!\n");
+            return 0x0;
+        }
+        s->handles[i] = data;
+        i |= GLES2_HANDLE_BASE;
+    }
+
+    GLES2_PRINT("Handle %x created for %p!\n", i, data);
+    return i;
+}
+
+uint32_t gles2_handle_find(gles2_State *s, void* data)
+{
+    uint32_t i = 0;
+
+    if (data) {
+        for(i = 0; i < GLES2_NHANDLES; ++i) {
+            if(s->handles[i] == data) {
+                break;
+            }
+        }
+
+        if (i == GLES2_NHANDLES) {
+//            fprintf(stderr, "ERROR: Handle not found!\n");
+            return 0x0;
+        }
+        i |= GLES2_HANDLE_BASE;
+    }
+
+    GLES2_PRINT("Handle %x found for %p!\n", i, data);
+    return i;
+}
+
+void* gles2_handle_get(gles2_State *s, uint32_t i)
+{
+#if(GLES2_DEBUG == 1)
+	if(i && (i & ~GLES2_HANDLE_MASK) != GLES2_HANDLE_BASE)
+	{
+		GLES2_PRINT("ERROR: Invalid handle %x!\n", i);
+		exit(1);
+	}
+#endif // GLES2_DEBUG == 1
+
+    void* data = i ? s->handles[i & GLES2_HANDLE_MASK] : NULL;
+
+    GLES2_PRINT("Reading handle %x => %p\n", i, data);
+
+    return data;
+}
+
+void* gles2_handle_free(gles2_State *s, uint32_t i)
+{
+    void* data = NULL;
+    if (i) {
+        data = s->handles[i & GLES2_HANDLE_MASK];
+        s->handles[i & GLES2_HANDLE_MASK] = NULL;
+    }
+
+    GLES2_PRINT("Freed handle %i => %p\n", i, data);
+    return data;
+}
+
+// Virtual register area write operation handler.
+static void gles2_write(void *opaque, target_phys_addr_t addr, uint32_t value)
+{
+    gles2_State *s = (gles2_State*)opaque;
+
+    target_ulong page = addr/(4*TARGET_PAGE_SIZE);
+    target_ulong callnr = TARGET_OFFSET(addr)/0x04;
+
+    GLES2_PRINT("Page %d write (call nr. %d).\n", page, callnr);
+
+    if (page) {
+        gles2_Call const *call = gles2_calls + callnr;
+	
+        if (page > 1) {
+            gles2_Client* client;
+ 
+            // Client API calls without active context should be ignored.
+            if ((page - 2 > GLES2_NCLIENTS) ||
+		!(client = s->clients[page - 2])) {
+                return;
+            }
+            
+            // Make sure nothing is running.
+            GLES2_PRINT("Syncing with worker...\n");
+            pthread_mutex_lock(&client->mutex_wait);
+            while (client->state != gles2_ClientState_ready) {
+                pthread_cond_wait(&client->cond_state, &client->mutex_wait);
+            }
+            pthread_mutex_lock(&client->mutex_run);
+            pthread_mutex_lock(&client->mutex_xcode);
+            client->call = call;
+            client->state = gles2_ClientState_pending;
+            client->phase_xcode = 0;
+            GLES2_PRINT("Requesting call %s...\n", call->name);
+            pthread_cond_signal(&client->cond_start);
+            pthread_mutex_unlock(&client->mutex_wait);
+
+            GLES2_PRINT("Releasing worker for decoding...\n");
+            pthread_mutex_unlock(&client->mutex_run);
+            do {
+                pthread_cond_wait(&client->cond_xcode, &client->mutex_xcode);
+            } while (client->phase_xcode < 1);
+
+            pthread_mutex_unlock(&client->mutex_xcode);
+            GLES2_PRINT("Decoding finished.\n");
+        } else {
+            gles2_decode_t d = 0;
+            GLES2_PRINT("Calling clientless function %s...\n", call->name);
+            call->callback(s, &d, 0);
+        }
+    } else {
+        gles2_Call const *call = gles2_kcalls + callnr;
+        gles2_decode_t d = 0;
+
+        GLES2_PRINT("Calling kernel function %s...\n", call->name);
+
+        call->callback(s, &d, 0);
+    }
+}
+
+// Virtual register area read operation handler.
+static uint32_t gles2_read(void *opaque, target_phys_addr_t addr)
+{
+    gles2_State *s = (gles2_State*)opaque;
+
+    target_ulong page = addr/(4*TARGET_PAGE_SIZE);
+
+    if (page) {
+        gles2_Client* client;
+ 
+        // Client API calls without active context should be ignored.
+        if ((page - 2 > GLES2_NCLIENTS) ||
+            !(client = s->clients[page - 2])) {
+            return 0;
+        }
+
+        if(pthread_mutex_trylock(&client->mutex_xcode)) {
+            return 1;
+        }
+                
+        if(client->phase_xcode == 2) {
+            while (client->phase_xcode < 4) {
+                client->phase_xcode = 3;
+                pthread_cond_signal(&client->cond_return);
+                pthread_cond_wait(&client->cond_xcode, &client->mutex_xcode);
+            } 
+            pthread_mutex_unlock(&client->mutex_xcode);
+            return 0;
+        }
+        int ret = (client->phase_xcode == 4 ? 0 : 1);
+        pthread_mutex_unlock(&client->mutex_xcode);
+        return ret;
+    }
+    return 0;
+}
+
+static CPUReadMemoryFunc *gles2_readfn[] = {
+    gles2_read,
+    gles2_read,
+    gles2_read,
+};
+
+static CPUWriteMemoryFunc *gles2_writefn[] = {
+    gles2_write,
+    gles2_write,
+    gles2_write,
+};
+
+// Initializes a new gles2 device.
+void *gles2_init(CPUState *env)
+{
+    gles2_State *s = qemu_malloc(sizeof(*s));
+    unsigned i;
+
+    s->env = env;
+    s->quality = gles2_quality;
+
+    GLES2_PRINT("GLES2 quality: %d\n", s->quality);
+    
+    cpu_register_physical_memory(GLES2_HWBASE,
+        GLES2_HWSIZE,
+        cpu_register_io_memory(gles2_readfn,
+            gles2_writefn, s));
+
+    for (i = 0; i < GLES2_NCLIENTS; ++i) {
+        s->clients[i] = NULL;
+    }
+
+    for (i = 0; i < GLES2_NHANDLES; ++i) {
+        s->handles[i] = NULL;
+    }
+
+    GLES2_PRINT("Registered IO memory!\n");
+    return s;
+}
+
+/******************************************************************************
+ *
+ * Kernel interface functions.
+ *
+ *****************************************************************************/
+
+static void* gles2_client_worker(void *opaque)
+{
+    gles2_Client *client = opaque;
+    int run = 1;
+
+    GLES2_PRINT("WORKER(%d): Starting!\n", client->nr);
+
+    pthread_mutex_lock(&client->mutex_xcode);
+    do
+    {
+        gles2_decode_t d = 0;
+        GLES2_PRINT("WORKER(%d): Waiting for call...\n", client->nr);
+        pthread_mutex_lock(&client->mutex_wait);
+
+        client->state = gles2_ClientState_ready;
+        pthread_cond_signal(&client->cond_state);
+        client->phase_xcode = 4;
+        pthread_cond_signal(&client->cond_xcode);
+        pthread_mutex_unlock(&client->mutex_xcode);
+        while (client->state != gles2_ClientState_pending) {
+            pthread_cond_wait(&client->cond_start, &client->mutex_wait);
+        }
+
+        GLES2_PRINT("WORKER(%d): Got call, waiting permission to run...\n", client->nr);
+
+        pthread_mutex_lock(&client->mutex_run);
+        GLES2_PRINT("WORKER(%d): Running!\n", client->nr);
+        client->state = gles2_ClientState_running;
+        pthread_mutex_unlock(&client->mutex_wait);
+
+        if (client->call) {
+            GLES2_PRINT("WORKER(%d): Calling function %s (%p)...\n",
+                        client->nr, client->call->name, client->call);
+            client->call->callback(client->s, &d, client);
+
+            GLES2_PRINT("\tWORKER(%d): Done.\n", client->nr);
+            client->state = gles2_ClientState_done;
+        } else {
+            GLES2_PRINT("WORKER(%d): Exit requested!\n", client->nr);
+            run = 0;
+            client->state = gles2_ClientState_exit;
+            pthread_cond_signal(&client->cond_state);
+        }
+        pthread_mutex_unlock(&client->mutex_run);
+    } while (run);
+    
+    GLES2_PRINT("WORKER(%d): Exiting!\n", client->nr);
+    return opaque;
+}
+
+// Called by kernel module when a new client connects.
+static void gles2_init_cb(gles2_State *s, gles2_decode_t *d, gles2_Client *c)
+{
+    unsigned i;
+    gles2_Client *client;
+    pthread_attr_t attr;
+
+    for (i = 0; i < GLES2_NCLIENTS; ++i) {
+        if (!s->clients[i]) {
+            break;
+        }
+    }
+
+    if (i == GLES2_NCLIENTS) {
+        GLES2_PRINT("ERROR: No free slots!\n");
+        gles2_ret_dword(s, 0);
+        return;
+    }
+
+    GLES2_PRINT("Initialization!\n");
+
+    client = malloc(sizeof(*client));
+    client->s = s;
+    client->nr = i + 1;
+    pthread_mutex_init(&client->mutex_wait, NULL);
+    pthread_mutex_init(&client->mutex_run, NULL);
+    pthread_mutex_init(&client->mutex_xcode, NULL);
+    pthread_cond_init(&client->cond_start, NULL);
+    pthread_cond_init(&client->cond_state, NULL);
+    pthread_cond_init(&client->cond_xcode, NULL);
+    pthread_cond_init(&client->cond_return, NULL);
+    client->state = gles2_ClientState_init;
+    pthread_attr_init(&attr);
+    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
+
+    pthread_mutex_lock(&client->mutex_wait);
+
+    GLES2_PRINT("Creating worker...\n");
+    pthread_create(&client->thread, &attr, gles2_client_worker, client);
+
+    do {
+        pthread_cond_wait(&client->cond_state, &client->mutex_wait);
+    } while(client->state != gles2_ClientState_ready);
+    pthread_mutex_unlock(&client->mutex_wait);
+
+    GLES2_PRINT("Worker initialized\n");
+
+    s->clients[i] = client;
+    gles2_ret_dword(s, client->nr);
+}
+
+// Called by kernel module when an existing client disconnects.
+static void gles2_exit_cb(gles2_State *s, gles2_decode_t *d, gles2_Client *c)
+{
+    uint32_t nr = gles2_arg_dword(s, d);
+    gles2_Client *client;
+
+    GLES2_PRINT("Exit called for client %d!\n", nr);
+    
+    if ((nr > GLES2_NCLIENTS + 1) ||
+	(nr == 0)) {
+        GLES2_PRINT("Client number out of range!\n");
+	return;	
+    }  else {
+        client = s->clients[nr - 1];
+    }
+    
+    if (!client) {
+        GLES2_PRINT("Can't exit NULL client!\n");
+        return;
+    }
+
+    GLES2_PRINT("\tRequesting worker to exit.\n");
+
+    // Make sure nothing is running.
+    GLES2_PRINT("Syncing with worker...\n");
+    pthread_mutex_lock(&client->mutex_wait);
+    while (client->state != gles2_ClientState_ready) {
+        pthread_cond_wait(&client->cond_state, &client->mutex_wait);
+    }
+    pthread_mutex_lock(&client->mutex_run);
+    client->call = NULL;
+    client->state = gles2_ClientState_pending;
+    GLES2_PRINT("Requesting exit...\n");
+    pthread_cond_signal(&client->cond_start);
+    pthread_mutex_unlock(&client->mutex_wait);
+
+    GLES2_PRINT("Waiting worker to exit...\n");
+    do {
+        pthread_cond_wait(&client->cond_state, &client->mutex_run);
+    } while (client->state != gles2_ClientState_exit);
+    pthread_mutex_unlock(&client->mutex_run);
+
+    GLES2_PRINT("\tJoining...\n");
+    pthread_join(client->thread, NULL);
+    pthread_mutex_destroy(&client->mutex_wait);
+    pthread_mutex_destroy(&client->mutex_run);
+    pthread_cond_destroy(&client->cond_start);
+    pthread_cond_destroy(&client->cond_state);
+
+    free(client);
+    s->clients[nr - 1] = NULL;
+
+    GLES2_PRINT("\tDone!\n");
+}
+
+/******************************************************************************
+ *
+ * Call tables
+ *
+ *****************************************************************************/
+
+/* Make a weak stub for every dummy function. */
+#define CALL_DUMMY(func) \
+    void gles2_##func##_cb(gles2_State *s, gles2_decode_t *d, struct gles2_Client *c); \
+    void gles2_##func##_cb(gles2_State *s, gles2_decode_t *d, struct gles2_Client *c) \
+    { \
+        fprintf(stderr, "GLES2: DUMMY " #func "\n"); \
+    }
+
+#define CALL_ENTRY(func) \
+    void gles2_##func##_cb(gles2_State *s, gles2_decode_t *d, struct gles2_Client *c);
+
+#include "gles2_calls.h"
+
+#undef CALL_ENTRY
+#undef CALL_DUMMY
+
+#define CALL_ENTRY(func) { #func, gles2_##func##_cb },
+#define CALL_DUMMY(func) { #func, gles2_##func##_cb },
+
+static gles2_Call const gles2_kcalls[] =
+{
+    CALL_ENTRY(init)
+    CALL_ENTRY(exit)
+};
+
+static gles2_Call const gles2_calls[] =
+{
+    { "<<none>>", 0 },
+#include "gles2_calls.h"
+};
+
+#undef CALL_ENTRY
+
diff --git a/hw/gles2.h b/hw/gles2.h
new file mode 100644
index 0000000..424d4ce
--- /dev/null
+++ b/hw/gles2.h
@@ -0,0 +1,323 @@
+/* Copyright (c) 2009-2010 Nokia Corporation
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) any later version of the License.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GLES2_H__
+#define GLES2_H__
+
+#include "qemu-common.h"
+#include "cpu.h"
+#include <pthread.h>
+
+#define GLES2_HWBASE 0x4f000000
+#define GLES2_HWSIZE 0x00100000
+#define GLES2_NCLIENTS (GLES2_HWSIZE/TARGET_PAGE_SIZE - 2)
+#define GLES2_NHANDLES GLES2_NCLIENTS * 16
+// Address base for host to guest pointer handles.
+#define GLES2_HANDLE_BASE 0xCAFE0000 
+#define GLES2_HANDLE_MASK 0x0000FFFF // Handle to index bitmask.
+
+// Round address to lower page boundary.
+#define TARGET_PAGE(addr) ((addr) & ~(TARGET_PAGE_SIZE - 1))
+// Return the page offset part of address.
+#define TARGET_OFFSET(addr) ((addr) & (TARGET_PAGE_SIZE - 1))
+
+#define GLES2_DEBUG 0
+#if(GLES2_DEBUG == 1)
+#   define GLES2_PRINT(format, args...) \
+        fprintf(stderr, "GLES2: " format, ##args)
+#else
+#   define GLES2_PRINT(format, args...) (void)0
+#endif // GLES_DEBUG != 1
+
+struct gles2_Array;
+typedef struct gles2_Array gles2_Array;
+struct gles2_Call;
+typedef struct gles2_Call gles2_Call;
+struct gles2_State;
+typedef struct gles2_State gles2_State;
+
+typedef enum gles2_ClientState
+{
+    gles2_ClientState_init,
+    gles2_ClientState_ready,
+    gles2_ClientState_pending,
+    gles2_ClientState_running,
+    gles2_ClientState_done,
+    gles2_ClientState_exit
+} gles2_ClientState;
+
+// A connected GLES2 client.
+typedef struct gles2_Client
+{
+    gles2_State *s;     // Link to the device state the client was connected to.
+    target_ulong nr;    // The client ID from kernel.
+
+    gles2_Call const *call;     // Next/current call to perform.
+    pthread_t thread;           // The worker thread.
+    pthread_cond_t cond_start;  // To wake worker for a call.
+    pthread_cond_t cond_state;  // Worker signals when state changes.
+    volatile gles2_ClientState state;// Status of the client.
+    pthread_mutex_t mutex_run;  // Locked when thread is running, or is
+                                // prevented from doing so.
+    pthread_mutex_t mutex_wait; // For synchronization of worker and caller.
+
+    pthread_mutex_t mutex_xcode; // For decode/encode synchronization.
+    volatile int phase_xcode;    // Phase of call 0: pending 1: decode done
+                                 // 2: exec done 3: encode done
+    pthread_cond_t cond_xcode;   // --''--
+    pthread_cond_t cond_return;  // --''--
+
+    gles2_Array *arrays;        // Host side vertex pointer arrays.
+    int narrays;                // Number of arrays (the maximum too).
+} gles2_Client;
+
+// The GLES2 device state holder.
+struct gles2_State
+{
+    CPUState *env;                         // The CPU the device is attached to.
+    gles2_Client *clients[GLES2_NCLIENTS]; // Array of clients.
+    void *handles[GLES2_NHANDLES];         // Handles passed from host to guest.
+    int quality;                           // Rendering quality.
+};
+
+typedef unsigned int gles2_decode_t; // Function call decoding state.
+typedef uint32_t gles2_target_arg_t; // Target unit argument type.
+// Callback for register area access. 
+typedef void gles2_Callback(gles2_State *s, gles2_decode_t *d,
+    gles2_Client *c); 
+
+// Create and initialize a GLES2 device and attach to CPU.
+extern void *gles2_init(CPUState *env);
+// Rendering quality option.
+extern int gles2_quality;
+
+/******************************************************************************
+ *
+ * Guest memory continuous access functions
+ *
+ *****************************************************************************/
+
+// Holder for compiled transfer holder.
+typedef struct gles2_CompiledTransfer
+{
+    unsigned nsections;   // Number of physical memory sections in the transfer.
+    struct
+    {
+        char* base;       // Base address of the section.
+        target_ulong len; // Length of the section.
+    } *sections;          // Sections of the transfer.
+} gles2_CompiledTransfer;
+
+// Pre-compile a transfer to or from virtual guest address.
+// NOTE: An assumption is made that the mapping is equal for read and write, for
+//       complicated transfers, use gles2_transfer or Fix It!
+extern int  gles2_transfer_compile(gles2_CompiledTransfer *tfr, gles2_State *s,
+    target_ulong va, target_ulong len);
+// Execute a pre-compiled transfer.
+extern void gles2_transfer_exec(gles2_CompiledTransfer *tfr, gles2_State *s,
+    void* data, int access_type);
+// Free a pre-compiled transfer.
+extern void gles2_transfer_free(gles2_CompiledTransfer *tfr);
+// Perform a non-compiled transfer between guest and host.
+// access_type, read = 0, write = 1, execute = 2
+extern int  gles2_transfer(gles2_State *s, target_ulong va, target_ulong len,
+    void* data, int access_type);
+
+/******************************************************************************
+ *
+ * Guest memory random access functions
+ *
+ *****************************************************************************/
+
+// Read an 8-bit byte from target system memory.
+extern uint8_t  gles2_get_byte(gles2_State *s, target_ulong va);
+// Write an 8-bit byte to target system memory.
+extern void     gles2_put_byte(gles2_State *s, target_ulong va, uint8_t byte);
+// Read a 16-bit word from target system memory.
+extern uint16_t gles2_get_word(gles2_State *s, target_ulong va);
+// Write a 16-bit word to target system memory.
+extern void     gles2_put_word(gles2_State *s, target_ulong va, uint16_t word);
+// Read a 32-bit double word from target system memory.
+extern uint32_t gles2_get_dword(gles2_State *s, target_ulong va);
+// Write a 32-bit double word to target system memory.
+extern void     gles2_put_dword(gles2_State *s, target_ulong va,
+    uint32_t dword);
+// Read a 32-bit float from target system memory.
+extern float    gles2_get_float(gles2_State *s, target_ulong va);
+// Write a 32-bit float to target system memory.
+extern void     gles2_put_float(gles2_State *s, target_ulong va, float flt);
+
+#define gles2_put_handle(s, va, handle) gles2_put_dword(s, va, handle)
+#define gles2_get_handle(s, va) gles2_get_dword(s, va)
+
+/******************************************************************************
+ *
+ * Handle management functions
+ *
+ *****************************************************************************/
+
+// Create a handle from host side pointer to be passed to guest.
+extern uint32_t gles2_handle_create(gles2_State *s, void* data);
+// Find if there is previously created handle for pointer.
+extern uint32_t gles2_handle_find(gles2_State *s, void* data);
+// Get the host pointer by guest handle.
+extern void* gles2_handle_get(gles2_State *s, uint32_t i);
+// Release a handle for reuse.
+extern void* gles2_handle_free(gles2_State *s, uint32_t i);
+
+// Get the smallest function argument according to target CPU ABI.
+static inline gles2_target_arg_t gles2_arg_raw(gles2_State *s, unsigned i);
+static inline gles2_target_arg_t gles2_arg_raw(gles2_State *s, unsigned i)
+{
+    if (i < 4) {
+        return s->env->regs[i];
+    }
+    
+    return gles2_get_dword(s, s->env->regs[13] + 2*0x04 + (i - 4)*0x04);
+}
+
+/******************************************************************************
+ *
+ * ABI function argument decoding functions
+ *
+ *****************************************************************************/
+
+#define GLES2_DEBUG_ARGS 1
+static inline uint8_t gles2_arg_byte(gles2_State *s, gles2_decode_t *d);
+static inline uint8_t gles2_arg_byte(gles2_State *s, gles2_decode_t *d)
+{
+    uint8_t byte = gles2_arg_raw(s, (*d)++) & 0xFF;
+#if (GLES2_DEBUG_ARGS == 1)
+    GLES2_PRINT("byte arg(%d) = %x\n", *d - 1, byte);
+#endif
+    return byte;
+}
+
+static inline uint16_t gles2_arg_word(gles2_State *s, gles2_decode_t *d);
+static inline uint16_t gles2_arg_word(gles2_State *s, gles2_decode_t *d)
+{
+    uint16_t word = gles2_arg_raw(s, (*d)++) & 0xFFFF;
+#if (GLES2_DEBUG_ARGS == 1)
+    GLES2_PRINT("word arg(%d) = %x\n", *d - 1, word);
+#endif
+    return word;
+}
+
+static inline uint32_t gles2_arg_dword(gles2_State *s, gles2_decode_t *d);
+static inline uint32_t gles2_arg_dword(gles2_State *s, gles2_decode_t *d)
+{
+    uint32_t dword = gles2_arg_raw(s, (*d)++);
+#if (GLES2_DEBUG_ARGS == 1)
+    GLES2_PRINT("dword arg(%d) = %x\n", *d - 1, dword);
+#endif
+    return dword;
+}
+
+static inline uint64_t gles2_arg_qword(gles2_State *s, gles2_decode_t *d);
+static inline uint64_t gles2_arg_qword(gles2_State *s, gles2_decode_t *d)
+{
+    uint64_t qword = gles2_arg_raw(s, (*d)++)
+        | ((uint64_t)gles2_arg_raw(s, (*d)++) << 32);
+#if (GLES2_DEBUG_ARGS == 1)
+    GLES2_PRINT("qword arg(%d) = %"PRIu64"\n", *d - 2, qword);
+#endif
+    return qword;
+}
+
+static inline uint32_t gles2_arg_handle(gles2_State *s, gles2_decode_t *d);
+static inline uint32_t gles2_arg_handle(gles2_State *s, gles2_decode_t *d)
+{
+    uint32_t handle = gles2_arg_raw(s, (*d)++);
+
+#if (GLES2_DEBUG_ARGS == 1)
+    GLES2_PRINT("handle arg(%d) = %x\n", *d - 1, handle);
+#endif
+    return handle;
+}
+
+// This needs to be its own special function, because we must preserve the byteorder.
+static inline float gles2_arg_float(gles2_State *s, gles2_decode_t *d);
+static inline float gles2_arg_float(gles2_State *s, gles2_decode_t *d)
+{
+    unsigned i = (*d)++;
+
+    if (i < 4) {
+        return *((float*)&s->env->regs[i]);
+    }
+    
+    return gles2_get_float(s, s->env->regs[13] + 2*0x04 + (i - 4)*0x04);
+}
+
+/******************************************************************************
+ *
+ * ABI return value encoding functions
+ *
+ *****************************************************************************/
+
+static inline void gles2_ret_byte(gles2_State *s, uint8_t byte);
+static inline void gles2_ret_byte(gles2_State *s, uint8_t byte)
+{
+    s->env->regs[0] = byte;
+#if (GLES2_DEBUG_ARGS == 1)
+    GLES2_PRINT("byte ret = %d\n", byte);
+#endif
+}
+
+static inline void gles2_ret_word(gles2_State *s, uint16_t word);
+static inline void gles2_ret_word(gles2_State *s, uint16_t word)
+{
+    s->env->regs[0] = word;
+#if (GLES2_DEBUG_ARGS == 1)
+    GLES2_PRINT("word ret = %d\n", word);
+#endif
+}
+
+static inline void gles2_ret_dword(gles2_State *s, uint32_t dword);
+static inline void gles2_ret_dword(gles2_State *s, uint32_t dword)
+{
+    s->env->regs[0] = dword;
+#if (GLES2_DEBUG_ARGS == 1)
+    GLES2_PRINT("dword ret = %d\n", dword);
+#endif
+}
+
+static inline void gles2_ret_qword(gles2_State *s, uint64_t qword);
+static inline void gles2_ret_qword(gles2_State *s, uint64_t qword)
+{
+    s->env->regs[0] = qword & 0xFFFFFFFF;
+    s->env->regs[1] = qword >> 32;
+#if (GLES2_DEBUG_ARGS == 1)
+    GLES2_PRINT("qword ret = %"PRIu64"\n", qword);
+#endif
+}
+
+static inline void gles2_ret_handle(gles2_State *s, uint32_t handle);
+static inline void gles2_ret_handle(gles2_State *s, uint32_t handle)
+{
+    s->env->regs[0] = handle;
+
+#if (GLES2_DEBUG_ARGS == 1)
+    GLES2_PRINT("handle ret = %x\n", handle);
+#endif
+}
+
+static inline void gles2_ret_float(gles2_State *s, float flt);
+static inline void gles2_ret_float(gles2_State *s, float flt)
+{
+    s->env->regs[0] = *(uint32_t*)&flt;
+
+#if (GLES2_DEBUG_ARGS == 1)
+    GLES2_PRINT("float ret = %f\n", flt);
+#endif
+}
+
+#endif // GLES2_H__
+
diff --git a/hw/gles2_calls.c b/hw/gles2_calls.c
new file mode 100644
index 0000000..e4bad5b
--- /dev/null
+++ b/hw/gles2_calls.c
@@ -0,0 +1,2409 @@
+/* Copyright (c) 2009-2010 Nokia Corporation
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) any later version of the License.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gles2.h"
+#include "EGL/degl.h"
+#include "GLES2/gl2.h"
+
+// Automatically create the prototype and function definition.
+#define GLES2_CB(FUNC) \
+    void gles2_##FUNC##_cb(gles2_State *s, \
+        gles2_decode_t *d, gles2_Client *c); \
+    void gles2_##FUNC##_cb(gles2_State *s, \
+        gles2_decode_t *d, gles2_Client *c)
+
+// Sizes of primitive types in the ABI.
+#define GLES2_HTYPE_byte uint8_t
+#define GLES2_HTYPE_word uint16_t
+#define GLES2_HTYPE_dword uint32_t
+#define GLES2_HTYPE_float float
+#define GLES2_HTYPE_handle uint32_t
+
+// Defines shorthands for handling types.
+#define GLES2_TYPE(TYPE, SIZE) \
+    typedef GLES2_HTYPE_##SIZE TYPE; \
+    inline void gles2_ret_##TYPE(gles2_State *s, TYPE value); \
+    inline void gles2_ret_##TYPE(gles2_State *s, TYPE value) \
+    { gles2_ret_##SIZE(s, value); } \
+    inline void gles2_put_##TYPE(gles2_State *s, target_ulong va, TYPE value); \
+    inline void gles2_put_##TYPE(gles2_State *s, target_ulong va, TYPE value) \
+    { gles2_put_##SIZE(s, va, value); } \
+    inline TYPE gles2_get_##TYPE(gles2_State *s, target_ulong va); \
+    inline TYPE gles2_get_##TYPE(gles2_State *s, target_ulong va) \
+    { return (TYPE)gles2_get_##SIZE(s, va); } \
+    inline TYPE gles2_arg_##TYPE(gles2_State *s, gles2_decode_t *d); \
+    inline TYPE gles2_arg_##TYPE(gles2_State *s, gles2_decode_t *d) \
+    { return (TYPE)gles2_arg_##SIZE(s, d); }
+
+// Bunch of expansions of previous macro to ease things up.
+GLES2_TYPE(Tptr, dword)
+GLES2_TYPE(TEGLBoolean, dword)
+GLES2_TYPE(TEGLint, dword)
+GLES2_TYPE(TEGLDisplay, handle)
+GLES2_TYPE(TEGLConfig, handle)
+GLES2_TYPE(TEGLContext, handle)
+GLES2_TYPE(TEGLSurface, handle)
+
+GLES2_TYPE(TGLclampf, float)
+GLES2_TYPE(TGLbitfield, dword)
+GLES2_TYPE(TGLboolean, byte)
+GLES2_TYPE(TGLint, dword)
+GLES2_TYPE(TGLuint, dword)
+GLES2_TYPE(TGLushort, word)
+GLES2_TYPE(TGLubyte, byte)
+GLES2_TYPE(TGLenum, dword)
+GLES2_TYPE(TGLsizei, dword)
+GLES2_TYPE(TGLfloat, float)
+
+// Just one more macro for even less typing.
+#define GLES2_ARG(TYPE, NAME) \
+    TYPE NAME = gles2_arg_##TYPE(s, d)
+
+#define GLES2_BARRIER_ARG \
+    if (c) { \
+        pthread_mutex_lock(&c->mutex_xcode); \
+        c->phase_xcode = 1; \
+        pthread_cond_signal(&c->cond_xcode); \
+        pthread_mutex_unlock(&c->mutex_xcode); \
+        GLES2_PRINT("-- ARG BARRIER --\n"); \
+    }
+#define GLES2_BARRIER_ARG_NORET \
+    if (c) { \
+        pthread_mutex_lock(&c->mutex_xcode); \
+        c->phase_xcode = 3; \
+        GLES2_PRINT("-- ARG & RETURN BARRIER --\n"); \
+    }
+#define GLES2_BARRIER_RET \
+    if (c) { \
+        pthread_mutex_lock(&c->mutex_xcode); \
+        c->phase_xcode = 2; \
+        do { \
+            pthread_cond_wait(&c->cond_return, &c->mutex_xcode); \
+        } while (c->phase_xcode == 2); \
+        GLES2_PRINT("-- RETURN BARRIER --\n"); \
+    }
+
+//        pthread_cond_signal(&s->cond_xcode);
+
+GLES2_CB(eglGetDisplay)
+{
+//	GLES2_ARG(TEGLDisplay, dpy);
+//	(void)dpy;
+    GLES2_BARRIER_ARG;
+
+    GLES2_PRINT("Getting display...\n");
+
+    EGLDisplay dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+
+    GLES2_PRINT("\tGot host display %p...\n", dpy);
+
+    GLES2_BARRIER_RET;
+    gles2_ret_TEGLDisplay(s, gles2_handle_create(s, dpy));
+}
+
+GLES2_CB(eglInitialize)
+{
+    GLES2_ARG(TEGLDisplay, dpy_);
+    GLES2_ARG(Tptr, majorp);
+    GLES2_ARG(Tptr, minorp);
+
+    GLES2_BARRIER_ARG;
+
+    EGLDisplay dpy = (EGLDisplay)gles2_handle_get(s, dpy_);
+
+    GLES2_PRINT("Request to initialize display %p...\n", dpy);
+
+    EGLint major, minor;
+    if (eglInitialize(dpy, &major, &minor)) {
+        GLES2_PRINT("Display initialized (EGL %d.%d)!\n", major, minor);
+        gles2_put_TEGLint(s, majorp, major);
+        gles2_put_TEGLint(s, minorp, minor);
+        GLES2_BARRIER_RET;
+        gles2_ret_TEGLBoolean(s, EGL_TRUE);
+        return;
+    }
+
+    GLES2_PRINT("Failed to initialize...\n");
+    GLES2_BARRIER_RET;
+    gles2_ret_TEGLBoolean(s, EGL_FALSE);
+}
+
+GLES2_CB(eglGetConfigs)
+{
+    GLES2_ARG(TEGLDisplay, dpy_);
+    GLES2_ARG(Tptr, configsp);
+    GLES2_ARG(TEGLint, config_size);
+    GLES2_ARG(Tptr, num_configp);
+    
+    GLES2_BARRIER_ARG;
+
+    EGLDisplay dpy = (EGLDisplay)gles2_handle_get(s, dpy_);
+
+    EGLConfig* configs = configsp ? malloc(sizeof(EGLConfig)*config_size) : NULL;
+
+    EGLint num_config;
+    EGLBoolean ret = eglGetConfigs(dpy, configs, config_size, &num_config);
+
+    GLES2_BARRIER_RET;
+    if (configs) {
+        EGLint i;
+
+        for (i = 0; i < num_config; ++i) {
+            uint32_t handle;
+            if (!(handle = gles2_handle_find(s, configs[i]))) {
+                handle = gles2_handle_create(s, configs[i]);
+            }
+            gles2_put_TEGLConfig(s, configsp + i*sizeof(TEGLConfig), handle);
+        }
+        
+        free(configs);
+    }
+    gles2_put_TEGLint(s, num_configp, num_config);
+
+    gles2_ret_TEGLBoolean(s, ret);
+}
+
+GLES2_CB(eglChooseConfig)
+{
+    GLES2_ARG(TEGLDisplay, dpy_);
+    GLES2_ARG(Tptr, attrib_listp);
+    GLES2_ARG(Tptr, configsp);
+    GLES2_ARG(TEGLint, config_size);
+    GLES2_ARG(Tptr, num_configp);
+    (void)config_size;
+    (void)attrib_listp;
+
+    EGLint attrib_list_n = 0;
+    while (gles2_get_TEGLint(s, attrib_listp
+        + attrib_list_n*sizeof(EGLint)) != EGL_NONE) {
+        attrib_list_n += 2;
+    }
+    EGLint* attrib_list = malloc((attrib_list_n + 1)*sizeof(EGLint));
+    EGLint i;
+
+    for (i = 0; i < attrib_list_n; ++i) {
+        attrib_list[i] = gles2_get_TEGLint(s, attrib_listp
+            + i*sizeof(EGLint));
+    }
+    attrib_list[attrib_list_n] = EGL_NONE;
+    GLES2_BARRIER_ARG;
+
+    EGLDisplay dpy = (EGLDisplay)gles2_handle_get(s, dpy_);
+
+    EGLConfig* configs = configsp ? malloc(sizeof(EGLConfig)*config_size) : NULL;
+
+    EGLint num_config;
+    EGLBoolean ret = eglChooseConfig(dpy, attrib_list, configs, config_size, &num_config);
+    free(attrib_list);
+    GLES2_BARRIER_RET;
+    if (configs) {
+        EGLint i;
+
+        for (i = 0; i < num_config; ++i) {
+            uint32_t handle;
+            if (!(handle = gles2_handle_find(s, configs[i]))) {
+                handle = gles2_handle_create(s, configs[i]);
+            }
+            gles2_put_TEGLConfig(s, configsp + i*sizeof(TEGLConfig), handle);
+        }
+        
+        free(configs);
+    }
+    gles2_put_TEGLint(s, num_configp, num_config);
+
+    gles2_ret_TEGLBoolean(s, ret);
+}
+
+GLES2_CB(eglGetConfigAttrib)
+{
+    GLES2_ARG(TEGLDisplay, dpy_);
+    GLES2_ARG(TEGLConfig, config);
+    GLES2_ARG(TEGLint, attribute);
+    GLES2_ARG(Tptr, valuep);
+    GLES2_BARRIER_ARG;
+
+    EGLDisplay dpy = (EGLDisplay)gles2_handle_get(s, dpy_);
+
+    EGLint value;
+    EGLBoolean ret = eglGetConfigAttrib(dpy, gles2_handle_get(s, config), attribute, &value);
+
+    GLES2_BARRIER_RET;
+
+    gles2_put_TEGLint(s, valuep, value);
+    gles2_ret_TEGLBoolean(s, ret);
+}
+
+typedef struct gles2_Surface
+{
+    uint32_t ddrawp;    // Pointer to the offscreen drawable in guest memory.
+    DEGLDrawable ddraw; // Offscreen drawable, read from guest memory.
+    EGLSurface surf;    // Pointer to the EGL surface.
+    uint32_t pixelsp;   // Pointer to pixels in guest memory.
+    int pixmap;         // True if surface is pixmap.
+    gles2_CompiledTransfer tfr; // Framebuffer transfer.
+} gles2_Surface;
+
+// See if guest offscreen drawable was changed and if so, update host copy.
+static int gles2_surface_update(gles2_State *s, gles2_Surface *surf)
+{
+    int ret = 0;
+
+    uint32_t width   = gles2_get_dword(s, surf->ddrawp + 0*sizeof(uint32_t));
+    uint32_t height  = gles2_get_dword(s, surf->ddrawp + 1*sizeof(uint32_t));
+    uint32_t depth   = gles2_get_dword(s, surf->ddrawp + 2*sizeof(uint32_t));
+    uint32_t bpp     = gles2_get_dword(s, surf->ddrawp + 3*sizeof(uint32_t));
+    uint32_t pixelsp = gles2_get_dword(s, surf->ddrawp + 4*sizeof(uint32_t));
+
+    if (width != surf->ddraw.width
+         || height != surf->ddraw.height
+         || depth != surf->ddraw.depth) {
+        surf->ddraw.width = width;
+        surf->ddraw.height = height;
+        surf->ddraw.depth = depth;
+        surf->ddraw.bpp = bpp;
+        ret = 1;
+    }
+
+    surf->pixelsp = pixelsp;
+
+    return ret;
+}
+
+// TODO: Support swapping of offscreen surfaces.
+static void gles2_eglSwapCallback(void* userdata)
+{
+    (void)userdata;
+    GLES2_PRINT("Swap called!\n");
+}
+
+GLES2_CB(eglCreateWindowSurface)
+{
+    GLES2_ARG(TEGLDisplay, dpy_);
+    GLES2_ARG(TEGLConfig, config_);
+    GLES2_ARG(Tptr, winp);
+    GLES2_ARG(Tptr, attrib_listp);
+
+
+    EGLDisplay dpy = (EGLDisplay)gles2_handle_get(s, dpy_);
+    EGLConfig config = (EGLConfig)gles2_handle_get(s, config_);
+    (void)attrib_listp;
+
+    gles2_Surface* fsurf;
+
+    if (!(fsurf = malloc(sizeof(*fsurf)))) {
+        GLES2_PRINT("\tFake window creation failed!\n");
+        gles2_ret_TEGLSurface(s, 0);
+        return;
+    }
+
+
+    fsurf->ddrawp = winp;
+    fsurf->pixmap = 0;
+    gles2_surface_update(s, fsurf);
+    GLES2_BARRIER_ARG;
+
+    GLES2_PRINT("Host window creation requested, %dx%d@%d(Bpp=%d) at 0x%x...\n",
+        fsurf->ddraw.width, fsurf->ddraw.height,
+        fsurf->ddraw.depth, fsurf->ddraw.bpp, fsurf->pixelsp);
+
+    unsigned nbytes = fsurf->ddraw.width*fsurf->ddraw.height*fsurf->ddraw.bpp;
+    fsurf->ddraw.pixels = malloc(nbytes);
+    fsurf->ddraw.userdata = fsurf;
+    fsurf->ddraw.swap = gles2_eglSwapCallback;
+
+    if((fsurf->surf = eglCreateWindowSurface(dpy, config,
+	    (EGLNativeWindowType)&fsurf->ddraw, NULL)) == EGL_NO_CONTEXT)
+    {
+        GLES2_PRINT("\tHost window creation failed!\n");
+        free(fsurf->ddraw.pixels);
+        free(fsurf);
+        GLES2_BARRIER_RET;
+        gles2_ret_TEGLSurface(s, 0);
+        return;
+    }
+
+    GLES2_PRINT("Created at %p!\n", fsurf);
+    GLES2_BARRIER_RET;
+    gles2_transfer_compile(&fsurf->tfr, s, fsurf->pixelsp, nbytes);
+    gles2_ret_TEGLSurface(s, gles2_handle_create(s, fsurf));
+}
+
+GLES2_CB(eglCreatePixmapSurface)
+{
+    GLES2_ARG(TEGLDisplay, dpy_);
+    GLES2_ARG(TEGLConfig, config_);
+    GLES2_ARG(Tptr, pixmapp);
+    GLES2_ARG(Tptr, attrib_listp);
+	
+    EGLDisplay dpy = (EGLDisplay)gles2_handle_get(s, dpy_);
+    EGLConfig config = (EGLConfig)gles2_handle_get(s, config_);
+    (void)attrib_listp;
+
+    gles2_Surface* fsurf;
+
+    if (!(fsurf = malloc(sizeof(*fsurf)))) {
+        GLES2_PRINT("\tFake pixmap creation failed!\n");
+        GLES2_BARRIER_ARG_NORET;
+        gles2_ret_TEGLSurface(s, 0);
+        return;
+    }
+
+    fsurf->ddrawp = pixmapp;
+    fsurf->pixmap = 1;
+    gles2_surface_update(s, fsurf);
+    GLES2_BARRIER_ARG;
+
+    GLES2_PRINT("Host pixmap creation requested, %dx%d@%d(Bpp=%d) at 0x%x...\n",
+        fsurf->ddraw.width, fsurf->ddraw.height,
+        fsurf->ddraw.depth, fsurf->ddraw.bpp, fsurf->pixelsp);
+
+    unsigned nbytes = fsurf->ddraw.width*fsurf->ddraw.height*fsurf->ddraw.bpp;
+    fsurf->ddraw.pixels = malloc(nbytes);
+    fsurf->ddraw.userdata = fsurf;
+    fsurf->ddraw.swap = gles2_eglSwapCallback;
+
+    if((fsurf->surf = eglCreatePixmapSurface(dpy, config,
+        (EGLNativeWindowType)&fsurf->ddraw, NULL)) == EGL_NO_CONTEXT) {
+        GLES2_PRINT("\tHost pixmap creation failed!\n");
+        free(fsurf->ddraw.pixels);
+        free(fsurf);
+        GLES2_BARRIER_RET;
+        gles2_ret_TEGLSurface(s, 0);
+        return;
+    }
+
+    GLES2_PRINT("Created at %p!\n", fsurf);
+    GLES2_BARRIER_RET;
+    gles2_ret_TEGLSurface(s, gles2_handle_create(s, fsurf));
+}
+
+GLES2_CB(eglDestroySurface)
+{
+    GLES2_ARG(TEGLDisplay, dpy_);
+    GLES2_ARG(TEGLSurface, surface_);
+    GLES2_BARRIER_ARG_NORET;
+
+    EGLDisplay dpy = (EGLDisplay)gles2_handle_get(s, dpy_);
+    gles2_Surface* fsurf = (EGLSurface)gles2_handle_get(s, surface_);
+    gles2_handle_free(s, surface_);
+
+    eglDestroySurface(dpy, fsurf->surf);
+    free(fsurf->ddraw.pixels);
+
+    if(fsurf->pixmap == 0) {
+        gles2_transfer_free(&fsurf->tfr);
+    }
+    free(fsurf);
+}
+
+GLES2_CB(eglBindTexImage)
+{
+    GLES2_ARG(TEGLDisplay, dpy_);
+    GLES2_ARG(TEGLSurface, surface_);
+    GLES2_ARG(TEGLint, buffer);
+    gles2_CompiledTransfer tfr;
+
+    EGLDisplay dpy = (EGLDisplay)gles2_handle_get(s, dpy_);
+    gles2_Surface* fsurf = (gles2_Surface*)gles2_handle_get(s, surface_);
+
+    // FIXME: Not a very clean way..
+    uint32_t pixelsp = gles2_get_dword(s, fsurf->ddrawp + 4*sizeof(uint32_t));
+    if (pixelsp) {
+        unsigned nbytes = fsurf->ddraw.width 
+            * fsurf->ddraw.height*fsurf->ddraw.bpp;
+        gles2_transfer_compile(&tfr, s, pixelsp, nbytes);
+    }
+    GLES2_BARRIER_ARG;
+
+    if (pixelsp) {
+//        gles2_transfer(s, pixelsp, nbytes, fsurf->ddraw.pixels, 0);
+        gles2_transfer_exec(&tfr, s, fsurf->ddraw.pixels, 0);
+    }
+
+    EGLBoolean ret = eglBindTexImage(dpy, &fsurf->ddraw, buffer);
+    if (pixelsp) {
+        gles2_transfer_free(&tfr);
+    }
+    
+    GLES2_BARRIER_RET;
+    gles2_ret_TEGLBoolean(s, ret);
+}
+
+GLES2_CB(eglReleaseTexImage)
+{
+    GLES2_ARG(TEGLDisplay, dpy_);
+    GLES2_ARG(TEGLSurface, surface_);
+    GLES2_ARG(TEGLint, buffer);
+    GLES2_BARRIER_ARG;
+
+    EGLDisplay dpy = (EGLDisplay)gles2_handle_get(s, dpy_);
+    gles2_Surface* fsurf = (gles2_Surface*)gles2_handle_get(s, surface_);
+
+    EGLBoolean ret = eglReleaseTexImage(dpy, &fsurf->ddraw, buffer);
+    GLES2_BARRIER_RET;
+    gles2_ret_TEGLBoolean(s, ret);
+}
+
+GLES2_CB(eglCreateContext)
+{
+    GLES2_ARG(TEGLDisplay, dpy_);
+    GLES2_ARG(TEGLConfig, config_);
+    GLES2_ARG(TEGLContext, share_context_);
+    GLES2_ARG(Tptr, attrib_listp);
+    GLES2_BARRIER_ARG;
+
+    EGLDisplay dpy = (EGLDisplay)gles2_handle_get(s, dpy_);
+    EGLConfig config = (EGLConfig)gles2_handle_get(s, config_);
+    EGLContext share_context = (EGLContext)gles2_handle_get(s, share_context_);
+
+    GLES2_PRINT("TODO: Handle attribs...\n");
+    (void)attrib_listp;
+
+    GLES2_PRINT("Host context creation requested...\n");
+    EGLContext ctx;
+    if ((ctx = eglCreateContext(dpy, config,
+        share_context, NULL)) == EGL_NO_CONTEXT) {
+        GLES2_PRINT("\tContext creation failed!\n");
+        GLES2_BARRIER_RET;
+        gles2_ret_TEGLContext(s, 0);
+        return;
+    }
+    GLES2_PRINT("Created at %p!\n", ctx);
+    GLES2_BARRIER_RET;
+    gles2_ret_TEGLContext(s, gles2_handle_create(s, ctx));
+}
+
+GLES2_CB(eglDestroyContext)
+{
+    GLES2_ARG(TEGLDisplay, dpy_);
+    GLES2_ARG(TEGLContext, ctx_);
+    GLES2_BARRIER_ARG;
+
+    EGLDisplay dpy = (EGLDisplay)gles2_handle_get(s, dpy_);
+    EGLContext ctx = (EGLContext)gles2_handle_get(s, ctx_);
+    gles2_handle_free(s, ctx_);
+    GLES2_PRINT("Destroyed %p!\n", ctx);
+
+    GLES2_BARRIER_RET;
+    gles2_ret_TEGLBoolean(s, eglDestroyContext(dpy, ctx));
+}
+
+// Host to guest vertex array copy.
+struct gles2_Array
+{
+    GLuint indx;          // Parameter of the call.
+    GLint size;           // --''--
+    GLenum type;          // --''--
+    GLboolean normalized; // --''--
+    GLsizei stride;       // --''--
+    Tptr tptr;            // Pointer in the guest memory.
+    void* ptr;            // Pointer in the host memory.
+
+    GLboolean enabled;    // State.
+};
+
+GLES2_CB(eglMakeCurrent)
+{
+    GLES2_ARG(TEGLDisplay, dpy_);
+    GLES2_ARG(TEGLSurface, draw_);
+    GLES2_ARG(TEGLSurface, read_);
+    GLES2_ARG(TEGLContext, ctx_);
+    GLES2_BARRIER_ARG;
+    int i;
+
+    EGLDisplay dpy = (EGLDisplay)gles2_handle_get(s, dpy_);
+    gles2_Surface* draw = (EGLSurface)gles2_handle_get(s, draw_);
+    gles2_Surface* read = (EGLSurface)gles2_handle_get(s, read_);
+    EGLContext ctx = (EGLContext)gles2_handle_get(s, ctx_);
+
+    GLES2_PRINT("Making host context current...\n");
+
+    if (!eglMakeCurrent(dpy,
+        draw ? draw->surf : NULL,
+        read ? read->surf : NULL,
+        ctx)) {
+        GLES2_PRINT("\tMakeCurrent failed!\n");
+        GLES2_BARRIER_RET;
+        gles2_ret_TEGLBoolean(s, EGL_FALSE);
+        return;
+    }
+
+    // Initialize client state.
+    if (ctx) {
+        glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &c->narrays);
+
+        GLES2_PRINT("Maximum number of host vertex arrays: %d.\n", c->narrays);
+
+        c->arrays = malloc(c->narrays * sizeof(*c->arrays));
+        for (i = 0; i < c->narrays; ++i) {
+            c->arrays[i].type = GL_NONE;
+            c->arrays[i].enabled = 0;
+            c->arrays[i].ptr = 0;
+        }
+    }
+
+    GLES2_PRINT("Made %p current!\n", ctx);
+    GLES2_BARRIER_RET;
+    gles2_ret_TEGLBoolean(s, EGL_TRUE);
+}
+
+GLES2_CB(eglSwapBuffers)
+{
+    GLES2_ARG(TEGLDisplay, dpy_);
+    GLES2_ARG(TEGLSurface, surface_);
+
+    EGLDisplay dpy = (EGLDisplay)gles2_handle_get(s, dpy_);
+    gles2_Surface* fsurf = (EGLSurface)gles2_handle_get(s, surface_);
+    if (!fsurf) {
+        fprintf(stderr, "ERROR: Trying to swap NULL surface!\n");
+        GLES2_BARRIER_RET;
+        gles2_ret_TEGLBoolean(s, EGL_TRUE);
+        return;
+    }
+
+    if (gles2_surface_update(s, fsurf)) {
+        GLES2_BARRIER_ARG;
+        GLES2_PRINT("DIMENSIONS CHANGED!\n");
+        glFinish();
+        free(fsurf->ddraw.pixels);
+        unsigned nbytes = fsurf->ddraw.width
+            * fsurf->ddraw.height*fsurf->ddraw.bpp;
+        fsurf->ddraw.pixels = malloc(nbytes);
+
+        gles2_transfer_free(&fsurf->tfr);
+        GLES2_BARRIER_RET;
+        gles2_transfer_compile(&fsurf->tfr, s, fsurf->pixelsp, nbytes);
+        eglSwapBuffers(dpy, fsurf->surf);
+        gles2_ret_TEGLBoolean(s, EGL_TRUE);
+        return;
+    }
+    GLES2_BARRIER_ARG;
+
+    GLES2_PRINT("Swapping DGLES2 surfaces!\n");
+    eglSwapBuffers(dpy, fsurf->surf);
+
+    GLES2_PRINT("Transferring frame!\n");
+    gles2_transfer_exec(&fsurf->tfr, s, fsurf->ddraw.pixels, 1);
+    GLES2_PRINT("\tDone!\n");
+    GLES2_BARRIER_RET;
+    gles2_ret_TEGLBoolean(s, EGL_TRUE);
+}
+
+GLES2_CB(glClearColor)
+{
+    GLES2_ARG(TGLclampf, red);
+    GLES2_ARG(TGLclampf, green);
+    GLES2_ARG(TGLclampf, blue);
+    GLES2_ARG(TGLclampf, alpha);
+    GLES2_BARRIER_ARG_NORET;
+
+    glClearColor(red, green, blue, alpha);
+}
+
+
+GLES2_CB(glClear)
+{
+    GLES2_ARG(TGLbitfield, mask);
+    GLES2_BARRIER_ARG_NORET;
+
+    glClear(mask);
+}
+
+GLES2_CB(glDisableVertexAttribArray)
+{
+    GLES2_ARG(TGLuint, index);
+    GLES2_BARRIER_ARG_NORET;
+
+    GLES2_PRINT("Disabling array %d\n", index);
+    c->arrays[index].enabled = 0;
+    glDisableVertexAttribArray(index);
+}
+
+static void fglTransferArrays(gles2_State *s, gles2_Client *c,
+    GLint first, GLsizei count)
+{
+    int i;
+
+    for(i = 0; i < c->narrays; ++i) {
+        gles2_Array* va = c->arrays + i;
+        if(!va->enabled) {
+            continue;
+        }
+        unsigned esize = 1;
+        switch (va->type) {
+            case GL_BYTE:
+            case GL_UNSIGNED_BYTE:  esize = 1; break;
+            case GL_SHORT:
+            case GL_UNSIGNED_SHORT: esize = 2; break;
+            case GL_FIXED:
+            case GL_FLOAT:          esize = 4; break;
+        }		    
+        if (!va->stride) {
+            va->stride = va->size*esize;
+        }
+
+        if (va->ptr) {
+            free(va->ptr);
+        }
+        unsigned nbytes = esize*count*va->size; 
+        va->ptr = malloc(nbytes);
+
+        GLsizei j;
+        for (j = 0; j < count; ++j) {
+            signed k;
+            for (k = 0; k < va->size; ++k) {
+                switch (esize) {
+                    case 1:
+                        ((TGLubyte*)va->ptr)[j*va->size + k] =
+                            gles2_get_byte(s, va->tptr + va->stride*(first + j)
+                            + k*sizeof(TGLubyte));
+                        break;
+                    case 2:
+                        ((TGLushort*)va->ptr)[j*va->size + k] =
+                            gles2_get_word(s, va->tptr + va->stride*(first + j)
+                            + k*sizeof(TGLushort));
+                        break;
+                    case 4:
+                        if(va->type == GL_FLOAT) {
+                            ((TGLfloat*)va->ptr)[j*va->size + k] =
+                                gles2_get_float(s, va->tptr
+                                + va->stride*(first + j)
+                                + k*sizeof(TGLfloat));
+                        } else {
+                            ((TGLuint*)va->ptr)[j*va->size + k] =
+                                gles2_get_dword(s, va->tptr
+                                + va->stride*(first + j)
+                                + k*sizeof(TGLuint));
+                        }
+                        break;
+                }
+            }
+        }
+        
+        glVertexAttribPointer(va->indx, va->size, va->type,
+            va->normalized, 0, va->ptr);
+        GLenum error;
+
+        if ((error = glGetError()) != GL_NO_ERROR) {
+            GLES2_PRINT("glVertexAttribPointer(%d, %d, 0x%x, 0, %d, %p\n)"
+                " failed with 0x%x!\n", va->indx, va->size, va->type,
+                va->normalized, va->ptr, error);
+        }
+    }
+}
+
+GLES2_CB(glDrawArrays)
+{
+    GLES2_ARG(TGLenum, mode);
+    GLES2_ARG(TGLint, first);
+    GLES2_ARG(TGLsizei, count);
+
+    fglTransferArrays(s, c, first, count);
+    GLES2_BARRIER_ARG_NORET;
+
+    glDrawArrays(mode, 0, count);
+}
+
+GLES2_CB(glDrawElements)
+{
+    GLES2_ARG(TGLenum, mode);
+    GLES2_ARG(TGLsizei, count);
+    GLES2_ARG(TGLenum, type);
+    GLES2_ARG(Tptr, indicesp);
+
+    (void)indicesp;
+    (void)count;
+    (void)mode;
+
+    GLint indice_size;
+    switch (type) {
+        case GL_UNSIGNED_BYTE: indice_size = sizeof(TGLubyte); break;
+        case GL_UNSIGNED_SHORT: indice_size = sizeof(TGLushort); break;
+        default:
+            fprintf(stderr, "ERROR: Invalid type %d!\n", type);
+            return;
+    }
+
+    fglTransferArrays(s, c, (GLint)indicesp/indice_size, count);
+    GLES2_BARRIER_ARG_NORET;
+
+    glDrawElements(mode, count, type, 0);
+}
+
+GLES2_CB(glEnableVertexAttribArray)
+{
+    GLES2_ARG(TGLuint, index);
+    GLES2_BARRIER_ARG_NORET;
+
+    GLES2_PRINT("Enabling array %d\n", index);
+    c->arrays[index].enabled = 1;
+    glEnableVertexAttribArray(index);
+}
+
+#if 0
+GL_APICALL void GL_APIENTRY glGetVertexAttribfv(GLuint index, GLenum pname,
+    GLfloat* params)
+{
+    DUMMY();
+}
+
+GL_APICALL void GL_APIENTRY glGetVertexAttribiv(GLuint index, GLenum pname,
+    GLint* params)
+{
+    DUMMY();
+}
+
+GL_APICALL void GL_APIENTRY glGetVertexAttribPointerv(GLuint index,
+    GLenum pname, void** pointer)
+{
+    DUMMY();
+}
+#endif //0
+
+GLES2_CB(glVertexAttrib1f)
+{
+    GLES2_ARG(TGLuint, indx);
+    GLES2_ARG(TGLfloat, x);
+    GLES2_BARRIER_ARG_NORET;
+
+    glVertexAttrib1f(indx, x);
+}
+
+GLES2_CB(glVertexAttrib1fv)
+{
+    GLES2_ARG(TGLuint, indx);
+    GLES2_ARG(Tptr, valuesp);
+    GLfloat x = gles2_get_float(s, valuesp);
+    GLES2_BARRIER_ARG_NORET;
+
+    glVertexAttrib1f(indx, x);
+}
+
+GLES2_CB(glVertexAttrib2f)
+{
+    GLES2_ARG(TGLuint, indx);
+    GLES2_ARG(TGLfloat, x);
+    GLES2_ARG(TGLfloat, y);
+    GLES2_BARRIER_ARG_NORET;
+
+    glVertexAttrib2f(indx, x, y);
+}
+
+GLES2_CB(glVertexAttrib2fv)
+{
+    GLES2_ARG(TGLuint, indx);
+    GLES2_ARG(Tptr, valuesp);
+
+    GLfloat x = gles2_get_float(s, valuesp);
+    GLfloat y = gles2_get_float(s, valuesp + sizeof(TGLfloat));
+    GLES2_BARRIER_ARG_NORET;
+
+    glVertexAttrib2f(indx, x, y);
+}
+
+GLES2_CB(glVertexAttrib3f)
+{
+    GLES2_ARG(TGLuint, indx);
+    GLES2_ARG(TGLfloat, x);
+    GLES2_ARG(TGLfloat, y);
+    GLES2_ARG(TGLfloat, z);
+    GLES2_BARRIER_ARG_NORET;
+
+    glVertexAttrib3f(indx, x, y, z);
+}
+
+GLES2_CB(glVertexAttrib3fv)
+{
+    GLES2_ARG(TGLuint, indx);
+    GLES2_ARG(Tptr, valuesp);
+
+    GLfloat x = gles2_get_float(s, valuesp + 0*sizeof(TGLfloat));
+    GLfloat y = gles2_get_float(s, valuesp + 1*sizeof(TGLfloat));
+    GLfloat z = gles2_get_float(s, valuesp + 2*sizeof(TGLfloat));
+    GLES2_BARRIER_ARG_NORET;
+
+    glVertexAttrib3f(indx, x, y, z);
+}
+
+GLES2_CB(glVertexAttrib4f)
+{
+    GLES2_ARG(TGLuint, indx);
+    GLES2_ARG(TGLfloat, x);
+    GLES2_ARG(TGLfloat, y);
+    GLES2_ARG(TGLfloat, z);
+    GLES2_ARG(TGLfloat, w);
+    GLES2_BARRIER_ARG_NORET;
+
+    glVertexAttrib4f(indx, x, y, z, w);
+}
+
+GLES2_CB(glVertexAttrib4fv)
+{
+    GLES2_ARG(TGLuint, indx);
+    GLES2_ARG(Tptr, valuesp);
+
+    GLfloat x = gles2_get_float(s, valuesp + 0*sizeof(TGLfloat));
+    GLfloat y = gles2_get_float(s, valuesp + 1*sizeof(TGLfloat));
+    GLfloat z = gles2_get_float(s, valuesp + 2*sizeof(TGLfloat));
+    GLfloat w = gles2_get_float(s, valuesp + 3*sizeof(TGLfloat));
+    GLES2_BARRIER_ARG_NORET;
+
+    glVertexAttrib4f(indx, x, y, z, w);
+}
+
+GLES2_CB(glVertexAttribPointer)
+{
+    GLES2_ARG(TGLuint, indx);
+    GLES2_ARG(TGLint, size);
+    GLES2_ARG(TGLenum, type);
+    GLES2_ARG(TGLboolean, normalized);
+    GLES2_ARG(TGLsizei, stride);
+    GLES2_ARG(Tptr, tptr);
+    GLES2_BARRIER_ARG_NORET;
+
+    GLES2_PRINT("Array %d at 0x%x (%d elements every %d bytes)\n",
+        indx, tptr, size, stride);
+
+    gles2_Array *va = c->arrays + indx;
+    va->type = type;
+    va->indx = indx;
+    va->size = size;
+    va->normalized = normalized;
+    va->stride = stride;
+    va->tptr = tptr;
+}
+
+static unsigned gles2_glGetCount(TGLenum pname)
+{
+    unsigned count;
+    switch(pname) {
+        case GL_ACTIVE_TEXTURE: count = 1; break;
+        case GL_ALIASED_LINE_WIDTH_RANGE: count = 2; break;
+        case GL_ALIASED_POINT_SIZE_RANGE: count = 2; break;
+        case GL_ALPHA_BITS: count = 1; break;
+        case GL_ARRAY_BUFFER_BINDING: count = 1; break;
+        case GL_BLEND: count = 1; break;
+        case GL_BLEND_COLOR: count = 4; break;
+        case GL_BLEND_DST_ALPHA: count = 1; break;
+        case GL_BLEND_DST_RGB: count = 1; break;
+        case GL_BLEND_EQUATION_ALPHA: count = 1; break;
+        case GL_BLEND_EQUATION_RGB: count = 1; break;
+        case GL_BLEND_SRC_ALPHA: count = 1; break;
+        case GL_BLEND_SRC_RGB: count = 1; break;
+        case GL_BLUE_BITS: count = 1; break;
+        case GL_COLOR_CLEAR_VALUE: count = 4; break;
+        case GL_COLOR_WRITEMASK: count = 4; break;
+        case GL_COMPRESSED_TEXTURE_FORMATS: count = GL_NUM_COMPRESSED_TEXTURE_FORMATS; break;
+        case GL_CULL_FACE: count = 1; break;
+        case GL_CULL_FACE_MODE: count = 1; break;
+        case GL_CURRENT_PROGRAM: count = 1; break;
+        case GL_DEPTH_BITS: count = 1; break;
+        case GL_DEPTH_CLEAR_VALUE: count = 1; break;
+        case GL_DEPTH_FUNC: count = 1; break;
+        case GL_DEPTH_RANGE: count = 2; break;
+        case GL_DEPTH_TEST: count = 1; break;
+        case GL_DEPTH_WRITEMASK: count = 1; break;
+        case GL_DITHER: count = 1; break;
+        case GL_ELEMENT_ARRAY_BUFFER_BINDING: count = 1; break;
+        case GL_FRAMEBUFFER_BINDING: count = 1; break;
+        case GL_FRONT_FACE: count = 1; break;
+        case GL_GENERATE_MIPMAP_HINT: count = 1; break;
+        case GL_GREEN_BITS: count = 1; break;
+        case GL_IMPLEMENTATION_COLOR_READ_FORMAT: count = 1; break;
+        case GL_IMPLEMENTATION_COLOR_READ_TYPE: count = 1; break;
+        case GL_LINE_WIDTH: count = 1; break;
+        case GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS: count = 1; break;
+        case GL_MAX_CUBE_MAP_TEXTURE_SIZE: count = 1; break;
+        case GL_MAX_FRAGMENT_UNIFORM_VECTORS: count = 1; break;
+        case GL_MAX_RENDERBUFFER_SIZE: count = 1; break;
+        case GL_MAX_TEXTURE_IMAGE_UNITS: count = 1; break;
+        case GL_MAX_TEXTURE_SIZE: count = 1; break;
+        case GL_MAX_VARYING_VECTORS: count = 1; break;
+        case GL_MAX_VERTEX_ATTRIBS: count = 1; break;
+        case GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS: count = 1; break;
+        case GL_MAX_VERTEX_UNIFORM_VECTORS: count = 1; break;
+        case GL_MAX_VIEWPORT_DIMS: count = 2; break;
+        case GL_NUM_COMPRESSED_TEXTURE_FORMATS: count = 1; break;
+        case GL_NUM_SHADER_BINARY_FORMATS: count = 1; break;
+        case GL_PACK_ALIGNMENT: count = 1; break;
+        case GL_POLYGON_OFFSET_FACTOR: count = 1; break;
+        case GL_POLYGON_OFFSET_FILL: count = 1; break;
+        case GL_POLYGON_OFFSET_UNITS: count = 1; break;
+        case GL_RED_BITS: count = 1; break;
+        case GL_RENDERBUFFER_BINDING: count = 1; break;
+        case GL_SAMPLE_BUFFERS: count = 1; break;
+        case GL_SAMPLE_COVERAGE_INVERT: count = 1; break;
+        case GL_SAMPLE_COVERAGE_VALUE: count = 1; break;
+        case GL_SAMPLES: count = 1; break;
+        case GL_SCISSOR_BOX: count = 4; break;
+        case GL_SCISSOR_TEST: count = 1; break;
+        case GL_SHADER_BINARY_FORMATS: count = GL_NUM_SHADER_BINARY_FORMATS; break;
+        case GL_SHADER_COMPILER: count = 1; break;
+        case GL_STENCIL_BACK_FAIL: count = 1; break;
+        case GL_STENCIL_BACK_FUNC: count = 1; break;
+        case GL_STENCIL_BACK_PASS_DEPTH_FAIL: count = 1; break;
+        case GL_STENCIL_BACK_PASS_DEPTH_PASS: count = 1; break;
+        case GL_STENCIL_BACK_REF: count = 1; break;
+        case GL_STENCIL_BACK_VALUE_MASK: count = 1; break;
+        case GL_STENCIL_BACK_WRITEMASK: count = 1; break;
+        case GL_STENCIL_BITS: count = 1; break;
+        case GL_STENCIL_CLEAR_VALUE: count = 1; break;
+        case GL_STENCIL_FAIL: count = 1; break;
+        case GL_STENCIL_FUNC: count = 1; break;
+        case GL_STENCIL_PASS_DEPTH_FAIL: count = 1; break;
+        case GL_STENCIL_PASS_DEPTH_PASS: count = 1; break;
+        case GL_STENCIL_REF: count = 1; break;
+        case GL_STENCIL_TEST: count = 1; break;
+        case GL_STENCIL_VALUE_MASK: count = 1; break;
+        case GL_STENCIL_WRITEMASK: count = 1; break;
+        case GL_SUBPIXEL_BITS: count = 1; break;
+        case GL_TEXTURE_BINDING_2D: count = 1; break;
+        case GL_TEXTURE_BINDING_CUBE_MAP: count = 1; break;
+        case GL_UNPACK_ALIGNMENT: count = 1; break;
+        case GL_VIEWPORT: count = 4; break;
+        default:
+            GLES2_PRINT("ERROR: Unknown pname 0x%x in glGet!\n", pname);
+            count = 1;
+            break;
+    }
+
+    GLES2_PRINT("glGet(0x%x) -> %u!\n", pname, count);
+
+    return count;
+}
+
+GLES2_CB(glGetBooleanv)
+{
+    GLES2_ARG(TGLenum, pname);
+    GLES2_ARG(Tptr, paramsp);
+    GLES2_BARRIER_ARG_NORET;
+
+    GLboolean params[4];
+    glGetBooleanv(pname, params);
+    unsigned const count = gles2_glGetCount(pname);
+    unsigned i;
+    for(i = 0; i < count; ++i) {
+        gles2_put_TGLboolean(s, paramsp + i*sizeof(TGLboolean), params[i]);
+    }
+}
+
+GLES2_CB(glGetError)
+{
+    GLES2_BARRIER_ARG;
+    GLES2_BARRIER_RET;
+    gles2_ret_TGLenum(s, glGetError());
+}
+
+GLES2_CB(eglGetError)
+{
+    GLES2_BARRIER_ARG;
+    GLES2_BARRIER_RET;
+    gles2_ret_TGLint(s, eglGetError());
+}
+
+GLES2_CB(glGetFloatv)
+{
+    GLES2_ARG(TGLenum, pname);
+    GLES2_ARG(Tptr, paramsp);
+    GLES2_BARRIER_ARG_NORET;
+
+    GLfloat params[4];
+    glGetFloatv(pname, params);
+    unsigned const count = gles2_glGetCount(pname);
+    unsigned i;
+    for(i = 0; i < count; ++i) {
+        gles2_put_TGLfloat(s, paramsp + i*sizeof(TGLfloat), params[i]);
+    }
+}
+
+GLES2_CB(glGetIntegerv)
+{
+    GLES2_ARG(TGLenum, pname);
+    GLES2_ARG(Tptr, paramsp);
+    GLES2_BARRIER_ARG_NORET;
+
+    GLint params[4];
+    glGetIntegerv(pname, params);
+    unsigned const count = gles2_glGetCount(pname);
+    unsigned i;
+    for(i = 0; i < count; ++i) {
+        gles2_put_TGLint(s, paramsp + i*sizeof(TGLint), params[i]);
+    }
+}
+
+GLES2_CB(glColorMask)
+{
+    GLES2_ARG(TGLboolean, red);
+    GLES2_ARG(TGLboolean, green);
+    GLES2_ARG(TGLboolean, blue);
+    GLES2_ARG(TGLboolean, alpha);
+    GLES2_BARRIER_ARG_NORET;
+
+    glColorMask(red, green, blue, alpha);
+}
+
+GLES2_CB(glCullFace)
+{
+    GLES2_ARG(TGLenum, mode);
+    GLES2_BARRIER_ARG_NORET;
+
+    glCullFace(mode);
+}
+
+GLES2_CB(glDisable)
+{
+    GLES2_ARG(TGLenum, cap);
+    GLES2_BARRIER_ARG_NORET;
+
+    glDisable(cap);
+}
+
+GLES2_CB(glEnable)
+{
+    GLES2_ARG(TGLenum, cap);
+    GLES2_BARRIER_ARG_NORET;
+
+    glEnable(cap);
+}
+
+GLES2_CB(glFinish)
+{
+    GLES2_BARRIER_ARG_NORET;
+    glFinish();
+}
+
+GLES2_CB(glFlush)
+{
+    GLES2_BARRIER_ARG_NORET;
+    glFlush();
+}
+
+GLES2_CB(glFrontFace)
+{
+    GLES2_ARG(TGLenum, mode);
+    GLES2_BARRIER_ARG_NORET;
+
+    glFrontFace(mode);
+}
+
+GLES2_CB(glIsEnabled)
+{
+    GLES2_ARG(TGLenum, cap);
+    GLES2_BARRIER_ARG;
+
+    GLES2_BARRIER_RET;
+    gles2_ret_TGLboolean(s, glIsEnabled(cap));
+}
+
+GLES2_CB(glHint)
+{
+    GLES2_ARG(TGLenum, target);
+    GLES2_ARG(TGLenum, mode);
+    GLES2_BARRIER_ARG_NORET;
+
+    if(s->quality <= 75)
+    {
+        switch(target)
+        {
+            default: mode = GL_FASTEST; break; 
+        }
+    }
+    
+    glHint(target, mode);
+}
+
+GLES2_CB(glLineWidth)
+{
+    GLES2_ARG(TGLfloat, width);
+    GLES2_BARRIER_ARG_NORET;
+
+    glLineWidth(width);
+}
+
+GLES2_CB(glPolygonOffset)
+{
+    GLES2_ARG(TGLfloat, factor);
+    GLES2_ARG(TGLfloat, units);
+    GLES2_BARRIER_ARG_NORET;
+
+    glPolygonOffset(factor, units);
+}
+
+GLES2_CB(glSampleCoverage)
+{
+    GLES2_ARG(TGLclampf, value);
+    GLES2_ARG(TGLboolean, invert);
+    GLES2_BARRIER_ARG_NORET;
+
+    glSampleCoverage(value, invert);
+}
+
+GLES2_CB(glScissor)
+{
+    GLES2_ARG(TGLint, x);
+    GLES2_ARG(TGLint, y);
+    GLES2_ARG(TGLsizei, width);
+    GLES2_ARG(TGLsizei, height);
+    GLES2_BARRIER_ARG_NORET;
+
+    glScissor(x, y, width, height);
+}
+
+GLES2_CB(glViewport)
+{
+    GLES2_ARG(TGLint, x);
+    GLES2_ARG(TGLint, y);
+    GLES2_ARG(TGLsizei, width);
+    GLES2_ARG(TGLsizei, height);
+    GLES2_BARRIER_ARG_NORET;
+
+    glViewport(x, y, width, height);
+}
+
+GLES2_CB(glActiveTexture)
+{
+    GLES2_ARG(TGLenum, texture);
+    GLES2_BARRIER_ARG_NORET;
+
+    glActiveTexture(texture);
+}
+
+GLES2_CB(glBindTexture)
+{
+    GLES2_ARG(TGLenum, target);
+    GLES2_ARG(TGLuint, texture);
+    GLES2_BARRIER_ARG_NORET;
+
+    glBindTexture(target, texture);
+}
+
+#if 0
+GL_APICALL void GL_APIENTRY glCompressedTexImage2D(GLenum target,
+    GLint level, GLenum internalformat, GLsizei width, GLsizei height,
+    GLint border, GLsizei imageSize, const void* data)
+{
+    DUMMY();
+}
+
+GL_APICALL void GL_APIENTRY glCompressedTexSubImage2D(GLenum target,
+    GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height,
+    GLenum format, GLsizei imageSize, const void* data)
+{
+    DUMMY();
+}
+
+GL_APICALL void GL_APIENTRY glCopyTexImage2D(GLenum target, GLint level,
+    GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height,
+    GLint border)
+{
+    DUMMY();
+}
+
+GL_APICALL void GL_APIENTRY glCopyTexSubImage2D(GLenum target, GLint level,
+    GLint xoffset, GLint yoffset, GLint x, GLint y,
+    GLsizei width, GLsizei height)
+{
+    DUMMY();
+}
+#endif // 0
+
+GLES2_CB(glDeleteTextures)
+{
+    GLES2_ARG(TGLsizei, n);
+    GLES2_ARG(Tptr, texturesp);
+
+    GLsizei i;
+    GLuint* textures = (GLuint*)malloc(sizeof(GLuint)*n);
+    for(i = 0; i < n; ++i) {
+        textures[i] = gles2_get_TGLuint(s, texturesp + i*sizeof(TGLuint));
+    }
+    GLES2_BARRIER_ARG_NORET;
+
+    glDeleteTextures(n, textures);
+    free(textures);
+}
+
+GLES2_CB(glGenerateMipmap)
+{
+    GLES2_ARG(TGLenum, target);
+    GLES2_BARRIER_ARG_NORET;
+
+    glGenerateMipmap(target);
+}
+
+GLES2_CB(glGenTextures)
+{
+    GLES2_ARG(TGLsizei, n);
+    GLES2_ARG(Tptr, texturesp);
+    GLES2_BARRIER_ARG_NORET;
+
+    GLsizei i;
+    GLuint* textures = (GLuint*)malloc(sizeof(GLuint)*n);
+    glGenTextures(n, textures);
+    for(i = 0; i < n; ++i) {
+        gles2_put_TGLuint(s, texturesp + i*sizeof(TGLuint), textures[i]);
+    }
+    free(textures);
+}
+
+static unsigned gles2_glTexParameterCount(GLenum pname)
+{
+    unsigned count;
+
+    switch(pname) {
+        case GL_TEXTURE_MIN_FILTER: count = 1; break;
+        case GL_TEXTURE_MAG_FILTER: count = 1; break;
+        case GL_TEXTURE_WRAP_S: count = 1; break;
+        case GL_TEXTURE_WRAP_T: count = 1; break;
+        default:
+            GLES2_PRINT("ERROR: Unknown texture parameter 0x%x!\n", pname);
+            count = 1;
+            break;
+    }
+    
+    return count;
+}
+
+GLES2_CB(glGetTexParameterfv)
+{
+    GLES2_ARG(TGLenum, target);
+    GLES2_ARG(TGLenum, pname);
+    GLES2_ARG(Tptr, paramsp);
+    GLES2_BARRIER_ARG_NORET;
+
+    GLfloat params[4];
+    glGetTexParameterfv(target, pname, params);
+    unsigned const count = gles2_glTexParameterCount(pname);
+    unsigned i;
+    for(i = 0; i < count; ++i) {
+        gles2_put_TGLfloat(s, paramsp + i*sizeof(TGLfloat), params[i]);
+    }
+}
+
+GLES2_CB(glGetTexParameteriv)
+{
+    GLES2_ARG(TGLenum, target);
+    GLES2_ARG(TGLenum, pname);
+    GLES2_ARG(Tptr, paramsp);
+    GLES2_BARRIER_ARG_NORET;
+
+    GLint params[4];
+    glGetTexParameteriv(target, pname, params);
+    unsigned const count = gles2_glTexParameterCount(pname);
+    unsigned i;
+    for(i = 0; i < count; ++i) {
+        gles2_put_TGLint(s, paramsp + i*sizeof(TGLint), params[i]);
+    }
+}
+
+GLES2_CB(glIsTexture)
+{
+    GLES2_ARG(TGLuint, texture);
+    GLES2_BARRIER_ARG;
+
+    GLES2_BARRIER_RET;
+    gles2_ret_TGLboolean(s, glIsTexture(texture));
+}
+
+GLES2_CB(glReadPixels)
+{
+    GLES2_ARG(TGLint, x);
+    GLES2_ARG(TGLint, y);
+    GLES2_ARG(TGLsizei, width);
+    GLES2_ARG(TGLsizei, height);
+    GLES2_ARG(TGLenum, format);
+    GLES2_ARG(TGLenum, type);
+    GLES2_ARG(Tptr, pixelsp);
+    GLES2_BARRIER_ARG;
+
+    unsigned bpp;
+    switch (format) {
+        case GL_ALPHA: bpp = 1; break;
+        case GL_RGB: bpp = (type == GL_UNSIGNED_BYTE) ? 3 : 2; break;
+        case GL_RGBA: bpp = (type == GL_UNSIGNED_BYTE) ? 4 : 2; break;
+        case GL_LUMINANCE: bpp = 1; break;
+        case GL_LUMINANCE_ALPHA: bpp = 2; break;
+        default:
+            GLES2_PRINT("ERROR: Unknown format 0x%x...\n", format);
+            bpp = 1;
+            break;
+    }
+
+    GLES2_PRINT("Reading %dx%dx%d image at %d,%d...\n",
+        width, height, bpp, x, y);
+    char* pixels = NULL;
+    unsigned nbytes = width*height*bpp;
+    pixels = malloc(nbytes);
+
+    glReadPixels(x, y, width, height, format, type, pixels);
+    GLES2_BARRIER_RET;
+    gles2_transfer(s, pixelsp, nbytes, pixels, 1);
+    free(pixels);
+}
+
+GLES2_CB(glTexImage2D)
+{
+    GLES2_ARG(TGLenum, target);
+    GLES2_ARG(TGLint, level);
+    GLES2_ARG(TGLint, internalformat);
+    GLES2_ARG(TGLsizei, width);
+    GLES2_ARG(TGLsizei, height);
+    GLES2_ARG(TGLint, border);
+    GLES2_ARG(TGLenum, format);
+    GLES2_ARG(TGLenum, type);
+    GLES2_ARG(Tptr, pixelsp);
+
+    unsigned bpp;
+
+    switch(format) {
+        case GL_ALPHA: bpp = 1; break;
+        case GL_RGB: bpp = (type == GL_UNSIGNED_BYTE) ? 3 : 2; break;
+        case GL_RGBA: bpp = (type == GL_UNSIGNED_BYTE) ? 4 : 2; break;
+        case GL_LUMINANCE: bpp = 1; break;
+        case GL_LUMINANCE_ALPHA: bpp = 2; break;
+        default:
+            GLES2_PRINT("ERROR: Unknown format 0x%x...\n", format);
+            bpp = 1;
+            break;
+    }
+
+    GLES2_PRINT("Uploading %dx%dx%d image...\n", width, height, bpp);
+    char* pixels = NULL;
+    if (pixelsp) {
+        unsigned nbytes = width*height*bpp;
+        pixels = malloc(nbytes);
+        gles2_transfer(s, pixelsp, nbytes, pixels, 0);
+    }
+    GLES2_BARRIER_ARG_NORET;
+
+    glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
+    free(pixels);
+}
+
+GLES2_CB(glTexParameterf)
+{
+    GLES2_ARG(TGLenum, target);
+    GLES2_ARG(TGLenum, pname);
+    GLES2_ARG(TGLfloat, param);
+    GLES2_BARRIER_ARG_NORET;
+
+    glTexParameterf(target, pname, param);
+}
+
+GLES2_CB(glTexParameterfv)
+{
+    GLES2_ARG(TGLenum, target);
+    GLES2_ARG(TGLenum, pname);
+    GLES2_ARG(Tptr, paramsp);
+  
+    GLfloat params[4];
+    unsigned const count = gles2_glTexParameterCount(pname);
+    unsigned i;
+    for (i = 0; i < count; ++i) {
+        params[i] = gles2_get_TGLfloat(s, paramsp + i*sizeof(TGLfloat));
+    }
+    GLES2_BARRIER_ARG_NORET;
+
+    glTexParameterfv(target, pname, params);
+}
+
+GLES2_CB(glTexParameteri)
+{
+    GLES2_ARG(TGLenum, target);
+    GLES2_ARG(TGLenum, pname);
+    GLES2_ARG(TGLint, param);
+    GLES2_BARRIER_ARG_NORET;
+
+    if(s->quality <= 50)
+    {
+        switch(pname)
+        {
+            case GL_TEXTURE_MIN_FILTER: param = GL_NEAREST; break;
+            case GL_TEXTURE_MAG_FILTER: param = GL_NEAREST; break;
+            default: break;
+        }
+    }
+    
+    glTexParameterf(target, pname, param);
+}
+
+GLES2_CB(glTexParameteriv)
+{
+    GLES2_ARG(TGLenum, target);
+    GLES2_ARG(TGLenum, pname);
+    GLES2_ARG(Tptr, paramsp);
+
+    GLint params[4];
+    unsigned const count = gles2_glTexParameterCount(pname);
+    unsigned i;
+    for(i = 0; i < count; ++i) {
+        params[i] = gles2_get_TGLint(s, paramsp + i*sizeof(GLint));
+    }
+    GLES2_BARRIER_ARG_NORET;
+    
+    glTexParameteriv(target, pname, params);
+}
+
+GLES2_CB(glTexSubImage2D)
+{
+    GLES2_ARG(TGLenum, target);
+    GLES2_ARG(TGLint, level);
+    GLES2_ARG(TGLint, xoffset);
+    GLES2_ARG(TGLint, yoffset);
+    GLES2_ARG(TGLsizei, width);
+    GLES2_ARG(TGLsizei, height);
+    GLES2_ARG(TGLenum, format);
+    GLES2_ARG(TGLenum, type);
+    GLES2_ARG(Tptr, pixelsp);
+
+    unsigned bpp;
+    switch (format) {
+        case GL_ALPHA: bpp = 1; break;
+        case GL_RGB: bpp = (type == GL_UNSIGNED_BYTE) ? 3 : 2; break;
+        case GL_RGBA: bpp = (type == GL_UNSIGNED_BYTE) ? 4 : 2; break;
+        case GL_LUMINANCE: bpp = 1; break;
+        case GL_LUMINANCE_ALPHA: bpp = 2; break;
+        default:
+            GLES2_PRINT("ERROR: Unknown format 0x%x...\n", format);
+            bpp = 1;
+            break;
+    }
+
+    GLES2_PRINT("Uploading partial %dx%dx%d image at %d,%d...\n",
+    width, height, bpp, xoffset, yoffset);
+
+    unsigned nbytes = width*height*bpp;
+    char* pixels = malloc(nbytes);
+    gles2_transfer(s, pixelsp, nbytes, pixels, 0);
+    GLES2_BARRIER_ARG_NORET;
+
+    glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
+    free(pixels);
+}
+
+
+GLES2_CB(glCompileShader)
+{
+    GLES2_ARG(TGLuint, shader);
+    GLES2_BARRIER_ARG_NORET;
+
+    glCompileShader(shader);
+}
+
+GLES2_CB(glCreateShader)
+{
+    GLES2_ARG(TGLenum, type);
+    GLES2_BARRIER_ARG_NORET;
+
+    gles2_ret_TGLuint(s, glCreateShader(type));
+}
+
+GLES2_CB(glDeleteShader)
+{
+    GLES2_ARG(TGLuint, shader);
+    GLES2_BARRIER_ARG_NORET;
+
+    glDeleteShader(shader);
+}
+
+GLES2_CB(glIsShader)
+{
+    GLES2_ARG(TGLuint, shader);
+    GLES2_BARRIER_ARG;
+
+    GLES2_BARRIER_RET;
+    gles2_ret_TGLboolean(s, glIsShader(shader));
+}
+
+GLES2_CB(glGetShaderiv)
+{
+    GLES2_ARG(TGLuint, shader);
+    GLES2_ARG(TGLenum, pname);
+    GLES2_ARG(Tptr, paramsp);
+    GLES2_BARRIER_ARG_NORET;
+
+    GLint param;
+    glGetShaderiv(shader, pname, &param);
+    gles2_put_TGLint(s, paramsp, param);
+}
+
+GLES2_CB(glGetShaderInfoLog)
+{
+    GLES2_ARG(TGLuint, shader);
+    GLES2_ARG(TGLsizei, bufsize);
+    GLES2_ARG(Tptr, lengthp);
+    GLES2_ARG(Tptr, infologp);
+
+    GLsizei length = gles2_get_TGLsizei(s, lengthp);
+    char* infolog = malloc(bufsize);
+    glGetShaderInfoLog(shader, bufsize, &length, infolog);
+    gles2_transfer(s, infologp, length, infolog, 1);
+    gles2_put_TGLsizei(s, lengthp, length);
+    GLES2_BARRIER_ARG_NORET;
+
+    GLES2_PRINT("shader %d infolog:\n%.*s\n", shader, length, infolog);
+    free(infolog);
+}
+
+#if 0
+GL_APICALL void GL_APIENTRY glGetShaderPrecisionFormat(GLenum shadertype,
+    GLenum precisiontype, GLint* range, GLint* precision)
+{
+    DUMMY();
+}
+
+GL_APICALL void GL_APIENTRY glGetShaderSource(GLuint shader, GLsizei bufsize,
+    GLsizei* length, char* source)
+{
+    DUMMY();
+}
+
+GL_APICALL GLboolean GL_APIENTRY glIsShader(GLuint shader)
+{
+    DUMMY();
+}
+
+GL_APICALL void GL_APIENTRY glReleaseShaderCompiler(void)
+{
+    DUMMY();
+}
+
+GL_APICALL void GL_APIENTRY glShaderBinary(GLsizei n, const GLuint* shaders,
+    GLenum binaryformat, const void* binary, GLsizei length)
+{
+    DUMMY();
+}
+#endif // 0
+
+GLES2_CB(glShaderSource)
+{
+    GLES2_ARG(TGLuint, shader);
+    GLES2_ARG(TGLsizei, count);
+    GLES2_ARG(Tptr, stringp);
+    GLES2_ARG(Tptr, lengthp);
+
+    char** string_fgl = malloc(sizeof(char*)*count);
+    GLint* length_fgl = malloc(sizeof(GLint)*count);
+
+    unsigned i;
+    for (i = 0; i < count; ++i) {
+        length_fgl[i] = gles2_get_TGLint(s, lengthp + i*sizeof(TGLint));
+        string_fgl[i] = malloc(length_fgl[i] + 1);
+        gles2_transfer(s, gles2_get_dword(s, stringp + i*sizeof(Tptr)),
+            length_fgl[i], string_fgl[i], 0);
+        string_fgl[i][length_fgl[i]] = 0;
+    }
+    GLES2_BARRIER_ARG_NORET;
+
+    GLES2_PRINT("shader %d source:\n", shader);
+    #if(GLES2_DEBUG == 1)
+    for(i = 0; i < count; ++i) {
+        fprintf(stderr, "%.*s", length_fgl[i], string_fgl[i]);
+    }
+    #endif // GLES2_DEBUG == 1
+    GLES2_PRINT("\n--END--");
+
+    glShaderSource(shader, (GLsizei)count,
+        (const char**)string_fgl, length_fgl);
+
+    for (i = 0; i < count; ++i) {
+        free(string_fgl[i]);
+    }
+    
+    free(string_fgl);
+    free(length_fgl);
+}
+
+GLES2_CB(glAttachShader)
+{
+    GLES2_ARG(TGLuint, program);
+    GLES2_ARG(TGLuint, shader);
+    GLES2_BARRIER_ARG_NORET;
+
+    glAttachShader(program, shader);
+}
+
+GLES2_CB(glBindAttribLocation)
+{
+    GLES2_ARG(TGLuint, program);
+    GLES2_ARG(TGLuint, index);
+    GLES2_ARG(Tptr, namep);
+
+    char name[120];
+    Tptr i;
+
+    for(i = 0; (name[i] = gles2_get_byte(s, namep + i)) ; ++i);
+    GLES2_BARRIER_ARG_NORET;
+
+    GLES2_PRINT("Binding attribute %s at %d...\n", name, index);
+    glBindAttribLocation(program, index, name);
+}
+
+GLES2_CB(glCreateProgram)//(void)
+{
+    GLES2_BARRIER_ARG;
+
+    GLES2_BARRIER_RET;
+    gles2_ret_TGLuint(s, glCreateProgram());
+}
+
+GLES2_CB(glDeleteProgram)//(GLuint program)
+{
+    GLES2_ARG(TGLuint, program);
+    GLES2_BARRIER_ARG_NORET;
+
+    glDeleteProgram(program);
+}
+
+#if 0
+GL_APICALL void GL_APIENTRY glDetachShader(GLuint program, GLuint shader)
+{
+    DUMMY();
+}
+
+GL_APICALL void GL_APIENTRY glGetActiveAttrib (GLuint program, GLuint index,
+    GLsizei bufsize, GLsizei* length, GLint* size, GLenum* type, char* name)
+{
+    DUMMY();
+}
+
+GL_APICALL void GL_APIENTRY glGetActiveUniform (GLuint program, GLuint index,
+    GLsizei bufsize, GLsizei* length, GLint* size, GLenum* type, char* name)
+{
+    DUMMY();
+}
+
+GL_APICALL void GL_APIENTRY glGetAttachedShaders (GLuint program,
+    GLsizei maxcount, GLsizei* count, GLuint* shaders)
+{
+    DUMMY();
+}
+#endif // 0
+
+GLES2_CB(glGetAttribLocation)
+{
+    GLES2_ARG(TGLuint, program);
+    GLES2_ARG(Tptr, namep);
+
+    char name[120];
+    Tptr i;
+
+    for (i = 0; (name[i] = gles2_get_byte(s, namep + i)) ; ++i);
+
+    GLES2_PRINT("Getting attribute %s location...\n", name);
+
+    gles2_ret_TGLint(s, glGetAttribLocation(program, name));
+    GLES2_BARRIER_ARG_NORET;
+}
+
+GLES2_CB(glGetProgramiv)
+{
+    GLES2_ARG(TGLuint, program);
+    GLES2_ARG(TGLenum, pname);
+    GLES2_ARG(Tptr, paramsp);
+    GLES2_BARRIER_ARG_NORET;
+
+    GLint param;
+
+    glGetProgramiv(program, pname, &param);
+    gles2_put_TGLint(s, paramsp, param);
+}
+
+GLES2_CB(glGetProgramInfoLog)
+{
+    GLES2_ARG(TGLuint, program);
+    GLES2_ARG(TGLsizei, bufsize);
+    GLES2_ARG(Tptr, lengthp);
+    GLES2_ARG(Tptr, infologp);
+
+    GLsizei length = gles2_get_TGLsizei(s, lengthp);
+    char* infolog = malloc(bufsize);
+    glGetProgramInfoLog(program, bufsize, &length, infolog);
+    gles2_transfer(s, infologp, length, infolog, 1);
+    gles2_put_TGLsizei(s, lengthp, length);
+    GLES2_BARRIER_ARG_NORET;
+    GLES2_PRINT("program %d infolog:\n%.*s\n", program, length, infolog);
+    free(infolog);
+}
+
+#if 0
+GL_APICALL void GL_APIENTRY glGetUniformfv(GLuint program, GLint location, GLfloat* params)
+{
+    DUMMY();
+}
+
+GL_APICALL void GL_APIENTRY glGetUniformiv(GLuint program, GLint location, GLint* params)
+{
+    DUMMY();
+}
+#endif // 0
+
+GLES2_CB(glGetUniformLocation)
+{
+    GLES2_ARG(TGLuint, program);
+    GLES2_ARG(Tptr, namep);
+
+    char name[120];
+    Tptr i;
+
+    for (i = 0; (name[i] = gles2_get_byte(s, namep + i)) ; ++i);
+
+    GLES2_PRINT("Getting uniform %s location...\n", name);
+
+    gles2_ret_TGLint(s, glGetUniformLocation(program, name));
+    GLES2_BARRIER_ARG_NORET;
+}
+
+GLES2_CB(glIsProgram)
+{
+    GLES2_ARG(TGLuint, program);
+    GLES2_BARRIER_ARG;
+
+    GLES2_BARRIER_RET;
+    gles2_ret_TGLboolean(s, glIsProgram(program));
+}
+
+GLES2_CB(glLinkProgram)
+{
+    GLES2_ARG(TGLuint, program);
+    GLES2_BARRIER_ARG_NORET;
+
+    glLinkProgram(program);
+}
+
+GLES2_CB(glUniform1f)
+{
+    GLES2_ARG(TGLint, location);
+    GLES2_ARG(TGLfloat, x);
+    GLES2_BARRIER_ARG_NORET;
+
+    glUniform1f(location, x);
+}
+
+GLES2_CB(glUniform1fv)
+{
+    GLES2_ARG(TGLint, location);
+    GLES2_ARG(TGLsizei, count);
+    GLES2_ARG(Tptr, vp);
+
+    unsigned nvs = count*1;
+    GLfloat* v = malloc(nvs*sizeof(*v));
+    unsigned i;
+
+    for (i = 0; i < nvs; ++i) {
+        v[i] = gles2_get_TGLint(s, vp + i*sizeof(TGLfloat));
+    }
+    GLES2_BARRIER_ARG_NORET;
+
+    glUniform1fv(location, count, v);
+    free(v);
+}
+
+GLES2_CB(glUniform1i)//(GLint location, GLint x)
+{
+    GLES2_ARG(TGLint, location);
+    GLES2_ARG(TGLint, x);
+    GLES2_BARRIER_ARG_NORET;
+
+    glUniform1i(location, x);
+}
+
+GLES2_CB(glUniform1iv)
+{
+    GLES2_ARG(TGLint, location);
+    GLES2_ARG(TGLsizei, count);
+    GLES2_ARG(Tptr, vp);
+
+    unsigned nvs = count*1;
+    GLint* v = malloc(nvs*sizeof(*v));
+    unsigned i;
+    for (i = 0; i < nvs; ++i) {
+        v[i] = gles2_get_TGLint(s, vp + i*sizeof(TGLint));
+    }
+    GLES2_BARRIER_ARG_NORET;
+
+    glUniform1iv(location, count, v);
+    free(v);
+}
+
+GLES2_CB(glUniform2f)
+{
+    GLES2_ARG(TGLint, location);
+    GLES2_ARG(TGLfloat, x);
+    GLES2_ARG(TGLfloat, y);
+    GLES2_BARRIER_ARG_NORET;
+
+    glUniform2f(location, x, y);
+}
+
+GLES2_CB(glUniform2fv)
+{
+    GLES2_ARG(TGLint, location);
+    GLES2_ARG(TGLsizei, count);
+    GLES2_ARG(Tptr, vp);
+ 
+    unsigned nvs = count*2;
+    GLfloat* v = malloc(nvs*sizeof(*v));
+    unsigned i;
+    for (i = 0; i < nvs; ++i) {
+        v[i] = gles2_get_TGLfloat(s, vp + i*sizeof(TGLfloat));
+    }
+    GLES2_BARRIER_ARG_NORET;
+
+    glUniform2fv(location, count, v);
+    free(v);
+}
+
+GLES2_CB(glUniform2i)
+{
+    GLES2_ARG(TGLint, location);
+    GLES2_ARG(TGLint, x);
+    GLES2_ARG(TGLint, y);
+    GLES2_BARRIER_ARG_NORET;
+
+    glUniform2i(location, x, y);
+}
+
+GLES2_CB(glUniform2iv)
+{
+    GLES2_ARG(TGLint, location);
+    GLES2_ARG(TGLsizei, count);
+    GLES2_ARG(Tptr, vp);
+
+    unsigned nvs = count*2;
+    GLint* v = malloc(nvs*sizeof(*v));
+    unsigned i;
+    for (i = 0; i < nvs; ++i) {
+        v[i] = gles2_get_TGLint(s, vp + i*sizeof(TGLint));
+    }
+    GLES2_BARRIER_ARG_NORET;
+    
+    glUniform2iv(location, count, v);
+    free(v);
+}
+
+GLES2_CB(glUniform3f)
+{
+    GLES2_ARG(TGLint, location);
+    GLES2_ARG(TGLfloat, x);
+    GLES2_ARG(TGLfloat, y);
+    GLES2_ARG(TGLfloat, z);
+    GLES2_BARRIER_ARG_NORET;
+
+    glUniform3f(location, x, y, z);
+}
+
+GLES2_CB(glUniform3fv)
+{
+    GLES2_ARG(TGLint, location);
+    GLES2_ARG(TGLsizei, count);
+    GLES2_ARG(Tptr, vp);
+ 
+    unsigned nvs = count*3;
+    GLfloat* v = malloc(nvs*sizeof(*v));
+    unsigned i;
+    for(i = 0; i < nvs; ++i) {
+        v[i] = gles2_get_TGLfloat(s, vp + i*sizeof(TGLfloat));
+    }
+    GLES2_BARRIER_ARG_NORET;
+   
+    glUniform3fv(location, count, v);
+    free(v);
+}
+
+GLES2_CB(glUniform3i)
+{
+    GLES2_ARG(TGLint, location);
+    GLES2_ARG(TGLint, x);
+    GLES2_ARG(TGLint, y);
+    GLES2_ARG(TGLint, z);
+    GLES2_BARRIER_ARG_NORET;
+
+    glUniform3i(location, x, y, z);
+}
+
+GLES2_CB(glUniform3iv)
+{
+    GLES2_ARG(TGLint, location);
+    GLES2_ARG(TGLsizei, count);
+    GLES2_ARG(Tptr, vp);
+
+    unsigned nvs = count*3;
+    GLint* v = malloc(nvs*sizeof(*v));
+    unsigned i;
+    for(i = 0; i < nvs; ++i) {
+        v[i] = gles2_get_TGLint(s, vp + i*sizeof(TGLint));
+    }
+    GLES2_BARRIER_ARG_NORET;
+    
+    glUniform3iv(location, count, v);
+    free(v);
+}
+
+GLES2_CB(glUniform4f)
+{
+    GLES2_ARG(TGLint, location);
+    GLES2_ARG(TGLfloat, x);
+    GLES2_ARG(TGLfloat, y);
+    GLES2_ARG(TGLfloat, z);
+    GLES2_ARG(TGLfloat, w);
+    GLES2_BARRIER_ARG_NORET;
+
+    glUniform4f(location, x, y, z, w);
+}
+
+GLES2_CB(glUniform4fv)
+{
+    GLES2_ARG(TGLint, location);
+    GLES2_ARG(TGLsizei, count);
+    GLES2_ARG(Tptr, vp);
+ 
+    unsigned nvs = count*4;
+    GLfloat* v = malloc(nvs*sizeof(*v));
+    unsigned i;
+    for(i = 0; i < nvs; ++i) {
+        v[i] = gles2_get_TGLfloat(s, vp + i*sizeof(TGLfloat));
+    }
+    
+    GLES2_BARRIER_ARG_NORET;
+    glUniform4fv(location, count, v);
+    free(v);
+}
+
+GLES2_CB(glUniform4i)
+{
+    GLES2_ARG(TGLint, location);
+    GLES2_ARG(TGLint, x);
+    GLES2_ARG(TGLint, y);
+    GLES2_ARG(TGLint, z);
+    GLES2_ARG(TGLint, w);
+    GLES2_BARRIER_ARG_NORET;
+
+    glUniform4i(location, x, y, z, w);
+}
+
+GLES2_CB(glUniform4iv)
+{
+    GLES2_ARG(TGLint, location);
+    GLES2_ARG(TGLsizei, count);
+    GLES2_ARG(Tptr, vp);
+
+    unsigned nvs = count*4;
+    GLint* v = malloc(nvs*sizeof(*v));
+    unsigned i;
+    for(i = 0; i < nvs; ++i) {
+        v[i] = gles2_get_TGLint(s, vp + i*sizeof(TGLint));
+    }
+    
+    GLES2_BARRIER_ARG_NORET;
+    glUniform4iv(location, count, v);
+    free(v);
+}
+
+GLES2_CB(glUniformMatrix2fv)
+{
+    GLES2_ARG(TGLint, location);
+    GLES2_ARG(TGLint, count);
+    GLES2_ARG(TGLboolean, transpose);
+    GLES2_ARG(Tptr, valuep);
+
+    unsigned nfloats = 2*2*count;
+    GLfloat* value = malloc(nfloats*sizeof(TGLfloat));
+    unsigned i;
+
+    for (i = 0; i < nfloats; ++i) {
+        value[i] = gles2_get_TGLfloat(s, valuep + i*sizeof(TGLfloat));
+	}
+
+    GLES2_BARRIER_ARG_NORET;
+
+    glUniformMatrix2fv(location, count, transpose, value);
+    free(value);
+}
+
+GLES2_CB(glUniformMatrix3fv)
+{
+    GLES2_ARG(TGLint, location);
+    GLES2_ARG(TGLint, count);
+    GLES2_ARG(TGLboolean, transpose);
+    GLES2_ARG(Tptr, valuep);
+
+    unsigned nfloats = 3*3*count;
+    GLfloat* value = malloc(nfloats*sizeof(TGLfloat));
+    unsigned i;
+    for(i = 0; i < nfloats; ++i) {
+        value[i] = gles2_get_TGLfloat(s, valuep + i*sizeof(TGLfloat));
+    }
+    GLES2_BARRIER_ARG_NORET;
+
+    glUniformMatrix3fv(location, count, transpose, value);
+    free(value);
+}
+
+GLES2_CB(glUniformMatrix4fv)
+{
+    GLES2_ARG(TGLint, location);
+    GLES2_ARG(TGLint, count);
+    GLES2_ARG(TGLboolean, transpose);
+    GLES2_ARG(Tptr, valuep);
+
+    unsigned nfloats = 4*4*count;
+    GLfloat* value = malloc(nfloats*sizeof(TGLfloat));
+    unsigned i;
+    for(i = 0; i < nfloats; ++i) {
+        value[i] = gles2_get_TGLfloat(s, valuep + i*sizeof(TGLfloat));
+    }
+    GLES2_BARRIER_ARG_NORET;
+    
+    glUniformMatrix4fv(location, count, transpose, value);
+    free(value);
+}
+
+GLES2_CB(glUseProgram)
+{
+    GLES2_ARG(TGLuint, program);
+    GLES2_BARRIER_ARG_NORET;
+
+    glUseProgram(program);
+}
+
+GLES2_CB(glBlendColor)
+{
+    GLES2_ARG(TGLclampf, red);
+    GLES2_ARG(TGLclampf, green);
+    GLES2_ARG(TGLclampf, blue);
+    GLES2_ARG(TGLclampf, alpha);
+    GLES2_BARRIER_ARG_NORET;
+
+    glBlendColor(red, green, blue, alpha);
+}
+
+GLES2_CB(glBlendEquation)
+{
+    GLES2_ARG(TGLenum, mode);
+    GLES2_BARRIER_ARG_NORET;
+
+    glBlendEquation(mode);
+}
+
+GLES2_CB(glBlendEquationSeparate)
+{
+    GLES2_ARG(TGLenum, modeRGB);
+    GLES2_ARG(TGLenum, modeAlpha);
+    GLES2_BARRIER_ARG_NORET;
+
+    glBlendEquationSeparate(modeRGB, modeAlpha);
+}
+
+GLES2_CB(glBlendFunc)
+{
+    GLES2_ARG(TGLenum, sfactor);
+    GLES2_ARG(TGLenum, dfactor);
+    GLES2_BARRIER_ARG_NORET;
+
+    glBlendFunc(sfactor, dfactor);
+}
+
+GLES2_CB(glBlendFuncSeparate)
+{
+    GLES2_ARG(TGLenum, srcRGB);
+    GLES2_ARG(TGLenum, dstRGB);
+    GLES2_ARG(TGLenum, srcAlpha);
+    GLES2_ARG(TGLenum, dstAlpha);
+    GLES2_BARRIER_ARG_NORET;
+
+    glBlendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha);
+}
+
+GLES2_CB(glPixelStorei)
+{
+    GLES2_ARG(TGLenum, pname);
+    GLES2_ARG(TGLint, param);
+    GLES2_BARRIER_ARG_NORET;
+
+    glPixelStorei(pname, param);
+}
+
+#if 0
+GL_APICALL void GL_APIENTRY glValidateProgram (GLuint program)
+{
+    DUMMY();
+}
+#endif // 0
+
+GLES2_CB(glClearStencil)
+{
+    GLES2_ARG(TGLint, s_);
+    GLES2_BARRIER_ARG_NORET;
+
+    glClearStencil(s_);
+}
+
+GLES2_CB(glStencilFunc)
+{
+    GLES2_ARG(TGLenum, func);
+    GLES2_ARG(TGLint, ref);
+    GLES2_ARG(TGLuint, mask);
+    GLES2_BARRIER_ARG_NORET;
+
+    glStencilFunc(func, ref, mask);
+}
+
+GLES2_CB(glStencilFuncSeparate)
+{
+    GLES2_ARG(TGLenum, face);
+    GLES2_ARG(TGLenum, func);
+    GLES2_ARG(TGLint, ref);
+    GLES2_ARG(TGLuint, mask);
+    GLES2_BARRIER_ARG_NORET;
+
+    glStencilFuncSeparate(face, func, ref, mask);
+}
+
+GLES2_CB(glStencilMask)
+{
+    GLES2_ARG(TGLuint, mask);
+    GLES2_BARRIER_ARG_NORET;
+
+    glStencilMask(mask);
+}
+
+GLES2_CB(glStencilMaskSeparate)
+{
+    GLES2_ARG(TGLenum, face);
+    GLES2_ARG(TGLuint, mask);
+    GLES2_BARRIER_ARG_NORET;
+
+    glStencilMaskSeparate(face, mask);
+}
+
+GLES2_CB(glStencilOp)
+{
+    GLES2_ARG(TGLenum, fail);
+    GLES2_ARG(TGLenum, zfail);
+    GLES2_ARG(TGLenum, zpass);
+    GLES2_BARRIER_ARG_NORET;
+
+    glStencilOp(fail, zfail, zpass);
+}
+
+GLES2_CB(glStencilOpSeparate)
+{
+    GLES2_ARG(TGLenum, face);
+    GLES2_ARG(TGLenum, fail);
+    GLES2_ARG(TGLenum, zfail);
+    GLES2_ARG(TGLenum, zpass);
+    GLES2_BARRIER_ARG_NORET;
+
+    glStencilOpSeparate(face, fail, zfail, zpass);
+}
+
+GLES2_CB(glBindFramebuffer)
+{
+    GLES2_ARG(TGLenum, target);
+    GLES2_ARG(TGLuint, framebuffer);
+    GLES2_BARRIER_ARG_NORET;
+
+    glBindFramebuffer(target, framebuffer);
+}
+
+GLES2_CB(glBindRenderbuffer)
+{
+    GLES2_ARG(TGLenum, target);
+    GLES2_ARG(TGLuint, renderbuffer);
+    GLES2_BARRIER_ARG_NORET;
+
+    glBindRenderbuffer(target, renderbuffer);
+}
+
+GLES2_CB(glCheckFramebufferStatus)
+{
+    GLES2_ARG(TGLenum, target);
+    GLES2_BARRIER_ARG;
+
+    GLES2_BARRIER_RET;
+    gles2_ret_TGLenum(s, glCheckFramebufferStatus(target));
+}
+
+GLES2_CB(glDeleteFramebuffers)
+{
+    GLES2_ARG(TGLsizei, n);
+    GLES2_ARG(Tptr, framebuffersp);
+
+    GLsizei i;
+    GLuint* framebuffers = (GLuint*)malloc(sizeof(GLuint)*n);
+
+    for (i = 0; i < n; ++i) {
+        framebuffers[i] = gles2_get_TGLuint(s,
+            framebuffersp + i*sizeof(TGLuint));
+    }
+    GLES2_BARRIER_ARG_NORET;
+    
+    glDeleteFramebuffers(n, framebuffers);
+    free(framebuffers);
+}
+
+GLES2_CB(glDeleteRenderbuffers)
+{
+    GLES2_ARG(TGLsizei, n);
+    GLES2_ARG(Tptr, renderbuffersp);
+
+    GLsizei i;
+    GLuint* renderbuffers = (GLuint*)malloc(sizeof(GLuint)*n);
+
+    for (i = 0; i < n; ++i) {
+        renderbuffers[i] = gles2_get_TGLuint(s,
+            renderbuffersp + i*sizeof(TGLuint));
+    }
+    GLES2_BARRIER_ARG_NORET;
+    
+    glDeleteRenderbuffers(n, renderbuffers);
+    free(renderbuffers);
+}
+
+GLES2_CB(glFramebufferRenderbuffer)
+{
+    GLES2_ARG(TGLenum, target);
+    GLES2_ARG(TGLenum, attachment);
+    GLES2_ARG(TGLenum, renderbuffertarget);
+    GLES2_ARG(TGLuint, renderbuffer);
+    GLES2_BARRIER_ARG_NORET;
+
+    glFramebufferRenderbuffer(target, attachment,
+        renderbuffertarget, renderbuffer);
+}
+
+GLES2_CB(glFramebufferTexture2D)
+{
+    GLES2_ARG(TGLenum, target);
+    GLES2_ARG(TGLenum, attachment);
+    GLES2_ARG(TGLenum, textarget);
+    GLES2_ARG(TGLuint, texture);
+    GLES2_ARG(TGLint, level);
+    GLES2_BARRIER_ARG_NORET;
+
+    glFramebufferTexture2D(target, attachment, textarget, texture, level);
+}
+
+GLES2_CB(glGenFramebuffers)
+{
+    GLES2_ARG(TGLsizei, n);
+    GLES2_ARG(Tptr, framebuffersp);
+
+    GLsizei i;
+    GLuint* framebuffers = (GLuint*)malloc(sizeof(GLuint)*n);
+
+    glGenFramebuffers(n, framebuffers);
+    for(i = 0; i < n; ++i) {
+        gles2_put_TGLuint(s, framebuffersp + i*sizeof(TGLuint),
+            framebuffers[i]);
+    }
+    GLES2_BARRIER_ARG_NORET;
+    
+    free(framebuffers);
+}
+
+GLES2_CB(glGenRenderbuffers)//(GLsizei n, GLuint* renderbuffers)
+{
+    GLES2_ARG(TGLsizei, n);
+    GLES2_ARG(Tptr, renderbuffersp);
+
+    GLsizei i;
+    GLuint* renderbuffers = (GLuint*)malloc(sizeof(GLuint)*n);
+
+    glGenRenderbuffers(n, renderbuffers);
+    for(i = 0; i < n; ++i) {
+        gles2_put_TGLuint(s, renderbuffersp + i*sizeof(TGLuint),
+            renderbuffers[i]);
+    }
+    GLES2_BARRIER_ARG_NORET;
+    
+    free(renderbuffers);
+}
+
+#if 0
+GLES2_CB(glGetFramebufferAttachmentParameteriv)//(GLenum target,
+    GLenum attachment, GLenum pname, GLint* params)
+{
+	DUMMY();
+}
+
+GLES2_CB(glGetRenderbufferParameteriv)//(GLenum target, GLenum pname,
+    GLint* params)
+{
+	DUMMY();
+}
+
+GLES2_CB(glIsFramebuffer)//(GLuint framebuffer)
+{
+	DUMMY();
+}
+
+GLES2_CB(glIsRenderbuffer)//(GLuint renderbuffer)
+{
+	DUMMY();
+}
+#endif // 0
+
+GLES2_CB(glBindBuffer)
+{
+    GLES2_ARG(TGLenum, target);
+    GLES2_ARG(TGLuint, buffer);
+    GLES2_BARRIER_ARG_NORET;
+
+    glBindTexture(target, buffer);
+}
+
+
+GLES2_CB(glRenderbufferStorage)
+{
+    GLES2_ARG(TGLenum, target);
+    GLES2_ARG(TGLenum, internalformat);
+    GLES2_ARG(TGLsizei, width);
+    GLES2_ARG(TGLsizei, height);
+    GLES2_BARRIER_ARG_NORET;
+
+    glRenderbufferStorage(target, internalformat, width, height);
+}
+
+GLES2_CB(glDepthFunc)
+{
+	GLES2_ARG(TGLenum, func);
+	GLES2_BARRIER_ARG_NORET;
+
+	glDepthFunc(func);
+}
+
+GLES2_CB(glDepthMask)
+{
+	GLES2_ARG(TGLboolean, flag);
+	GLES2_BARRIER_ARG_NORET;
+
+	glDepthMask(flag);
+}
+
+GLES2_CB(glDepthRangef)
+{
+	GLES2_ARG(TGLclampf, zNear);
+	GLES2_ARG(TGLclampf, zFar);
+	GLES2_BARRIER_ARG_NORET;
+
+	glDepthRangef(zNear, zFar);
+}
+
+GLES2_CB(glClearDepthf)
+{
+	GLES2_ARG(TGLclampf, depth);
+	GLES2_BARRIER_ARG_NORET;
+
+	glClearDepthf(depth);
+}
+
diff --git a/hw/gles2_calls.h b/hw/gles2_calls.h
new file mode 100644
index 0000000..208693b
--- /dev/null
+++ b/hw/gles2_calls.h
@@ -0,0 +1,189 @@
+/* Copyright (c) 2009-2010 Nokia Corporation
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) any later version of the License.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+CALL_ENTRY(eglGetError)
+CALL_ENTRY(eglGetDisplay)
+CALL_ENTRY(eglInitialize)
+CALL_DUMMY(eglTerminate)
+CALL_DUMMY(eglQueryString)
+CALL_ENTRY(eglGetConfigs)
+CALL_ENTRY(eglChooseConfig)
+CALL_ENTRY(eglGetConfigAttrib)
+CALL_ENTRY(eglCreateWindowSurface)
+CALL_DUMMY(eglCreatePbufferSurface)
+CALL_ENTRY(eglCreatePixmapSurface)
+CALL_ENTRY(eglDestroySurface)
+CALL_DUMMY(eglQuerySurface)
+CALL_DUMMY(eglBindAPI)
+CALL_DUMMY(eglQueryAPI)
+CALL_DUMMY(eglWaitClient)
+CALL_DUMMY(eglReleaseThread)
+CALL_DUMMY(eglCreatePbufferFromClientBuffer)
+CALL_DUMMY(eglSurfaceAttrib)
+CALL_ENTRY(eglBindTexImage)
+CALL_ENTRY(eglReleaseTexImage)
+CALL_DUMMY(eglSwapInterval)
+CALL_ENTRY(eglCreateContext)
+CALL_ENTRY(eglDestroyContext)
+CALL_ENTRY(eglMakeCurrent)
+CALL_DUMMY(eglGetCurrentContext)
+CALL_DUMMY(eglGetCurrentSurface)
+CALL_DUMMY(eglGetCurrentDisplay)
+CALL_DUMMY(eglQueryContext)
+CALL_DUMMY(eglWaitGL)
+CALL_DUMMY(eglWaitNative)
+CALL_ENTRY(eglSwapBuffers)
+CALL_DUMMY(eglCopyBuffers)
+CALL_DUMMY(eglGetProcAddress)
+
+CALL_ENTRY(glActiveTexture)
+CALL_ENTRY(glAttachShader)
+CALL_ENTRY(glBindAttribLocation)
+CALL_ENTRY(glBindBuffer)
+CALL_ENTRY(glBindFramebuffer)
+CALL_ENTRY(glBindRenderbuffer)
+CALL_ENTRY(glBindTexture)
+CALL_ENTRY(glBlendColor)
+CALL_ENTRY(glBlendEquation)
+CALL_ENTRY(glBlendEquationSeparate)
+CALL_ENTRY(glBlendFunc)
+CALL_ENTRY(glBlendFuncSeparate)
+CALL_DUMMY(glBufferData)
+CALL_DUMMY(glBufferSubData)
+CALL_ENTRY(glCheckFramebufferStatus)
+CALL_ENTRY(glClear)
+CALL_ENTRY(glClearColor)
+CALL_ENTRY(glClearDepthf)
+CALL_ENTRY(glClearStencil)
+CALL_ENTRY(glColorMask)
+CALL_ENTRY(glCompileShader)
+CALL_DUMMY(glCompressedTexImage2D)
+CALL_DUMMY(glCompressedTexSubImage2D)
+CALL_DUMMY(glCopyTexImage2D)
+CALL_DUMMY(glCopyTexSubImage2D)
+CALL_ENTRY(glCreateProgram)
+CALL_ENTRY(glCreateShader)
+CALL_ENTRY(glCullFace)
+CALL_DUMMY(glDeleteBuffers)
+CALL_ENTRY(glDeleteFramebuffers)
+CALL_ENTRY(glDeleteProgram)
+CALL_ENTRY(glDeleteRenderbuffers)
+CALL_ENTRY(glDeleteShader)
+CALL_ENTRY(glDeleteTextures)
+CALL_ENTRY(glDepthFunc)
+CALL_ENTRY(glDepthMask)
+CALL_ENTRY(glDepthRangef)
+CALL_DUMMY(glDetachShader)
+CALL_ENTRY(glDisable)
+CALL_ENTRY(glDisableVertexAttribArray)
+CALL_ENTRY(glDrawArrays)
+CALL_ENTRY(glDrawElements)
+CALL_ENTRY(glEnable)
+CALL_ENTRY(glEnableVertexAttribArray)
+CALL_ENTRY(glFinish)
+CALL_ENTRY(glFlush)
+CALL_ENTRY(glFramebufferRenderbuffer)
+CALL_ENTRY(glFramebufferTexture2D)
+CALL_ENTRY(glFrontFace)
+CALL_DUMMY(glGenBuffers)
+CALL_ENTRY(glGenerateMipmap)
+CALL_ENTRY(glGenFramebuffers)
+CALL_ENTRY(glGenRenderbuffers)
+CALL_ENTRY(glGenTextures)
+CALL_DUMMY(glGetActiveAttrib)
+CALL_DUMMY(glGetActiveUniform)
+CALL_DUMMY(glGetAttachedShaders)
+CALL_ENTRY(glGetAttribLocation)
+CALL_ENTRY(glGetBooleanv)
+CALL_DUMMY(glGetBufferParameteriv)
+CALL_ENTRY(glGetError)
+CALL_ENTRY(glGetFloatv)
+CALL_DUMMY(glGetFramebufferAttachmentParameteriv)
+CALL_ENTRY(glGetIntegerv)
+CALL_ENTRY(glGetProgramiv)
+CALL_ENTRY(glGetProgramInfoLog)
+CALL_DUMMY(glGetRenderbufferParameteriv)
+CALL_ENTRY(glGetShaderiv)
+CALL_ENTRY(glGetShaderInfoLog)
+CALL_DUMMY(glGetShaderPrecisionFormat)
+CALL_DUMMY(glGetShaderSource)
+CALL_DUMMY(glGetString)
+CALL_ENTRY(glGetTexParameterfv)
+CALL_ENTRY(glGetTexParameteriv)
+CALL_DUMMY(glGetUniformfv)
+CALL_DUMMY(glGetUniformiv)
+CALL_ENTRY(glGetUniformLocation)
+CALL_DUMMY(glGetVertexAttribfv)
+CALL_DUMMY(glGetVertexAttribiv)
+CALL_DUMMY(glGetVertexAttribPointerv)
+CALL_ENTRY(glHint)
+CALL_DUMMY(glIsBuffer)
+CALL_ENTRY(glIsEnabled)
+CALL_DUMMY(glIsFramebuffer)
+CALL_ENTRY(glIsProgram)
+CALL_DUMMY(glIsRenderbuffer)
+CALL_ENTRY(glIsShader)
+CALL_ENTRY(glIsTexture)
+CALL_ENTRY(glLineWidth)
+CALL_ENTRY(glLinkProgram)
+CALL_ENTRY(glPixelStorei)
+CALL_ENTRY(glPolygonOffset)
+CALL_ENTRY(glReadPixels)
+CALL_DUMMY(glReleaseShaderCompiler)
+CALL_ENTRY(glRenderbufferStorage)
+CALL_ENTRY(glSampleCoverage)
+CALL_ENTRY(glScissor)
+CALL_DUMMY(glShaderBinary)
+CALL_ENTRY(glShaderSource)
+CALL_ENTRY(glStencilFunc)
+CALL_ENTRY(glStencilFuncSeparate)
+CALL_ENTRY(glStencilMask)
+CALL_ENTRY(glStencilMaskSeparate)
+CALL_ENTRY(glStencilOp)
+CALL_ENTRY(glStencilOpSeparate)
+CALL_ENTRY(glTexImage2D)
+CALL_ENTRY(glTexParameterf)
+CALL_ENTRY(glTexParameterfv)
+CALL_ENTRY(glTexParameteri)
+CALL_ENTRY(glTexParameteriv)
+CALL_ENTRY(glTexSubImage2D)
+CALL_ENTRY(glUniform1f)
+CALL_ENTRY(glUniform1fv)
+CALL_ENTRY(glUniform1i)
+CALL_ENTRY(glUniform1iv)
+CALL_ENTRY(glUniform2f)
+CALL_ENTRY(glUniform2fv)
+CALL_ENTRY(glUniform2i)
+CALL_ENTRY(glUniform2iv)
+CALL_ENTRY(glUniform3f)
+CALL_ENTRY(glUniform3fv)
+CALL_ENTRY(glUniform3i)
+CALL_ENTRY(glUniform3iv)
+CALL_ENTRY(glUniform4f)
+CALL_ENTRY(glUniform4fv)
+CALL_ENTRY(glUniform4i)
+CALL_ENTRY(glUniform4iv)
+CALL_ENTRY(glUniformMatrix2fv)
+CALL_ENTRY(glUniformMatrix3fv)
+CALL_ENTRY(glUniformMatrix4fv)
+CALL_ENTRY(glUseProgram)
+CALL_DUMMY(glValidateProgram)
+CALL_ENTRY(glVertexAttrib1f)
+CALL_ENTRY(glVertexAttrib1fv)
+CALL_ENTRY(glVertexAttrib2f)
+CALL_ENTRY(glVertexAttrib2fv)
+CALL_ENTRY(glVertexAttrib3f)
+CALL_ENTRY(glVertexAttrib3fv)
+CALL_ENTRY(glVertexAttrib4f)
+CALL_ENTRY(glVertexAttrib4fv)
+CALL_ENTRY(glVertexAttribPointer)
+CALL_ENTRY(glViewport)
+
diff --git a/hw/nseries.c b/hw/nseries.c
index 27d517b..2617de8 100644
--- a/hw/nseries.c
+++ b/hw/nseries.c
@@ -1554,6 +1554,10 @@ static QEMUMachine n810_machine = {
     .init = n810_init,
 };
 
+#ifdef CONFIG_GLES2
+#include "gles2.h"
+#endif
+
 #define N900_SDRAM_SIZE (256 * 1024 * 1024)
 #define N900_ONENAND_CS 0
 #define N900_ONENAND_BUFSIZE (0xc000 << 1)
@@ -2135,6 +2139,9 @@ struct n900_s {
     void *tpa6130;
     void *lis302dl;
     void *smc;
+#ifdef CONFIG_GLES2
+    void *gles2;
+#endif
     int extended_key;
     int slide_open;
     int camera_cover_open;
@@ -2397,6 +2404,9 @@ static void n900_init(ram_addr_t ram_size,
     qemu_add_kbd_event_handler(n900_key_handler, s);
     qemu_set_display_close_handler(n900_display_close_callback, s);
 
+#ifdef CONFIG_GLES2
+    s->gles2 = gles2_init(s->cpu->env);
+#endif
     qemu_register_reset(n900_reset, s);
     n900_reset(s);
 }
diff --git a/qemu-options.hx b/qemu-options.hx
index 8450b45..ea07497 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -863,6 +863,25 @@ STEXI
 @end table
 ETEXI
 
+#ifdef TARGET_ARM
+DEFHEADING(arm target only:)
+#endif
+STEXI
+@table @option
+ETEXI
+
+#if(defined TARGET_ARM && defined CONFIG_GLES2)
+DEF("gles2-quality", HAS_ARG, QEMU_OPTION_gles2_quality,
+    "-gles2-quality  set GLES 2.0 rendering quality [0 ... 100]\n")
+#endif
+
+#ifdef TARGET_ARM
+DEFHEADING()
+#endif
+STEXI
+@end table
+ETEXI
+
 DEFHEADING(Network options:)
 STEXI
 @table @option
diff --git a/target-arm/helper.c b/target-arm/helper.c
index 99c5b12..a726947 100644
--- a/target-arm/helper.c
+++ b/target-arm/helper.c
@@ -1168,7 +1168,15 @@ static int get_phys_addr_mpu(CPUState *env, uint32_t address, int access_type,
     return 0;
 }
 
-static inline int get_phys_addr(CPUState *env, uint32_t address,
+#ifdef CONFIG_GLES2
+int get_phys_addr(CPUState *env, uint32_t address,
+                  int access_type, int is_user,
+                  uint32_t *phys_ptr, int *prot,
+                  target_ulong *page_size);
+#else
+static
+#endif
+int get_phys_addr(CPUState *env, uint32_t address,
                                 int access_type, int is_user,
                                 uint32_t *phys_ptr, int *prot,
                                 target_ulong *page_size)
diff --git a/vl.c b/vl.c
index bc7635c..f43879b 100644
--- a/vl.c
+++ b/vl.c
@@ -4342,6 +4342,16 @@ int main(int argc, char **argv, char **envp)
                 }
                 break;
 #endif
+#ifdef TARGET_ARM
+#ifdef CONFIG_GLES2
+            case QEMU_OPTION_gles2_quality:
+                {
+                    extern int gles2_quality;
+                    gles2_quality = strtoul(optarg, NULL, 10);
+                }
+                break;
+#endif
+#endif
 #ifdef CONFIG_KVM
             case QEMU_OPTION_enable_kvm:
                 kvm_allowed = 1;
