/* Window-specific OpenGL functions implementation.
 *
 * Copyright (c) 1999 Lionel Ulmer
 * Copyright (c) 2005 Raphael Junqueira
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#if 0
#pragma makedep unix
#endif

#include "config.h"

#include <stdarg.h>
#include <stdlib.h>
#include <assert.h>

#include <pthread.h>

#include "ntstatus.h"
#include "windef.h"
#include "winbase.h"
#include "winreg.h"
#include "ntuser.h"

#include "unixlib.h"
#include "unix_private.h"

#include "wine/debug.h"
#include "wine/rbtree.h"
#include "wine/vulkan_driver.h"

WINE_DEFAULT_DEBUG_CHANNEL(opengl);

static const BOOL is_win64 = (sizeof(void *) > sizeof(int));

static BOOL is_wow64(void)
{
    return !!NtCurrentTeb()->WowTebOffset;
}

static UINT64 call_gl_debug_message_callback;
pthread_mutex_t wgl_lock = PTHREAD_MUTEX_INITIALIZER;

/* context state management */

struct pixel_mode_state
{
    GLenum read_buffer;
};

struct light_model_state
{
    GLfloat ambient[4];
    GLint two_side;
};

struct lighting_state
{
    struct light_model_state model;
    GLenum shade_model;
};

struct depth_buffer_state
{
    GLenum depth_func;
};

struct viewport_state
{
    GLint x;
    GLint y;
    GLsizei w;
    GLsizei h;
};

struct enable_state
{
    GLboolean cull_face;
    GLboolean depth_test;
    GLboolean dither;
    GLboolean fog;
    GLboolean lighting;
    GLboolean normalize;
};

#define MAX_DRAW_BUFFERS 16

struct color_buffer_state
{
    GLfloat clear_color[4];
    GLenum  draw_buffers[MAX_DRAW_BUFFERS];
};

struct hint_state
{
    GLenum perspective_correction;
    GLenum point_smooth;
    GLenum line_smooth;
    GLenum polygon_smooth;
    GLenum fog;
    GLenum multisample_nv;
};

struct buffers
{
    unsigned int ref;
    struct rb_tree map;
    struct vk_device *vk_device;
};

struct context
{
    struct opengl_context base;

    HGLRC client;                  /* client-side context handle */
    HGLRC share;                   /* context to be shared with */
    int *attribs;                  /* creation attributes */
    UINT64 debug_callback;         /* client pointer */
    UINT64 debug_user;             /* client pointer */
    GLubyte *extensions;           /* extension string */
    char *wow64_version;           /* wow64 GL version override */
    struct buffers *buffers;       /* wow64 buffers map */
    BOOL use_pinned_memory;        /* use GL_AMD_pinned_memory to emulate persistent maps */

    /* semi-stub state tracker for wglCopyContext */
    GLbitfield used;                            /* context state used bits */
    struct pixel_mode_state pixel_mode;         /* GL_PIXEL_MODE_BIT */
    struct lighting_state lighting;             /* GL_LIGHTING_BIT */
    struct depth_buffer_state depth_buffer;     /* GL_DEPTH_BUFFER_BIT */
    struct viewport_state viewport;             /* GL_VIEWPORT_BIT */
    struct enable_state enable;                 /* GL_ENABLE_BIT */
    struct color_buffer_state color_buffer;     /* GL_COLOR_BUFFER_BIT */
    struct hint_state hint;                     /* GL_HINT_BIT */
    GLuint draw_fbo;                            /* currently bound draw FBO name */
    GLuint read_fbo;                            /* currently bound read FBO name */
    GLboolean has_viewport;                     /* whether viewport has been initialized */
};

struct buffer
{
    struct rb_entry entry;
    GLuint name;
    size_t size;
    void *host_ptr;
    void *map_ptr;
    size_t copy_length;
    void *vm_ptr;
    SIZE_T vm_size;
    BOOL pinned;

    /* members of Vulkan-backed buffer storages */
    struct vk_device *vk_device;
    VkDeviceMemory vk_memory;
    GLuint gl_memory;
    GLbitfield flags;
};

struct vk_device
{
    struct rb_entry entry;
    VkDevice vk_device;
    GUID uuid;
    VkPhysicalDeviceMemoryProperties memory_properties;

    PFN_vkAllocateMemory p_vkAllocateMemory;
    PFN_vkDestroyDevice p_vkDestroyDevice;
    PFN_vkFlushMappedMemoryRanges p_vkFlushMappedMemoryRanges;
    PFN_vkFreeMemory p_vkFreeMemory;
    PFN_vkGetMemoryFdKHR p_vkGetMemoryFdKHR;
    PFN_vkMapMemory2KHR p_vkMapMemory2KHR;
    PFN_vkUnmapMemory2KHR p_vkUnmapMemory2KHR;
};

static ULONG_PTR zero_bits;

static const struct vulkan_funcs *vk_funcs;
static VkInstance vk_instance;
static PFN_vkDestroyInstance p_vkDestroyInstance;
static BOOLEAN enabled_extensions[GL_EXTENSION_COUNT];

static int vk_device_cmp( const void *key, const struct rb_entry *entry )
{
    struct vk_device *vk_device = RB_ENTRY_VALUE( entry, struct vk_device, entry );
    return memcmp( key, &vk_device->uuid, sizeof(vk_device->uuid) );
}

struct rb_tree vk_devices = { vk_device_cmp };

static void opengl_client_context_init( HGLRC client_context, struct context *context, const struct opengl_funcs *funcs )
{
    struct opengl_client_context *client = opengl_client_context_from_client( client_context );
    client->unix_handle = (UINT_PTR)context;
    client->unix_funcs = (UINT_PTR)funcs;
}

/* the current context is assumed valid and doesn't need locking */
static struct context *get_current_context( TEB *teb, struct opengl_drawable **draw, struct opengl_drawable **read )
{
    struct opengl_context *base;
    struct context *context;

    if (!(base = teb->glContext)) return NULL;
    context = CONTAINING_RECORD( base, struct context, base );
    if (draw) *draw = context->base.draw;
    if (read) *read = context->base.read;
    return context;
}

struct context_attribute_desc
{
    GLenum name;
    GLbitfield bit;
    unsigned short offset;
    unsigned short size;
};

static struct context_attribute_desc context_attributes[] =
{
#define CONTEXT_ATTRIBUTE_DESC(bit, name, field) { name, bit, offsetof(struct context, field), sizeof(((struct context *)0)->field) }
    CONTEXT_ATTRIBUTE_DESC( GL_COLOR_BUFFER_BIT, GL_COLOR_CLEAR_VALUE, color_buffer.clear_color ),
    CONTEXT_ATTRIBUTE_DESC( GL_DEPTH_BUFFER_BIT, GL_DEPTH_FUNC, depth_buffer.depth_func ),
    CONTEXT_ATTRIBUTE_DESC( GL_ENABLE_BIT, GL_CULL_FACE, enable.cull_face ),
    CONTEXT_ATTRIBUTE_DESC( GL_ENABLE_BIT, GL_DEPTH_TEST, enable.depth_test ),
    CONTEXT_ATTRIBUTE_DESC( GL_ENABLE_BIT, GL_DITHER, enable.dither ),
    CONTEXT_ATTRIBUTE_DESC( GL_ENABLE_BIT, GL_FOG, enable.fog ),
    CONTEXT_ATTRIBUTE_DESC( GL_ENABLE_BIT, GL_LIGHTING, enable.lighting ),
    CONTEXT_ATTRIBUTE_DESC( GL_ENABLE_BIT, GL_NORMALIZE, enable.normalize ),
    CONTEXT_ATTRIBUTE_DESC( GL_HINT_BIT, GL_PERSPECTIVE_CORRECTION_HINT, hint.perspective_correction ),
    CONTEXT_ATTRIBUTE_DESC( GL_HINT_BIT, GL_POINT_SMOOTH_HINT, hint.point_smooth ),
    CONTEXT_ATTRIBUTE_DESC( GL_HINT_BIT, GL_LINE_SMOOTH_HINT, hint.line_smooth ),
    CONTEXT_ATTRIBUTE_DESC( GL_HINT_BIT, GL_POLYGON_SMOOTH_HINT, hint.polygon_smooth ),
    CONTEXT_ATTRIBUTE_DESC( GL_HINT_BIT, GL_FOG_HINT, hint.fog ),
    CONTEXT_ATTRIBUTE_DESC( GL_HINT_BIT, GL_MULTISAMPLE_FILTER_HINT_NV, hint.multisample_nv ),
    CONTEXT_ATTRIBUTE_DESC( GL_LIGHTING_BIT, GL_LIGHT_MODEL_AMBIENT, lighting.model.ambient ),
    CONTEXT_ATTRIBUTE_DESC( GL_LIGHTING_BIT, GL_LIGHT_MODEL_TWO_SIDE, lighting.model.two_side ),
    CONTEXT_ATTRIBUTE_DESC( GL_LIGHTING_BIT, GL_SHADE_MODEL, lighting.shade_model ),
    CONTEXT_ATTRIBUTE_DESC( GL_VIEWPORT_BIT, GL_VIEWPORT, viewport ),
#undef CONTEXT_ATTRIBUTE_DESC
};

static int compare_context_attributes( const void *v1, const void *v2 )
{
    const struct context_attribute_desc *a1 = v1, *a2 = v2;

    return (int)a1->name - (int)a2->name;
};

void set_context_attribute( TEB *teb, GLenum name, const void *value, size_t size )
{
    struct context_attribute_desc key = { .name = name };
    const struct context_attribute_desc *desc;
    struct context *ctx;

    if (!(ctx = get_current_context( teb, NULL, NULL ))) return;

    if (name != -1 && (desc = bsearch( &key, context_attributes, ARRAY_SIZE(context_attributes),
                                       sizeof(*context_attributes), compare_context_attributes )))
    {
        if (size && size != desc->size) ERR( "Invalid state attrib %#x parameter size %#zx\n", name, size );
        else
        {
            memcpy( (char *)ctx + desc->offset, value, desc->size );
            ctx->used |= desc->bit;
        }
    }
    else
    {
        if (ctx->used != -1) WARN( "Unsupported attribute on context %p/%p\n", teb->glCurrentRC, ctx );
        ctx->used |= -1;
    }
}

static BOOL copy_context_attributes( TEB *teb, HGLRC client_dst, struct context *dst,
                                     HGLRC client_src, struct context *src, GLbitfield mask )
{
    struct context *old_ctx = CONTAINING_RECORD( teb->glContext, struct context, base );
    HDC draw_hdc = teb->glReserved1[0], read_hdc = teb->glReserved1[1];
    const struct opengl_funcs *old_funcs = teb->glTable, *funcs;
    static const WCHAR staticW[] = {'s','t','a','t','i','c',0};
    UNICODE_STRING static_us = RTL_CONSTANT_STRING( staticW );
    HDC hdc = NULL;
    HWND hwnd;

    if (dst == old_ctx || !(funcs = get_context_funcs( client_dst )))
    {
        RtlSetLastWin32Error( ERROR_INVALID_HANDLE );
        return FALSE;
    }

    if (!mask) return TRUE;
    if (src->used == -1) FIXME( "Unsupported attributes on context %p/%p\n", client_src, src );
    if (src != dst && dst->used == -1) FIXME( "Unsupported attributes on context %p/%p\n", client_dst, dst );

    if (!(hwnd = NtUserCreateWindowEx( 0, &static_us, NULL, &static_us, WS_POPUP, 0, 0, 0, 0,
                                       NULL, NULL, NULL, NULL, 0, NULL, NULL, FALSE )) ||
        !(hdc = NtUserGetWindowDC( hwnd )) || !funcs->p_wglSetPixelFormat( hdc, dst->base.format, NULL ))
    {
        WARN( "Failed to create dummy window to update context attributes\n" );
        if (hdc) NtUserReleaseDC( hwnd, hdc );
        if (hwnd) NtUserDestroyWindow( hwnd );
        return FALSE;
    }

    funcs->p_wglMakeContextCurrentARB( hdc, hdc, client_dst );

    if (mask & GL_COLOR_BUFFER_BIT)
    {
        const GLfloat *floats = src->color_buffer.clear_color;
        funcs->p_glClearColor( floats[0], floats[1], floats[2], floats[3] );
        dst->color_buffer = src->color_buffer;
    }
    if (mask & GL_DEPTH_BUFFER_BIT)
    {
        funcs->p_glDepthFunc( src->depth_buffer.depth_func );
        dst->depth_buffer = src->depth_buffer;
    }
    if (mask & GL_ENABLE_BIT)
    {
        if (src->enable.cull_face) funcs->p_glEnable( GL_CULL_FACE );
        else funcs->p_glDisable( GL_CULL_FACE );
        if (src->enable.depth_test) funcs->p_glEnable( GL_DEPTH_TEST );
        else funcs->p_glDisable( GL_DEPTH_TEST );
        if (src->enable.dither) funcs->p_glEnable( GL_DITHER );
        else funcs->p_glDisable( GL_DITHER );
        if (src->enable.fog) funcs->p_glEnable( GL_FOG );
        else funcs->p_glDisable( GL_FOG );
        if (src->enable.lighting) funcs->p_glEnable( GL_LIGHTING );
        else funcs->p_glDisable( GL_LIGHTING );
        if (src->enable.normalize) funcs->p_glEnable( GL_NORMALIZE );
        else funcs->p_glDisable( GL_NORMALIZE );
        dst->enable = src->enable;
    }
    if (mask & GL_HINT_BIT)
    {
        if (src->hint.perspective_correction) funcs->p_glHint( GL_PERSPECTIVE_CORRECTION_HINT, src->hint.perspective_correction );
        if (src->hint.point_smooth)           funcs->p_glHint( GL_POINT_SMOOTH_HINT, src->hint.point_smooth );
        if (src->hint.line_smooth)            funcs->p_glHint( GL_LINE_SMOOTH_HINT, src->hint.line_smooth );
        if (src->hint.polygon_smooth)         funcs->p_glHint( GL_POLYGON_SMOOTH_HINT, src->hint.polygon_smooth );
        if (src->hint.fog)                    funcs->p_glHint( GL_FOG_HINT, src->hint.fog );
        if (src->hint.multisample_nv)         funcs->p_glHint( GL_MULTISAMPLE_FILTER_HINT_NV, src->hint.multisample_nv );
        dst->hint = src->hint;
    }
    if (mask & GL_LIGHTING_BIT)
    {
        funcs->p_glLightModelfv( GL_LIGHT_MODEL_AMBIENT, src->lighting.model.ambient );
        funcs->p_glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, src->lighting.model.two_side );
        funcs->p_glShadeModel( src->lighting.shade_model );
        dst->lighting = src->lighting;
    }
    if (mask & GL_VIEWPORT_BIT)
    {
        funcs->p_glViewport( src->viewport.x, src->viewport.y, src->viewport.w, src->viewport.h );
        dst->viewport = src->viewport;
    }
    dst->used |= (src->used & mask);

    if (!old_ctx) funcs->p_wglMakeContextCurrentARB( NULL, NULL, NULL );
    else old_funcs->p_wglMakeContextCurrentARB( draw_hdc, read_hdc, old_ctx->client );

    NtUserReleaseDC( hwnd, hdc );
    NtUserDestroyWindow( hwnd );

    return dst->used != -1 && src->used != -1;
}

static void unmap_vk_buffer( struct buffer *buffer )
{
    VkMemoryUnmapInfoKHR unmap_info =
    {
        .sType = VK_STRUCTURE_TYPE_MEMORY_UNMAP_INFO_KHR,
        .memory = buffer->vk_memory,
        .flags = VK_MEMORY_UNMAP_RESERVE_BIT_EXT,
    };
    VkResult vr;

    vr = buffer->vk_device->p_vkUnmapMemory2KHR( buffer->vk_device->vk_device, &unmap_info );
    if (vr) ERR( "VkMemoryUnmapInfoKHR failed: %x\n", vr);
}

static int compare_buffer_name( const void *key, const struct rb_entry *entry )
{
    struct buffer *buffer = RB_ENTRY_VALUE( entry, struct buffer, entry );
    return memcmp( key, &buffer->name, sizeof(buffer->name) );
}

void free_buffer( const struct opengl_funcs *funcs, struct buffer *buffer )
{
    if (buffer->vk_memory)
    {
        if (buffer->map_ptr) unmap_vk_buffer( buffer );
        buffer->vk_device->p_vkFreeMemory( buffer->vk_device->vk_device, buffer->vk_memory, NULL );
    }
    if (buffer->gl_memory) funcs->p_glDeleteMemoryObjectsEXT( 1, &buffer->gl_memory );
    if (buffer->vm_ptr) NtFreeVirtualMemory( GetCurrentProcess(), &buffer->vm_ptr, &buffer->vm_size, MEM_RELEASE );
    free( buffer );
}

static void release_buffers( const struct opengl_funcs *funcs, struct buffers *buffers )
{
    struct buffer *buffer, *next;

    if (--buffers->ref) return;

    RB_FOR_EACH_ENTRY_DESTRUCTOR( buffer, next, &buffers->map, struct buffer, entry )
        free_buffer( funcs, buffer );
    free( buffers );
}

static struct context *context_from_client_context( HGLRC client_context )
{
    struct opengl_context *base = opengl_context_from_handle( client_context );
    return base ? CONTAINING_RECORD( base, struct context, base ) : NULL;
}

static struct context *get_updated_context( TEB *teb, HGLRC client_context );

/* update context if it has been re-shared with another one */
static struct context *update_context( TEB *teb, HGLRC client_context, struct context *ctx )
{
    struct opengl_client_context *client = opengl_client_context_from_client( client_context );
    const struct opengl_funcs *funcs = get_context_funcs( client_context );
    struct context *share;

    if (client->current_tid) return ctx; /* currently in use */
    if (ctx->share == (HGLRC)-1) return ctx; /* not re-shared */

    share = ctx->share ? get_updated_context( teb, ctx->share ) : NULL;
    if (!funcs->p_context_reset( &ctx->base, share ? &share->base : NULL, ctx->attribs ))
    {
        WARN( "Failed to re-create context for wglShareLists\n" );
        return ctx;
    }
    if (share && share->buffers)
    {
        release_buffers( funcs, ctx->buffers );
        ctx->buffers = share->buffers;
        ctx->buffers->ref++;
    }
    ctx->share = (HGLRC)-1; /* initial shared context */
    copy_context_attributes( teb, client_context, ctx, client_context, ctx, ctx->used );
    return ctx;
}

static struct context *get_updated_context( TEB *teb, HGLRC client_context )
{
    struct context *context = context_from_client_context( client_context );
    return client_context ? update_context( teb, client_context, context ) : NULL;
}

static int *memdup_attribs( const int *attribs )
{
    const int *attr;
    size_t size;
    int *copy;

    if (!(attr = attribs)) return NULL;
    while (*attr++) { /* nothing */ }

    size = (attr - attribs) * sizeof(*attr);
    if (!(copy = malloc( size ))) return NULL;
    memcpy( copy, attribs, size );
    return copy;
}

struct extension_entry
{
    const char *name;
    size_t len;
};

#define USE_GL_EXT(x) [x] = { .name = #x, .len = sizeof(#x) - 1 },
static const struct extension_entry all_extensions[] = { ALL_GL_EXTS ALL_WGL_EXTS };
#undef USE_GL_EXT

static int extension_entry_cmp( const void *a, const void *b )
{
    const struct extension_entry *entry_a = a, *entry_b = b;
    size_t len = max( entry_a->len, entry_b->len );
    return strncmp( entry_a->name, entry_b->name, len );
};

static enum opengl_extension parse_extension( const char *ext, size_t len )
{
    const struct extension_entry entry = { .name = ext, .len = len }, *found;

    if ((found = bsearch( &entry, all_extensions, ARRAY_SIZE(all_extensions), sizeof(entry), extension_entry_cmp )))
        return found - all_extensions;

    /* Map host extensions */
    if (len == ARRAYSIZE("GL_EXT_memory_object_fd") - 1 && !memcmp( ext, "GL_EXT_memory_object_fd", len ))
        return GL_EXT_memory_object_win32;
    if (len == ARRAYSIZE("GL_EXT_semaphore_fd") - 1 && !memcmp( ext, "GL_EXT_semaphore_fd", len ))
        return GL_EXT_semaphore_win32;

    WARN( "Extension %s unknown\n", debugstr_an(ext, len) );
    return GL_EXTENSION_COUNT;
}

static const char *parse_gl_version( const char *gl_version, int *major, int *minor )
{
    const char *ptr = gl_version;

    *major = atoi( ptr );
    if (*major <= 0)
        ERR( "Invalid OpenGL major version %d.\n", *major );

    while (isdigit( *ptr )) ++ptr;
    if (*ptr++ != '.')
        ERR( "Invalid OpenGL version string %s.\n", debugstr_a(gl_version) );

    *minor = atoi( ptr );

    while (isdigit( *ptr )) ++ptr;
    return ptr;
}

static inline void ascii_to_unicode( WCHAR *dst, const char *src, size_t len )
{
    while (len--) *dst++ = (unsigned char)*src++;
}

static inline UINT asciiz_to_unicode( WCHAR *dst, const char *src )
{
    WCHAR *p = dst;
    while ((*p++ = *src++));
    return (p - dst) * sizeof(WCHAR);
}

static inline void unicode_to_ascii( char *dst, const WCHAR *src, size_t len )
{
    while (len--) *dst++ = *src++;
}

static HKEY reg_open_key( HKEY root, const WCHAR *name, ULONG name_len )
{
    UNICODE_STRING nameW = { name_len, name_len, (WCHAR *)name };
    OBJECT_ATTRIBUTES attr;
    HANDLE ret;

    attr.Length = sizeof(attr);
    attr.RootDirectory = root;
    attr.ObjectName = &nameW;
    attr.Attributes = 0;
    attr.SecurityDescriptor = NULL;
    attr.SecurityQualityOfService = NULL;

    return NtOpenKeyEx( &ret, MAXIMUM_ALLOWED, &attr, 0 ) ? 0 : ret;
}

static HKEY open_hkcu_key( const char *name )
{
    WCHAR bufferW[256];
    static HKEY hkcu;

    if (!hkcu)
    {
        char buffer[256];
        DWORD_PTR sid_data[(sizeof(TOKEN_USER) + SECURITY_MAX_SID_SIZE) / sizeof(DWORD_PTR)];
        DWORD i, len = sizeof(sid_data);
        SID *sid;

        if (NtQueryInformationToken( GetCurrentThreadEffectiveToken(), TokenUser, sid_data, len, &len ))
            return 0;

        sid = ((TOKEN_USER *)sid_data)->User.Sid;
        len = snprintf( buffer, sizeof(buffer), "\\Registry\\User\\S-%u-%u", sid->Revision,
                        MAKELONG( MAKEWORD( sid->IdentifierAuthority.Value[5],
                                            sid->IdentifierAuthority.Value[4] ),
                                  MAKEWORD( sid->IdentifierAuthority.Value[3],
                                            sid->IdentifierAuthority.Value[2] )));
        for (i = 0; i < sid->SubAuthorityCount; i++)
            len += snprintf( buffer + len, sizeof(buffer) - len, "-%u", sid->SubAuthority[i] );

        ascii_to_unicode( bufferW, buffer, len );
        hkcu = reg_open_key( NULL, bufferW, len * sizeof(WCHAR) );
    }

    return reg_open_key( hkcu, bufferW, asciiz_to_unicode( bufferW, name ) - sizeof(WCHAR) );
}

static ULONG query_reg_value( HKEY hkey, const WCHAR *name, KEY_VALUE_PARTIAL_INFORMATION *info, ULONG size )
{
    unsigned int name_size = name ? lstrlenW( name ) * sizeof(WCHAR) : 0;
    UNICODE_STRING nameW = { name_size, name_size, (WCHAR *)name };

    if (NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation,
                         info, size, &size ))
        return 0;

    return size - FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION, Data);
}

static ULONG query_reg_ascii_value( HKEY hkey, const char *name, KEY_VALUE_PARTIAL_INFORMATION *info, ULONG size )
{
    WCHAR nameW[64];
    asciiz_to_unicode( nameW, name );
    return query_reg_value( hkey, nameW, info, size );
}

static DWORD get_ascii_config_key( HKEY defkey, HKEY appkey, const char *name,
                                   char *buffer, DWORD size )
{
    char buf[offsetof(KEY_VALUE_PARTIAL_INFORMATION, Data[4096])];
    KEY_VALUE_PARTIAL_INFORMATION *info = (void *)buf;

    if (appkey && query_reg_ascii_value( appkey, name, info, sizeof(buf) ))
    {
        size = min( info->DataLength, size - sizeof(WCHAR) ) / sizeof(WCHAR);
        unicode_to_ascii( buffer, (WCHAR *)info->Data, size );
        buffer[size] = 0;
        return 0;
    }

    if (defkey && query_reg_ascii_value( defkey, name, info, sizeof(buf) ))
    {
        size = min( info->DataLength, size - sizeof(WCHAR) ) / sizeof(WCHAR);
        unicode_to_ascii( buffer, (WCHAR *)info->Data, size );
        buffer[size] = 0;
        return 0;
    }

    return ERROR_FILE_NOT_FOUND;
}

static char *query_opengl_option( const char *name )
{
    WCHAR bufferW[MAX_PATH + 16], *p, *appname;
    HKEY defkey, appkey = 0;
    char buffer[4096];
    char *str = NULL;
    DWORD len;

    /* @@ Wine registry key: HKCU\Software\Wine\OpenGL */
    defkey = open_hkcu_key( "Software\\Wine\\OpenGL" );

    /* open the app-specific key */
    appname = NtCurrentTeb()->Peb->ProcessParameters->ImagePathName.Buffer;
    if ((p = wcsrchr( appname, '/' ))) appname = p + 1;
    if ((p = wcsrchr( appname, '\\' ))) appname = p + 1;
    len = lstrlenW( appname );

    if (len && len < MAX_PATH)
    {
        HKEY tmpkey;
        int i;

        for (i = 0; appname[i]; i++) bufferW[i] = RtlDowncaseUnicodeChar( appname[i] );
        bufferW[i] = 0;
        appname = bufferW;

        /* @@ Wine registry key: HKCU\Software\Wine\AppDefaults\app.exe\OpenGL */
        if ((tmpkey = open_hkcu_key( "Software\\Wine\\AppDefaults" )))
        {
            static const WCHAR openglW[] = {'\\','O','p','e','n','G','L',0};
            memcpy( appname + i, openglW, sizeof(openglW) );
            appkey = reg_open_key( tmpkey, appname, lstrlenW( appname ) * sizeof(WCHAR) );
            NtClose( tmpkey );
        }
    }

    if (!get_ascii_config_key( defkey, appkey, name, buffer, sizeof(buffer) ))
        str = strdup( buffer );

    if (appkey) NtClose( appkey );
    if (defkey) NtClose( defkey );
    return str;
}

static size_t parse_extensions( const char *name, enum opengl_extension extensions[GL_EXTENSION_COUNT] )
{
    size_t count = 0;

    while (*name)
    {
        const char *end = name + 1;
        while (*end && *end != ' ') end++;
        if ((extensions[count] = parse_extension( name, end - name )) != GL_EXTENSION_COUNT) count++;
        name = end;
        if (*name == ' ') name++;
    }

    return count;
}

/* build the extension string by filtering out the disabled extensions */
static GLubyte *filter_extensions( struct context *ctx, const char *str, const struct opengl_funcs *funcs )
{
    struct opengl_client_context *client = opengl_client_context_from_client( ctx->base.client_context );
    enum opengl_extension extensions[GL_EXTENSION_COUNT];
    size_t count, i, size = 1;
    char *ret, *p;

    if (!(count = parse_extensions( str, extensions ))) return NULL;
    extensions[count++] = WGL_EXT_extensions_string;
    extensions[count++] = WGL_EXT_swap_control;

    for (i = 0; i < count; i++)
    {
        if (!client->extensions[extensions[i]]) continue;
        size += all_extensions[extensions[i]].len + 1;
    }

    if (!(p = ret = malloc( size ))) return NULL;

    for (i = 0; i < count; i++)
    {
        if (!client->extensions[extensions[i]]) continue;
        memcpy( p, all_extensions[extensions[i]].name, all_extensions[extensions[i]].len );
        p += all_extensions[extensions[i]].len;
        *p++ = ' ';
    }

    return (GLubyte *)ret;
}

static void set_gl_error( TEB *teb, GLenum error )
{
    const struct opengl_funcs *funcs = teb->glTable;
    struct opengl_client_context *client;
    struct context *ctx;

    if (!(ctx = get_current_context( teb, NULL, NULL ))) return;
    if (!(client = opengl_client_context_from_client( ctx->base.client_context ))) return;
    if (!client->last_error && !(client->last_error = funcs->p_glGetError())) client->last_error = error;
}

static BOOL get_default_fbo_integer( struct context *ctx, struct opengl_drawable *draw, struct opengl_drawable *read,
                                     GLenum pname, GLint *data )
{
    if (pname == GL_READ_BUFFER && !ctx->read_fbo)
    {
        *data = ctx->pixel_mode.read_buffer;
        return TRUE;
    }
    if ((pname == GL_DRAW_BUFFER || pname == GL_DRAW_BUFFER0) && !ctx->draw_fbo)
    {
        *data = ctx->color_buffer.draw_buffers[0];
        return TRUE;
    }
    if (pname >= GL_DRAW_BUFFER1 && pname <= GL_DRAW_BUFFER15 && !ctx->draw_fbo)
    {
        *data = ctx->color_buffer.draw_buffers[pname - GL_DRAW_BUFFER0];
        return TRUE;
    }
    if (pname == GL_DOUBLEBUFFER && !ctx->draw_fbo)
    {
        *data = draw->doublebuffer;
        return TRUE;
    }
    if (pname == GL_STEREO && !ctx->draw_fbo)
    {
        *data = draw->stereo;
        return TRUE;
    }

    return FALSE;
}

static BOOL get_integer( TEB *teb, GLenum pname, GLint *data )
{
    const struct opengl_funcs *funcs = teb->glTable;
    struct opengl_drawable *draw, *read;
    struct context *ctx;

    if (!(ctx = get_current_context( teb, &draw, &read )))
    {
        set_gl_error( teb, GL_INVALID_OPERATION );
        return FALSE;
    }

    switch (pname)
    {
    case GL_DRAW_FRAMEBUFFER_BINDING:
        *data = ctx->draw_fbo;
        return TRUE;
    case GL_READ_FRAMEBUFFER_BINDING:
        *data = ctx->read_fbo;
        return TRUE;
    case GL_DEVICE_NODE_MASK_EXT:
        if (!funcs->p_query_renderer) break;
        return funcs->p_query_renderer( pname, data );
    }

    return get_default_fbo_integer( ctx, draw, read, pname, data );
}

void wrap_glGetUnsignedBytevEXT( TEB *teb, GLenum pname, GLubyte *data, PFN_glGetUnsignedBytevEXT p_glGetUnsignedBytevEXT )
{
    const struct opengl_funcs *funcs = teb->glTable;

    switch (pname)
    {
    case GL_DEVICE_LUID_EXT:
        if (!funcs->p_query_renderer) break;
        funcs->p_query_renderer( pname, data );
        return;
    }

    return p_glGetUnsignedBytevEXT( pname, data );
}

const GLubyte *wrap_glGetString( TEB *teb, GLenum name, PFN_glGetString p_glGetString )
{
    const struct opengl_funcs *funcs = teb->glTable;
    const GLubyte *ret;

    if ((ret = p_glGetString( name )))
    {
        if (name == GL_VENDOR && funcs->p_wglQueryCurrentRendererStringWINE)
        {
            const char *vendor = funcs->p_wglQueryCurrentRendererStringWINE( WGL_RENDERER_VENDOR_ID_WINE );
            return vendor ? (const GLubyte *)vendor : ret;
        }
        if (name == GL_RENDERER && funcs->p_wglQueryCurrentRendererStringWINE)
        {
            const char *renderer = funcs->p_wglQueryCurrentRendererStringWINE( WGL_RENDERER_DEVICE_ID_WINE );
            return renderer ? (const GLubyte *)renderer : ret;
        }
        if (name == GL_EXTENSIONS)
        {
            struct context *ctx = get_current_context( teb, NULL, NULL );
            GLubyte **extensions = &ctx->extensions;
            if (*extensions || (*extensions = filter_extensions( ctx, (const char *)ret, funcs ))) return *extensions;
        }
        else if (name == GL_VERSION)
        {
            struct context *ctx = get_current_context( teb, NULL, NULL );
            if (ctx->wow64_version) return (const GLubyte *)ctx->wow64_version;
        }
    }

    return ret;
}

BOOL wrap_wglCopyContext( TEB *teb, HGLRC client_src, HGLRC client_dst, UINT mask )
{
    struct context *src, *dst;
    if (!(src = get_updated_context( teb, client_src ))) return FALSE;
    if (!(dst = get_updated_context( teb, client_dst ))) return FALSE;
    return copy_context_attributes( teb, client_dst, dst, client_src, src, mask );
}

static BOOL initialize_vk_device( TEB *teb, struct context *ctx )
{
    struct opengl_client_context *client = opengl_client_context_from_client( ctx->base.client_context );
    struct opengl_funcs *funcs = teb->glTable;
    VkPhysicalDevice *vk_physical_devices = NULL;
    struct vk_device *vk_device = NULL;
    GLint uuid_count = 0, i, j;
    VkResult vr;

    static PFN_vkCreateDevice p_vkCreateDevice;
    static PFN_vkEnumeratePhysicalDevices p_vkEnumeratePhysicalDevices;
    static PFN_vkGetPhysicalDeviceFeatures2KHR p_vkGetPhysicalDeviceFeatures2KHR;
    static PFN_vkGetPhysicalDeviceMemoryProperties p_vkGetPhysicalDeviceMemoryProperties;
    static PFN_vkGetPhysicalDeviceProperties2KHR p_vkGetPhysicalDeviceProperties2KHR;

    if (ctx->buffers->vk_device) return TRUE; /* already initialized */
    if (!client->extensions[GL_EXT_memory_object_win32] )
    {
        TRACE( "GL_EXT_memory_object_win32 is not supported\n" );
        return FALSE;
    }

    if (!vk_funcs)
    {
        PFN_vkCreateInstance p_vkCreateInstance;
        static const char *instance_extensions[] =
        {
            "VK_KHR_get_physical_device_properties2",
        };
        VkApplicationInfo app_info =
        {
            .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
            .pEngineName = "Wine WGL",
            .engineVersion = 1,
        };
        VkInstanceCreateInfo create_info =
        {
            .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
            .pApplicationInfo = &app_info,
            .ppEnabledExtensionNames = instance_extensions,
            .enabledExtensionCount = ARRAYSIZE(instance_extensions),
        };

        if (!(vk_funcs = __wine_get_vulkan_driver( WINE_VULKAN_DRIVER_VERSION ))) return FALSE;
        if (!vk_funcs->p_vkGetInstanceProcAddr
            || !(p_vkCreateInstance = (void *)vk_funcs->p_vkGetInstanceProcAddr( NULL, "vkCreateInstance" )))
            return FALSE;
        if ((vr = p_vkCreateInstance( &create_info, NULL, &vk_instance )))
        {
            WARN( "Failed to create a Vulkan instance, vr %d.\n", vr );
            return FALSE;
        }
#define GET_VK_FUNC(name) p_##name = (void *)vk_funcs->p_vkGetInstanceProcAddr( vk_instance, #name )
        GET_VK_FUNC( vkCreateDevice );
        GET_VK_FUNC( vkDestroyInstance );
        GET_VK_FUNC( vkEnumeratePhysicalDevices );
        GET_VK_FUNC( vkGetPhysicalDeviceFeatures2KHR );
        GET_VK_FUNC( vkGetPhysicalDeviceMemoryProperties );
        GET_VK_FUNC( vkGetPhysicalDeviceProperties2KHR );
#undef GET_VK_FUNC
    }
    if (!vk_instance) return FALSE;

    funcs->p_glGetIntegerv( GL_NUM_DEVICE_UUIDS_EXT, &uuid_count );
    for (i = 0; i < uuid_count; i++)
    {
        GLubyte uuid[GL_UUID_SIZE_EXT];
        struct rb_entry *entry;
        uint32_t count = 0;

        funcs->p_glGetUnsignedBytei_vEXT( GL_DEVICE_UUID_EXT, i, uuid );
        if ((entry = rb_get( &vk_devices, uuid )))
        {
            vk_device = RB_ENTRY_VALUE( entry, struct vk_device, entry );
            if (!vk_device->vk_device) continue; /* known incompatible device */
            TRACE( "Found existing device %p for uuid %s\n", vk_device, debugstr_guid(&vk_device->uuid) );
            ctx->buffers->vk_device = vk_device;
            free( vk_physical_devices );
            return TRUE;
        }

        if (!(vk_device = calloc( 1, sizeof(*vk_device) ))) return FALSE;
        memcpy( &vk_device->uuid, uuid, sizeof(uuid) );
        rb_put( &vk_devices, uuid, &vk_device->entry );

        if (!vk_physical_devices)
        {
            vr = p_vkEnumeratePhysicalDevices( vk_instance, &count, NULL );
            if (!vr && count)
            {
                vk_physical_devices = calloc( count, sizeof(*vk_physical_devices) );
                vr = p_vkEnumeratePhysicalDevices( vk_instance, &count, vk_physical_devices );
            }
            if (vr || !count)
            {
                WARN( "Could not get vulkan physical devices: %d\n", vr );
                continue;
            }
        }

        for (j = 0; j < count; j++)
        {
            static const char *device_extensions[] =
            {
                "VK_KHR_external_memory",
                "VK_KHR_external_memory_fd",
                "VK_EXT_map_memory_placed",
                "VK_KHR_map_memory2",
            };
            VkPhysicalDeviceIDProperties id_props =
            {
                .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES,
            };
            VkPhysicalDeviceProperties2 props =
            {
                .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
                .pNext = &id_props,
            };
            float priority = 0.0f;
            VkDeviceQueueCreateInfo queue_info =
            {
                .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
                .queueCount = 1,
                .pQueuePriorities = &priority,
            };
            VkDeviceCreateInfo device_info =
            {
                .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
                .queueCreateInfoCount = 1,
                .pQueueCreateInfos = &queue_info,
                .enabledExtensionCount = ARRAYSIZE(device_extensions),
                .ppEnabledExtensionNames = device_extensions,
            };
            VkPhysicalDeviceMapMemoryPlacedFeaturesEXT map_placed_feature =
            {
                .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAP_MEMORY_PLACED_FEATURES_EXT,
            };
            VkPhysicalDeviceFeatures2 features =
            {
                .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
                .pNext = &map_placed_feature,
            };

            p_vkGetPhysicalDeviceProperties2KHR( vk_physical_devices[j], &props );
            if (memcmp( id_props.deviceUUID, uuid, sizeof(uuid) )) continue;

            p_vkGetPhysicalDeviceFeatures2KHR( vk_physical_devices[j], &features );
            if (!map_placed_feature.memoryMapPlaced || !map_placed_feature.memoryUnmapReserve)
            {
                WARN( "Device %s does not support placed memory mapping\n", debugstr_guid(&vk_device->uuid) );
                continue;
            }

            p_vkGetPhysicalDeviceMemoryProperties( vk_physical_devices[j], &vk_device->memory_properties );
            vr = p_vkCreateDevice( vk_physical_devices[j], &device_info, NULL, &vk_device->vk_device );
            if (vr)
            {
                WARN( "Could not create vulkan device: %d\n", vr );
                continue;
            }

#define GET_VK_FUNC(name) vk_device->p_##name = (void *)vk_funcs->p_vkGetDeviceProcAddr( vk_device->vk_device, #name )
            GET_VK_FUNC( vkAllocateMemory );
            GET_VK_FUNC( vkDestroyDevice );
            GET_VK_FUNC( vkFreeMemory );
            GET_VK_FUNC( vkGetMemoryFdKHR );
            GET_VK_FUNC( vkMapMemory2KHR );
            GET_VK_FUNC( vkUnmapMemory2KHR );
            GET_VK_FUNC( vkFlushMappedMemoryRanges );
#undef GET_VK_FUNC
            TRACE( "Created %p device for uuid %s\n", vk_device, debugstr_guid(&vk_device->uuid) );
            free( vk_physical_devices );
            return TRUE;
        }
    }

    free( vk_physical_devices );
    WARN( "Could not find compatible Vulkan device\n" );
    return FALSE;
}

static void init_enabled_extensions(void)
{
    enum opengl_extension parsed_extensions[GL_EXTENSION_COUNT];
    char *enabled, *disabled;
    size_t count, i;

    if ((enabled = query_opengl_option( "EnabledExtensions" )))
    {
        count = parse_extensions( enabled, parsed_extensions );
        for (i = 0; i < count; i++) enabled_extensions[parsed_extensions[i]] = TRUE;
    }
    else
    {
        memset( enabled_extensions, TRUE, sizeof(enabled_extensions) );
    }

    if ((disabled = query_opengl_option( "DisabledExtensions" )))
    {
        count = parse_extensions( disabled, parsed_extensions );
        for (i = 0; i < count; i++) enabled_extensions[parsed_extensions[i]] = FALSE;
    }

    free( enabled );
    free( disabled );
}

static void make_context_current( TEB *teb, const struct opengl_funcs *funcs, HDC draw_hdc, HDC read_hdc,
                                  HGLRC client_context, struct context *ctx )
{
    struct opengl_client_context *client = opengl_client_context_from_client( ctx->base.client_context );
    const char *version, *rest = "";
    size_t count = 0, i;

    static pthread_once_t once = PTHREAD_ONCE_INIT;

    teb->glReserved1[0] = draw_hdc;
    teb->glReserved1[1] = read_hdc;
    teb->glTable = (void *)funcs;
    pop_default_fbo( teb );

    if (client->major_version) return; /* already synced */

    version = (const char *)funcs->p_glGetString( GL_VERSION );
    if (version) rest = parse_gl_version( version, &client->major_version, &client->minor_version );
    if (!client->major_version) client->major_version = 1;
    TRACE( "context %p version %d.%d\n", ctx, client->major_version, client->minor_version );

    funcs->p_init_extensions( client->extensions );

    if (client->major_version >= 3)
    {
        GLint extensions_count;
        funcs->p_glGetIntegerv( GL_NUM_EXTENSIONS, &extensions_count );
        for (i = 0; i < extensions_count; i++)
        {
            const char *name = (const char *)funcs->p_glGetStringi( GL_EXTENSIONS, i );
            enum opengl_extension ext = parse_extension( name, strlen( name ) );
            if (ext != GL_EXTENSION_COUNT) client->extensions[ext] = TRUE;
        }
    }
    else
    {
        enum opengl_extension extensions[GL_EXTENSION_COUNT];
        size_t extension_count = parse_extensions( (const char *)funcs->p_glGetString( GL_EXTENSIONS ), extensions);
        for (i = 0; i < extension_count; i++) client->extensions[i] = TRUE;
    }

    pthread_once( &once, init_enabled_extensions );

    for (i = 0; i < WGL_FIRST_EXTENSION; i++)
    {
        if (enabled_extensions[i] || !client->extensions[i]) continue;
        client->extensions[i] = FALSE;
        TRACE( "-- %s (disabled by config)\n", all_extensions[i].name );
    }

    if (is_win64 && ctx->buffers && !initialize_vk_device( teb, ctx )
        && !(ctx->use_pinned_memory = client->extensions[GL_AMD_pinned_memory]))
    {
        if (client->major_version > 4 || (client->major_version == 4 && client->minor_version > 3))
        {
            FIXME( "GL version %d.%d is not supported on wow64, using 4.3\n", client->major_version, client->minor_version );
            client->major_version = 4;
            client->minor_version = 3;
            asprintf( &ctx->wow64_version, "4.3%s", rest );
        }
        if (client->extensions[GL_ARB_buffer_storage])
        {
            FIXME( "Disabling has_GL_ARB_buffer_storage extension on wow64\n" );
            client->extensions[GL_ARB_buffer_storage] = FALSE;
        }
    }

    for (i = 0; i < WGL_FIRST_EXTENSION; i++) if (client->extensions[i]) client->extension_array[count++] = i;
    if (client->extensions[WGL_EXT_extensions_string]) client->extension_array[count++] = WGL_EXT_extensions_string;
    if (client->extensions[WGL_EXT_swap_control])      client->extension_array[count++] = WGL_EXT_swap_control;
    client->extension_count = count;

    if (TRACE_ON(opengl)) for (i = 0; i < count; i++) TRACE( "++ %s\n", all_extensions[client->extension_array[i]].name );

    ctx->color_buffer.draw_buffers[0] = ctx->base.draw->doublebuffer ? GL_BACK : GL_FRONT;
    ctx->pixel_mode.read_buffer = ctx->base.draw->doublebuffer ? GL_BACK : GL_FRONT;
}

static void free_context( struct context *ctx )
{
    free( ctx->wow64_version );
    free( ctx->extensions );
    free( ctx->attribs );
    free( ctx );
}

BOOL wrap_wglDeleteContext( TEB *teb, HGLRC client_context )
{
    const struct opengl_funcs *funcs = get_context_funcs( client_context );
    struct context *ctx = context_from_client_context( client_context );
    funcs->p_context_destroy( &ctx->base );
    free_context( ctx );
    return TRUE;
}

static GLenum drawable_buffer_from_buffer( struct opengl_drawable *drawable, GLenum buffer )
{
    if (buffer == GL_NONE) return GL_NONE;
    if (buffer < GL_FRONT_LEFT || buffer > GL_AUX3) return GL_NONE;
    return drawable->buffer_map[buffer - GL_FRONT_LEFT];
}

enum buffer_mask
{
    MASK_NONE        = 0,
    MASK_FRONT_LEFT  = 1,
    MASK_FRONT_RIGHT = 2,
    MASK_BACK_LEFT   = 4,
    MASK_BACK_RIGHT  = 8,
    MASK_BACK        = MASK_BACK_LEFT | MASK_BACK_RIGHT,
    MASK_FRONT       = MASK_FRONT_LEFT | MASK_FRONT_RIGHT,
    MASK_LEFT        = MASK_FRONT_LEFT | MASK_BACK_LEFT,
    MASK_RIGHT       = MASK_FRONT_RIGHT | MASK_BACK_RIGHT,
    MASK_ALL         = MASK_FRONT | MASK_BACK,
};

static enum buffer_mask buffer_mask_from_enum( GLenum buffer )
{
    switch (buffer)
    {
    case GL_BACK:           return MASK_BACK;
    case GL_BACK_LEFT:      return MASK_BACK_LEFT;
    case GL_BACK_RIGHT:     return MASK_BACK_LEFT;
    case GL_FRONT:          return MASK_FRONT;
    case GL_FRONT_LEFT:     return MASK_FRONT_LEFT;
    case GL_FRONT_RIGHT:    return MASK_FRONT_RIGHT;
    case GL_LEFT:           return MASK_LEFT;
    case GL_RIGHT:          return MASK_RIGHT;
    case GL_FRONT_AND_BACK: return MASK_ALL;
    default:                return MASK_NONE;
    }
}

static BOOL context_draws_back( struct context *ctx )
{
    for (int i = 0; i < ARRAY_SIZE(ctx->color_buffer.draw_buffers); i++)
        if (buffer_mask_from_enum( ctx->color_buffer.draw_buffers[i] ) & MASK_BACK)
            return TRUE;

    return FALSE;
}

static BOOL context_draws_front( struct context *ctx )
{
    for (int i = 0; i < ARRAY_SIZE(ctx->color_buffer.draw_buffers); i++)
        if (buffer_mask_from_enum( ctx->color_buffer.draw_buffers[i] ) & MASK_FRONT)
            return TRUE;
    return FALSE;
}

static void flush_context( TEB *teb, void (*flush)(void) )
{
    struct opengl_drawable *read, *draw;
    struct context *ctx = get_current_context( teb, &read, &draw );
    HWND draw_hwnd = ctx && draw && draw->client ? draw->client->hwnd : NULL;
    const struct opengl_funcs *funcs = teb->glTable;
    UINT flags = 0;

    if (flush && ctx && !ctx->draw_fbo && context_draws_front( ctx ) && draw->client) flags |= GL_FLUSH_PRESENT;
    if ((flags & GL_FLUSH_PRESENT) && draw->buffer_map[0] == GL_BACK_LEFT) flags |= GL_FLUSH_FORCE_SWAP;

    if (!ctx || !funcs->p_context_flush( &ctx->base, flush, flags ))
    {
        /* default implementation: call the functions directly */
        if (flush) flush();
    }

    if (flags & GL_FLUSH_FORCE_SWAP)
    {
        GLenum mask = GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT;
        RECT rect;

        WARN( "Front buffer rendering emulation, copying front buffer back\n" );

        NtUserGetClientRect( draw_hwnd, &rect, NtUserGetDpiForWindow( draw_hwnd ) );
        if (ctx->read_fbo) funcs->p_glBindFramebuffer( GL_READ_FRAMEBUFFER, 0 );
        funcs->p_glReadBuffer( GL_FRONT_LEFT );
        funcs->p_glBlitFramebuffer( 0, 0, rect.right, rect.bottom, 0, 0, rect.right, rect.bottom, mask, GL_NEAREST );
        if (ctx->read_fbo) funcs->p_glBindFramebuffer( GL_READ_FRAMEBUFFER, ctx->read_fbo );
        else funcs->p_glReadBuffer( drawable_buffer_from_buffer( read, ctx->pixel_mode.read_buffer ) );
    }
}

void wrap_glFinish( TEB *teb, PFN_glFinish p_glFinish )
{
    resolve_default_fbo( teb, FALSE );
    flush_context( teb, p_glFinish );
}

void wrap_glFlush( TEB *teb, PFN_glFlush p_glFlush )
{
    resolve_default_fbo( teb, FALSE );
    flush_context( teb, p_glFlush );
}

void wrap_glClear( TEB *teb, GLbitfield mask, PFN_glClear p_glClear )
{
    flush_context( teb, NULL );
    p_glClear( mask );
    resolve_default_fbo( teb, FALSE );
}

void wrap_glDrawPixels( TEB *teb, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels, PFN_glDrawPixels p_glDrawPixels )
{
    flush_context( teb, NULL );
    p_glDrawPixels( width, height, format, type, pixels );
    resolve_default_fbo( teb, FALSE );
}

void wrap_glReadPixels( TEB *teb, GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void *pixels, PFN_glReadPixels p_glReadPixels )
{
    flush_context( teb, NULL );
    p_glReadPixels( x, y, width, height, format, type, pixels );
}

void wrap_glViewport( TEB *teb, GLint x, GLint y, GLsizei width, GLsizei height, PFN_glViewport p_glViewport )
{
    flush_context( teb, NULL );
    p_glViewport( x, y, width, height );
    resolve_default_fbo( teb, FALSE );
}

BOOL wrap_wglSwapBuffers( TEB *teb, HDC hdc )
{
    const struct opengl_funcs *funcs = get_dc_funcs( hdc );
    BOOL ret;

    resolve_default_fbo( teb, FALSE );

    if (!(ret = funcs->p_wglSwapBuffers( hdc )))
    {
        /* default implementation: implicitly flush the context */
        flush_context( teb, funcs->p_glFlush );
    }

    return ret;
}

BOOL wrap_wglShareLists( TEB *teb, HGLRC client_src, HGLRC client_dst )
{
    const struct opengl_funcs *src_funcs = get_context_funcs( client_src ), *dst_funcs = get_context_funcs( client_dst );
    struct context *src, *dst;
    BOOL ret = FALSE;

    if (!(src = context_from_client_context( client_src ))) return FALSE;
    if (!(dst = context_from_client_context( client_dst ))) return FALSE;
    if (src_funcs != dst_funcs) RtlSetLastWin32Error( ERROR_INVALID_HANDLE );
    else if ((ret = dst->used != -1)) dst->share = client_src;
    else FIXME( "Unsupported attributes on context %p/%p\n", client_dst, dst );

    return ret;
}

HGLRC wrap_wglCreateContextAttribsARB( TEB *teb, HDC hdc, HGLRC client_shared, const int *attribs, HGLRC client_context )
{
    const struct opengl_funcs *funcs = get_dc_funcs( hdc );
    struct context *context, *shared = get_updated_context( teb, client_shared );

    if (!funcs->p_context_create) return 0;
    if (!(context = calloc( 1, sizeof(*context) )))
    {
        RtlSetLastWin32Error( ERROR_OUTOFMEMORY );
        return 0;
    }
    context->base.client_context = client_context;
    context->share = (HGLRC)-1; /* initial shared context */
    context->attribs = memdup_attribs( attribs );

    if (is_win64 && is_wow64())
    {
        if (shared)
        {
            context->buffers = shared->buffers;
            context->buffers->ref++;
        }
        else if (!(context->buffers = malloc( sizeof(*context->buffers ))))
        {
            free_context( context );
            return 0;
        }
        else
        {
            context->buffers->ref = 1;
            context->buffers->vk_device = NULL;
            rb_init( &context->buffers->map, compare_buffer_name );
        }
    }

    if (!(funcs->p_context_create( &context->base, hdc, shared ? &shared->base : NULL, attribs )))
    {
        free_context( context );
        return 0;
    }

    context->client = client_context;
    opengl_client_context_init( client_context, context, funcs );
    return client_context;
}

BOOL wrap_wglMakeContextCurrentARB( TEB *teb, HDC draw_hdc, HDC read_hdc, HGLRC client_context )
{
    struct context *ctx, *prev = get_current_context( teb, NULL, NULL );

    if (client_context)
    {
        const struct opengl_funcs *funcs = get_context_funcs( client_context );
        if (!(ctx = get_updated_context( teb, client_context ))) return FALSE;
        if (!funcs->p_wglMakeContextCurrentARB( draw_hdc, read_hdc, client_context )) return FALSE;
        make_context_current( teb, funcs, draw_hdc, read_hdc, client_context, ctx );
    }
    else if (prev)
    {
        const struct opengl_funcs *funcs = teb->glTable;
        if (!funcs->p_wglMakeContextCurrentARB( NULL, NULL, NULL )) return FALSE;
        teb->glTable = &null_opengl_funcs;
    }
    return TRUE;
}

HPBUFFERARB wrap_wglCreatePbufferARB( TEB *teb, HDC hdc, int format, int width, int height, const int *attribs, HPBUFFERARB client_pbuffer )
{
    const struct opengl_funcs *funcs = get_dc_funcs( hdc );
    if (!funcs->p_pbuffer_create( hdc, format, width, height, attribs, client_pbuffer )) return 0;
    return client_pbuffer;
}

static void gl_debug_message_callback( GLenum source, GLenum type, GLuint id, GLenum severity,
                                       GLsizei length, const GLchar *message, const void *user )
{
    struct gl_debug_message_callback_params *params;
    void *ret_ptr;
    ULONG ret_len;
    struct context *ctx = (struct context *)user;
    UINT len = strlen( message ) + 1, size;

    if (!ctx->debug_callback) return;
    if (!NtCurrentTeb())
    {
        fprintf( stderr, "msg:gl_debug_message_callback called from native thread, severity %#x, message \"%.*s\".\n",
                 severity, length, message );
        return;
    }

    size = offsetof(struct gl_debug_message_callback_params, message[len] );
    if (!(params = malloc( size ))) return;
    params->dispatch.callback = call_gl_debug_message_callback;
    params->debug_callback = ctx->debug_callback;
    params->debug_user = ctx->debug_user;
    params->source = source;
    params->type = type;
    params->id = id;
    params->severity = severity;
    params->length = length;
    memcpy( params->message, message, len );

    KeUserDispatchCallback( &params->dispatch, size, &ret_ptr, &ret_len );
    free( params );
}

void wrap_glDebugMessageCallback( TEB *teb, GLDEBUGPROC callback, const void *user, PFN_glDebugMessageCallback p_glDebugMessageCallback )
{
    struct context *ctx = get_current_context( teb, NULL, NULL );
    ctx->debug_callback = (UINT_PTR)callback;
    ctx->debug_user     = (UINT_PTR)user;
    p_glDebugMessageCallback( gl_debug_message_callback, ctx );
    set_context_attribute( teb, -1 /* unsupported */, NULL, 0 );
}

void wrap_glDebugMessageCallbackAMD( TEB *teb, GLDEBUGPROCAMD callback, void *user, PFN_glDebugMessageCallbackAMD p_glDebugMessageCallbackAMD )
{
    struct context *ctx = get_current_context( teb, NULL, NULL );
    ctx->debug_callback = (UINT_PTR)callback;
    ctx->debug_user     = (UINT_PTR)user;
    p_glDebugMessageCallbackAMD( gl_debug_message_callback, ctx );
    set_context_attribute( teb, -1 /* unsupported */, NULL, 0 );
}

void set_current_fbo( TEB *teb, GLenum target, GLuint fbo )
{
    struct context *ctx;

    if (!(ctx = get_current_context( teb, NULL, NULL ))) return;
    if (target == GL_FRAMEBUFFER) ctx->draw_fbo = ctx->read_fbo = fbo;
    if (target == GL_DRAW_FRAMEBUFFER) ctx->draw_fbo = fbo;
    if (target == GL_READ_FRAMEBUFFER) ctx->read_fbo = fbo;
}

GLuint get_default_fbo( TEB *teb, GLenum target )
{
    struct opengl_drawable *draw, *read;
    struct context *ctx;

    if (!(ctx = get_current_context( teb, &draw, &read ))) return 0;
    if (target == GL_FRAMEBUFFER) return draw->draw_fbo;
    if (target == GL_DRAW_FRAMEBUFFER) return draw->draw_fbo;
    if (target == GL_READ_FRAMEBUFFER) return read->read_fbo;
    return 0;
}

void push_default_fbo( TEB *teb )
{
    const struct opengl_funcs *funcs = teb->glTable;
    struct opengl_drawable *draw, *read;
    struct context *ctx;

    if (!(ctx = get_current_context( teb, &draw, &read ))) return;
    if (!ctx->draw_fbo && draw->draw_fbo) funcs->p_glBindFramebuffer( GL_DRAW_FRAMEBUFFER, 0 );
    if (!ctx->read_fbo && read->read_fbo) funcs->p_glBindFramebuffer( GL_READ_FRAMEBUFFER, 0 );
}

void pop_default_fbo( TEB *teb )
{
    const struct opengl_funcs *funcs = teb->glTable;
    struct opengl_drawable *draw, *read;
    struct context *ctx;
    RECT rect;

    if (!(ctx = get_current_context( teb, &draw, &read ))) return;
    if (!ctx->draw_fbo) funcs->p_glBindFramebuffer( GL_DRAW_FRAMEBUFFER, draw->draw_fbo );
    if (!ctx->read_fbo) funcs->p_glBindFramebuffer( GL_READ_FRAMEBUFFER, read->read_fbo );
    if (!ctx->has_viewport && draw->draw_fbo && draw->client)
    {
        NtUserGetClientRect( draw->client->hwnd, &rect, NtUserGetDpiForWindow( draw->client->hwnd ) );
        funcs->p_glViewport( 0, 0, rect.right, rect.bottom );
        ctx->has_viewport = GL_TRUE;
    }
}

void resolve_default_fbo( TEB *teb, BOOL read )
{
    const struct opengl_funcs *funcs = teb->glTable;
    struct opengl_drawable *drawable;
    struct context *ctx;

    if (!(ctx = get_current_context( teb, read ? NULL : &drawable, read ? &drawable : NULL )) || !drawable) return;

    if (drawable->draw_fbo && drawable->read_fbo && drawable->draw_fbo != drawable->read_fbo)
    {
        GLenum mask = GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT;
        RECT rect;

        NtUserGetClientRect( drawable->client->hwnd, &rect, NtUserGetDpiForWindow( drawable->client->hwnd ) );

        funcs->p_glBindFramebuffer( GL_READ_FRAMEBUFFER, drawable->draw_fbo );
        funcs->p_glBindFramebuffer( GL_DRAW_FRAMEBUFFER, drawable->read_fbo );

        if (context_draws_front( ctx ))
        {
            funcs->p_glReadBuffer( GL_COLOR_ATTACHMENT0 );
            funcs->p_glDrawBuffer( GL_COLOR_ATTACHMENT0 );
            funcs->p_glBlitFramebuffer( 0, 0, 0, 0, rect.right, rect.bottom, rect.right, rect.bottom, mask, GL_NEAREST );
            mask &= ~(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
        }

        if ((drawable->doublebuffer && context_draws_back( ctx )) || (!drawable->doublebuffer && drawable->stereo && context_draws_front( ctx )))
        {
            funcs->p_glReadBuffer( GL_COLOR_ATTACHMENT1 );
            funcs->p_glDrawBuffer( GL_COLOR_ATTACHMENT1 );
            funcs->p_glBlitFramebuffer( 0, 0, 0, 0, rect.right, rect.bottom, rect.right, rect.bottom, mask, GL_NEAREST );
            mask &= ~(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
        }

        if (drawable->doublebuffer && drawable->stereo && context_draws_front( ctx ))
        {
            funcs->p_glReadBuffer( GL_COLOR_ATTACHMENT2 );
            funcs->p_glDrawBuffer( GL_COLOR_ATTACHMENT2 );
            funcs->p_glBlitFramebuffer( 0, 0, 0, 0, rect.right, rect.bottom, rect.right, rect.bottom, mask, GL_NEAREST );
            mask &= ~(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
        }

        if (drawable->doublebuffer && drawable->stereo && context_draws_back( ctx ))
        {
            funcs->p_glReadBuffer( GL_COLOR_ATTACHMENT3 );
            funcs->p_glDrawBuffer( GL_COLOR_ATTACHMENT3 );
            funcs->p_glBlitFramebuffer( 0, 0, 0, 0, rect.right, rect.bottom, rect.right, rect.bottom, mask, GL_NEAREST );
            mask &= ~(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
        }

        funcs->p_glBindFramebuffer( GL_READ_FRAMEBUFFER, ctx->read_fbo );
        funcs->p_glBindFramebuffer( GL_DRAW_FRAMEBUFFER, ctx->draw_fbo );
    }
}

static GLenum *set_default_fbo_draw_buffers( struct context *ctx, struct opengl_drawable *draw,
                                             GLsizei count, const GLenum *src, GLenum *dst )
{
    UINT used[4] = {0};

    for (GLsizei i = 0; i < count; i++) dst[i] = GL_BACK_LEFT; /* some invalid combination */
    for (GLsizei i = 0; i < count; i++)
    {
        if ((buffer_mask_from_enum( src[i] ) & MASK_FRONT_LEFT) && used[0]++) return dst;
        if ((buffer_mask_from_enum( src[i] ) & MASK_FRONT_RIGHT) && used[1]++) return dst;
        if ((buffer_mask_from_enum( src[i] ) & MASK_BACK_LEFT) && used[2]++) return dst;
        if ((buffer_mask_from_enum( src[i] ) & MASK_BACK_RIGHT) && used[3]++) return dst;
        if (src[i] && !drawable_buffer_from_buffer( draw, src[i] )) return dst;
    }

    memset( ctx->color_buffer.draw_buffers, 0, sizeof(ctx->color_buffer.draw_buffers) );
    for (GLsizei i = 0; i < count; i++)
    {
        dst[i] = drawable_buffer_from_buffer( draw, src[i] );
        if (i >= MAX_DRAW_BUFFERS) FIXME( "Needs %u draw buffers\n", i );
        else ctx->color_buffer.draw_buffers[i] = src[i];
    }

    return dst;
}

void wrap_glDrawBuffers( TEB *teb, GLsizei n, const GLenum *bufs, PFN_glDrawBuffers p_glDrawBuffers )
{
    GLenum buffer[MAX_DRAW_BUFFERS];
    struct opengl_drawable *draw;
    struct context *ctx;

    if ((ctx = get_current_context( teb, &draw, NULL )) && !ctx->draw_fbo)
        bufs = set_default_fbo_draw_buffers( ctx, draw, n, bufs, buffer );

    p_glDrawBuffers( n, bufs );
}

void wrap_glFramebufferDrawBuffersEXT( TEB *teb, GLuint fbo, GLsizei n, const GLenum *bufs, PFN_glFramebufferDrawBuffersEXT p_glFramebufferDrawBuffersEXT )
{
    GLenum buffer[MAX_DRAW_BUFFERS];
    struct opengl_drawable *draw;
    struct context *ctx;

    if ((ctx = get_current_context( teb, &draw, NULL )) && !fbo)
        bufs = set_default_fbo_draw_buffers( ctx, draw, n, bufs, buffer );

    p_glFramebufferDrawBuffersEXT( fbo, n, bufs );
}

void wrap_glNamedFramebufferDrawBuffers( TEB *teb, GLuint fbo, GLsizei n, const GLenum *bufs, PFN_glNamedFramebufferDrawBuffers p_glNamedFramebufferDrawBuffers )
{
    GLenum buffer[MAX_DRAW_BUFFERS];
    struct opengl_drawable *draw;
    struct context *ctx;

    if ((ctx = get_current_context( teb, &draw, NULL )) && !fbo)
        bufs = set_default_fbo_draw_buffers( ctx, draw, n, bufs, buffer );

    p_glNamedFramebufferDrawBuffers( fbo, n, bufs );
}

static GLenum set_default_fbo_draw_buffer( struct context *ctx, struct opengl_drawable *draw, GLint src )
{
    GLenum dst = drawable_buffer_from_buffer( draw, src );
    if (src && !dst)
    {
        WARN( "Invalid draw buffer %#x for context %p\n", src, ctx );
        return src;
    }
    memset( ctx->color_buffer.draw_buffers, 0, sizeof(ctx->color_buffer.draw_buffers) );
    ctx->color_buffer.draw_buffers[0] = src;
    return dst;
}

void wrap_glDrawBuffer( TEB *teb, GLenum buf, PFN_glDrawBuffer p_glDrawBuffer )
{
    struct opengl_drawable *draw;
    struct context *ctx;

    if ((ctx = get_current_context( teb, &draw, NULL )) && !ctx->draw_fbo)
        buf = set_default_fbo_draw_buffer( ctx, draw, buf );

    p_glDrawBuffer( buf );
}

void wrap_glFramebufferDrawBufferEXT( TEB *teb, GLuint fbo, GLenum mode, PFN_glFramebufferDrawBufferEXT p_glFramebufferDrawBufferEXT )
{
    struct opengl_drawable *draw;
    struct context *ctx;

    if ((ctx = get_current_context( teb, &draw, NULL )) && !fbo)
        mode = set_default_fbo_draw_buffer( ctx, draw, mode );

    p_glFramebufferDrawBufferEXT( fbo, mode );
}

void wrap_glNamedFramebufferDrawBuffer( TEB *teb, GLuint fbo, GLenum buf, PFN_glNamedFramebufferDrawBuffer p_glNamedFramebufferDrawBuffer )
{
    struct opengl_drawable *draw;
    struct context *ctx;

    if ((ctx = get_current_context( teb, &draw, NULL )) && !fbo)
        buf = set_default_fbo_draw_buffer( ctx, draw, buf );

    p_glNamedFramebufferDrawBuffer( fbo, buf );
}

static GLenum set_default_fbo_read_buffer( struct context *ctx, struct opengl_drawable *read, GLint src )
{
    GLenum dst = drawable_buffer_from_buffer( read, src );
    if (src && !dst)
    {
        WARN( "Invalid read buffer %#x for context %p\n", src, ctx );
        return src;
    }
    ctx->pixel_mode.read_buffer = src;
    return dst;
}

void wrap_glReadBuffer( TEB *teb, GLenum src, PFN_glReadBuffer p_glReadBuffer )
{
    struct opengl_drawable *read;
    struct context *ctx;

    if ((ctx = get_current_context( teb, NULL, &read )) && !ctx->read_fbo)
        src = set_default_fbo_read_buffer( ctx, read, src );

    p_glReadBuffer( src );
}

void wrap_glFramebufferReadBufferEXT( TEB *teb, GLuint fbo, GLenum mode, PFN_glFramebufferReadBufferEXT p_glFramebufferReadBufferEXT )
{
    struct opengl_drawable *read;
    struct context *ctx;

    if ((ctx = get_current_context( teb, NULL, &read )) && !fbo)
        mode = set_default_fbo_read_buffer( ctx, read, mode );

    p_glFramebufferReadBufferEXT( fbo, mode );
}

void wrap_glNamedFramebufferReadBuffer( TEB *teb, GLuint fbo, GLenum src, PFN_glNamedFramebufferReadBuffer p_glNamedFramebufferReadBuffer )
{
    struct opengl_drawable *read;
    struct context *ctx;

    if ((ctx = get_current_context( teb, NULL, &read )) && !fbo)
        src = set_default_fbo_read_buffer( ctx, read, src );

    p_glNamedFramebufferReadBuffer( fbo, src );
}

void wrap_glGetIntegerv( TEB *teb, GLenum pname, GLint *data, PFN_glGetIntegerv p_glGetIntegerv )
{
    if (get_integer( teb, pname, data )) return;
    else p_glGetIntegerv( pname, data );
}

void wrap_glGetBooleanv( TEB *teb, GLenum pname, GLboolean *data, PFN_glGetBooleanv p_glGetBooleanv )
{
    GLint value;
    if (get_integer( teb, pname, &value )) *data = !!value;
    else p_glGetBooleanv( pname, data );
}

void wrap_glGetDoublev( TEB *teb, GLenum pname, GLdouble *data, PFN_glGetDoublev p_glGetDoublev )
{
    GLint value;
    if (get_integer( teb, pname, &value )) *data = value;
    else p_glGetDoublev( pname, data );
}

void wrap_glGetFloatv( TEB *teb, GLenum pname, GLfloat *data, PFN_glGetFloatv p_glGetFloatv )
{
    GLint value;
    if (get_integer( teb, pname, &value )) *data = value;
    else p_glGetFloatv( pname, data );
}

void wrap_glGetInteger64v( TEB *teb, GLenum pname, GLint64 *data, PFN_glGetInteger64v p_glGetInteger64v )
{
    GLint value;
    if (get_integer( teb, pname, &value )) *data = value;
    else p_glGetInteger64v( pname, data );
}

void wrap_glGetFramebufferParameteriv( TEB *teb, GLuint fbo, GLenum pname, GLint *params, PFN_glGetFramebufferParameteriv p_glGetFramebufferParameteriv )
{
    struct opengl_drawable *draw, *read;
    struct context *ctx;

    if ((ctx = get_current_context( teb, &draw, &read )) && !fbo)
    {
        if (get_default_fbo_integer( ctx, draw, read, pname, params )) return;
        fbo = draw->draw_fbo;
    }

    p_glGetFramebufferParameteriv( fbo, pname, params );
}

GLenum wrap_glGetError( TEB *teb, PFN_glGetError p_glGetError )
{
    struct opengl_client_context *client;
    GLenum error, wrapped;
    struct context *ctx;

    if (!(ctx = get_current_context( teb, NULL, NULL ))) return GL_INVALID_OPERATION;
    if (!(client = opengl_client_context_from_client( ctx->base.client_context ))) return GL_INVALID_OPERATION;

    error = p_glGetError();
    wrapped = client->last_error;
    client->last_error = GL_NO_ERROR;
    return wrapped ? wrapped : error;
}

GLsync wrap_glCreateSyncFromCLeventARB( TEB *teb, struct _cl_context *context, struct _cl_event *event, GLbitfield flags, GLsync client_sync, PFN_glCreateSyncFromCLeventARB p_glCreateSyncFromCLeventARB )
{
    GLsync sync;

    FIXME( "stub!\n" );

    if (!(sync = p_glCreateSyncFromCLeventARB( context, event, flags ))) return NULL;
    client_sync->unix_handle = (UINT_PTR)sync;

    return client_sync;
}

GLsync wrap_glFenceSync( TEB *teb, GLenum condition, GLbitfield flags, GLsync client_sync, PFN_glFenceSync p_glFenceSync )
{
    GLsync sync;

    TRACE( "condition %#x, flags %#x, client_sync %p\n", condition, flags, client_sync );

    if (!(sync = p_glFenceSync( condition, flags ))) return NULL;
    client_sync->unix_handle = (UINT_PTR)sync;

    return client_sync;
}

GLsync wrap_glImportSyncEXT( TEB *teb, GLenum external_sync_type, GLintptr external_sync, GLbitfield flags, GLsync client_sync, PFN_glImportSyncEXT p_glImportSyncEXT )
{
    GLsync sync;

    FIXME( "stub!\n" );

    if (!(sync = p_glImportSyncEXT( external_sync_type, external_sync, flags ))) return NULL;
    client_sync->unix_handle = (UINT_PTR)sync;

    return client_sync;
}

NTSTATUS process_attach( void *args )
{
    struct process_attach_params *params = args;
    call_gl_debug_message_callback = params->call_gl_debug_message_callback;
    if (is_win64 && is_wow64())
    {
        SYSTEM_BASIC_INFORMATION info;

        NtQuerySystemInformation( SystemEmulationBasicInformation, &info, sizeof(info), NULL );
        zero_bits = (ULONG_PTR)info.HighestUserAddress | 0x7fffffff;
    }

    qsort( context_attributes, ARRAY_SIZE(context_attributes), sizeof(*context_attributes), compare_context_attributes );
    return STATUS_SUCCESS;
}

NTSTATUS thread_attach( void *args )
{
    TEB *teb = args;
    teb->glTable = &null_opengl_funcs;
    return STATUS_SUCCESS;
}

NTSTATUS process_detach( void *args )
{
    struct vk_device *vk_device, *next;

    RB_FOR_EACH_ENTRY_DESTRUCTOR( vk_device, next, &vk_devices, struct vk_device, entry )
    {
        if (vk_device->vk_device) vk_device->p_vkDestroyDevice( vk_device->vk_device, NULL );
        free( vk_device );
    }
    rb_destroy( &vk_devices, NULL, NULL );
    if (vk_instance)
    {
        p_vkDestroyInstance( vk_instance, NULL );
        vk_instance = NULL;
    }
    vk_funcs = NULL;
    return STATUS_SUCCESS;
}

NTSTATUS get_pixel_formats( void *args )
{
    struct get_pixel_formats_params *params = args;
    const struct opengl_funcs *funcs = get_dc_funcs( params->hdc );
    if (!funcs || !funcs->p_get_pixel_formats) return STATUS_NOT_IMPLEMENTED;
    funcs->p_get_pixel_formats( params->formats, params->max_formats,
                                    &params->num_formats, &params->num_onscreen_formats );
    return STATUS_SUCCESS;
}

#ifdef _WIN64

struct wow64_string_entry
{
    const char *str;
    PTR32 wow64_str;
};
static struct wow64_string_entry *wow64_strings;
static SIZE_T wow64_strings_count;

NTSTATUS return_wow64_string( const void *str, PTR32 *wow64_str )
{
    void *tmp;
    SIZE_T i;

    if (!str)
    {
        *wow64_str = 0;
        return STATUS_SUCCESS;
    }

    pthread_mutex_lock( &wgl_lock );

    for (i = 0; i < wow64_strings_count; i++) if (wow64_strings[i].str == str) break;
    if (i == wow64_strings_count && (tmp = realloc( wow64_strings, (i + 1) * sizeof(*wow64_strings) )))
    {
        wow64_strings = tmp;
        wow64_strings[i].str = str;
        wow64_strings[i].wow64_str = 0;
        wow64_strings_count += 1;
    }

    if (i == wow64_strings_count) ERR( "Failed to allocate memory for wow64 strings\n" );
    else if (wow64_strings[i].wow64_str) *wow64_str = wow64_strings[i].wow64_str;
    else if (*wow64_str)
    {
        strcpy( UlongToPtr(*wow64_str), str );
        wow64_strings[i].wow64_str = *wow64_str;
    }

    pthread_mutex_unlock( &wgl_lock );

    if (*wow64_str) return STATUS_SUCCESS;
    *wow64_str = strlen( str ) + 1;
    return STATUS_BUFFER_TOO_SMALL;
}

static GLuint get_target_name( TEB *teb, GLenum target )
{
    const struct opengl_funcs *funcs = teb->glTable;
    GLenum binding_name;
    GLint name = 0;

    switch (target)
    {
    case GL_ARRAY_BUFFER: binding_name = GL_ARRAY_BUFFER_BINDING; break;
    case GL_ATOMIC_COUNTER_BUFFER: binding_name = GL_ATOMIC_COUNTER_BUFFER_BINDING; break;
    case GL_COPY_READ_BUFFER: binding_name = GL_COPY_READ_BUFFER_BINDING; break;
    case GL_COPY_WRITE_BUFFER: binding_name = GL_COPY_WRITE_BUFFER_BINDING; break;
    case GL_DISPATCH_INDIRECT_BUFFER: binding_name = GL_DISPATCH_INDIRECT_BUFFER_BINDING; break;
    case GL_DRAW_INDIRECT_BUFFER: binding_name = GL_DRAW_INDIRECT_BUFFER_BINDING; break;
    case GL_ELEMENT_ARRAY_BUFFER: binding_name = GL_ELEMENT_ARRAY_BUFFER_BINDING; break;
    case GL_PIXEL_PACK_BUFFER: binding_name = GL_PIXEL_PACK_BUFFER_BINDING; break;
    case GL_PIXEL_UNPACK_BUFFER: binding_name = GL_PIXEL_UNPACK_BUFFER_BINDING; break;
    case GL_QUERY_BUFFER: binding_name = GL_QUERY_BUFFER_BINDING; break;
    case GL_SHADER_STORAGE_BUFFER: binding_name = GL_SHADER_STORAGE_BUFFER_BINDING; break;
    case GL_TEXTURE_BUFFER: binding_name = GL_TEXTURE_BUFFER_BINDING; break;
    case GL_TRANSFORM_FEEDBACK_BUFFER: binding_name = GL_TRANSFORM_FEEDBACK_BUFFER_BINDING; break;
    case GL_UNIFORM_BUFFER: binding_name = GL_UNIFORM_BUFFER_BINDING; break;
    default:
        FIXME( "unknown target %x\n", target );
        return 0;
    };

    funcs->p_glGetIntegerv( binding_name, &name );
    return name;
}

static struct buffer *get_named_buffer( TEB *teb, GLuint name )
{
    struct context *ctx = get_current_context( teb, NULL, NULL );
    struct rb_entry *entry;

    if (ctx && (entry = rb_get( &ctx->buffers->map, &name )))
        return RB_ENTRY_VALUE( entry, struct buffer, entry );
    return NULL;
}

struct buffer *invalidate_buffer_name( TEB *teb, GLuint name )
{
    struct buffer *buffer = get_named_buffer( teb, name );
    if (buffer)
    {
        struct context *ctx = get_current_context( teb, NULL, NULL );
        rb_remove( &ctx->buffers->map, &buffer->entry );
    }
    return buffer;
}

struct buffer *invalidate_buffer_target( TEB *teb, GLenum target )
{
    GLuint name = get_target_name( teb, target );
    return name ? invalidate_buffer_name( teb, name ) : NULL;
}

static struct buffer *get_target_buffer( TEB *teb, GLenum target )
{
    GLuint name = get_target_name( teb, target );
    return name ? get_named_buffer( teb, name ) : NULL;
}

static BOOL use_driver_buffer_map( struct buffer *buffer )
{
    return !buffer || (!buffer->vk_memory && !buffer->pinned);
}

static BOOL buffer_vm_alloc( TEB *teb, struct buffer *buffer, SIZE_T size )
{
    if (buffer->vm_size >= size) return TRUE;
    if (buffer->vm_ptr)
    {
        NtFreeVirtualMemory( GetCurrentProcess(), &buffer->vm_ptr, &buffer->vm_size, MEM_RELEASE );
        buffer->vm_ptr = NULL;
        buffer->vm_size = 0;
    }
    if (NtAllocateVirtualMemory( GetCurrentProcess(), &buffer->vm_ptr, zero_bits, &size,
                                 MEM_COMMIT, PAGE_READWRITE ))
    {
        ERR("NtAllocateVirtualMemory failed\n");
        set_gl_error( teb, GL_OUT_OF_MEMORY );
        return FALSE;
    }
    buffer->vm_size = size;
    return TRUE;
}

static void flush_buffer( TEB *teb, struct buffer *buffer, size_t offset, size_t length )
{
    VkMappedMemoryRange memory_range =
    {
        .sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
        .memory = buffer->vk_memory,
        .offset = (char *)buffer->map_ptr - (char *)buffer->host_ptr + offset,
        .size = length,
    };
    VkResult vr;

    if (!buffer->vk_memory) return;

    vr = buffer->vk_device->p_vkFlushMappedMemoryRanges( buffer->vk_device->vk_device, 1, &memory_range );
    if (vr) ERR( "vkFlushMappedMemoryRanges failed: %x\n", vr );
}

static int find_vk_memory_type( struct vk_device *vk_device, uint32_t flags, uint32_t mask )
{
    uint32_t i;
    flags &= mask;
    for (i = 0; i < vk_device->memory_properties.memoryTypeCount; i++)
    {
        if ((vk_device->memory_properties.memoryTypes[i].propertyFlags & mask) == flags) return i;
    }
    return -1;
}

static struct buffer *create_buffer_storage( TEB *teb, GLenum target, GLuint name, size_t size, const void *data, GLbitfield flags )
{
    VkExportMemoryAllocateInfo export_alloc =
    {
        .sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO,
        .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT,
    };
    VkMemoryAllocateInfo alloc_info =
    {
        .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
        .pNext = &export_alloc,
        .allocationSize = size,
    };
    VkMemoryGetFdInfoKHR fd_info =
    {
        .sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR,
        .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT,
    };
    struct opengl_funcs *funcs = teb->glTable;
    GLuint buffer_name = name ? name : get_target_name( teb, target );
    uint32_t type_mask = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
    uint32_t desired_type = type_mask;
    struct context *ctx = get_current_context( teb, NULL, NULL );
    struct vk_device *vk_device;
    struct buffer *buffer;
    int fd, memory_type;
    VkResult vr;

    if (!(flags & (GL_MAP_READ_BIT | GL_MAP_WRITE_BIT))) return NULL;
    if ((!(vk_device = ctx->buffers->vk_device) || !vk_device->vk_device) && !ctx->use_pinned_memory) return NULL;

    if (!(buffer = calloc( 1, sizeof(*buffer) ))) return NULL;
    buffer->name = buffer_name;
    buffer->flags = flags;
    buffer->size = size;
    buffer->vk_device = vk_device;

    if (ctx->use_pinned_memory)
    {
        if (!buffer_vm_alloc( teb, buffer, size )) return NULL;
        if (data) memcpy( buffer->vm_ptr, data, size );
        buffer->pinned = TRUE;

        /* FIXME: we may interfere with GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD if the
         * application uses it as well. Unlike other targets, there’s no way to query
         * the currently bound target, so we’d need to track it ourselves if we want
         * to support it. */
        funcs->p_glBindBuffer( GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, buffer_name );
        funcs->p_glBufferData( GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, size, buffer->vm_ptr, GL_DYNAMIC_COPY );
        rb_put( &ctx->buffers->map, &buffer->name, &buffer->entry );
        TRACE( "created buffer %p with pinned memory %p\n", buffer, buffer->vm_ptr );
        return buffer;
    }

    if (flags & GL_CLIENT_STORAGE_BIT) desired_type &= ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
    memory_type = find_vk_memory_type( vk_device, desired_type, type_mask );
    if (memory_type == -1) /* if we can’t find a matching type, try ignoring GL_CLIENT_STORAGE_BIT */
        memory_type = find_vk_memory_type( vk_device, desired_type, type_mask & ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT );
    if (memory_type == -1)
    {
        WARN( "Could not find memory type\n" );
        free_buffer( funcs, buffer );
        return NULL;
    }
    alloc_info.memoryTypeIndex = memory_type;

    vr = vk_device->p_vkAllocateMemory( vk_device->vk_device, &alloc_info, NULL, &buffer->vk_memory );
    if (vr)
    {
        ERR( "vkAllocateMemory failed: %d\n", vr );
        free_buffer( funcs, buffer );
        return NULL;
    }

    if (data)
    {
        VkMemoryMapInfoKHR map_info =
        {
            .sType = VK_STRUCTURE_TYPE_MEMORY_MAP_INFO_KHR,
            .memory = buffer->vk_memory,
            .size = VK_WHOLE_SIZE,
        };
        VkMemoryUnmapInfoKHR unmap_info =
        {
            .sType = VK_STRUCTURE_TYPE_MEMORY_UNMAP_INFO_KHR,
            .memory = buffer->vk_memory,
        };
        void *ptr;

        vr = vk_device->p_vkMapMemory2KHR( vk_device->vk_device, &map_info, &ptr );
        if (vr)
        {
            ERR( "vkMapMemory2KHR failed: %d\n", vr );
            free_buffer( funcs, buffer );
            return NULL;
        }

        memcpy( ptr, data, size );
        vk_device->p_vkUnmapMemory2KHR( vk_device->vk_device, &unmap_info );
    }

    fd_info.memory = buffer->vk_memory;
    vr = vk_device->p_vkGetMemoryFdKHR( vk_device->vk_device, &fd_info, &fd );
    if (vr)
    {
        ERR( "vkGetMemoryFdKHR failed: %d\n", vr );
        free_buffer( funcs, buffer );
        return NULL;
    }

    funcs->p_glCreateMemoryObjectsEXT( 1, &buffer->gl_memory );
    funcs->p_glImportMemoryFdEXT( buffer->gl_memory, size, GL_HANDLE_TYPE_OPAQUE_FD_EXT, fd );
    if (name)
        funcs->p_glNamedBufferStorageMemEXT( buffer->name, size, buffer->gl_memory, 0 );
    else
        funcs->p_glBufferStorageMemEXT( target, size, buffer->gl_memory, 0 );
    rb_put( &ctx->buffers->map, &buffer->name, &buffer->entry );
    TRACE( "created buffer_storage %p\n", buffer );
    return buffer;
}

static void *wow64_map_buffer( TEB *teb, struct buffer *buffer, GLenum target, GLuint name, GLintptr offset,
                               size_t length, GLbitfield access, void *ptr )
{
    const struct opengl_funcs *funcs = teb->glTable;

    if (buffer && buffer->map_ptr)
    {
        set_gl_error( teb, GL_INVALID_OPERATION );
        return NULL;
    }

    if (buffer && buffer->vk_memory)
    {
        VkMemoryMapPlacedInfoEXT placed_info =
        {
            .sType = VK_STRUCTURE_TYPE_MEMORY_MAP_PLACED_INFO_EXT,
        };
        VkMemoryMapInfoKHR map_info =
        {
            .sType = VK_STRUCTURE_TYPE_MEMORY_MAP_INFO_KHR,
            .flags = VK_MEMORY_MAP_PLACED_BIT_EXT,
            .pNext = &placed_info,
            .memory = buffer->vk_memory,
            .size = VK_WHOLE_SIZE,
        };
        VkResult vr;
        struct context *ctx = get_current_context( teb, NULL, NULL );
        struct vk_device *vk_device = ctx->buffers->vk_device;

        if (!buffer_vm_alloc( teb, buffer, buffer->size )) return NULL;
        placed_info.pPlacedAddress = buffer->vm_ptr;
        vr = vk_device->p_vkMapMemory2KHR( vk_device->vk_device, &map_info, &buffer->host_ptr );
        if (vr)
        {
            ERR( "vkMapMemory2KHR failed: %d\n", vr );
            return NULL;
        }
        assert( buffer->host_ptr == buffer->vm_ptr );
        buffer->map_ptr = (char *)buffer->vm_ptr + offset;
        TRACE( "returning vk mapping %p\n", buffer->map_ptr );
        return buffer->map_ptr;
    }

    if (buffer && buffer->pinned)
    {
        buffer->map_ptr = (char *)buffer->vm_ptr + offset;
        TRACE( "returning pinned ptr %p\n", buffer->map_ptr );
        return buffer->map_ptr;
    }

    if (!ptr) return NULL;

    if (!buffer)
    {
        struct context *ctx = get_current_context( teb, NULL, NULL );
        GLint size;

        if (name) funcs->p_glGetNamedBufferParameteriv( name, GL_BUFFER_SIZE, &size );
        else funcs->p_glGetBufferParameteriv( target, GL_BUFFER_SIZE, &size );

        if (!(buffer = calloc( 1, sizeof(*buffer) ))) return NULL;
        buffer->name = name ? name : get_target_name( teb, target );
        buffer->size = size;
        rb_put( &ctx->buffers->map, &buffer->name, &buffer->entry );
        TRACE( "allocated buffer %p for %u\n", buffer, buffer->name );
    }

    buffer->host_ptr = ptr;
    if (!offset && !length) length = buffer->size;
    if (ULongToPtr(PtrToUlong(ptr)) == ptr) /* we're lucky */
    {
        buffer->map_ptr = ptr;
        TRACE( "returning %p\n", buffer->map_ptr );
        return buffer->map_ptr;
    }

    if (access & GL_MAP_PERSISTENT_BIT)
    {
        FIXME( "GL_MAP_PERSISTENT_BIT not supported!\n" );
        goto unmap;
    }

    if (!buffer_vm_alloc( teb, buffer, length + (offset & 0xf) )) goto unmap;
    buffer->map_ptr = (char *)buffer->vm_ptr + (offset & 0xf);
    buffer->copy_length = (access & GL_MAP_WRITE_BIT) ? length : 0;
    if (!(access & (GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT)))
    {
        static int once;
        if (!once++)
            FIXME( "Doing a copy of a mapped buffer (expect performance issues)\n" );

        TRACE( "Copying %#zx from buffer at %p to wow64 buffer %p\n", length, buffer->host_ptr,
               buffer->map_ptr );
        memcpy( buffer->map_ptr, buffer->host_ptr, length );
    }
    TRACE( "returning copy buffer %p\n", buffer->map_ptr );
    return buffer->map_ptr;

unmap:
    if (name) funcs->p_glUnmapNamedBuffer( name );
    else funcs->p_glUnmapBuffer( target );
    return 0;
}

static GLbitfield map_range_flags_from_map_flags( GLenum flags )
{
    switch (flags)
    {
        case GL_READ_ONLY: return GL_MAP_READ_BIT;
        case GL_WRITE_ONLY: return GL_MAP_WRITE_BIT;
        case GL_READ_WRITE: return GL_MAP_READ_BIT | GL_MAP_WRITE_BIT;
        default:
            ERR( "invalid map flags %#x\n", flags );
            return GL_MAP_READ_BIT | GL_MAP_WRITE_BIT;
    }
}

void wow64_glDeleteBuffers( TEB *teb, GLsizei n, const GLuint *buffers, PFN_glDeleteBuffers p_glDeleteBuffers )
{
    const struct opengl_funcs *funcs = teb->glTable;
    struct buffer *buffer;
    GLsizei i;

    pthread_mutex_lock( &wgl_lock );

    p_glDeleteBuffers( n, buffers );
    for (i = 0; i < n; i++)
    {
        if ((buffer = invalidate_buffer_name( teb, buffers[i] ))) free_buffer( funcs, buffer );
    }

    pthread_mutex_unlock( &wgl_lock );
}

void wow64_glBufferStorage( TEB *teb, GLenum target, GLsizeiptr size, const void *data,
                            GLbitfield flags, PFN_glBufferStorage p_glBufferStorage )
{
    struct buffer *buffer = NULL;

    if (flags & GL_MAP_PERSISTENT_BIT)
        buffer = create_buffer_storage( teb, target, 0, size, data, flags );

    if (!buffer) p_glBufferStorage( target, size, data, flags );
}

void wow64_glNamedBufferStorage( TEB *teb, GLuint name, GLsizeiptr size, const void *data,
                                 GLbitfield flags, PFN_glNamedBufferStorage p_glNamedBufferStorage )
{
    struct buffer *buffer = NULL;

    if (flags & GL_MAP_PERSISTENT_BIT)
        buffer = create_buffer_storage( teb, 0, name, size, data, flags );

    if (!buffer) p_glNamedBufferStorage( name, size, data, flags );
}

void wow64_glNamedBufferStorageEXT( TEB *teb, GLuint name, GLsizeiptr size, const void *data,
                                    GLbitfield flags, PFN_glNamedBufferStorageEXT p_glNamedBufferStorageEXT )
{
    struct buffer *buffer = NULL;

    if (flags & GL_MAP_PERSISTENT_BIT)
        buffer = create_buffer_storage( teb, 0, name, size, data, flags );

    if (!buffer) p_glNamedBufferStorageEXT( name, size, data, flags );
}

static BOOL wow64_gl_get_buffer_pointer_v( TEB *teb, GLenum target, GLuint name, GLenum pname, PTR32 *wow_ptr )
{
    struct buffer *buffer;
    BOOL ret = FALSE;

    if (pname != GL_BUFFER_MAP_POINTER) return FALSE;

    pthread_mutex_lock( &wgl_lock );
    buffer = name ? get_named_buffer( teb, name ) : get_target_buffer( teb, target );
    if (buffer)
    {
        *wow_ptr = PtrToUlong( buffer->map_ptr );
        ret = TRUE;
    }
    pthread_mutex_unlock( &wgl_lock );
    return ret;
}

void wow64_glGetBufferPointerv( TEB *teb, GLenum target, GLenum pname, PTR32 *params, PFN_glGetBufferPointerv p_glGetBufferPointerv )
{
    void *ptr;
    if (wow64_gl_get_buffer_pointer_v( teb, target, 0, pname, params )) return;
    p_glGetBufferPointerv( target, pname, &ptr );
    *params = PtrToUlong(ptr);
}

void wow64_glGetBufferPointervARB( TEB *teb, GLenum target, GLenum pname, PTR32 *params )
{
    const struct opengl_funcs *funcs = teb->glTable;
    void *ptr;
    if (wow64_gl_get_buffer_pointer_v( teb, target, 0, pname, params )) return;
    funcs->p_glGetBufferPointerv( target, pname, &ptr );
    *params = PtrToUlong(ptr);
}

void wow64_glGetNamedBufferPointerv( TEB *teb, GLuint buffer, GLenum pname, PTR32 *params,
                                     PFN_glGetNamedBufferPointerv p_glGetNamedBufferPointerv )
{
    void *ptr;
    if (buffer && wow64_gl_get_buffer_pointer_v( teb, 0, buffer, pname, params )) return;
    p_glGetNamedBufferPointerv( buffer, pname, &ptr );
    *params = PtrToUlong(ptr);
}

void wow64_glGetNamedBufferPointervEXT( TEB *teb, GLuint buffer, GLenum pname, PTR32 *params )
{
    const struct opengl_funcs *funcs = teb->glTable;
    void *ptr;
    if (buffer && wow64_gl_get_buffer_pointer_v( teb, 0, buffer, pname, params )) return;
    funcs->p_glGetNamedBufferPointervEXT( buffer, pname, &ptr );
    *params = PtrToUlong(ptr);
}

void *wow64_glMapBuffer( TEB *teb, GLenum target, GLenum access, PFN_glMapBuffer p_glMapBuffer )
{
    GLbitfield range_access = map_range_flags_from_map_flags( access );
    struct buffer *buffer;
    void *ptr = NULL;

    pthread_mutex_lock( &wgl_lock );
    buffer = get_target_buffer( teb, target );
    if (use_driver_buffer_map( buffer )) ptr = p_glMapBuffer( target, access );
    ptr = wow64_map_buffer( teb, buffer, target, 0, 0, 0, range_access, ptr );
    pthread_mutex_unlock( &wgl_lock );
    return ptr;
}

void *wow64_glMapBufferRange( TEB *teb, GLenum target, GLintptr offset, GLsizeiptr length,
                              GLbitfield access, PFN_glMapBufferRange p_glMapBufferRange )
{
    struct buffer *buffer;
    void *ptr = NULL;

    pthread_mutex_lock( &wgl_lock );
    buffer = get_target_buffer( teb, target );
    if (use_driver_buffer_map( buffer )) ptr = p_glMapBufferRange( target, offset, length, access );
    ptr = wow64_map_buffer( teb, buffer, target, 0, offset, length, access, ptr );
    pthread_mutex_unlock( &wgl_lock );
    return ptr;
}

void *wow64_glMapNamedBuffer( TEB *teb, GLuint name, GLenum access, PFN_glMapNamedBuffer p_glMapNamedBuffer )
{
    GLbitfield range_access = map_range_flags_from_map_flags( access );
    struct buffer *buffer;
    void *ptr = NULL;

    pthread_mutex_lock( &wgl_lock );
    buffer = get_named_buffer( teb, name );
    if (use_driver_buffer_map( buffer )) ptr = p_glMapNamedBuffer( name, access );
    ptr = wow64_map_buffer( teb, buffer, 0, name, 0, 0, range_access, ptr );
    pthread_mutex_unlock( &wgl_lock );
    return ptr;
}

void *wow64_glMapNamedBufferRange( TEB *teb, GLuint name, GLintptr offset, GLsizeiptr length,
                                   GLbitfield access, PFN_glMapNamedBufferRange p_glMapNamedBufferRange )
{
    struct buffer *buffer;
    void *ptr = NULL;

    pthread_mutex_lock( &wgl_lock );
    buffer = get_named_buffer( teb, name );
    if (use_driver_buffer_map( buffer )) ptr = p_glMapNamedBufferRange( name, offset, length, access );
    ptr = wow64_map_buffer( teb, buffer, 0, name, offset, length, access, ptr );
    pthread_mutex_unlock( &wgl_lock );
    return ptr;
}

static BOOL wow64_unmap_buffer( TEB *teb, struct buffer *buffer )
{
    if (buffer->vk_memory)
    {
        if (!buffer->map_ptr)
        {
            set_gl_error( teb, GL_INVALID_OPERATION );
            return FALSE;
        }
        unmap_vk_buffer( buffer );
    }

    if (buffer->copy_length)
    {
        TRACE( "Copying %#zx from wow64 buffer %p to buffer %p\n", buffer->copy_length,
               buffer->map_ptr, buffer->host_ptr );
        memcpy( buffer->host_ptr, buffer->map_ptr, buffer->copy_length );
        buffer->copy_length = 0;
    }

    buffer->host_ptr = buffer->map_ptr = NULL;
    return TRUE;
}

GLboolean wow64_glUnmapBuffer( TEB *teb, GLenum target, PFN_glUnmapBuffer p_glUnmapBuffer )
{
    struct buffer *buffer;
    GLboolean ret;

    pthread_mutex_lock( &wgl_lock );
    if ((buffer = get_target_buffer( teb, target ))) ret = wow64_unmap_buffer( teb, buffer );
    if (use_driver_buffer_map( buffer )) ret = p_glUnmapBuffer( target );
    pthread_mutex_unlock( &wgl_lock );
    return ret;
}

GLboolean wow64_glUnmapNamedBuffer( TEB *teb, GLuint name, PFN_glUnmapBuffer p_glUnmapNamedBuffer )
{
    struct buffer *buffer;
    GLboolean ret;

    pthread_mutex_lock( &wgl_lock );
    if ((buffer = get_named_buffer( teb, name ))) ret = wow64_unmap_buffer( teb, buffer );
    if (use_driver_buffer_map( buffer )) ret = p_glUnmapNamedBuffer( name );
    pthread_mutex_unlock( &wgl_lock );
    return ret;
}

void wow64_glFlushMappedBufferRange( TEB *teb, GLenum target, GLintptr offset, GLsizeiptr length,
                                     PFN_glFlushMappedBufferRange p_glFlushMappedBufferRange )
{
    struct buffer *buffer;

    pthread_mutex_lock( &wgl_lock );
    if ((buffer = get_target_buffer( teb, target ))) flush_buffer( teb, buffer, offset, length );
    if (use_driver_buffer_map( buffer )) p_glFlushMappedBufferRange( target, offset, length );
    pthread_mutex_unlock( &wgl_lock );
}

void wow64_glFlushMappedNamedBufferRange( TEB *teb, GLuint name, GLintptr offset, GLsizeiptr length,
                                          PFN_glFlushMappedNamedBufferRange p_glFlushMappedNamedBufferRange )
{
    struct buffer *buffer;

    pthread_mutex_lock( &wgl_lock );
    if ((buffer = get_named_buffer( teb, name ))) flush_buffer( teb, buffer, offset, length );
    if (use_driver_buffer_map( buffer )) p_glFlushMappedNamedBufferRange( name, offset, length );
    pthread_mutex_unlock( &wgl_lock );
}

void wow64_glFlushMappedNamedBufferRangeEXT( TEB *teb, GLuint name, GLintptr offset, GLsizeiptr length )
{
    const struct opengl_funcs *funcs = teb->glTable;
    struct buffer *buffer;

    pthread_mutex_lock( &wgl_lock );
    if ((buffer = get_named_buffer( teb, name ))) flush_buffer( teb, buffer, offset, length );
    if (use_driver_buffer_map( buffer )) funcs->p_glFlushMappedNamedBufferRangeEXT( name, offset, length );
    pthread_mutex_unlock( &wgl_lock );
}

NTSTATUS wow64_thread_attach( void *args )
{
    return thread_attach( get_teb64( (ULONG_PTR)args ));
}

NTSTATUS wow64_process_detach( void *args )
{
    NTSTATUS status;

    if ((status = process_detach( NULL ))) return status;

    free( wow64_strings );
    wow64_strings = NULL;
    wow64_strings_count = 0;

    return STATUS_SUCCESS;
}

NTSTATUS wow64_get_pixel_formats( void *args )
{
    struct
    {
        PTR32 teb;
        PTR32 hdc;
        PTR32 formats;
        UINT max_formats;
        UINT num_formats;
        UINT num_onscreen_formats;
    } *params32 = args;
    struct get_pixel_formats_params params =
    {
        .teb = get_teb64(params32->teb),
        .hdc = ULongToPtr(params32->hdc),
        .formats = ULongToPtr(params32->formats),
        .max_formats = params32->max_formats,
    };
    NTSTATUS status;
    status = get_pixel_formats( &params );
    params32->num_formats = params.num_formats;
    params32->num_onscreen_formats = params.num_onscreen_formats;
    return status;
}

#endif
