#include <LibWeb/WebGL/WebGLContextProxy.h>

namespace Web::WebGL {

void WebGLContextProxy::active_texture(GLenum texture)
{
    Commands::ActiveTexture command { .texture = texture };
    record(command);
}

void WebGLContextProxy::attach_shader(GLuint program, GLuint shader)
{
    Commands::AttachShader command { .program = program, .shader = shader };
    record(command);
}

void WebGLContextProxy::begin_query(GLenum target, GLuint id)
{
    Commands::BeginQuery command { .target = target, .id = id };
    record(command);
}

void WebGLContextProxy::begin_transform_feedback(GLenum primitiveMode)
{
    Commands::BeginTransformFeedback command { .primitive_mode = primitiveMode };
    record(command);
}

void WebGLContextProxy::bind_attrib_location(GLuint program, GLuint index, GLchar const* name)
{
    ReadonlyBytes name_bytes { name, __builtin_strlen(name) + 1 };
    Commands::BindAttribLocation command { .program = program, .index = index };
    command.name = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(name_bytes.size()) };
    record(command, name_bytes);
}

void WebGLContextProxy::bind_buffer(GLenum target, GLuint buffer)
{
    Commands::BindBuffer command { .target = target, .buffer = buffer };
    record(command);
}

void WebGLContextProxy::bind_buffer_base(GLenum target, GLuint index, GLuint buffer)
{
    Commands::BindBufferBase command { .target = target, .index = index, .buffer = buffer };
    record(command);
}

void WebGLContextProxy::bind_buffer_range(GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size)
{
    Commands::BindBufferRange command { .target = target, .index = index, .buffer = buffer, .offset = offset, .size = size };
    record(command);
}

void WebGLContextProxy::bind_framebuffer(GLenum target, GLuint framebuffer)
{
    Commands::BindFramebuffer command { .target = target, .framebuffer = framebuffer };
    record(command);
}

void WebGLContextProxy::bind_renderbuffer(GLenum target, GLuint renderbuffer)
{
    Commands::BindRenderbuffer command { .target = target, .renderbuffer = renderbuffer };
    record(command);
}

void WebGLContextProxy::bind_sampler(GLuint unit, GLuint sampler)
{
    Commands::BindSampler command { .unit = unit, .sampler = sampler };
    record(command);
}

void WebGLContextProxy::bind_texture(GLenum target, GLuint texture)
{
    Commands::BindTexture command { .target = target, .texture = texture };
    record(command);
}

void WebGLContextProxy::bind_transform_feedback(GLenum target, GLuint id)
{
    Commands::BindTransformFeedback command { .target = target, .id = id };
    record(command);
}

void WebGLContextProxy::bind_vertex_array(GLuint array)
{
    Commands::BindVertexArray command { .array = array };
    record(command);
}

void WebGLContextProxy::bind_vertex_array_oes(GLuint array)
{
    Commands::BindVertexArrayOES command { .array = array };
    record(command);
}

void WebGLContextProxy::blend_color(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha)
{
    Commands::BlendColor command { .red = red, .green = green, .blue = blue, .alpha = alpha };
    record(command);
}

void WebGLContextProxy::blend_equation(GLenum mode)
{
    Commands::BlendEquation command { .mode = mode };
    record(command);
}

void WebGLContextProxy::blend_equation_separate(GLenum modeRGB, GLenum modeAlpha)
{
    Commands::BlendEquationSeparate command { .mode_rgb = modeRGB, .mode_alpha = modeAlpha };
    record(command);
}

void WebGLContextProxy::blend_func(GLenum sfactor, GLenum dfactor)
{
    Commands::BlendFunc command { .sfactor = sfactor, .dfactor = dfactor };
    record(command);
}

void WebGLContextProxy::blend_func_separate(GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha)
{
    Commands::BlendFuncSeparate command { .sfactor_rgb = sfactorRGB, .dfactor_rgb = dfactorRGB, .sfactor_alpha = sfactorAlpha, .dfactor_alpha = dfactorAlpha };
    record(command);
}

void WebGLContextProxy::blit_framebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter)
{
    Commands::BlitFramebuffer command { .src_x0 = srcX0, .src_y0 = srcY0, .src_x1 = srcX1, .src_y1 = srcY1, .dst_x0 = dstX0, .dst_y0 = dstY0, .dst_x1 = dstX1, .dst_y1 = dstY1, .mask = mask, .filter = filter };
    record(command);
}

void WebGLContextProxy::buffer_data(GLenum target, GLsizeiptr size, void const* data, GLenum usage)
{
    ReadonlyBytes data_bytes { data, data ? static_cast<size_t>(size) : 0 };
    Commands::BufferData command { .target = target, .size = size, .has_data = data != nullptr, .usage = usage };
    command.data = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(data_bytes.size()) };
    record(command, data_bytes);
}

void WebGLContextProxy::buffer_sub_data(GLenum target, GLintptr offset, GLsizeiptr size, void const* data)
{
    ReadonlyBytes data_bytes { data, static_cast<size_t>(size) };
    Commands::BufferSubData command { .target = target, .offset = offset, .size = size };
    command.data = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(data_bytes.size()) };
    record(command, data_bytes);
}

GLenum WebGLContextProxy::check_framebuffer_status(GLenum target)
{
    SyncCalls::CheckFramebufferStatus::Request request { .target = target };
    auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request<SyncCalls::CheckFramebufferStatus>(request));
    if (is_lost())
        return {};
    auto reply = WebGLSyncCall::decode_reply<SyncCalls::CheckFramebufferStatus::Reply>(reply_bytes);
    return reply.return_value;
}

void WebGLContextProxy::clear(GLbitfield mask)
{
    Commands::Clear command { .mask = mask };
    record(command);
}

void WebGLContextProxy::clear_bufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil)
{
    Commands::ClearBufferfi command { .buffer = buffer, .drawbuffer = drawbuffer, .depth = depth, .stencil = stencil };
    record(command);
}

void WebGLContextProxy::clear_bufferfv(GLenum buffer, GLint drawbuffer, GLfloat const* value)
{
    ReadonlyBytes value_bytes { value, static_cast<size_t>((buffer == GL_COLOR ? 4 : 1) * sizeof(GLfloat)) };
    Commands::ClearBufferfv command { .buffer = buffer, .drawbuffer = drawbuffer };
    command.value = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(value_bytes.size()) };
    record(command, value_bytes);
}

void WebGLContextProxy::clear_bufferiv(GLenum buffer, GLint drawbuffer, GLint const* value)
{
    ReadonlyBytes value_bytes { value, static_cast<size_t>((buffer == GL_COLOR ? 4 : 1) * sizeof(GLint)) };
    Commands::ClearBufferiv command { .buffer = buffer, .drawbuffer = drawbuffer };
    command.value = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(value_bytes.size()) };
    record(command, value_bytes);
}

void WebGLContextProxy::clear_bufferuiv(GLenum buffer, GLint drawbuffer, GLuint const* value)
{
    ReadonlyBytes value_bytes { value, static_cast<size_t>((buffer == GL_COLOR ? 4 : 1) * sizeof(GLuint)) };
    Commands::ClearBufferuiv command { .buffer = buffer, .drawbuffer = drawbuffer };
    command.value = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(value_bytes.size()) };
    record(command, value_bytes);
}

void WebGLContextProxy::clear_color(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha)
{
    Commands::ClearColor command { .red = red, .green = green, .blue = blue, .alpha = alpha };
    record(command);
}

void WebGLContextProxy::clear_depthf(GLfloat d)
{
    Commands::ClearDepthf command { .d = d };
    record(command);
}

void WebGLContextProxy::clear_stencil(GLint s)
{
    Commands::ClearStencil command { .s = s };
    record(command);
}

GLenum WebGLContextProxy::client_wait_sync(GLsync sync, GLbitfield flags, GLuint64 timeout)
{
    SyncCalls::ClientWaitSync::Request request { .sync = static_cast<WebGLObjectId>(reinterpret_cast<uintptr_t>(sync)), .flags = flags, .timeout = timeout };
    auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request<SyncCalls::ClientWaitSync>(request));
    if (is_lost())
        return {};
    auto reply = WebGLSyncCall::decode_reply<SyncCalls::ClientWaitSync::Reply>(reply_bytes);
    return reply.return_value;
}

void WebGLContextProxy::color_mask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha)
{
    Commands::ColorMask command { .red = red, .green = green, .blue = blue, .alpha = alpha };
    record(command);
}

void WebGLContextProxy::compile_shader(GLuint shader)
{
    Commands::CompileShader command { .shader = shader };
    record(command);
}

void WebGLContextProxy::compressed_tex_image2d_robust_angle(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, GLsizei bufSize, void const* data)
{
    ReadonlyBytes data_bytes { data, static_cast<size_t>(bufSize) };
    Commands::CompressedTexImage2DRobustANGLE command { .target = target, .level = level, .internalformat = internalformat, .width = width, .height = height, .border = border, .image_size = imageSize, .buf_size = bufSize };
    command.data = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(data_bytes.size()) };
    record(command, data_bytes);
}

void WebGLContextProxy::compressed_tex_image3d_robust_angle(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, GLsizei bufSize, void const* data)
{
    ReadonlyBytes data_bytes { data, static_cast<size_t>(bufSize) };
    Commands::CompressedTexImage3DRobustANGLE command { .target = target, .level = level, .internalformat = internalformat, .width = width, .height = height, .depth = depth, .border = border, .image_size = imageSize, .buf_size = bufSize };
    command.data = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(data_bytes.size()) };
    record(command, data_bytes);
}

void WebGLContextProxy::compressed_tex_sub_image2d_robust_angle(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, GLsizei bufSize, void const* data)
{
    ReadonlyBytes data_bytes { data, static_cast<size_t>(bufSize) };
    Commands::CompressedTexSubImage2DRobustANGLE command { .target = target, .level = level, .xoffset = xoffset, .yoffset = yoffset, .width = width, .height = height, .format = format, .image_size = imageSize, .buf_size = bufSize };
    command.data = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(data_bytes.size()) };
    record(command, data_bytes);
}

void WebGLContextProxy::compressed_tex_sub_image3d_robust_angle(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, GLsizei bufSize, void const* data)
{
    ReadonlyBytes data_bytes { data, static_cast<size_t>(bufSize) };
    Commands::CompressedTexSubImage3DRobustANGLE command { .target = target, .level = level, .xoffset = xoffset, .yoffset = yoffset, .zoffset = zoffset, .width = width, .height = height, .depth = depth, .format = format, .image_size = imageSize, .buf_size = bufSize };
    command.data = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(data_bytes.size()) };
    record(command, data_bytes);
}

void WebGLContextProxy::copy_buffer_sub_data(GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size)
{
    Commands::CopyBufferSubData command { .read_target = readTarget, .write_target = writeTarget, .read_offset = readOffset, .write_offset = writeOffset, .size = size };
    record(command);
}

void WebGLContextProxy::copy_tex_image2d(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border)
{
    Commands::CopyTexImage2D command { .target = target, .level = level, .internalformat = internalformat, .x = x, .y = y, .width = width, .height = height, .border = border };
    record(command);
}

void WebGLContextProxy::copy_tex_sub_image2d(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height)
{
    Commands::CopyTexSubImage2D command { .target = target, .level = level, .xoffset = xoffset, .yoffset = yoffset, .x = x, .y = y, .width = width, .height = height };
    record(command);
}

GLuint WebGLContextProxy::create_program()
{
    auto id = allocate_object_id();
    record(Commands::CreateProgram { .id = id });
    return id;
}

GLuint WebGLContextProxy::create_shader(GLenum type)
{
    auto id = allocate_object_id();
    record(Commands::CreateShader { .id = id, .type = type });
    return id;
}

void WebGLContextProxy::cull_face(GLenum mode)
{
    Commands::CullFace command { .mode = mode };
    record(command);
}

void WebGLContextProxy::delete_buffers(GLsizei n, GLuint const* buffers)
{
    ReadonlyBytes buffers_bytes { buffers, static_cast<size_t>(n * sizeof(GLuint)) };
    Commands::DeleteBuffers command { .n = n };
    command.buffers = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(buffers_bytes.size()) };
    record(command, buffers_bytes);
}

void WebGLContextProxy::delete_framebuffers(GLsizei n, GLuint const* framebuffers)
{
    ReadonlyBytes framebuffers_bytes { framebuffers, static_cast<size_t>(n * sizeof(GLuint)) };
    Commands::DeleteFramebuffers command { .n = n };
    command.framebuffers = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(framebuffers_bytes.size()) };
    record(command, framebuffers_bytes);
}

void WebGLContextProxy::delete_program(GLuint program)
{
    Commands::DeleteProgram command { .program = program };
    record(command);
}

void WebGLContextProxy::delete_queries(GLsizei n, GLuint const* ids)
{
    ReadonlyBytes ids_bytes { ids, static_cast<size_t>(n * sizeof(GLuint)) };
    Commands::DeleteQueries command { .n = n };
    command.ids = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(ids_bytes.size()) };
    record(command, ids_bytes);
}

void WebGLContextProxy::delete_renderbuffers(GLsizei n, GLuint const* renderbuffers)
{
    ReadonlyBytes renderbuffers_bytes { renderbuffers, static_cast<size_t>(n * sizeof(GLuint)) };
    Commands::DeleteRenderbuffers command { .n = n };
    command.renderbuffers = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(renderbuffers_bytes.size()) };
    record(command, renderbuffers_bytes);
}

void WebGLContextProxy::delete_samplers(GLsizei count, GLuint const* samplers)
{
    ReadonlyBytes samplers_bytes { samplers, static_cast<size_t>(count * sizeof(GLuint)) };
    Commands::DeleteSamplers command { .count = count };
    command.samplers = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(samplers_bytes.size()) };
    record(command, samplers_bytes);
}

void WebGLContextProxy::delete_shader(GLuint shader)
{
    Commands::DeleteShader command { .shader = shader };
    record(command);
}

void WebGLContextProxy::delete_sync(GLsync sync)
{
    Commands::DeleteSync command { .sync = static_cast<WebGLObjectId>(reinterpret_cast<uintptr_t>(sync)) };
    record(command);
}

void WebGLContextProxy::delete_textures(GLsizei n, GLuint const* textures)
{
    ReadonlyBytes textures_bytes { textures, static_cast<size_t>(n * sizeof(GLuint)) };
    Commands::DeleteTextures command { .n = n };
    command.textures = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(textures_bytes.size()) };
    record(command, textures_bytes);
}

void WebGLContextProxy::delete_transform_feedbacks(GLsizei n, GLuint const* ids)
{
    ReadonlyBytes ids_bytes { ids, static_cast<size_t>(n * sizeof(GLuint)) };
    Commands::DeleteTransformFeedbacks command { .n = n };
    command.ids = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(ids_bytes.size()) };
    record(command, ids_bytes);
}

void WebGLContextProxy::delete_vertex_arrays(GLsizei n, GLuint const* arrays)
{
    ReadonlyBytes arrays_bytes { arrays, static_cast<size_t>(n * sizeof(GLuint)) };
    Commands::DeleteVertexArrays command { .n = n };
    command.arrays = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(arrays_bytes.size()) };
    record(command, arrays_bytes);
}

void WebGLContextProxy::delete_vertex_arrays_oes(GLsizei n, GLuint const* arrays)
{
    ReadonlyBytes arrays_bytes { arrays, static_cast<size_t>(n * sizeof(GLuint)) };
    Commands::DeleteVertexArraysOES command { .n = n };
    command.arrays = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(arrays_bytes.size()) };
    record(command, arrays_bytes);
}

void WebGLContextProxy::depth_func(GLenum func)
{
    Commands::DepthFunc command { .func = func };
    record(command);
}

void WebGLContextProxy::depth_mask(GLboolean flag)
{
    Commands::DepthMask command { .flag = flag };
    record(command);
}

void WebGLContextProxy::depth_rangef(GLfloat n, GLfloat f)
{
    Commands::DepthRangef command { .n = n, .f = f };
    record(command);
}

void WebGLContextProxy::detach_shader(GLuint program, GLuint shader)
{
    Commands::DetachShader command { .program = program, .shader = shader };
    record(command);
}

void WebGLContextProxy::disable(GLenum cap)
{
    Commands::Disable command { .cap = cap };
    record(command);
}

void WebGLContextProxy::disable_vertex_attrib_array(GLuint index)
{
    Commands::DisableVertexAttribArray command { .index = index };
    record(command);
}

void WebGLContextProxy::draw_arrays(GLenum mode, GLint first, GLsizei count)
{
    Commands::DrawArrays command { .mode = mode, .first = first, .count = count };
    record(command);
}

void WebGLContextProxy::draw_arrays_instanced(GLenum mode, GLint first, GLsizei count, GLsizei instancecount)
{
    Commands::DrawArraysInstanced command { .mode = mode, .first = first, .count = count, .instancecount = instancecount };
    record(command);
}

void WebGLContextProxy::draw_arrays_instanced_angle(GLenum mode, GLint first, GLsizei count, GLsizei primcount)
{
    Commands::DrawArraysInstancedANGLE command { .mode = mode, .first = first, .count = count, .primcount = primcount };
    record(command);
}

void WebGLContextProxy::draw_buffers(GLsizei n, GLenum const* bufs)
{
    ReadonlyBytes bufs_bytes { bufs, static_cast<size_t>(n * sizeof(GLenum)) };
    Commands::DrawBuffers command { .n = n };
    command.bufs = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(bufs_bytes.size()) };
    record(command, bufs_bytes);
}

void WebGLContextProxy::draw_buffers_ext(GLsizei n, GLenum const* bufs)
{
    ReadonlyBytes bufs_bytes { bufs, static_cast<size_t>(n * sizeof(GLenum)) };
    Commands::DrawBuffersEXT command { .n = n };
    command.bufs = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(bufs_bytes.size()) };
    record(command, bufs_bytes);
}

void WebGLContextProxy::draw_elements(GLenum mode, GLsizei count, GLenum type, void const* indices)
{
    Commands::DrawElements command { .mode = mode, .count = count, .type = type, .indices = static_cast<GLintptr>(reinterpret_cast<uintptr_t>(indices)) };
    record(command);
}

void WebGLContextProxy::draw_elements_instanced(GLenum mode, GLsizei count, GLenum type, void const* indices, GLsizei instancecount)
{
    Commands::DrawElementsInstanced command { .mode = mode, .count = count, .type = type, .indices = static_cast<GLintptr>(reinterpret_cast<uintptr_t>(indices)), .instancecount = instancecount };
    record(command);
}

void WebGLContextProxy::draw_elements_instanced_angle(GLenum mode, GLsizei count, GLenum type, void const* indices, GLsizei primcount)
{
    Commands::DrawElementsInstancedANGLE command { .mode = mode, .count = count, .type = type, .indices = static_cast<GLintptr>(reinterpret_cast<uintptr_t>(indices)), .primcount = primcount };
    record(command);
}

void WebGLContextProxy::draw_range_elements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, void const* indices)
{
    Commands::DrawRangeElements command { .mode = mode, .start = start, .end = end, .count = count, .type = type, .indices = static_cast<GLintptr>(reinterpret_cast<uintptr_t>(indices)) };
    record(command);
}

void WebGLContextProxy::enable(GLenum cap)
{
    Commands::Enable command { .cap = cap };
    record(command);
}

void WebGLContextProxy::enable_vertex_attrib_array(GLuint index)
{
    Commands::EnableVertexAttribArray command { .index = index };
    record(command);
}

void WebGLContextProxy::end_query(GLenum target)
{
    Commands::EndQuery command { .target = target };
    record(command);
}

void WebGLContextProxy::end_transform_feedback()
{
    Commands::EndTransformFeedback command {  };
    record(command);
}

GLsync WebGLContextProxy::fence_sync(GLenum condition, GLbitfield flags)
{
    auto id = allocate_object_id();
    record(Commands::FenceSync { .id = id, .condition = condition, .flags = flags });
    return reinterpret_cast<GLsync>(static_cast<uintptr_t>(id));
}

void WebGLContextProxy::finish()
{
    SyncCalls::Finish::Request request {  };
    auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request<SyncCalls::Finish>(request));
    if (is_lost())
        return;
}

void WebGLContextProxy::flush()
{
    Commands::Flush command {  };
    record(command);
}

void WebGLContextProxy::framebuffer_renderbuffer(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer)
{
    Commands::FramebufferRenderbuffer command { .target = target, .attachment = attachment, .renderbuffertarget = renderbuffertarget, .renderbuffer = renderbuffer };
    record(command);
}

void WebGLContextProxy::framebuffer_texture2d(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level)
{
    Commands::FramebufferTexture2D command { .target = target, .attachment = attachment, .textarget = textarget, .texture = texture, .level = level };
    record(command);
}

void WebGLContextProxy::framebuffer_texture_layer(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer)
{
    Commands::FramebufferTextureLayer command { .target = target, .attachment = attachment, .texture = texture, .level = level, .layer = layer };
    record(command);
}

void WebGLContextProxy::front_face(GLenum mode)
{
    Commands::FrontFace command { .mode = mode };
    record(command);
}

void WebGLContextProxy::gen_buffers(GLsizei n, GLuint* buffers)
{
    if (n <= 0)
        return;
    static_assert(IsSame<WebGLObjectId, GLuint>);
    for (GLsizei i = 0; i < n; ++i)
        buffers[i] = allocate_object_id();
    ReadonlyBytes buffers_bytes { buffers, static_cast<size_t>(n) * sizeof(WebGLObjectId) };
    Commands::GenBuffers command { .n = n };
    command.buffers = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(buffers_bytes.size()) };
    record(command, buffers_bytes);
}

void WebGLContextProxy::gen_framebuffers(GLsizei n, GLuint* framebuffers)
{
    if (n <= 0)
        return;
    static_assert(IsSame<WebGLObjectId, GLuint>);
    for (GLsizei i = 0; i < n; ++i)
        framebuffers[i] = allocate_object_id();
    ReadonlyBytes framebuffers_bytes { framebuffers, static_cast<size_t>(n) * sizeof(WebGLObjectId) };
    Commands::GenFramebuffers command { .n = n };
    command.framebuffers = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(framebuffers_bytes.size()) };
    record(command, framebuffers_bytes);
}

void WebGLContextProxy::gen_queries(GLsizei n, GLuint* ids)
{
    if (n <= 0)
        return;
    static_assert(IsSame<WebGLObjectId, GLuint>);
    for (GLsizei i = 0; i < n; ++i)
        ids[i] = allocate_object_id();
    ReadonlyBytes ids_bytes { ids, static_cast<size_t>(n) * sizeof(WebGLObjectId) };
    Commands::GenQueries command { .n = n };
    command.ids = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(ids_bytes.size()) };
    record(command, ids_bytes);
}

void WebGLContextProxy::gen_renderbuffers(GLsizei n, GLuint* renderbuffers)
{
    if (n <= 0)
        return;
    static_assert(IsSame<WebGLObjectId, GLuint>);
    for (GLsizei i = 0; i < n; ++i)
        renderbuffers[i] = allocate_object_id();
    ReadonlyBytes renderbuffers_bytes { renderbuffers, static_cast<size_t>(n) * sizeof(WebGLObjectId) };
    Commands::GenRenderbuffers command { .n = n };
    command.renderbuffers = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(renderbuffers_bytes.size()) };
    record(command, renderbuffers_bytes);
}

void WebGLContextProxy::gen_samplers(GLsizei count, GLuint* samplers)
{
    if (count <= 0)
        return;
    static_assert(IsSame<WebGLObjectId, GLuint>);
    for (GLsizei i = 0; i < count; ++i)
        samplers[i] = allocate_object_id();
    ReadonlyBytes samplers_bytes { samplers, static_cast<size_t>(count) * sizeof(WebGLObjectId) };
    Commands::GenSamplers command { .count = count };
    command.samplers = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(samplers_bytes.size()) };
    record(command, samplers_bytes);
}

void WebGLContextProxy::gen_textures(GLsizei n, GLuint* textures)
{
    if (n <= 0)
        return;
    static_assert(IsSame<WebGLObjectId, GLuint>);
    for (GLsizei i = 0; i < n; ++i)
        textures[i] = allocate_object_id();
    ReadonlyBytes textures_bytes { textures, static_cast<size_t>(n) * sizeof(WebGLObjectId) };
    Commands::GenTextures command { .n = n };
    command.textures = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(textures_bytes.size()) };
    record(command, textures_bytes);
}

void WebGLContextProxy::gen_transform_feedbacks(GLsizei n, GLuint* ids)
{
    if (n <= 0)
        return;
    static_assert(IsSame<WebGLObjectId, GLuint>);
    for (GLsizei i = 0; i < n; ++i)
        ids[i] = allocate_object_id();
    ReadonlyBytes ids_bytes { ids, static_cast<size_t>(n) * sizeof(WebGLObjectId) };
    Commands::GenTransformFeedbacks command { .n = n };
    command.ids = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(ids_bytes.size()) };
    record(command, ids_bytes);
}

void WebGLContextProxy::gen_vertex_arrays(GLsizei n, GLuint* arrays)
{
    if (n <= 0)
        return;
    static_assert(IsSame<WebGLObjectId, GLuint>);
    for (GLsizei i = 0; i < n; ++i)
        arrays[i] = allocate_object_id();
    ReadonlyBytes arrays_bytes { arrays, static_cast<size_t>(n) * sizeof(WebGLObjectId) };
    Commands::GenVertexArrays command { .n = n };
    command.arrays = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(arrays_bytes.size()) };
    record(command, arrays_bytes);
}

void WebGLContextProxy::gen_vertex_arrays_oes(GLsizei n, GLuint* arrays)
{
    if (n <= 0)
        return;
    static_assert(IsSame<WebGLObjectId, GLuint>);
    for (GLsizei i = 0; i < n; ++i)
        arrays[i] = allocate_object_id();
    ReadonlyBytes arrays_bytes { arrays, static_cast<size_t>(n) * sizeof(WebGLObjectId) };
    Commands::GenVertexArraysOES command { .n = n };
    command.arrays = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(arrays_bytes.size()) };
    record(command, arrays_bytes);
}

void WebGLContextProxy::generate_mipmap(GLenum target)
{
    Commands::GenerateMipmap command { .target = target };
    record(command);
}

void WebGLContextProxy::get_active_attrib(GLuint program, GLuint index, GLsizei bufSize, GLsizei* length, GLint* size, GLenum* type, GLchar* name)
{
    SyncCalls::GetActiveAttrib::Request request { .program = program, .index = index, .buf_size = bufSize };
    auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request<SyncCalls::GetActiveAttrib>(request));
    if (is_lost())
        return;
    auto reply = WebGLSyncCall::decode_reply<SyncCalls::GetActiveAttrib::Reply>(reply_bytes);
    if (length)
        *length = reply.length;
    if (size)
        *size = reply.size;
    if (type)
        *type = reply.type;
    if (name)
        WebGLCommandList::copy_data_span(reply_bytes, reply.name, { name, static_cast<size_t>(bufSize * sizeof(GLchar)) });
}

void WebGLContextProxy::get_active_uniform(GLuint program, GLuint index, GLsizei bufSize, GLsizei* length, GLint* size, GLenum* type, GLchar* name)
{
    SyncCalls::GetActiveUniform::Request request { .program = program, .index = index, .buf_size = bufSize };
    auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request<SyncCalls::GetActiveUniform>(request));
    if (is_lost())
        return;
    auto reply = WebGLSyncCall::decode_reply<SyncCalls::GetActiveUniform::Reply>(reply_bytes);
    if (length)
        *length = reply.length;
    if (size)
        *size = reply.size;
    if (type)
        *type = reply.type;
    if (name)
        WebGLCommandList::copy_data_span(reply_bytes, reply.name, { name, static_cast<size_t>(bufSize * sizeof(GLchar)) });
}

void WebGLContextProxy::get_active_uniform_block_name(GLuint program, GLuint uniformBlockIndex, GLsizei bufSize, GLsizei* length, GLchar* uniformBlockName)
{
    SyncCalls::GetActiveUniformBlockName::Request request { .program = program, .uniform_block_index = uniformBlockIndex, .buf_size = bufSize };
    auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request<SyncCalls::GetActiveUniformBlockName>(request));
    if (is_lost())
        return;
    auto reply = WebGLSyncCall::decode_reply<SyncCalls::GetActiveUniformBlockName::Reply>(reply_bytes);
    if (length)
        *length = reply.length;
    if (uniformBlockName)
        WebGLCommandList::copy_data_span(reply_bytes, reply.uniform_block_name, { uniformBlockName, static_cast<size_t>(bufSize * sizeof(GLchar)) });
}

void WebGLContextProxy::get_active_uniform_blockiv_robust_angle(GLuint program, GLuint uniformBlockIndex, GLenum pname, GLsizei bufSize, GLsizei* length, GLint* params)
{
    SyncCalls::GetActiveUniformBlockivRobustANGLE::Request request { .program = program, .uniform_block_index = uniformBlockIndex, .pname = pname, .buf_size = bufSize };
    auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request<SyncCalls::GetActiveUniformBlockivRobustANGLE>(request));
    if (is_lost())
        return;
    auto reply = WebGLSyncCall::decode_reply<SyncCalls::GetActiveUniformBlockivRobustANGLE::Reply>(reply_bytes);
    if (length)
        *length = reply.length;
    if (params)
        WebGLCommandList::copy_data_span(reply_bytes, reply.params, { params, static_cast<size_t>(bufSize * sizeof(GLint)) });
}

void WebGLContextProxy::get_active_uniformsiv(GLuint program, GLsizei uniformCount, GLuint const* uniformIndices, GLenum pname, GLint* params)
{
    ReadonlyBytes uniform_indices_bytes { uniformIndices, static_cast<size_t>(uniformCount * sizeof(GLuint)) };
    SyncCalls::GetActiveUniformsiv::Request request { .program = program, .uniform_count = uniformCount, .pname = pname };
    request.uniform_indices = { WebGLCommandList::first_inline_data_offset(sizeof(request)), static_cast<u32>(uniform_indices_bytes.size()) };
    auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request<SyncCalls::GetActiveUniformsiv>(request, uniform_indices_bytes));
    if (is_lost())
        return;
    auto reply = WebGLSyncCall::decode_reply<SyncCalls::GetActiveUniformsiv::Reply>(reply_bytes);
    if (params)
        WebGLCommandList::copy_data_span(reply_bytes, reply.params, { params, static_cast<size_t>(uniformCount * sizeof(GLint)) });
}

GLint WebGLContextProxy::get_attrib_location(GLuint program, GLchar const* name)
{
    ReadonlyBytes name_bytes { name, __builtin_strlen(name) + 1 };
    SyncCalls::GetAttribLocation::Request request { .program = program };
    request.name = { WebGLCommandList::first_inline_data_offset(sizeof(request)), static_cast<u32>(name_bytes.size()) };
    auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request<SyncCalls::GetAttribLocation>(request, name_bytes));
    if (is_lost())
        return {};
    auto reply = WebGLSyncCall::decode_reply<SyncCalls::GetAttribLocation::Reply>(reply_bytes);
    return reply.return_value;
}

void WebGLContextProxy::get_booleanv_robust_angle(GLenum pname, GLsizei bufSize, GLsizei* length, GLboolean* data)
{
    SyncCalls::GetBooleanvRobustANGLE::Request request { .pname = pname, .buf_size = bufSize };
    auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request<SyncCalls::GetBooleanvRobustANGLE>(request));
    if (is_lost())
        return;
    auto reply = WebGLSyncCall::decode_reply<SyncCalls::GetBooleanvRobustANGLE::Reply>(reply_bytes);
    if (length)
        *length = reply.length;
    if (data)
        WebGLCommandList::copy_data_span(reply_bytes, reply.data, { data, static_cast<size_t>(bufSize * sizeof(GLboolean)) });
}

void WebGLContextProxy::get_buffer_parameteriv_robust_angle(GLenum target, GLenum pname, GLsizei bufSize, GLsizei* length, GLint* params)
{
    SyncCalls::GetBufferParameterivRobustANGLE::Request request { .target = target, .pname = pname, .buf_size = bufSize };
    auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request<SyncCalls::GetBufferParameterivRobustANGLE>(request));
    if (is_lost())
        return;
    auto reply = WebGLSyncCall::decode_reply<SyncCalls::GetBufferParameterivRobustANGLE::Reply>(reply_bytes);
    if (length)
        *length = reply.length;
    if (params)
        WebGLCommandList::copy_data_span(reply_bytes, reply.params, { params, static_cast<size_t>(bufSize * sizeof(GLint)) });
}

GLenum WebGLContextProxy::get_error()
{
    SyncCalls::GetError::Request request {  };
    auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request<SyncCalls::GetError>(request));
    if (is_lost())
        return {};
    auto reply = WebGLSyncCall::decode_reply<SyncCalls::GetError::Reply>(reply_bytes);
    return reply.return_value;
}

void WebGLContextProxy::get_floatv_robust_angle(GLenum pname, GLsizei bufSize, GLsizei* length, GLfloat* data)
{
    SyncCalls::GetFloatvRobustANGLE::Request request { .pname = pname, .buf_size = bufSize };
    auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request<SyncCalls::GetFloatvRobustANGLE>(request));
    if (is_lost())
        return;
    auto reply = WebGLSyncCall::decode_reply<SyncCalls::GetFloatvRobustANGLE::Reply>(reply_bytes);
    if (length)
        *length = reply.length;
    if (data)
        WebGLCommandList::copy_data_span(reply_bytes, reply.data, { data, static_cast<size_t>(bufSize * sizeof(GLfloat)) });
}

void WebGLContextProxy::get_integer64v_robust_angle(GLenum pname, GLsizei bufSize, GLsizei* length, GLint64* data)
{
    SyncCalls::GetInteger64vRobustANGLE::Request request { .pname = pname, .buf_size = bufSize };
    auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request<SyncCalls::GetInteger64vRobustANGLE>(request));
    if (is_lost())
        return;
    auto reply = WebGLSyncCall::decode_reply<SyncCalls::GetInteger64vRobustANGLE::Reply>(reply_bytes);
    if (length)
        *length = reply.length;
    if (data)
        WebGLCommandList::copy_data_span(reply_bytes, reply.data, { data, static_cast<size_t>(bufSize * sizeof(GLint64)) });
}

void WebGLContextProxy::get_integerv_robust_angle(GLenum pname, GLsizei bufSize, GLsizei* length, GLint* data)
{
    SyncCalls::GetIntegervRobustANGLE::Request request { .pname = pname, .buf_size = bufSize };
    auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request<SyncCalls::GetIntegervRobustANGLE>(request));
    if (is_lost())
        return;
    auto reply = WebGLSyncCall::decode_reply<SyncCalls::GetIntegervRobustANGLE::Reply>(reply_bytes);
    if (length)
        *length = reply.length;
    if (data)
        WebGLCommandList::copy_data_span(reply_bytes, reply.data, { data, static_cast<size_t>(bufSize * sizeof(GLint)) });
}

void WebGLContextProxy::get_internalformativ_robust_angle(GLenum target, GLenum internalformat, GLenum pname, GLsizei bufSize, GLsizei* length, GLint* params)
{
    SyncCalls::GetInternalformativRobustANGLE::Request request { .target = target, .internalformat = internalformat, .pname = pname, .buf_size = bufSize };
    auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request<SyncCalls::GetInternalformativRobustANGLE>(request));
    if (is_lost())
        return;
    auto reply = WebGLSyncCall::decode_reply<SyncCalls::GetInternalformativRobustANGLE::Reply>(reply_bytes);
    if (length)
        *length = reply.length;
    if (params)
        WebGLCommandList::copy_data_span(reply_bytes, reply.params, { params, static_cast<size_t>(bufSize * sizeof(GLint)) });
}

void WebGLContextProxy::get_program_info_log(GLuint program, GLsizei bufSize, GLsizei* length, GLchar* infoLog)
{
    SyncCalls::GetProgramInfoLog::Request request { .program = program, .buf_size = bufSize };
    auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request<SyncCalls::GetProgramInfoLog>(request));
    if (is_lost())
        return;
    auto reply = WebGLSyncCall::decode_reply<SyncCalls::GetProgramInfoLog::Reply>(reply_bytes);
    if (length)
        *length = reply.length;
    if (infoLog)
        WebGLCommandList::copy_data_span(reply_bytes, reply.info_log, { infoLog, static_cast<size_t>(bufSize * sizeof(GLchar)) });
}

void WebGLContextProxy::get_programiv(GLuint program, GLenum pname, GLint* params)
{
    SyncCalls::GetProgramiv::Request request { .program = program, .pname = pname };
    auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request<SyncCalls::GetProgramiv>(request));
    if (is_lost())
        return;
    auto reply = WebGLSyncCall::decode_reply<SyncCalls::GetProgramiv::Reply>(reply_bytes);
    if (params)
        WebGLCommandList::copy_data_span(reply_bytes, reply.params, { params, static_cast<size_t>(sizeof(GLint)) });
}

void WebGLContextProxy::get_programiv_robust_angle(GLuint program, GLenum pname, GLsizei bufSize, GLsizei* length, GLint* params)
{
    SyncCalls::GetProgramivRobustANGLE::Request request { .program = program, .pname = pname, .buf_size = bufSize };
    auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request<SyncCalls::GetProgramivRobustANGLE>(request));
    if (is_lost())
        return;
    auto reply = WebGLSyncCall::decode_reply<SyncCalls::GetProgramivRobustANGLE::Reply>(reply_bytes);
    if (length)
        *length = reply.length;
    if (params)
        WebGLCommandList::copy_data_span(reply_bytes, reply.params, { params, static_cast<size_t>(bufSize * sizeof(GLint)) });
}

void WebGLContextProxy::get_query_objectuiv_robust_angle(GLuint id, GLenum pname, GLsizei bufSize, GLsizei* length, GLuint* params)
{
    SyncCalls::GetQueryObjectuivRobustANGLE::Request request { .id = id, .pname = pname, .buf_size = bufSize };
    auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request<SyncCalls::GetQueryObjectuivRobustANGLE>(request));
    if (is_lost())
        return;
    auto reply = WebGLSyncCall::decode_reply<SyncCalls::GetQueryObjectuivRobustANGLE::Reply>(reply_bytes);
    if (length)
        *length = reply.length;
    if (params)
        WebGLCommandList::copy_data_span(reply_bytes, reply.params, { params, static_cast<size_t>(bufSize * sizeof(GLuint)) });
}

void WebGLContextProxy::get_renderbuffer_parameteriv_robust_angle(GLenum target, GLenum pname, GLsizei bufSize, GLsizei* length, GLint* params)
{
    SyncCalls::GetRenderbufferParameterivRobustANGLE::Request request { .target = target, .pname = pname, .buf_size = bufSize };
    auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request<SyncCalls::GetRenderbufferParameterivRobustANGLE>(request));
    if (is_lost())
        return;
    auto reply = WebGLSyncCall::decode_reply<SyncCalls::GetRenderbufferParameterivRobustANGLE::Reply>(reply_bytes);
    if (length)
        *length = reply.length;
    if (params)
        WebGLCommandList::copy_data_span(reply_bytes, reply.params, { params, static_cast<size_t>(bufSize * sizeof(GLint)) });
}

void WebGLContextProxy::get_shader_info_log(GLuint shader, GLsizei bufSize, GLsizei* length, GLchar* infoLog)
{
    SyncCalls::GetShaderInfoLog::Request request { .shader = shader, .buf_size = bufSize };
    auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request<SyncCalls::GetShaderInfoLog>(request));
    if (is_lost())
        return;
    auto reply = WebGLSyncCall::decode_reply<SyncCalls::GetShaderInfoLog::Reply>(reply_bytes);
    if (length)
        *length = reply.length;
    if (infoLog)
        WebGLCommandList::copy_data_span(reply_bytes, reply.info_log, { infoLog, static_cast<size_t>(bufSize * sizeof(GLchar)) });
}

void WebGLContextProxy::get_shader_precision_format(GLenum shadertype, GLenum precisiontype, GLint* range, GLint* precision)
{
    SyncCalls::GetShaderPrecisionFormat::Request request { .shadertype = shadertype, .precisiontype = precisiontype };
    auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request<SyncCalls::GetShaderPrecisionFormat>(request));
    if (is_lost())
        return;
    auto reply = WebGLSyncCall::decode_reply<SyncCalls::GetShaderPrecisionFormat::Reply>(reply_bytes);
    if (range)
        WebGLCommandList::copy_data_span(reply_bytes, reply.range, { range, static_cast<size_t>(2 * sizeof(GLint)) });
    if (precision)
        WebGLCommandList::copy_data_span(reply_bytes, reply.precision, { precision, static_cast<size_t>(sizeof(GLint)) });
}

void WebGLContextProxy::get_shader_source(GLuint shader, GLsizei bufSize, GLsizei* length, GLchar* source)
{
    SyncCalls::GetShaderSource::Request request { .shader = shader, .buf_size = bufSize };
    auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request<SyncCalls::GetShaderSource>(request));
    if (is_lost())
        return;
    auto reply = WebGLSyncCall::decode_reply<SyncCalls::GetShaderSource::Reply>(reply_bytes);
    if (length)
        *length = reply.length;
    if (source)
        WebGLCommandList::copy_data_span(reply_bytes, reply.source, { source, static_cast<size_t>(bufSize * sizeof(GLchar)) });
}

void WebGLContextProxy::get_shaderiv(GLuint shader, GLenum pname, GLint* params)
{
    SyncCalls::GetShaderiv::Request request { .shader = shader, .pname = pname };
    auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request<SyncCalls::GetShaderiv>(request));
    if (is_lost())
        return;
    auto reply = WebGLSyncCall::decode_reply<SyncCalls::GetShaderiv::Reply>(reply_bytes);
    if (params)
        WebGLCommandList::copy_data_span(reply_bytes, reply.params, { params, static_cast<size_t>(sizeof(GLint)) });
}

void WebGLContextProxy::get_shaderiv_robust_angle(GLuint shader, GLenum pname, GLsizei bufSize, GLsizei* length, GLint* params)
{
    SyncCalls::GetShaderivRobustANGLE::Request request { .shader = shader, .pname = pname, .buf_size = bufSize };
    auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request<SyncCalls::GetShaderivRobustANGLE>(request));
    if (is_lost())
        return;
    auto reply = WebGLSyncCall::decode_reply<SyncCalls::GetShaderivRobustANGLE::Reply>(reply_bytes);
    if (length)
        *length = reply.length;
    if (params)
        WebGLCommandList::copy_data_span(reply_bytes, reply.params, { params, static_cast<size_t>(bufSize * sizeof(GLint)) });
}

void WebGLContextProxy::get_synciv(GLsync sync, GLenum pname, GLsizei count, GLsizei* length, GLint* values)
{
    SyncCalls::GetSynciv::Request request { .sync = static_cast<WebGLObjectId>(reinterpret_cast<uintptr_t>(sync)), .pname = pname, .count = count };
    auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request<SyncCalls::GetSynciv>(request));
    if (is_lost())
        return;
    auto reply = WebGLSyncCall::decode_reply<SyncCalls::GetSynciv::Reply>(reply_bytes);
    if (length)
        *length = reply.length;
    if (values)
        WebGLCommandList::copy_data_span(reply_bytes, reply.values, { values, static_cast<size_t>(count * sizeof(GLint)) });
}

void WebGLContextProxy::get_tex_parameterfv_robust_angle(GLenum target, GLenum pname, GLsizei bufSize, GLsizei* length, GLfloat* params)
{
    SyncCalls::GetTexParameterfvRobustANGLE::Request request { .target = target, .pname = pname, .buf_size = bufSize };
    auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request<SyncCalls::GetTexParameterfvRobustANGLE>(request));
    if (is_lost())
        return;
    auto reply = WebGLSyncCall::decode_reply<SyncCalls::GetTexParameterfvRobustANGLE::Reply>(reply_bytes);
    if (length)
        *length = reply.length;
    if (params)
        WebGLCommandList::copy_data_span(reply_bytes, reply.params, { params, static_cast<size_t>(bufSize * sizeof(GLfloat)) });
}

void WebGLContextProxy::get_tex_parameteriv_robust_angle(GLenum target, GLenum pname, GLsizei bufSize, GLsizei* length, GLint* params)
{
    SyncCalls::GetTexParameterivRobustANGLE::Request request { .target = target, .pname = pname, .buf_size = bufSize };
    auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request<SyncCalls::GetTexParameterivRobustANGLE>(request));
    if (is_lost())
        return;
    auto reply = WebGLSyncCall::decode_reply<SyncCalls::GetTexParameterivRobustANGLE::Reply>(reply_bytes);
    if (length)
        *length = reply.length;
    if (params)
        WebGLCommandList::copy_data_span(reply_bytes, reply.params, { params, static_cast<size_t>(bufSize * sizeof(GLint)) });
}

GLuint WebGLContextProxy::get_uniform_block_index(GLuint program, GLchar const* uniformBlockName)
{
    ReadonlyBytes uniform_block_name_bytes { uniformBlockName, __builtin_strlen(uniformBlockName) + 1 };
    SyncCalls::GetUniformBlockIndex::Request request { .program = program };
    request.uniform_block_name = { WebGLCommandList::first_inline_data_offset(sizeof(request)), static_cast<u32>(uniform_block_name_bytes.size()) };
    auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request<SyncCalls::GetUniformBlockIndex>(request, uniform_block_name_bytes));
    if (is_lost())
        return {};
    auto reply = WebGLSyncCall::decode_reply<SyncCalls::GetUniformBlockIndex::Reply>(reply_bytes);
    return reply.return_value;
}

GLint WebGLContextProxy::get_uniform_location(GLuint program, GLchar const* name)
{
    ReadonlyBytes name_bytes { name, __builtin_strlen(name) + 1 };
    SyncCalls::GetUniformLocation::Request request { .program = program };
    request.name = { WebGLCommandList::first_inline_data_offset(sizeof(request)), static_cast<u32>(name_bytes.size()) };
    auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request<SyncCalls::GetUniformLocation>(request, name_bytes));
    if (is_lost())
        return {};
    auto reply = WebGLSyncCall::decode_reply<SyncCalls::GetUniformLocation::Reply>(reply_bytes);
    return reply.return_value;
}

void WebGLContextProxy::get_vertex_attribfv_robust_angle(GLuint index, GLenum pname, GLsizei bufSize, GLsizei* length, GLfloat* params)
{
    SyncCalls::GetVertexAttribfvRobustANGLE::Request request { .index = index, .pname = pname, .buf_size = bufSize };
    auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request<SyncCalls::GetVertexAttribfvRobustANGLE>(request));
    if (is_lost())
        return;
    auto reply = WebGLSyncCall::decode_reply<SyncCalls::GetVertexAttribfvRobustANGLE::Reply>(reply_bytes);
    if (length)
        *length = reply.length;
    if (params)
        WebGLCommandList::copy_data_span(reply_bytes, reply.params, { params, static_cast<size_t>(bufSize * sizeof(GLfloat)) });
}

void WebGLContextProxy::get_vertex_attribiv_robust_angle(GLuint index, GLenum pname, GLsizei bufSize, GLsizei* length, GLint* params)
{
    SyncCalls::GetVertexAttribivRobustANGLE::Request request { .index = index, .pname = pname, .buf_size = bufSize };
    auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request<SyncCalls::GetVertexAttribivRobustANGLE>(request));
    if (is_lost())
        return;
    auto reply = WebGLSyncCall::decode_reply<SyncCalls::GetVertexAttribivRobustANGLE::Reply>(reply_bytes);
    if (length)
        *length = reply.length;
    if (params)
        WebGLCommandList::copy_data_span(reply_bytes, reply.params, { params, static_cast<size_t>(bufSize * sizeof(GLint)) });
}

void WebGLContextProxy::hint(GLenum target, GLenum mode)
{
    Commands::Hint command { .target = target, .mode = mode };
    record(command);
}

void WebGLContextProxy::invalidate_framebuffer(GLenum target, GLsizei numAttachments, GLenum const* attachments)
{
    ReadonlyBytes attachments_bytes { attachments, static_cast<size_t>(numAttachments * sizeof(GLenum)) };
    Commands::InvalidateFramebuffer command { .target = target, .num_attachments = numAttachments };
    command.attachments = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(attachments_bytes.size()) };
    record(command, attachments_bytes);
}

void WebGLContextProxy::invalidate_sub_framebuffer(GLenum target, GLsizei numAttachments, GLenum const* attachments, GLint x, GLint y, GLsizei width, GLsizei height)
{
    ReadonlyBytes attachments_bytes { attachments, static_cast<size_t>(numAttachments * sizeof(GLenum)) };
    Commands::InvalidateSubFramebuffer command { .target = target, .num_attachments = numAttachments, .x = x, .y = y, .width = width, .height = height };
    command.attachments = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(attachments_bytes.size()) };
    record(command, attachments_bytes);
}

GLboolean WebGLContextProxy::is_buffer(GLuint buffer)
{
    SyncCalls::IsBuffer::Request request { .buffer = buffer };
    auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request<SyncCalls::IsBuffer>(request));
    if (is_lost())
        return {};
    auto reply = WebGLSyncCall::decode_reply<SyncCalls::IsBuffer::Reply>(reply_bytes);
    return reply.return_value;
}

GLboolean WebGLContextProxy::is_enabled(GLenum cap)
{
    SyncCalls::IsEnabled::Request request { .cap = cap };
    auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request<SyncCalls::IsEnabled>(request));
    if (is_lost())
        return {};
    auto reply = WebGLSyncCall::decode_reply<SyncCalls::IsEnabled::Reply>(reply_bytes);
    return reply.return_value;
}

GLboolean WebGLContextProxy::is_framebuffer(GLuint framebuffer)
{
    SyncCalls::IsFramebuffer::Request request { .framebuffer = framebuffer };
    auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request<SyncCalls::IsFramebuffer>(request));
    if (is_lost())
        return {};
    auto reply = WebGLSyncCall::decode_reply<SyncCalls::IsFramebuffer::Reply>(reply_bytes);
    return reply.return_value;
}

GLboolean WebGLContextProxy::is_program(GLuint program)
{
    SyncCalls::IsProgram::Request request { .program = program };
    auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request<SyncCalls::IsProgram>(request));
    if (is_lost())
        return {};
    auto reply = WebGLSyncCall::decode_reply<SyncCalls::IsProgram::Reply>(reply_bytes);
    return reply.return_value;
}

GLboolean WebGLContextProxy::is_renderbuffer(GLuint renderbuffer)
{
    SyncCalls::IsRenderbuffer::Request request { .renderbuffer = renderbuffer };
    auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request<SyncCalls::IsRenderbuffer>(request));
    if (is_lost())
        return {};
    auto reply = WebGLSyncCall::decode_reply<SyncCalls::IsRenderbuffer::Reply>(reply_bytes);
    return reply.return_value;
}

GLboolean WebGLContextProxy::is_shader(GLuint shader)
{
    SyncCalls::IsShader::Request request { .shader = shader };
    auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request<SyncCalls::IsShader>(request));
    if (is_lost())
        return {};
    auto reply = WebGLSyncCall::decode_reply<SyncCalls::IsShader::Reply>(reply_bytes);
    return reply.return_value;
}

GLboolean WebGLContextProxy::is_texture(GLuint texture)
{
    SyncCalls::IsTexture::Request request { .texture = texture };
    auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request<SyncCalls::IsTexture>(request));
    if (is_lost())
        return {};
    auto reply = WebGLSyncCall::decode_reply<SyncCalls::IsTexture::Reply>(reply_bytes);
    return reply.return_value;
}

GLboolean WebGLContextProxy::is_vertex_array(GLuint array)
{
    SyncCalls::IsVertexArray::Request request { .array = array };
    auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request<SyncCalls::IsVertexArray>(request));
    if (is_lost())
        return {};
    auto reply = WebGLSyncCall::decode_reply<SyncCalls::IsVertexArray::Reply>(reply_bytes);
    return reply.return_value;
}

GLboolean WebGLContextProxy::is_vertex_array_oes(GLuint array)
{
    SyncCalls::IsVertexArrayOES::Request request { .array = array };
    auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request<SyncCalls::IsVertexArrayOES>(request));
    if (is_lost())
        return {};
    auto reply = WebGLSyncCall::decode_reply<SyncCalls::IsVertexArrayOES::Reply>(reply_bytes);
    return reply.return_value;
}

void WebGLContextProxy::line_width(GLfloat width)
{
    Commands::LineWidth command { .width = width };
    record(command);
}

void WebGLContextProxy::link_program(GLuint program)
{
    Commands::LinkProgram command { .program = program };
    record(command);
}

void WebGLContextProxy::pause_transform_feedback()
{
    Commands::PauseTransformFeedback command {  };
    record(command);
}

void WebGLContextProxy::pixel_storei(GLenum pname, GLint param)
{
    Commands::PixelStorei command { .pname = pname, .param = param };
    record(command);
}

void WebGLContextProxy::polygon_offset(GLfloat factor, GLfloat units)
{
    Commands::PolygonOffset command { .factor = factor, .units = units };
    record(command);
}

void WebGLContextProxy::read_buffer(GLenum src)
{
    Commands::ReadBuffer command { .src = src };
    record(command);
}

void WebGLContextProxy::renderbuffer_storage(GLenum target, GLenum internalformat, GLsizei width, GLsizei height)
{
    Commands::RenderbufferStorage command { .target = target, .internalformat = internalformat, .width = width, .height = height };
    record(command);
}

void WebGLContextProxy::renderbuffer_storage_multisample(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height)
{
    Commands::RenderbufferStorageMultisample command { .target = target, .samples = samples, .internalformat = internalformat, .width = width, .height = height };
    record(command);
}

void WebGLContextProxy::request_extension_angle(GLchar const* name)
{
    ReadonlyBytes name_bytes { name, __builtin_strlen(name) + 1 };
    Commands::RequestExtensionANGLE command {  };
    command.name = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(name_bytes.size()) };
    record(command, name_bytes);
}

void WebGLContextProxy::resume_transform_feedback()
{
    Commands::ResumeTransformFeedback command {  };
    record(command);
}

void WebGLContextProxy::sample_coverage(GLfloat value, GLboolean invert)
{
    Commands::SampleCoverage command { .value = value, .invert = invert };
    record(command);
}

void WebGLContextProxy::sampler_parameterf(GLuint sampler, GLenum pname, GLfloat param)
{
    Commands::SamplerParameterf command { .sampler = sampler, .pname = pname, .param = param };
    record(command);
}

void WebGLContextProxy::sampler_parameteri(GLuint sampler, GLenum pname, GLint param)
{
    Commands::SamplerParameteri command { .sampler = sampler, .pname = pname, .param = param };
    record(command);
}

void WebGLContextProxy::scissor(GLint x, GLint y, GLsizei width, GLsizei height)
{
    Commands::Scissor command { .x = x, .y = y, .width = width, .height = height };
    record(command);
}

void WebGLContextProxy::stencil_func(GLenum func, GLint ref, GLuint mask)
{
    Commands::StencilFunc command { .func = func, .ref = ref, .mask = mask };
    record(command);
}

void WebGLContextProxy::stencil_func_separate(GLenum face, GLenum func, GLint ref, GLuint mask)
{
    Commands::StencilFuncSeparate command { .face = face, .func = func, .ref = ref, .mask = mask };
    record(command);
}

void WebGLContextProxy::stencil_mask(GLuint mask)
{
    Commands::StencilMask command { .mask = mask };
    record(command);
}

void WebGLContextProxy::stencil_mask_separate(GLenum face, GLuint mask)
{
    Commands::StencilMaskSeparate command { .face = face, .mask = mask };
    record(command);
}

void WebGLContextProxy::stencil_op(GLenum fail, GLenum zfail, GLenum zpass)
{
    Commands::StencilOp command { .fail = fail, .zfail = zfail, .zpass = zpass };
    record(command);
}

void WebGLContextProxy::stencil_op_separate(GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass)
{
    Commands::StencilOpSeparate command { .face = face, .sfail = sfail, .dpfail = dpfail, .dppass = dppass };
    record(command);
}

void WebGLContextProxy::tex_image2d_robust_angle(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, GLsizei bufSize, void const* pixels)
{
    ReadonlyBytes pixels_bytes { pixels, pixels ? static_cast<size_t>(bufSize) : 0 };
    Commands::TexImage2DRobustANGLE command { .target = target, .level = level, .internalformat = internalformat, .width = width, .height = height, .border = border, .format = format, .type = type, .buf_size = bufSize, .has_pixels = pixels != nullptr };
    command.pixels = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(pixels_bytes.size()) };
    record(command, pixels_bytes);
}

void WebGLContextProxy::tex_image3d_robust_angle(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, GLsizei bufSize, void const* pixels)
{
    ReadonlyBytes pixels_bytes { pixels, pixels ? static_cast<size_t>(bufSize) : 0 };
    Commands::TexImage3DRobustANGLE command { .target = target, .level = level, .internalformat = internalformat, .width = width, .height = height, .depth = depth, .border = border, .format = format, .type = type, .buf_size = bufSize, .has_pixels = pixels != nullptr };
    command.pixels = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(pixels_bytes.size()) };
    record(command, pixels_bytes);
}

void WebGLContextProxy::tex_parameterf(GLenum target, GLenum pname, GLfloat param)
{
    Commands::TexParameterf command { .target = target, .pname = pname, .param = param };
    record(command);
}

void WebGLContextProxy::tex_parameteri(GLenum target, GLenum pname, GLint param)
{
    Commands::TexParameteri command { .target = target, .pname = pname, .param = param };
    record(command);
}

void WebGLContextProxy::tex_storage2d(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height)
{
    Commands::TexStorage2D command { .target = target, .levels = levels, .internalformat = internalformat, .width = width, .height = height };
    record(command);
}

void WebGLContextProxy::tex_storage3d(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth)
{
    Commands::TexStorage3D command { .target = target, .levels = levels, .internalformat = internalformat, .width = width, .height = height, .depth = depth };
    record(command);
}

void WebGLContextProxy::tex_sub_image2d_robust_angle(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei bufSize, void const* pixels)
{
    ReadonlyBytes pixels_bytes { pixels, pixels ? static_cast<size_t>(bufSize) : 0 };
    Commands::TexSubImage2DRobustANGLE command { .target = target, .level = level, .xoffset = xoffset, .yoffset = yoffset, .width = width, .height = height, .format = format, .type = type, .buf_size = bufSize, .has_pixels = pixels != nullptr };
    command.pixels = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(pixels_bytes.size()) };
    record(command, pixels_bytes);
}

void WebGLContextProxy::tex_sub_image3d_robust_angle(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, GLsizei bufSize, void const* pixels)
{
    ReadonlyBytes pixels_bytes { pixels, pixels ? static_cast<size_t>(bufSize) : 0 };
    Commands::TexSubImage3DRobustANGLE command { .target = target, .level = level, .xoffset = xoffset, .yoffset = yoffset, .zoffset = zoffset, .width = width, .height = height, .depth = depth, .format = format, .type = type, .buf_size = bufSize, .has_pixels = pixels != nullptr };
    command.pixels = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(pixels_bytes.size()) };
    record(command, pixels_bytes);
}

void WebGLContextProxy::uniform1f(GLint location, GLfloat v0)
{
    Commands::Uniform1f command { .location = location, .v0 = v0 };
    record(command);
}

void WebGLContextProxy::uniform1fv(GLint location, GLsizei count, GLfloat const* value)
{
    ReadonlyBytes value_bytes { value, static_cast<size_t>(count * 1 * sizeof(GLfloat)) };
    Commands::Uniform1fv command { .location = location, .count = count };
    command.value = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(value_bytes.size()) };
    record(command, value_bytes);
}

void WebGLContextProxy::uniform1i(GLint location, GLint v0)
{
    Commands::Uniform1i command { .location = location, .v0 = v0 };
    record(command);
}

void WebGLContextProxy::uniform1iv(GLint location, GLsizei count, GLint const* value)
{
    ReadonlyBytes value_bytes { value, static_cast<size_t>(count * 1 * sizeof(GLint)) };
    Commands::Uniform1iv command { .location = location, .count = count };
    command.value = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(value_bytes.size()) };
    record(command, value_bytes);
}

void WebGLContextProxy::uniform1ui(GLint location, GLuint v0)
{
    Commands::Uniform1ui command { .location = location, .v0 = v0 };
    record(command);
}

void WebGLContextProxy::uniform1uiv(GLint location, GLsizei count, GLuint const* value)
{
    ReadonlyBytes value_bytes { value, static_cast<size_t>(count * 1 * sizeof(GLuint)) };
    Commands::Uniform1uiv command { .location = location, .count = count };
    command.value = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(value_bytes.size()) };
    record(command, value_bytes);
}

void WebGLContextProxy::uniform2f(GLint location, GLfloat v0, GLfloat v1)
{
    Commands::Uniform2f command { .location = location, .v0 = v0, .v1 = v1 };
    record(command);
}

void WebGLContextProxy::uniform2fv(GLint location, GLsizei count, GLfloat const* value)
{
    ReadonlyBytes value_bytes { value, static_cast<size_t>(count * 2 * sizeof(GLfloat)) };
    Commands::Uniform2fv command { .location = location, .count = count };
    command.value = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(value_bytes.size()) };
    record(command, value_bytes);
}

void WebGLContextProxy::uniform2i(GLint location, GLint v0, GLint v1)
{
    Commands::Uniform2i command { .location = location, .v0 = v0, .v1 = v1 };
    record(command);
}

void WebGLContextProxy::uniform2iv(GLint location, GLsizei count, GLint const* value)
{
    ReadonlyBytes value_bytes { value, static_cast<size_t>(count * 2 * sizeof(GLint)) };
    Commands::Uniform2iv command { .location = location, .count = count };
    command.value = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(value_bytes.size()) };
    record(command, value_bytes);
}

void WebGLContextProxy::uniform2ui(GLint location, GLuint v0, GLuint v1)
{
    Commands::Uniform2ui command { .location = location, .v0 = v0, .v1 = v1 };
    record(command);
}

void WebGLContextProxy::uniform2uiv(GLint location, GLsizei count, GLuint const* value)
{
    ReadonlyBytes value_bytes { value, static_cast<size_t>(count * 2 * sizeof(GLuint)) };
    Commands::Uniform2uiv command { .location = location, .count = count };
    command.value = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(value_bytes.size()) };
    record(command, value_bytes);
}

void WebGLContextProxy::uniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2)
{
    Commands::Uniform3f command { .location = location, .v0 = v0, .v1 = v1, .v2 = v2 };
    record(command);
}

void WebGLContextProxy::uniform3fv(GLint location, GLsizei count, GLfloat const* value)
{
    ReadonlyBytes value_bytes { value, static_cast<size_t>(count * 3 * sizeof(GLfloat)) };
    Commands::Uniform3fv command { .location = location, .count = count };
    command.value = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(value_bytes.size()) };
    record(command, value_bytes);
}

void WebGLContextProxy::uniform3i(GLint location, GLint v0, GLint v1, GLint v2)
{
    Commands::Uniform3i command { .location = location, .v0 = v0, .v1 = v1, .v2 = v2 };
    record(command);
}

void WebGLContextProxy::uniform3iv(GLint location, GLsizei count, GLint const* value)
{
    ReadonlyBytes value_bytes { value, static_cast<size_t>(count * 3 * sizeof(GLint)) };
    Commands::Uniform3iv command { .location = location, .count = count };
    command.value = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(value_bytes.size()) };
    record(command, value_bytes);
}

void WebGLContextProxy::uniform3ui(GLint location, GLuint v0, GLuint v1, GLuint v2)
{
    Commands::Uniform3ui command { .location = location, .v0 = v0, .v1 = v1, .v2 = v2 };
    record(command);
}

void WebGLContextProxy::uniform3uiv(GLint location, GLsizei count, GLuint const* value)
{
    ReadonlyBytes value_bytes { value, static_cast<size_t>(count * 3 * sizeof(GLuint)) };
    Commands::Uniform3uiv command { .location = location, .count = count };
    command.value = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(value_bytes.size()) };
    record(command, value_bytes);
}

void WebGLContextProxy::uniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3)
{
    Commands::Uniform4f command { .location = location, .v0 = v0, .v1 = v1, .v2 = v2, .v3 = v3 };
    record(command);
}

void WebGLContextProxy::uniform4fv(GLint location, GLsizei count, GLfloat const* value)
{
    ReadonlyBytes value_bytes { value, static_cast<size_t>(count * 4 * sizeof(GLfloat)) };
    Commands::Uniform4fv command { .location = location, .count = count };
    command.value = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(value_bytes.size()) };
    record(command, value_bytes);
}

void WebGLContextProxy::uniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3)
{
    Commands::Uniform4i command { .location = location, .v0 = v0, .v1 = v1, .v2 = v2, .v3 = v3 };
    record(command);
}

void WebGLContextProxy::uniform4iv(GLint location, GLsizei count, GLint const* value)
{
    ReadonlyBytes value_bytes { value, static_cast<size_t>(count * 4 * sizeof(GLint)) };
    Commands::Uniform4iv command { .location = location, .count = count };
    command.value = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(value_bytes.size()) };
    record(command, value_bytes);
}

void WebGLContextProxy::uniform4ui(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3)
{
    Commands::Uniform4ui command { .location = location, .v0 = v0, .v1 = v1, .v2 = v2, .v3 = v3 };
    record(command);
}

void WebGLContextProxy::uniform4uiv(GLint location, GLsizei count, GLuint const* value)
{
    ReadonlyBytes value_bytes { value, static_cast<size_t>(count * 4 * sizeof(GLuint)) };
    Commands::Uniform4uiv command { .location = location, .count = count };
    command.value = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(value_bytes.size()) };
    record(command, value_bytes);
}

void WebGLContextProxy::uniform_block_binding(GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding)
{
    Commands::UniformBlockBinding command { .program = program, .uniform_block_index = uniformBlockIndex, .uniform_block_binding = uniformBlockBinding };
    record(command);
}

void WebGLContextProxy::uniform_matrix2fv(GLint location, GLsizei count, GLboolean transpose, GLfloat const* value)
{
    ReadonlyBytes value_bytes { value, static_cast<size_t>(count * 4 * sizeof(GLfloat)) };
    Commands::UniformMatrix2fv command { .location = location, .count = count, .transpose = transpose };
    command.value = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(value_bytes.size()) };
    record(command, value_bytes);
}

void WebGLContextProxy::uniform_matrix2x3fv(GLint location, GLsizei count, GLboolean transpose, GLfloat const* value)
{
    ReadonlyBytes value_bytes { value, static_cast<size_t>(count * 6 * sizeof(GLfloat)) };
    Commands::UniformMatrix2x3fv command { .location = location, .count = count, .transpose = transpose };
    command.value = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(value_bytes.size()) };
    record(command, value_bytes);
}

void WebGLContextProxy::uniform_matrix2x4fv(GLint location, GLsizei count, GLboolean transpose, GLfloat const* value)
{
    ReadonlyBytes value_bytes { value, static_cast<size_t>(count * 8 * sizeof(GLfloat)) };
    Commands::UniformMatrix2x4fv command { .location = location, .count = count, .transpose = transpose };
    command.value = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(value_bytes.size()) };
    record(command, value_bytes);
}

void WebGLContextProxy::uniform_matrix3fv(GLint location, GLsizei count, GLboolean transpose, GLfloat const* value)
{
    ReadonlyBytes value_bytes { value, static_cast<size_t>(count * 9 * sizeof(GLfloat)) };
    Commands::UniformMatrix3fv command { .location = location, .count = count, .transpose = transpose };
    command.value = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(value_bytes.size()) };
    record(command, value_bytes);
}

void WebGLContextProxy::uniform_matrix3x2fv(GLint location, GLsizei count, GLboolean transpose, GLfloat const* value)
{
    ReadonlyBytes value_bytes { value, static_cast<size_t>(count * 6 * sizeof(GLfloat)) };
    Commands::UniformMatrix3x2fv command { .location = location, .count = count, .transpose = transpose };
    command.value = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(value_bytes.size()) };
    record(command, value_bytes);
}

void WebGLContextProxy::uniform_matrix3x4fv(GLint location, GLsizei count, GLboolean transpose, GLfloat const* value)
{
    ReadonlyBytes value_bytes { value, static_cast<size_t>(count * 12 * sizeof(GLfloat)) };
    Commands::UniformMatrix3x4fv command { .location = location, .count = count, .transpose = transpose };
    command.value = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(value_bytes.size()) };
    record(command, value_bytes);
}

void WebGLContextProxy::uniform_matrix4fv(GLint location, GLsizei count, GLboolean transpose, GLfloat const* value)
{
    ReadonlyBytes value_bytes { value, static_cast<size_t>(count * 16 * sizeof(GLfloat)) };
    Commands::UniformMatrix4fv command { .location = location, .count = count, .transpose = transpose };
    command.value = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(value_bytes.size()) };
    record(command, value_bytes);
}

void WebGLContextProxy::uniform_matrix4x2fv(GLint location, GLsizei count, GLboolean transpose, GLfloat const* value)
{
    ReadonlyBytes value_bytes { value, static_cast<size_t>(count * 8 * sizeof(GLfloat)) };
    Commands::UniformMatrix4x2fv command { .location = location, .count = count, .transpose = transpose };
    command.value = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(value_bytes.size()) };
    record(command, value_bytes);
}

void WebGLContextProxy::uniform_matrix4x3fv(GLint location, GLsizei count, GLboolean transpose, GLfloat const* value)
{
    ReadonlyBytes value_bytes { value, static_cast<size_t>(count * 12 * sizeof(GLfloat)) };
    Commands::UniformMatrix4x3fv command { .location = location, .count = count, .transpose = transpose };
    command.value = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(value_bytes.size()) };
    record(command, value_bytes);
}

void WebGLContextProxy::use_program(GLuint program)
{
    Commands::UseProgram command { .program = program };
    record(command);
}

void WebGLContextProxy::validate_program(GLuint program)
{
    Commands::ValidateProgram command { .program = program };
    record(command);
}

void WebGLContextProxy::vertex_attrib1f(GLuint index, GLfloat x)
{
    Commands::VertexAttrib1f command { .index = index, .x = x };
    record(command);
}

void WebGLContextProxy::vertex_attrib1fv(GLuint index, GLfloat const* v)
{
    ReadonlyBytes v_bytes { v, static_cast<size_t>(1 * sizeof(GLfloat)) };
    Commands::VertexAttrib1fv command { .index = index };
    command.v = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(v_bytes.size()) };
    record(command, v_bytes);
}

void WebGLContextProxy::vertex_attrib2f(GLuint index, GLfloat x, GLfloat y)
{
    Commands::VertexAttrib2f command { .index = index, .x = x, .y = y };
    record(command);
}

void WebGLContextProxy::vertex_attrib2fv(GLuint index, GLfloat const* v)
{
    ReadonlyBytes v_bytes { v, static_cast<size_t>(2 * sizeof(GLfloat)) };
    Commands::VertexAttrib2fv command { .index = index };
    command.v = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(v_bytes.size()) };
    record(command, v_bytes);
}

void WebGLContextProxy::vertex_attrib3f(GLuint index, GLfloat x, GLfloat y, GLfloat z)
{
    Commands::VertexAttrib3f command { .index = index, .x = x, .y = y, .z = z };
    record(command);
}

void WebGLContextProxy::vertex_attrib3fv(GLuint index, GLfloat const* v)
{
    ReadonlyBytes v_bytes { v, static_cast<size_t>(3 * sizeof(GLfloat)) };
    Commands::VertexAttrib3fv command { .index = index };
    command.v = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(v_bytes.size()) };
    record(command, v_bytes);
}

void WebGLContextProxy::vertex_attrib4f(GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w)
{
    Commands::VertexAttrib4f command { .index = index, .x = x, .y = y, .z = z, .w = w };
    record(command);
}

void WebGLContextProxy::vertex_attrib4fv(GLuint index, GLfloat const* v)
{
    ReadonlyBytes v_bytes { v, static_cast<size_t>(4 * sizeof(GLfloat)) };
    Commands::VertexAttrib4fv command { .index = index };
    command.v = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(v_bytes.size()) };
    record(command, v_bytes);
}

void WebGLContextProxy::vertex_attrib_divisor(GLuint index, GLuint divisor)
{
    Commands::VertexAttribDivisor command { .index = index, .divisor = divisor };
    record(command);
}

void WebGLContextProxy::vertex_attrib_divisor_angle(GLuint index, GLuint divisor)
{
    Commands::VertexAttribDivisorANGLE command { .index = index, .divisor = divisor };
    record(command);
}

void WebGLContextProxy::vertex_attrib_i4i(GLuint index, GLint x, GLint y, GLint z, GLint w)
{
    Commands::VertexAttribI4i command { .index = index, .x = x, .y = y, .z = z, .w = w };
    record(command);
}

void WebGLContextProxy::vertex_attrib_i4iv(GLuint index, GLint const* v)
{
    ReadonlyBytes v_bytes { v, static_cast<size_t>(4 * sizeof(GLint)) };
    Commands::VertexAttribI4iv command { .index = index };
    command.v = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(v_bytes.size()) };
    record(command, v_bytes);
}

void WebGLContextProxy::vertex_attrib_i4ui(GLuint index, GLuint x, GLuint y, GLuint z, GLuint w)
{
    Commands::VertexAttribI4ui command { .index = index, .x = x, .y = y, .z = z, .w = w };
    record(command);
}

void WebGLContextProxy::vertex_attrib_i4uiv(GLuint index, GLuint const* v)
{
    ReadonlyBytes v_bytes { v, static_cast<size_t>(4 * sizeof(GLuint)) };
    Commands::VertexAttribI4uiv command { .index = index };
    command.v = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast<u32>(v_bytes.size()) };
    record(command, v_bytes);
}

void WebGLContextProxy::vertex_attrib_i_pointer(GLuint index, GLint size, GLenum type, GLsizei stride, void const* pointer)
{
    Commands::VertexAttribIPointer command { .index = index, .size = size, .type = type, .stride = stride, .pointer = static_cast<GLintptr>(reinterpret_cast<uintptr_t>(pointer)) };
    record(command);
}

void WebGLContextProxy::vertex_attrib_pointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, void const* pointer)
{
    Commands::VertexAttribPointer command { .index = index, .size = size, .type = type, .normalized = normalized, .stride = stride, .pointer = static_cast<GLintptr>(reinterpret_cast<uintptr_t>(pointer)) };
    record(command);
}

void WebGLContextProxy::viewport(GLint x, GLint y, GLsizei width, GLsizei height)
{
    Commands::Viewport command { .x = x, .y = y, .width = width, .height = height };
    record(command);
}

void WebGLContextProxy::wait_sync(GLsync sync, GLbitfield flags, GLuint64 timeout)
{
    Commands::WaitSync command { .sync = static_cast<WebGLObjectId>(reinterpret_cast<uintptr_t>(sync)), .flags = flags, .timeout = timeout };
    record(command);
}

}
