//! Run this with
//! ```sh
//! cargo run --example gl_raw_fns_generator >src/glitz/raw_functions.rs && rustfmt src/glitz/raw_functions.rs
//! ```

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct RequiredFnInfo {
  pub name: &'static str,
  pub args: &'static [(&'static str, &'static str)],
  pub ret: &'static str,
  pub safe: bool,
  pub alt_url: Option<&'static str>,
}
impl RequiredFnInfo {
  pub const DEFAULT: Self = RequiredFnInfo {
    name: "DEFAULT_NAME",
    args: &[("default_arg_name", "!")],
    ret: "()",
    safe: false,
    alt_url: None,
  };
}
impl Default for RequiredFnInfo {
  fn default() -> Self {
    RequiredFnInfo::DEFAULT
  }
}

pub const FUNCTIONS: &[RequiredFnInfo] = &[
  RequiredFnInfo {
    name: "glGetString",
    args: &[("name", "GLenum")],
    ret: "*const GLubyte",
    safe: true,
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glPixelStorei",
    args: &[("pname", "GLenum"), ("param", "GLint")],
    alt_url: Some("glPixelStore"),
    safe: true,
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glGetStringi",
    args: &[("name", "GLenum"), ("index", "GLuint")],
    ret: "*const GLubyte",
    safe: true,
    alt_url: Some("glGetString"),
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glActiveTexture",
    args: &[("texture", "GLenum")],
    safe: true,
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glClear",
    args: &[("mask", "GLbitfield")],
    safe: true,
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glClearColor",
    args: &[
      ("red", "GLfloat"),
      ("green", "GLfloat"),
      ("blue", "GLfloat"),
      ("alpha", "GLfloat"),
    ],
    safe: true,
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glViewport",
    args: &[
      ("x", "GLint"),
      ("y", "GLint"),
      ("width", "GLsizei"),
      ("height", "GLsizei"),
    ],
    safe: true,
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glGenBuffers",
    args: &[("n", "GLsizei"), ("buffers", "*mut GLuint")],
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glDeleteBuffers",
    args: &[("n", "GLsizei"), ("buffers", "*const GLuint")],
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glBindBuffer",
    args: &[("target", "GLenum"), ("buffer", "GLuint")],
    safe: true,
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glBufferData",
    args: &[
      ("target", "GLenum"),
      ("size", "GLsizeiptr"),
      ("data", "*const c_void"),
      ("usage", "GLenum"),
    ],
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glCreateShader",
    args: &[("shaderType", "GLenum")],
    ret: "GLuint",
    safe: true,
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glShaderSource",
    args: &[
      ("shader", "GLuint"),
      ("count", "GLsizei"),
      ("string", "*const *const GLchar"),
      ("length", "*const GLint"),
    ],
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glCompileShader",
    args: &[("shader", "GLuint")],
    safe: true,
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glGetShaderiv",
    args: &[
      ("shader", "GLuint"),
      ("pname", "GLenum"),
      ("params", "*mut GLint"),
    ],
    alt_url: Some("glGetShader"),
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glGetShaderInfoLog",
    args: &[
      ("shader", "GLuint"),
      ("maxLength", "GLsizei"),
      ("length", "*mut GLsizei"),
      ("infoLog", "*mut GLchar"),
    ],
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glCreateProgram",
    ret: "GLuint",
    args: &[],
    safe: true,
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glAttachShader",
    args: &[("program", "GLuint"), ("shader", "GLuint")],
    safe: true,
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glLinkProgram",
    args: &[("program", "GLuint")],
    safe: true,
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glGetProgramiv",
    args: &[
      ("program", "GLuint"),
      ("pname", "GLenum"),
      ("params", "*mut GLint"),
    ],
    alt_url: Some("glGetProgram"),
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glGetProgramInfoLog",
    args: &[
      ("program", "GLuint"),
      ("maxLength", "GLsizei"),
      ("length", "*mut GLsizei"),
      ("infoLog", "*mut GLchar"),
    ],
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glUseProgram",
    args: &[("program", "GLuint")],
    safe: true,
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glDeleteShader",
    args: &[("program", "GLuint")],
    safe: true,
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glDeleteProgram",
    args: &[("program", "GLuint")],
    safe: true,
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glVertexAttribPointer",
    args: &[
      ("index", "GLuint"),
      ("size", "GLint"),
      ("type_", "GLenum"),
      ("normalized", "GLboolean"),
      ("stride", "GLsizei"),
      ("pointer", "*const c_void"),
    ],
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glEnableVertexAttribArray",
    args: &[("index", "GLuint")],
    safe: true,
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glDisableVertexAttribArray",
    args: &[("index", "GLuint")],
    safe: true,
    alt_url: Some("glEnableVertexAttribArray"),
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glGenVertexArrays",
    args: &[("n", "GLsizei"), ("arrays", "*mut GLuint")],
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glDeleteVertexArrays",
    args: &[("n", "GLsizei"), ("arrays", "*const GLuint")],
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glBindVertexArray",
    args: &[("array", "GLuint")],
    safe: true,
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glDrawArrays",
    args: &[("mode", "GLenum"), ("first", "GLint"), ("count", "GLsizei")],
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glDrawElements",
    args: &[
      ("mode", "GLenum"),
      ("count", "GLsizei"),
      ("type_", "GLenum"),
      ("indices", "*const c_void"),
    ],
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glPolygonMode",
    args: &[("face", "GLenum"), ("mode", "GLenum")],
    safe: true,
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glGetIntegerv",
    args: &[("pname", "GLenum"), ("data", "*mut GLint")],
    alt_url: Some("glGet"),
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glGetUniformLocation",
    args: &[("program", "GLuint"), ("name", "*const GLchar")],
    ret: "GLint",
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glUniform3f",
    args: &[
      ("location", "GLint"),
      ("v0", "GLfloat"),
      ("v1", "GLfloat"),
      ("v2", "GLfloat"),
    ],
    safe: true,
    alt_url: Some("glUniform"),
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glUniform4f",
    args: &[
      ("location", "GLint"),
      ("v0", "GLfloat"),
      ("v1", "GLfloat"),
      ("v2", "GLfloat"),
      ("v3", "GLfloat"),
    ],
    safe: true,
    alt_url: Some("glUniform"),
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glUniform1i",
    args: &[("location", "GLint"), ("v0", "GLint")],
    safe: true,
    alt_url: Some("glUniform"),
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glUniform1f",
    args: &[("location", "GLint"), ("v0", "GLfloat")],
    safe: true,
    alt_url: Some("glUniform"),
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glTexParameteri",
    args: &[("target", "GLenum"), ("pname", "GLenum"), ("param", "GLint")],
    safe: true,
    alt_url: Some("glTexParameter"),
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glTexParameterfv",
    args: &[
      ("target", "GLenum"),
      ("pname", "GLenum"),
      ("params", "*const GLfloat"),
    ],
    alt_url: Some("glTexParameter"),
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glGenTextures",
    args: &[("n", "GLsizei"), ("textures", "*mut GLuint")],
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glDeleteTextures",
    args: &[("n", "GLsizei"), ("textures", "*const GLuint")],
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glBindTexture",
    args: &[("target", "GLenum"), ("texture", "GLuint")],
    safe: true,
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glTexImage2D",
    args: &[
      ("target", "GLenum"),
      ("level", "GLint"),
      ("internalformat", "GLint"),
      ("width", "GLsizei"),
      ("height", "GLsizei"),
      ("border", "GLint"),
      ("format", "GLenum"),
      ("type_", "GLenum"),
      ("data", "*const c_void"),
    ],
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glGenerateMipmap",
    args: &[("target", "GLenum")],
    safe: true,
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glUniformMatrix4fv",
    args: &[
      ("location", "GLint"),
      ("count", "GLsizei"),
      ("transpose", "GLboolean"),
      ("value", "*const [GLfloat; 16]"),
    ],
    alt_url: Some("glUniform"),
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glEnable",
    args: &[("cap", "GLenum")],
    safe: true,
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glDisable",
    args: &[("cap", "GLenum")],
    alt_url: Some("glEnable"),
    safe: true,
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glDepthMask",
    args: &[("flag", "GLboolean")],
    safe: true,
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glDepthFunc",
    args: &[("func", "GLenum")],
    safe: true,
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glStencilMask",
    args: &[("mask", "GLuint")],
    safe: true,
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glStencilFunc",
    args: &[("func", "GLenum"), ("ref_", "GLint"), ("mask", "GLuint")],
    safe: true,
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glStencilOp",
    args: &[("sfail", "GLenum"), ("dpfail", "GLenum"), ("dppass", "GLenum")],
    safe: true,
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glBlendFunc",
    args: &[("sfactor", "GLenum"), ("dfactor", "GLenum")],
    safe: true,
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glBlendFuncSeparate",
    args: &[
      ("srcRGB", "GLenum"),
      ("dstRGB", "GLenum"),
      ("srcAlpha", "GLenum"),
      ("dstAlpha", "GLenum"),
    ],
    safe: true,
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glCullFace",
    args: &[("mode", "GLenum")],
    safe: true,
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glFrontFace",
    args: &[("mode", "GLenum")],
    safe: true,
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glGenFramebuffers",
    args: &[("n", "GLsizei"), ("ids", "*mut GLuint")],
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glBindFramebuffer",
    args: &[("target", "GLenum"), ("framebuffer", "GLuint")],
    safe: true,
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glCheckFramebufferStatus",
    args: &[("target", "GLenum")],
    ret: "GLenum",
    safe: true,
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glDeleteFramebuffers",
    args: &[("n", "GLsizei"), ("framebuffers", "*mut GLuint")],
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glFramebufferTexture2D",
    args: &[
      ("target", "GLenum"),
      ("attachment", "GLenum"),
      ("textarget", "GLenum"),
      ("texture", "GLuint"),
      ("level", "GLint"),
    ],
    safe: true,
    alt_url: Some("glFramebufferTexture"),
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glGenRenderbuffers",
    args: &[("n", "GLsizei"), ("renderbuffers", "*mut GLuint")],
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glDeleteRenderbuffers",
    args: &[("n", "GLsizei"), ("renderbuffers", "*mut GLuint")],
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glBindRenderbuffer",
    args: &[("target", "GLenum"), ("renderbuffer", "GLuint")],
    safe: true,
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glRenderbufferStorage",
    args: &[
      ("target", "GLenum"),
      ("internalformat", "GLenum"),
      ("width", "GLsizei"),
      ("height", "GLsizei"),
    ],
    // safe: true, ??? probably is safe?
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glFramebufferRenderbuffer",
    args: &[
      ("target", "GLenum"),
      ("attachment", "GLenum"),
      ("renderbuffertarget", "GLenum"),
      ("renderbuffer", "GLuint"),
    ],
    safe: true,
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glBufferSubData",
    args: &[
      ("target", "GLenum"),
      ("offset", "GLintptr"),
      ("size", "GLsizeiptr"),
      ("data", "*const c_void"),
    ],
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glMapBuffer",
    args: &[("target", "GLenum"), ("access", "GLenum")],
    ret: "*mut c_void",
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glUnmapBuffer",
    args: &[("target", "GLenum")],
    ret: "GLboolean",
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glCopyBufferSubData",
    args: &[
      ("readTarget", "GLenum"),
      ("writeTarget", "GLenum"),
      ("readOffset", "GLintptr"),
      ("writeOffset", "GLintptr"),
      ("size", "GLsizeiptr"),
    ],
    // not safe! doesn't accept pointer args, but does memcpy stuff.
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glBindBufferBase",
    args: &[("target", "GLenum"), ("index", "GLuint"), ("buffer", "GLuint")],
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glBindBufferRange",
    args: &[
      ("target", "GLenum"),
      ("index", "GLuint"),
      ("buffer", "GLuint"),
      ("offset", "GLintptr"),
      ("size", "GLsizeiptr"),
    ],
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glUniformBlockBinding",
    args: &[
      ("program", "GLuint"),
      ("uniformBlockIndex", "GLuint"),
      ("uniformBlockBinding", "GLuint"),
    ],
    safe: true,
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glDrawArraysInstanced",
    args: &[
      ("mode", "GLenum"),
      ("first", "GLint"),
      ("count", "GLsizei"),
      ("instancecount", "GLsizei"),
    ],
    // not safe!
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glVertexAttribDivisor",
    args: &[("index", "GLuint"), ("divisor", "GLuint")],
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glDrawElementsInstanced",
    args: &[
      ("mode", "GLenum "),
      ("count", "GLsizei "),
      ("type_", "GLenum "),
      ("indices", "*const c_void"),
      ("instancecount", "GLsizei "),
    ],
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glTexImage2DMultisample",
    args: &[
      ("target", "GLenum"),
      ("samples", "GLsizei"),
      ("internalformat", "GLenum"),
      ("width", "GLsizei"),
      ("height", "GLsizei"),
      ("fixedsamplelocations", "GLboolean"),
    ],
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glRenderbufferStorageMultisample",
    args: &[
      ("target", "GLenum"),
      ("samples", "GLsizei"),
      ("internalformat", "GLenum"),
      ("width", "GLsizei"),
      ("height", "GLsizei"),
    ],
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glGetError",
    args: &[],
    ret: "GLenum",
    safe: true,
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glDebugMessageCallback",
    args: &[("callback", "DEBUGPROC"), ("userParam", "*const c_void")],
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glDebugMessageControl",
    args: &[
      ("source", "GLenum"),
      ("type_", "GLenum"),
      ("severity", "GLenum"),
      ("count", "GLsizei"),
      ("ids", "*const GLuint"),
      ("enabled", "GLboolean"),
    ],
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glDebugMessageInsert",
    args: &[
      ("source", "GLenum"),
      ("type_", "GLenum"),
      ("id", "GLuint"),
      ("severity", "GLenum"),
      ("length", "GLsizei"),
      ("message", "*const c_char"),
    ],
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glPatchParameteri",
    args: &[("pname", "GLenum"), ("value", "GLint")],
    alt_url: Some("glPatchParameter"),
    safe: true,
    ..RequiredFnInfo::DEFAULT
  },
  RequiredFnInfo {
    name: "glGetBufferParameteriv",
    args: &[("target", "GLenum"), ("value", "GLenum"), ("data", "*mut GLint")],
    alt_url: Some("glGetBufferParameter"),
    ..RequiredFnInfo::DEFAULT
  },
];

fn main() {
  println!("#![allow(unused_unsafe)]");
  println!("#![allow(non_snake_case)]");
  println!("#![allow(non_upper_case_globals)]");
  println!("#![allow(unsafe_op_in_unsafe_fn)]");
  println!("#![allow(clippy::too_many_arguments)]");
  println!("#![allow(clippy::unused_unit)]");
  // Write imports
  println!(
    "{}",
    "
    use super::*;
    use crate::Zstr;
    use core::{
      ffi::c_void,
      mem::transmute,
      ptr::{null, null_mut},
      sync::atomic::{AtomicPtr, Ordering},
    };"
  );
  println!();

  let mut functions: Vec<RequiredFnInfo> = FUNCTIONS.iter().copied().collect();
  functions.sort();

  // Write atomics and functions
  for f in functions.iter().copied() {
    let name = f.name;
    let url = f.alt_url.unwrap_or(f.name);
    let ret = f.ret;
    //
    println!(
      "static {name}_p: AtomicPtr<c_void> = AtomicPtr::new(null_mut());"
    );
    println!("/// Khronos: [{name}](https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/{url}.xhtml)");
    println!("#[inline(always)]");
    print!("pub ");
    if !f.safe {
      print!("unsafe ");
    }
    print!("fn {name}(");
    for (a, t) in f.args.iter().copied() {
      print!("{a}: {t}, ");
    }
    println!(") -> {ret} {{");
    println!("  let p: *mut c_void = {name}_p.load(Ordering::Relaxed);");
    // Note(Lokathor): Here we're printing out a guard for an early return when
    // the function pointer is missing. Precisely what we want to do depends on
    // the return type.
    if ret == "()" {
      println!("  if p.is_null() {{ return; }}");
    } else if ret == "GLuint" {
      println!("  if p.is_null() {{ return 0; }}");
    } else if ret == "*mut c_void" {
      println!("  if p.is_null() {{ return null_mut(); }}");
    } else if ret == "*const GLubyte" {
      println!("  if p.is_null() {{ return null(); }}");
    } else if ret == "GLboolean" {
      println!("  if p.is_null() {{ return 0; }}");
    } else if ret == "GLint" {
      if name == "glGetUniformLocation" {
        // This is the "uniform name not recognized" value.
        println!("  if p.is_null() {{ return -1; }}");
      } else {
        println!("  if p.is_null() {{ return 0; }}");
      }
    } else if ret == "GLenum" {
      if name == "glCheckFramebufferStatus" {
        println!("  if p.is_null() {{ return GL_FRAMEBUFFER_UNSUPPORTED; }}");
      } else if name == "glGetError" {
        println!("  if p.is_null() {{ return GL_INVALID_OPERATION; }}");
      } else {
        panic!("{name}");
      }
    } else {
      // If we're trying to return a data type we've never seen before that's
      // bad, we should panic the generator and reprogram it to understand the
      // new type.
      panic!("{ret}")
    }
    print!("  let f: ");
    if !f.safe {
      print!("unsafe ");
    }
    print!("extern \"system\" fn(");
    for (_, t) in f.args.iter().copied() {
      print!("{t},");
    }
    println!(")->{ret} = unsafe {{ transmute(p) }};", ret = f.ret);
    print!("  f(");
    for (a, _) in f.args.iter().copied() {
      print!("{a},");
    }
    println!(")");

    println!("}}");
    println!();
  }

  // Write loader
  println!("#[inline(never)]");
  println!("pub unsafe fn load_all_gl_functions_with(loader: &dyn Fn(Zstr)->*mut c_void) {{");
  for f in functions.iter().copied() {
    let name = f.name;
    //
    println!("  {name}_p.store(loader(zstr!(\"{name}\")), Ordering::Relaxed);");
  }
  println!("}}");
}
