qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-devel] [PATCH 33/48] Add OpenGL ES accelerator support


From: Riku Voipio
Subject: [Qemu-devel] [PATCH 33/48] Add OpenGL ES accelerator support
Date: Fri, 26 Mar 2010 16:06:53 +0000

From: Riku Voipio <address@hidden>

To make it possible to use UI's based OpenGL ES efficiently
in qemu emulator, we need to offload the rendering to the host.
This patch provides the QEMU side of the code for omap3 emulation.
A kernel module, gles wrapper library inside qemu target filesystem,
and a gles library outside qemu are still needed. These will be
provided outside qemu project at:

http://maemo.gitorious.org/qemu-maemo/gles-libs

Signed-Off-By: Riku Voipio <address@hidden>
Signed-Off-By: Juha Riihimäki <address@hidden>

---
 Makefile.target     |   18 +
 configure           |   41 +-
 hw/gles2.c          |  764 ++++++++++++++++
 hw/gles2.h          |  323 +++++++
 hw/gles2_calls.c    | 2409 +++++++++++++++++++++++++++++++++++++++++++++++++++
 hw/gles2_calls.h    |  189 ++++
 hw/nseries.c        |   10 +
 qemu-options.hx     |   19 +
 target-arm/helper.c |   10 +-
 vl.c                |   10 +
 10 files changed, 3790 insertions(+), 3 deletions(-)
 create mode 100644 hw/gles2.c
 create mode 100644 hw/gles2.h
 create mode 100644 hw/gles2_calls.c
 create mode 100644 hw/gles2_calls.h

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, address@hidden(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, address@hidden(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
address@hidden @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
address@hidden 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;
-- 
1.6.5





reply via email to

[Prev in Thread] Current Thread [Next in Thread]