
gfx = {}
local C = ffi.C
local function setEnum(t, name, prefix)
  return setmetatable(t, {
    __index = function(_, key)
      local ok, res = pcall(ffi.cast, name, string.format("%s_%s", prefix, key))
      if not ok then error(key .. "is not a valid enum", 2) end
      return tonumber(res)
    end,
  })
end
local windowW, windowH = ffi.new("int[1]"), ffi.new("int[1]")
local batchVerts = ffi.new("m_vert[10000]")
local shapeVerts = ffi.new("m_vert[10000]")
local batchIndex = ffi.new("u32[10000]")
local tempVerts = ffi.new("m_vert[16000]")
local vertMap = ffi.new("u32[32000]")
local screenMat = mat4():identity()
local identity = mat4():identity()
local t_vec = vec3()
local updatedBuffers = {}
local screenSize = {}
local matstack = {}
local batchVN = 0
local batchIN = 0
local mf = math.floor
local textPos = vec2(0, 0):cast()
local ffiStrings = {}
local str = ffi.new("char[?]", 8000)
local sze = ffi.new("float[1]", 10)
local spc = ffi.new("float[1]", 0)
local char = ffi.sizeof("char")
local textSize = ffi.new("Vector2")
local camFactor = 1
local camScale = vec3(camFactor, camFactor, camFactor)
local viewTransform = mat4()
local defaultTransform = mat4():scale(mat4(), camScale)
local modelTransform = mat4():scale(mat4(), camScale)
local origin = ffi.new("Vector2", 0, 0)
local srcR = ffi.new("Rectangle", 0, 0, 0, 0)
local dstR = ffi.new("Rectangle", 0, 0, 0, 0)
local campos = vec3()
local maxTextureSize = ffi.C.getMaxTextureSize()
local maxTexelCount = maxTextureSize*maxTextureSize
local temp_mvp = mat4()
local passTime = 0
local clear_pass = ffi.new("sg_pass_action")
local blank_action = ffi.new("sg_pass_action")
local clear_col = clear_pass.colors[0].clear_value
local tmpQuad = { x = 0, y = 0, w = 0, h = 0 }
local temp_mat = mat4()
local temp_vec = vec3()
local blankColor = ffi.new("Color", 0, 0, 0, 0)
local imgColor = ffi.new("Color", 0, 0, 0, 0)
local enums = {
  sg_constants = "SG",
  sg_load_action = "SG_LOADACTION",
  sg_blend_factor = "SG_BLENDFACTOR",
  sg_pixel_format = "SG_PIXELFORMAT",
  sg_filter = "SG_FILTER",
  sg_compare_func = "SG_COMPAREFUNC",
  sg_vertex_format = "SG_VERTEXFORMAT",
  sg_index_type = "SG_INDEXTYPE",
  sg_cull_mode = "SG_CULLMODE",
  sg_color_mask = "SG_COLORMASK",
  sg_primitive_type = "SG_PRIMITIVETYPE",
  sg_usage = "SG_USAGE",
}
local SG = setEnum({}, "sg_constants", "SG")
local SG_LOADACTION = setEnum({}, "sg_load_action", "SG_LOADACTION")
local SG_BLENDFACTOR = setEnum({}, "sg_blend_factor", "SG_BLENDFACTOR")
local SG_PIXELFORMAT = setEnum({}, "sg_pixel_format", "SG_PIXELFORMAT")
local SG_FILTER = setEnum({}, "sg_filter", "SG_FILTER")
local SG_COMPAREFUNC = setEnum({}, "sg_compare_func", "SG_COMPAREFUNC")
local SG_VERTEXFORMAT = setEnum({}, "sg_vertex_format", "SG_VERTEXFORMAT")
local SG_INDEXTYPE = setEnum({}, "sg_index_type", "SG_INDEXTYPE")
local SG_CULLMODE = setEnum({}, "sg_cull_mode", "SG_CULLMODE")
local SG_COLORMASK = setEnum({}, "sg_color_mask", "SG_COLORMASK")
local SG_PRIMITIVETYPE = setEnum({}, "sg_primitive_type", "SG_PRIMITIVETYPE")
local SG_USAGE = setEnum({}, "sg_usage", "SG_USAGE")
local cullModes = {
  front = SG_CULLMODE.FRONT,
  back = SG_CULLMODE.BACK,
  none = SG_CULLMODE.NONE,
}
local canvasFormats = {
  rgba8 = SG_PIXELFORMAT.RGBA8,
  raster = SG_PIXELFORMAT.RGBA8,
  r32f = SG_PIXELFORMAT.R32F,
  rgb565 = SG_PIXELFORMAT.RGBA8,
  rgba16f = SG_PIXELFORMAT.RGBA16F,
  rgba32f = SG_PIXELFORMAT.RGBA32F,
  depth = SG_PIXELFORMAT.DEPTH,
  depth8 = SG_PIXELFORMAT.DEPTH,
  depth16 = SG_PIXELFORMAT.DEPTH,
}
local canvasFormatSizes = {
  rgba8 = 4,
  raster = 4,
  r32f = 4,
  rgb565 = 4,
  rgba16f = 8,
  rgba32f = 16,
  depth = 1,
  depth8 = 1,
  depth16 = 2,
}
local fontIndex = {
  [fonts.inconsolata] = 1,
  [fonts.interfont] = 2,
}
local tempRange = ffi.new("sg_range")
local function SG_RANGE(ptr, size)
  tempRange.ptr = ptr
  tempRange.size = size
  return tempRange
end

for i,v in ipairs({
   "SG_PIXELFORMAT_NONE",
   "SG_PIXELFORMAT_R8",
   "SG_PIXELFORMAT_R8SN",
   "SG_PIXELFORMAT_R8UI",
   "SG_PIXELFORMAT_R8SI",
   "SG_PIXELFORMAT_R16",
   "SG_PIXELFORMAT_R16SN",
   "SG_PIXELFORMAT_R16UI",
   "SG_PIXELFORMAT_R16SI",
   "SG_PIXELFORMAT_R16F",
   "SG_PIXELFORMAT_RG8",
   "SG_PIXELFORMAT_RG8SN",
   "SG_PIXELFORMAT_RG8UI",
   "SG_PIXELFORMAT_RG8SI",
   "SG_PIXELFORMAT_R32UI",
   "SG_PIXELFORMAT_R32SI",
   "SG_PIXELFORMAT_R32F",
   "SG_PIXELFORMAT_RG16",
   "SG_PIXELFORMAT_RG16SN",
   "SG_PIXELFORMAT_RG16UI",
   "SG_PIXELFORMAT_RG16SI",
   "SG_PIXELFORMAT_RG16F",
   "SG_PIXELFORMAT_RGBA8",
   "SG_PIXELFORMAT_SRGB8A8",
   "SG_PIXELFORMAT_RGBA8SN",
   "SG_PIXELFORMAT_RGBA8UI",
   "SG_PIXELFORMAT_RGBA8SI",
   "SG_PIXELFORMAT_BGRA8",
   "SG_PIXELFORMAT_RGB10A2",
   "SG_PIXELFORMAT_RG11B10F",
   "SG_PIXELFORMAT_RG32UI",
   "SG_PIXELFORMAT_RG32SI",
   "SG_PIXELFORMAT_RG32F",
   "SG_PIXELFORMAT_RGBA16",
   "SG_PIXELFORMAT_RGBA16SN",
   "SG_PIXELFORMAT_RGBA16UI",
   "SG_PIXELFORMAT_RGBA16SI",
   "SG_PIXELFORMAT_RGBA16F",
   "SG_PIXELFORMAT_RGBA32UI",
   "SG_PIXELFORMAT_RGBA32SI",
   "SG_PIXELFORMAT_RGBA32F",
   "SG_PIXELFORMAT_DEPTH",
   "SG_PIXELFORMAT_DEPTH_STENCIL",
   "SG_PIXELFORMAT_BC1_RGBA",
   "SG_PIXELFORMAT_BC2_RGBA",
   "SG_PIXELFORMAT_BC3_RGBA",
   "SG_PIXELFORMAT_BC4_R",
   "SG_PIXELFORMAT_BC4_RSN",
   "SG_PIXELFORMAT_BC5_RG",
   "SG_PIXELFORMAT_BC5_RGSN",
   "SG_PIXELFORMAT_BC6H_RGBF",
   "SG_PIXELFORMAT_BC6H_RGBUF",
   "SG_PIXELFORMAT_BC7_RGBA",
   "SG_PIXELFORMAT_PVRTC_RGB_2BPP",
   "SG_PIXELFORMAT_PVRTC_RGB_4BPP",
   "SG_PIXELFORMAT_PVRTC_RGBA_2BPP",
   "SG_PIXELFORMAT_PVRTC_RGBA_4BPP",
   "SG_PIXELFORMAT_ETC2_RGB8",
   "SG_PIXELFORMAT_ETC2_RGB8A1",
   "SG_PIXELFORMAT_ETC2_RGBA8",
   "SG_PIXELFORMAT_ETC2_RG11",
   "SG_PIXELFORMAT_ETC2_RG11SN",
   "SG_PIXELFORMAT_RGB9E5",
})do
  if i>1 then 
--       local info = ffi.new("sg_pixelformat_info",ffi.C.sg_query_pixelformat(i))
-- print(i,v,
--   "sample",tostring(info.sample),
--   "filter",tostring(info.filter),
--     "render",tostring(info.render),
--     "blend" ,tostring(info.blend),
--     "msaa"  ,tostring(info.msaa),
--     "depth" ,tostring(info.depth)
--     )
  end 
end

zformat = {
  { "VertexPosition", "float", 3, SG_VERTEXFORMAT.FLOAT3 },
  { "VertexColor", "float",   4, SG_VERTEXFORMAT.FLOAT4 },
  { "VertexNormal", "float",   4, SG_VERTEXFORMAT.FLOAT4 },
  { "VertexTexCoord", "float", 2, SG_VERTEXFORMAT.FLOAT2 },
}

vformat = {
  { "VertexPosition", "float", 3, SG_VERTEXFORMAT.FLOAT3 },
  { "VertexColor", "byte", 4, SG_VERTEXFORMAT.UBYTE4 },
  { "VertexTexCoord", "byte", 4, SG_VERTEXFORMAT.BYTE4 },
  { "VertexNormal", "byte", 4, SG_VERTEXFORMAT.BYTE4 },
}

animatedVertexFormat = {
  { "VertexPosition", "float", 3 },
  { "VertexColor", "float", 4 },
  { "VertexTexCoord", "float", 2 },
  { "VertexNormal", "float", 3 },
  { "VertexBone", "byte", 4 },
  { "VertexWeight", "byte", 4 },
}

vertexFormats = {
  zformat = zformat,
  vformat = vformat,
  animatedVertexFormat = animatedVertexFormat,
}

for f, format in pairs(vertexFormats) do
  format.name = f
  format.stride = 0
  format.comp = 0
  for a, attr in ipairs(format) do
    if a>1 and attr[2]~=format[a-1][2] then 
      format.split = true
    end
    format.stride = format.stride + attr[3] * (attr[2] == "float" and 4 or 1)
    format.comp = format.comp + attr[3]
  end
end

clear_pass.colors[0].load_action = SG_LOADACTION.CLEAR
blank_action.colors[0].load_action = SG_LOADACTION.LOAD

gfx.defaultFilter = "linear"
gfx.meshCullMode = "none"
gfx.blendMode = "alpha"
gfx.alphaMode = "alphamultiply"
gfx.depthMode = "less"
gfx.depthWrite=false

gfx.numPassed = 0
gfx.textureMemoryUsage = 0
gfx.meshMemoryUsage = 0
gfx.meshCount = 0
gfx.renderTextureCount = 0
gfx.textureCount = 0
gfx.frameBufferCounter = 0
gfx.pushed = 0
gfx.currentFontSize = 12
gfx.lineWidth = 1
gfx.pip_count = 0
gfx.piplineSwitches = 0

gfx.transform = mat4()

gfx.stack = {}
gfx.textureSizes = {}
gfx.meshSizes = {}
gfx.tempDepthTextures = {}
gfx.scissor = {}
gfx.renderTextures = {}

gfx.depthFunc = {
  less = SG_COMPAREFUNC.LESS,
  lequal = SG_COMPAREFUNC.LESS_EQUAL,
  greater = SG_COMPAREFUNC.GREATER,
  gequal = SG_COMPAREFUNC.GREATER_EQUAL,
  equal = SG_COMPAREFUNC.EQUAL,
  always = SG_COMPAREFUNC.ALWAYS,
  never = SG_COMPAREFUNC.NEVER,
}

gfx.shaderList = {
  "defaultShader",

  -- "probeLights",

  "forwardRender",
  -- "forwardStatic",

  -- "shadowStatic",
  -- "shadowPreview",

  -- "deferredStatic",
  -- "deferredDynamic",
  -- "deferredLight",
  -- "deferredCombine",

  -- "voxelRender",
  -- "voxelAlpha",
  -- "voxelAverage",
  -- "voxelFlip",
  -- "voxelTrace",
  -- "voxelInject",
  -- "voxelLight",
  -- "voxelCombine",
  -- "voxelOcclusion",
  -- "voxelBasemip",
  -- "voxelMipmap",
  -- "voxelLightmip",
  -- "voxelNormalmip",

  -- "postProcess",
  -- "pathTrace",
}

function gfx.getSystemLimits()
  return {}
end

local function returnType(self)
  return self._type
end
local function getWidth(self)
  return self.width
end
local function getHeight(self)
  return self.height
end
local function getDimensions(self)
  return self.width, self.height
end
local function getFormat(self)
  return self.format
end
local function getMSAA(self)
  return self.msaa
end
local function getFilter(self)
  return self.filter
end
local function setFilter(self, filter, filteru)
  -- TODO texture filtering
  self.filter = filter
  local tex
  if self.isCanvas and self.texture then
    tex = self.texture.color
  else
    tex = self.texture
  end
end
local function releaseImageData(self)
  -- TODO free imagedata
  -- ffi.C.free(self.data)
end
local function deleteMesh(self)
  if self.vertexCount > 0 then
    print("DELETE MESH")
    self.vertexCount = 0
    ffi.C.free(self.vertices)
    print("free verts")
    if self.colors then
      print("free colors")
      ffi.C.free(self.colors)
    end
    if self.uvs then
      print("free uvs")
      ffi.C.free(self.uvs)
    end
    gfx.meshMemoryUsage = gfx.meshMemoryUsage - gfx.meshSizes[self.bind.vertex_buffers[0].id]
    gfx.meshCount = gfx.meshCount - 1
  end
end
local function releaseMesh(self)
  print("RELEASE MESH")
  ffi.C.sg_destroy_buffer(self.mesh.bind.vertex_buffers[0])
  ffi.C.sg_destroy_buffer(self.mesh.bind.vertex_buffers[1])
  ffi.C.sg_destroy_buffer(self.mesh.bind.vertex_buffers[2])
  ffi.C.sg_destroy_buffer(self.mesh.bind.index_buffer)
  deleteMesh(self.mesh)
end
local function tostringcanvas(self)
  return "Canvas"
end
local function setPixel(self, x, y, r, g, b, a)
  local ptr = ffi.cast("uint8_t*", self.data.data)
  local col = ffi.cast("unsigned char *", ptr)
  ptr = ptr + self.stride * (x + y * self.width)
  col[0], col[1], col[2], col[3] = r * 255, g * 255, b * 255, a * 255
end
local function getPixel(self, x, y)
  local ptr = ffi.cast("uint8_t*", self.data.data)
  ptr = ptr + ((self.width * y) + x) * self.stride
  local col = ffi.C.GetPixelColor(ptr, self.data.format)
  local r, g, b, a = col.r / 255, col.g / 255, col.b / 255, col.a / 255
  return r, g, b, a
end
local function mapPixel(self, func)
  for y = 0, self.height - 1, 1 do
    for x = 0, self.width - 1, 1 do
      local r, g, b, a = self:getPixel(x, y)
      self:setPixel(x, y, func(x, y, r, g, b, a))
    end
  end
end
local function encode(self, format, name)
  -- TODO image encoding
  print("encode")
  print_info(5)
  local channels = 4
  -- ffi.C.setCompressionPNG()
  if format == "png" then
    success = ffi.C.stbi_write_png(name, self.width, self.height, channels, ffi.cast("unsigned char *", self.data), self.width * channels)
  elseif format == "tga" then
    success = ffi.C.stbi_write_tga(name, self.width, self.height, channels, self.data)
  end

  print("encode ", name, success and "success" or "failure")
  return success
end
local function newImageDataFromImage(self)
  return gfx.newImageData(rl.GetTextureData(self.texture))
end
local function newImageDataFromCanvas(self)
  if gfx.canvas == self then error("cant make image data from canvas while its still active") end
  -- TODO glReadPixels
  -- return gfx.newImageData(rl.GetTextureData(self.texture.color))
  return gfx.newImageData(1, 1)
end

function gfx.maxTextureSize()
  return maxTextureSize
end
function gfx.maxTexelCount()
  return maxTexelCount
end

function gfx.newImageData(width, height)
  local print = nothing
  print("width (height)", width, height)
  local imageData = {}
  if height then
    print("blank")
    imageData.data = ffi.new("Image")
    imageData.data.data = ffi.cast("Color*", ffi.C.calloc(width * height, ffi.sizeof("Color")))
    imageData.data.width = width
    imageData.data.height = height
    imageData.data.format = canvasFormats["rgba8"]
    imageData.data.mipmaps = 1

  -- ffi.gc( imageData.data, ffi.C.free)
  elseif type(width) == "string" then
    local interface = _internal_interface_image_list[width] == true
    print("load image",width)
    if interface then
      print("new imagedate of interface")
      local info = ffi.C.getInterfaceImages()[width]
      imageData.data = ffi.C.imgFromMem(info.ptr, info.len)
      print("imageData.data.width", imageData.data.width)
    end
  else
    imageData.data = width
  end
  imageData.width = imageData.data.width
  imageData.height = imageData.data.height
  local imageSize = imageData.width * imageData.height * canvasFormatSizes.rgba8
  imageData.size = imageSize
  imageData.pixeltype = imageData.data.format == 7 and "char*" or "float*"
  imageData.stride = imageSize / (imageData.data.width * imageData.data.height)
  return setmetatable(imageData, imageDataMT)
end

-- select/generate correct pipeline for current (mesh/layout/shader/canvas/blend/cull) config
-- local desc_buf = ffi.new("sg_pipeline_desc[64]")
local function updatePipeline(mesh)
  ffi.C.gfx_updatePipeline(mesh.mesh)
end

local function replacePixelsImage(self, data)
  ffi.C.update_image(self.texture.color.img, data.data)
end

local function releaseTexture(texture)
  print("FREEEEEEEEEE TEX")
  if texture.img then
    print_info(3)
    ffi.C.sg_destroy_image(texture.img)
    gfx.textureMemoryUsage = gfx.textureMemoryUsage - gfx.textureSizes[texture.img.id]
    gfx.textureCount = gfx.textureCount - 1
  end
end

local function renderTo(self, func)
  local prev = gfx.getCanvas()
  gfx.setCanvas(self)
  func()
  gfx.setCanvas(prev)
end

local function replacePixelsCanvas(self, data)
  ffi.C.sg_destroy_image(self.texture.color.img)
  self.texture.color.img.id = ffi.C.texFromData(data.data).id
end

local function loadTexture(width, height, format, samples, mipmaps)
  local desc = ffi.new("sg_image_desc")
  -- .int num_mipmaps
  -- .sg_usage usage
  -- .int sample_count
  -- .sg_wrap wrap_u
  -- .sg_wrap wrap_v
  -- .sg_wrap wrap_w
  -- .sg_border_colo, border_color
  -- .uint32_t max_aisotropy
  -- .float min_lod
  -- .float max_lod
  -- .sg_image_data data
  desc.render_target = true
  desc.width = width
  desc.height = height
  desc.pixel_format = format
  desc.sample_count = samples
  -- desc.usage = SG_USAGE.STREAM
  desc.label = "image"
  return ffi.C.sg_make_image(desc)
end

local function releaseCanvas(self)
  if self.attachments~=gfx.attachments and self.texture and not self.released then
    -- print("free? ",target.id,gfx.renderTextures[target.id])
    ffi.C.sg_destroy_attachments(self.attachments)
    local target = self.texture
    if gfx.renderTextures[target.id] == true then
      if target.color.img.id > 0 then ffi.C.sg_destroy_image(target.color.img) end
      if target.depth.img.id > 0 then ffi.C.sg_destroy_image(target.depth.img) end
      if target.depth.img.id > 0 then ffi.C.sg_destroy_image(target.depth.img) end
      -- print("free render texture", target.id)
      --   target.texture.id,
      --   target.texture.width,
      --   target.texture.height,
      --   gfx.textureSizes[target.texture.id]
      -- )
      if gfx.textureSizes[target.color.img.id] then gfx.textureMemoryUsage = gfx.textureMemoryUsage - gfx.textureSizes[target.color.img.id] end
      gfx.renderTextureCount = gfx.renderTextureCount - 1
      gfx.renderTextures[target.id] = false
    end
    -- print("   freed? ",target.id,gfx.renderTextures[target.id])

    self.released = true
    if gfx.tempDepthTextures[self.texture.id] then gfx.tempDepthTextures[self.texture.id] = nil end
    lume.clear(self)
  end
end

local imageDataMT = {
  __index = {
    isImageData = true,
    _type = "ImageData",
    release = releaseImageData,
    type = returnType,
    getHeight = getHeight,
    getWidth = getWidth,
    encode = encode,
    getPixel = getPixel,
    setPixel = setPixel,
    mapPixel = mapPixel,
    -- Data:getString
    -- ImageData:getDimensions
    -- ImageData:getFormat
    -- ImageData:getString
    -- ImageData:paste
    -- Object:release
    -- Object:type
    -- Object:typeOf
  },
}

local imageMT = {
  __index = {
    isImage = true,
    _type = "Image",
    type = returnType,
    getFilter = getFilter,
    setFilter = setFilter,
    release = releaseTexture,
    newImageData = newImageDataFromImage,
    getWidth = getWidth,
    getHeight = getHeight,
    replacePixels = replacePixelsImage,
  },
}

local canvasMT = {
  __index = {
    isCanvas = true,
    type = returnType,
    newImageData = newImageDataFromCanvas,
    getFilter = getFilter,
    setFilter = setFilter,
    getFormat = getFormat,
    getMSAA = getMSAA,
    release = releaseTexture,
    getWidth = getWidth,
    getHeight = getHeight,
    getDimensions = getDimensions,
    renderTo = renderTo,
    _type = "Canvas",
    replacePixels = replacePixelsCanvas,
    release = releaseCanvas,
  },
  __tostring = tostringcanvas,
}

function gfx.newImage(file_or_data, absolute)
  local print = nothing
  local image = {
    filter = gfx.defaultFilter,
  }
  print("\ngfx.newimage")
  if type(file_or_data) == "string" then
    print(file_or_data)
    local texture
    if _internal_interface_image_list[file_or_data] then
      print("newimage internal")
      texture = ffi.C.texFromData(file_or_data)
      print(texture)
      print(texture.img.id)
      print(texture.width)
    else
      print("newimage external")
      local temp = lfs.getSaveDirectory() .. file_or_data
      local file = lfs.exists(file_or_data) and temp or file_or_data

      if absolute then
        if lfs.absExists(file_or_data) then
          file = file_or_data
        else
          return
        end
      end

      texture = ffi.C.texFromFile(file, false)

      if lfs.isFused() and (texture.img.id == 0 or not texture) then texture = ffi.C.texFromFile(file, false) end
    end

    image.texture = texture
    ffi.gc(image.texture, releaseTexture)
  else
    image.texture =
      -- ffi.gc(
      ffi.C.texFromData(file_or_data.data)
    -- ,releaseTexture)
    print("   image.texture.id", image.texture.img.id)
    print("   image.texture.id", image.texture.width)
  end

  image.width = image.texture.width
  image.height = image.texture.height

  local size = image.width * image.height * 4

  gfx.textureCount = gfx.textureCount + 1
  gfx.textureMemoryUsage = gfx.textureMemoryUsage + size
  gfx.textureSizes[image.texture.img.id] = size

  gfx.setFilter(image, image.filter)

  return setmetatable(image, imageMT)
end

function gfx.longline(line)
  local length = 100
  if #line > length then
    for i = 1, #line, length do
      local slice = lume.slice(line, i, math.min(i + length, #line))
      --if #slice%2==0 then
      gfx.line(slice)
      --end
    end
  else
    gfx.line(line)
  end
end

function gfx.setAlpha(a)
  local r, g, b = gfx.getColor()
  gfx.setColor(r, g, b, a)
end

function gfx.brightness(c, b, a)
  local b = b or 1
  local c = c or { 1, 1, 1, 1 }
  if b then
    if c.r then
      if a then
        return c.r * b, c.g * b, c.b * b, c.a or 1
      else
        return { r = c.r * b, g = c.g * b, b = c.b * b, a = c.a or 1 }
      end
    else
      return c[1] * b, c[2] * b, c[3] * b, c[4] or 1
    end
  else
    return c[1], c[2], c[3], c[4] or 1
  end
end

function gfx.newCanvas(width, height, format)
  local width, height = math.ceil(width), math.ceil(height)
  local msaa = 1--math.max(math.min(4, format and format.msaa or 0), 1)
  -- print("new canvas", width, height, msaa)

  local formatName = format and format.format or "rgba8"
  local formatIndex = canvasFormats[formatName]
  local canvas = {
    width = width,
    height = height,
    format = formatName,
    fmt = formatIndex,
    filter = "linear",
    msaa = msaa,
    size = 0,
  }
  local target = ffi.new("RenderTexture2D", 0)

  gfx.frameBufferCounter = gfx.frameBufferCounter + 1
  target.id = gfx.frameBufferCounter

  -- print("new render texture", target.id, width, height, msaa, formatName, formatIndex)
  gfx.renderTextures[target.id] = true
  local depthOnly = formatName == "depth" or formatName == "depth8" or formatName == "depth16"
  local size = 0
  if not depthOnly then
    -- print(". add color")
    if not formatIndex then error("no index for format " .. formatName) end
    target.color.img = loadTexture(width, height, formatIndex, msaa, 1)
    target.color.width = width
    target.color.height = height
    target.color.format = formatIndex
    target.color.mipmaps = 1
    size = size + width * height * canvasFormatSizes[formatName]
  end

  if true then
    -- print(". add depth")
    target.depth.img = loadTexture(width, height, SG_PIXELFORMAT.DEPTH, 0, 1)
    target.depth.width = width
    target.depth.height = height
    target.depth.format = SG_PIXELFORMAT.DEPTH
    target.depth.mipmaps = 1
    size = size + width * height * canvasFormatSizes.depth
  else 
    target.depth.format = SG_PIXELFORMAT.NONE
  end

  gfx.textureMemoryUsage = gfx.textureMemoryUsage + size
  gfx.textureSizes[depthOnly and target.depth.img.id or target.color.img.id] = size
  gfx.renderTextureCount = gfx.renderTextureCount + 1

  canvas.texture, canvas.size = target, size
  -- print("texloaded")

  print("canvas.texture.depth.img.id", canvas.texture.depth.img.id)

  if canvas.texture.color.img.id > 0 then
    local desc = ffi.new("sg_attachments_desc")
    print("color.img.id,depth.img.id",canvas.texture.color.img.id,canvas.texture.depth.img.id)
    desc.colors[0].image = canvas.texture.color.img
    if canvas.texture.depth.img.id > 0 then 
      desc.depth_stencil.image = canvas.texture.depth.img 
    end

    print("exhausted?")

    canvas.attachments = ffi.C.sg_make_attachments(desc)
    assert(canvas.attachments.id>0)
  end

  -- TODO setGC
  return setGC(canvas, releaseCanvas, canvasMT)
end

function gfx.setFilter(tex, filter)
  if tex then
    setFilter(tex, filter)
  else
    error("invalid texture")
  end
end

-- MESH
local function clone_function(fn)
  local dumped = string.dump(fn)
  local cloned = loadstring(dumped)
  local i = 1
  while true do
    local name = debug.getupvalue(fn, i)
    if not name then break end
    debug.upvaluejoin(cloned, i, fn, i)
    i = i + 1
  end
  return cloned
end

local function getTexture(self)
  return self.texture
end
local function setTexture(self, target)
  if not target then
    self.texture = nil
    return
  end
  self.texture = target
  local tex = target.isCanvas and target.texture.color or target.texture
  self.mesh.bind.fs.images[0] = tex.img
end
local function getVertexCount(self)
  return self.vertexCount
end
local function getVertex(self, i)
  -- local j = (i - 1) * 3
  -- local k = (i - 1) * 2
  -- local l = (i - 1) * 4
  -- return
  --   self.mesh.vertices[j],
  --   self.mesh.vertices[j + 1],
  --   self.mesh.vertices[j + 2],
  --   1,
  --   self.mesh.texcoords[k],
  --   self.mesh.texcoords[k + 1],
  --   self.mesh.colors[l],
  --   self.mesh.colors[l + 1],
  --   self.mesh.colors[l + 2],
  --   self.mesh.colors[l + 3],
  --   self.mesh.normals[j],
  --   self.mesh.normals[j + 1],
  --   self.mesh.normals[j + 2]

  local j = (i - 1) * 13
  return
    self.mesh.vertices[j + 0],
    self.mesh.vertices[j + 1],
    self.mesh.vertices[j + 2],
    1,
    self.mesh.vertices[j + 11],
    self.mesh.vertices[j + 12],
    self.mesh.vertices[j + 3],
    self.mesh.vertices[j + 4],
    self.mesh.vertices[j + 5],
    self.mesh.vertices[j + 6],
    self.mesh.vertices[j + 7],
    self.mesh.vertices[j + 8],
    self.mesh.vertices[j + 9]
end
local function valloc(typ, n, m)
  _VALLOCED = _VALLOCED + n * m * ffi.sizeof(typ)
  if n * m < 0 then error("invalid calloc size") end
  return ffi.cast(typ .. "*", ffi.C.calloc(n * m, ffi.sizeof(typ)))
end
local function setVertices(self, verts, start, num, map, indices, append)
  local fromT, fromC = type(verts) == "table", type(verts) == "cdata"

  if not (fromT or fromC) then error("Invalid vertices") end

  local numv = num  
  if fromT then
    numv = #verts
    local f, to
    for i = 1, #verts, 1 do
      f = verts[i]
      to = tempVerts[i]
      to.x, to.y, to.z, to.w, to.u, to.v, to.r, to.g, to.b, to.a =
        f[1], f[2], f[3], f[4], f[5] or 0.5, f[6] or 0.5, f[7] or 1, f[8] or 1, f[9] or 1, f[10] or 1
    end
    verts = tempVerts
  end

  local map = map
  local indices = indices or 0
  if self.mode == "strip" then
    for i = 0, numv-1, 1 do
      vertMap[i * 3] = i
      vertMap[i * 3 + 1] = i + 1
      vertMap[i * 3 + 2] = i + 2
    end
    map = vertMap
    indices = numv * 3
  elseif self.mode == "fan" then
    for i = 0, numv-1, 1 do
      vertMap[i * 3] = 0
      vertMap[i * 3 + 1] = i + 1
      vertMap[i * 3 + 2] = i + 2
    end
    map = vertMap
    indices = numv * 3
  end

  if map then
    if indices < 3 then error("BAD MAP " .. tostring(numv) .. " " .. tostring(indices)) end
    if indices / 3 > self.mesh.triangleCount then
      C.free(self.mesh.map)
      self.mesh.map = valloc("u32", self.mesh.vertexCount, 3)
    end
    ffi.copy(self.mesh.map, map, indices * ffi.sizeof("u32"))
  end

  local start = start and start - 1 or 0
  local num = math.min(self.mesh.vertexRange, numv)
  local last = start + num -1

  ffi.C.gfx_setVertices(
    ffi.cast("Mesh*",self.mesh),
    ffi.cast("void*",verts),

    start,
    last,

    ffi.istype("strkvec", verts[1]),
    append==true,
    
    false,
    updatedBuffers[self.id] == true
    )

  if append then
    -- print("append buffer")
    local offset = ffi.C.sg_append_buffer(self.mesh.bind.vertex_buffers[0], SG_RANGE(self.mesh.vertices, num * self.format.stride))
    self.mesh.bind.vertex_buffer_offsets[0] = offset
    if map then
      local ioffset = ffi.C.sg_append_buffer(self.mesh.bind.index_buffer, SG_RANGE(map, indices * ffi.sizeof("u32")))
      self.mesh.bind.index_buffer_offset = ioffset
    end
  else
    -- print("check ",self.id,updatedBuffers[self.id])
    if updatedBuffers[self.id] == true then
      print(ansicolors("%{redbg} buffer already updated once %{reset}"), self.id)
      print_info(3)
      return
    end
    if ffi.istype("strkvec", verts[1]) then
      -- print("update strkvsc")

      ffi.C.sg_update_buffer(self.mesh.bind.vertex_buffers[0], SG_RANGE(self.mesh.vertices, num * 3 * 4))

      ffi.C.sg_update_buffer(self.mesh.bind.vertex_buffers[1], SG_RANGE(self.mesh.colors, num * 4))

      ffi.C.sg_update_buffer(self.mesh.bind.vertex_buffers[2], SG_RANGE(self.mesh.uvs, num * 4))
    else
      -- print("update buffer",self.size,num*self.format.stride)
      -- print_info(3)
      ffi.C.sg_update_buffer(self.mesh.bind.vertex_buffers[0], SG_RANGE(self.mesh.vertices, num * self.format.stride))
    end
    if map then
      -- print("update map",indices)
      ffi.C.sg_update_buffer(self.mesh.bind.index_buffer, SG_RANGE(map, indices * ffi.sizeof("u32")))
    end
  end
  -- ffi.C.printf("buffer updated")
  -- print("buffer updated",self.id)
  updatedBuffers[self.id] = true
  return last
end

local function setVerticesMapped(self, verts, start, num, map, indices, append)
end

local function setDrawRange(self, first, last)
  local first = first - 1
  local count = last - first
  if self.mode ~= "triangles" then
    first = first
    count = (count - 3) * 3
  end
  self.mesh.vertexStart = first
  self.mesh.vertexRange = count
end
local function setVertexMap(self) end

gfx.setTexture = setTexture
gfx.getTexture = getTexture
local meshMT = {
  __index = {
    isMesh = true,
    _type = "Mesh",

    type = returnType,
    release = releaseMesh,

    getVertexCount = getVertexCount,
    getVertex = getVertex,
    getTexture = getTexture,

    setVertices = setVertices,
    setDrawRange = setDrawRange,
    setTexture = setTexture,
    setVertexMap = setVertexMap,
  },
}
function gfx.setup()
  gfx.state = ffi.C.gfx_setupState()

  print("\nGFX SETUP START",gfx.state.currentFontSize)

  gfx.setDefaultFilter("nearest", "nearest")
  -- gfx.window = engine.window.handle
  gfx.color = ffi.new("double[5]")
  gfx.identityMatrix = mat4.identity()
  gfx.batchMesh = gfx.newMesh(zformat, 10000, "triangles", "dynamic", true)

  gfx.screenPass = ffi.new("sg_pass")
  gfx.canvasPass = ffi.new("sg_pass")

  shaders = {}

  setmetatable(shaders, {
    __index = function(t, k)
      -- print("k",k,rawget(t,k))
      -- print_info(4)
      local v = rawget(t, k)
      if v then
        return v
      else
        return rawget(t, "defaultShader")
      end
    end,
  })

  for s, shader in ipairs(gfx.shaderList) do
    shaders[shader] = gfx.newShader(shader)
  end
  gfx.setShader()

  gfx.rectMesh = gfx.newMesh(zformat, {
    { 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 },
    { 1, 0, 0, 0, 1, 0, 1, 1, 1, 1 },
    { 1, 1, 0, 0, 1, 1, 1, 1, 1, 1 },

    { 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 },
    { 1, 1, 0, 0, 1, 1, 1, 1, 1, 1 },
    { 0, 1, 0, 0, 0, 1, 1, 1, 1, 1 },
  }, "triangles")
end
function gfx.newFrame()
  -- print("\nframe--------------------------------",engine_getTime())
  gfx.piplineSwitches = 0
  gfx.batchMesh.mesh.bind.vertex_buffer_offsets[0] = 0
  gfx.batchMesh.mesh.bind.index_buffer_offset = 0
  gfx.numPassed = 0
  gfx.state.inPass = false
  -- for k, v in pairs(updatedBuffers) do
  --   v = false
  --   print(k,v)
  -- end
  updatedBuffers = {}
end
function gfx.setPointSize(size) end
function gfx.setMeshCullMode(mode)
  if not cullModes[mode] then error("invalid cull mode") end
  gfx.meshCullMode = mode
  ffi.C.gfx_setMeshCullMode(cullModes[mode])
end
function gfx.newMesh(format, verts, mode, usage, indexed)
  -- print("NEW MESHHHMESHHHMESHHHMESHHHMESHHHMESHHHMESHHHMESHHH")
  -- print_info(4)
  local mode = mode or "triangles"
  local n = type(verts) == "table" and #verts or verts
  local indexed = indexed
  if mode ~= "triangles" then indexed = true end

  gfx.meshCount = gfx.meshCount + 1
  local id = gfx.meshCount

  -- print(ansicolors("%{greenbg} size="..d_size.." %{reset}"))
  -- print("comp",format.comp,"stride",format.stride)
  local sg_mesh = ffi.new("Mesh")
  local desc = ffi.new("sg_buffer_desc")
  local d_size = format.stride * n

  -- local smp = ffi.C.sg_make_sampler(ffi.new("sg_sampler_desc"))

  sg_mesh.vertexCount = n
  sg_mesh.triangleCount = mode == "triangles" and n / 3 or n - 2
  if format == vformat then
    sg_mesh.format = 1
    desc.size = 3 * 4 * n
    desc.usage = SG_USAGE.STREAM
    sg_mesh.components = 4
    sg_mesh.vertices = valloc("float", n, 4)
    sg_mesh.bind.vertex_buffers[0] = ffi.C.sg_make_buffer(desc)

    desc.size = 4 * n
    desc.usage = SG_USAGE.STREAM
    sg_mesh.colors = valloc("uint8_t", n, 4)
    sg_mesh.bind.vertex_buffers[1] = ffi.C.sg_make_buffer(desc)

    desc.size = 4 * n
    desc.usage = SG_USAGE.STREAM
    sg_mesh.uvs = valloc("uint8_t", n, 4)
    sg_mesh.bind.vertex_buffers[2] = ffi.C.sg_make_buffer(desc)

    desc.size = 4 * n
    desc.usage = SG_USAGE.STREAM
    -- sg_mesh.uvs = valloc("uint8_t", n, 4)
    sg_mesh.bind.vertex_buffers[3] = ffi.C.sg_make_buffer(desc)
    -- _VALLOCED = _VALLOCED + ((3 * 4 + 4 + 4) * n)
  else
    sg_mesh.format = 0
    desc.size = d_size
    desc.usage = SG_USAGE.STREAM
    sg_mesh.components = format.comp
    sg_mesh.vertices = valloc("float", n, format.comp)
    sg_mesh.bind.vertex_buffers[0] = ffi.C.sg_make_buffer(desc)
  end

  if indexed then
    -- print("new indexed")
    local d_size = ffi.sizeof("u32") * n * 3
    local desc = ffi.new("sg_buffer_desc")
    sg_mesh.map = valloc("u32", sg_mesh.vertexCount, 3)
    desc.size = d_size
    desc.type = 2 -- SG_BUFFERTYPE_INDEXBUFFER
    desc.usage = SG_USAGE.STREAM
    sg_mesh.bind.index_buffer = ffi.C.sg_make_buffer(desc)
    sg_mesh.indexed = true
  end

  local mesh = {
    id = sg_mesh.bind.vertex_buffers[0].id,
    mesh = sg_mesh,
    vertexStart = 0,
    vertexRange = mode == "triangles" and n or (n-2)*3,
    size = d_size,
    format = format,
    mode = mode,
    allocLocation = "Mesh allocated at " .. get_info(1, 1),
    vertexCount = n,
    mapping = {},
  }
  mesh.mesh.vertexRange = mode == "triangles" and n or (n-2)*3
  gfx.meshMemoryUsage = gfx.meshMemoryUsage + d_size
  gfx.meshSizes[mesh.id] = d_size
  ffi.gc(mesh.mesh, deleteMesh)

  if type(verts) ~= "number" then
    -- print("set verts from array")
    setVertices(mesh, verts)
  end

  -- print(mesh.allocLocation, sg_mesh.bind.vertex_buffers[0].id, "alloced")
  if sg_mesh.bind.vertex_buffers[0].id == 0 then error("invalid mesh buffer") end
  -- print("mesh made--------------------")

  return setmetatable(mesh, meshMT)
end
function gfx.quad(w, h)
  return gfx.newMesh(vformat, {
    { 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 },
    { w, 0, 0, 0, 1, 0, 1, 1, 1, 1 },
    { w, h, 0, 0, 1, 1, 1, 1, 1, 1 },

    { 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 },
    { w, h, 0, 0, 1, 1, 1, 1, 1, 1 },
    { 0, h, 0, 0, 0, 1, 1, 1, 1, 1 },
  })
end
function gfx.gradientMesh(dir, ...)
  -- Check for direction
  local meshData = {}
  local isHorizontal = true

  if dir == "vertical" then
    isHorizontal = false
  elseif dir ~= "horizontal" then
    error("bad argument #1 to 'gradient' (invalid value)", 2)
  end

  -- Check for colors
  local colorLen = select("#", ...)
  if colorLen < 2 then error("color list is less than two", 2) end


  -- Generate mesh
  if isHorizontal then
    for i = 1, colorLen do
      local color = select(i, ...)
      local x = (i - 1) / (colorLen - 1)

      meshData[#meshData + 1] = { x, 1, 0, 1, 0, 0, color[1] or 1, color[2] or 1, color[3] or 1, color[4] or 1 }
      meshData[#meshData + 1] = { x, 0, 0, 1, 0, 0, color[1] or 1, color[2] or 1, color[3] or 1, color[4] or 1 }
    end
  else
    for i = 1, colorLen do
      local color = select(i, ...)
      local y = (i - 1) / (colorLen - 1)

      meshData[#meshData + 1] = { 1, y, 0, 1, 0, 0, color[1] or 1, color[2] or 1, color[3] or 1, color[4] or 1 }
      meshData[#meshData + 1] = { 0, y, 0, 1, 0, 0, color[1] or 1, color[2] or 1, color[3] or 1, color[4] or 1 }
    end
  end

  return meshData
end
function gfx.setCamera(camera)
  gfx.camera = camera
end

-- DRAW
local mvps={}
local tmpMat = ffi.new("mat4")
local function drawMesh(mesh, mvp)

  setMat(mvp,gfx.shader.vs_params.mvp)

  for s, samp in ipairs(gfx.shader.samplers) do
    if samp then
      local tex = samp.isCanvas and samp.texture.color or samp.texture
      mesh.mesh.bind.fs.images[s].id = tex.img.id
    end
  end

  ffi.C.gfx_drawMesh(mesh.mesh,tmpMat)
 
end

function gfx.draw3D(mesh, transform)
  if not mesh then
    print((ansicolors("%{redbg} invalid mesh %{reset}")))
    return
  end
  gfx.drawBatch()
  if not mesh then error("nil drawable") end

  updatePipeline(mesh)

  mat4.mul(temp_mvp, gfx.camera.vp, transform or gfx.identityMatrix)

  gfx.shader:send("has_tex", mesh.texture and 1 or 0)
  gfx.shader:send("mainColor", gfx.color)

  drawMesh(mesh, temp_mvp)

end

function gfx.beginPass(final, pass_action)
  if gfx.finalPass then return end

  passTime = passTime or engine_getTime()
  gfx.numPassed = gfx.numPassed + 1
  if final  then
    if gfx.state.inPass then gfx.endPass() end
    gfx.finalPass = true
    gfx.canvas = nil
    clear_col.r, clear_col.g, clear_col.b, clear_col.a = 0, 0, 0, 1
    gfx.setColor(1, 1, 1, 1)
    gfx.shader:send("mainColor", gfx.color)
    gfx.setDepthMode("always", false)

    gfx.screenPass.action = clear_pass
    gfx.screenPass.swapchain = ffi.C.getSwapchain()
    ffi.C.sg_begin_pass(gfx.screenPass);

    gfx.attachments = clear_pass
    gfx.state.pipeNext.depth.pixel_format = SG_PIXELFORMAT.DEPTH_STENCIL
    gfx.state.pipeNext.colors[0].pixel_format = 0--SG_PIXELFORMAT.RGBA8
    gfx.state.pipeNext.sample_count = 0
    gfx.state.inPass = true
    local w, h = engine.window.size.width, engine.window.size.height
    updatePipeline(gfx.rectMesh)
    gfx.rectMesh:setTexture(engine.canvas)
    gfx.shader:send("has_tex", 1)
    temp_vec:set(w, h, 1)
    mat4.scale(temp_mat, gfx.identityMatrix, temp_vec)
    mat4.mul(temp_mat, gfx.getScreenMatrix(), temp_mat)
    drawMesh(gfx.rectMesh, temp_mat)
    -- print("FINAL PASSS")
  else
    -- print("OFFSCREEN PASSS")
    -- print_info(6)
    if gfx.state.inPass then error("in pass") end
    
    gfx.canvasPass.attachments = gfx.canvas.attachments
    gfx.canvasPass.action = pass_action or blank_action
    ffi.C.sg_begin_pass(gfx.canvasPass)

    gfx.attachments = gfx.canvas.attachments
    gfx.state.pipeNext.depth.pixel_format = gfx.canvas.texture.depth.format
    gfx.state.pipeNext.colors[0].pixel_format = gfx.canvas.texture.color.format
    gfx.state.pipeNext.sample_count = gfx.canvas.msaa
    gfx.state.inPass = true
  end
end
function gfx.endPass()
  if gfx.finalPass then 
    -- print("CANT END FINAL PASS")
    return 
  else 
    -- print("END OFF PASS")
  end
  gfx.finalPass = false
  ffi.C.sg_end_pass()
  gfx.state.inPass = false
  local timePassed = engine_getTime() - passTime
  -- if timePassed>0.1 then
  --   print("gfx.numPassed",gfx.numPassed,timePassed)
  --   error("longpass")
  -- end
  passTime = engine_getTime()
end
function gfx.draw(thing, x, y, a, sx, sy, ox, oy)
  gfx.drawBatch()
  local thing = thing
  local sx,sy = sx or 1, sy or 1
  if not thing then error("nil drawable") end

  if not gfx.getDepthMode() == "always" then gfx.setDepthMode("always", false) end

  gfx.push()
  if x then
    if ox then gfx.translate(-ox, -oy) end
    gfx.translate(x, y)
  end

  gfx.shader:send("mainColor", gfx.color)
  local w,h
  if thing.isMesh then
    updatePipeline(thing)
    gfx.shader:send("has_tex", thing.texture and 1 or 0)
    w, h = sx,sy

    --todo draw image
  elseif thing.isCanvas or thing.isImage then
    updatePipeline(gfx.rectMesh)
    w, h = thing.width*sx, thing.height*sy
    gfx.rectMesh:setTexture(thing)
    gfx.shader:send("has_tex", 1)
    thing = gfx.rectMesh
  end

  temp_vec:set(w,h, 1)
  mat4.scale(temp_mat, gfx.identityMatrix, temp_vec)
  if a then
    temp_vec:set(0, 0, 1)
    mat4.rotate(temp_mat, temp_mat, a or 0, temp_vec)
  end
  -- if ox then
  --   temp_vec:set(ox,oy,0)
  --   mat4.translate(temp_mat, temp_mat, temp_vec)
  -- end
  mat4.mul(temp_mat, gfx.transform, temp_mat)
    
  drawMesh(thing, temp_mat)

  gfx.pop()
end

local function getViewport(self)
  return self.x, self.y, self.w, self.h
end
local function getReferenceDimensions(self)
  return self.rw, self.rh
end
local quadMT = {
  __index = {
    isQuad = true,
    _type = "Quad",

    type = returnType,
    getViewport = getViewport,
    getTextureDimensions = getReferenceDimensions,
  },
}
function gfx.newQuad(x, y, w, h, rw, rh)
  quad = {
    x = x,
    y = y,
    w = w,
    h = h,
    rw = rw,
    rh = rh,
  }
  setmetatable(quad, quadMT)
  return quad
end

-- SHAPES
local batched = 0
local white = { 1, 1, 1, 1 }

local function draw_line(x1, y1, x2, y2)
  batchVN = batchVN + 1
  shapeVerts[batchVN].x, shapeVerts[batchVN].y = x1, y1
  batchVN = batchVN + 1
  shapeVerts[batchVN].x, shapeVerts[batchVN].y = x2, y2
end
local function draw_arc(x, y, r, a1, a2, segs, line)
  local step = ((a2 - a1) / segs)
  local w = gfx.lineWidth / 2
  for i = 0, segs, 1 do
    local a = a1 + step * i - pi / 2
    if line then
      local nx, ny = math.cos(a), math.sin(a)
      local ax, ay = x + nx * r, y + ny * r
      batchVN = batchVN + 1
      shapeVerts[batchVN].x, shapeVerts[batchVN].y = ax + nx * w, ay + ny * w
      batchVN = batchVN + 1
      shapeVerts[batchVN].x, shapeVerts[batchVN].y = ax - nx * w, ay - ny * w
    else
      batchVN = batchVN + 1
      shapeVerts[batchVN].x, shapeVerts[batchVN].y = x + math.cos(a) * r, y + math.sin(a) * r
    end
  end
  if line and a2 - a1 >= 2 * pi then
    local nx, ny = 0, -1
    local ax, ay = x + nx * r, y + ny * r
    batchVN = batchVN + 1
    shapeVerts[batchVN].x, shapeVerts[batchVN].y = ax + nx * w, ay + ny * w
    batchVN = batchVN + 1
    shapeVerts[batchVN].x, shapeVerts[batchVN].y = ax - nx * w, ay - ny * w
  end
end
local function finishShapeBatch(start, strip)
  local bv = batchVerts
  for i = start + 1, batchVN, 1 do
    bv[i].x, bv[i].y = mat4.mul_vec2(gfx.transform, shapeVerts[i].x, shapeVerts[i].y)
    bv[i].r, bv[i].g, bv[i].b, bv[i].a = gfx.color[1], gfx.color[2], gfx.color[3], gfx.color[4]
  end

  local count = (batchVN - start)

  -- print("count ",count)

  if count == 0 then error("bad batch") end

  if strip then
    for i = 0, count - 3, 1 do
      batchIndex[batchIN + i * 3] = start + i
      batchIndex[batchIN + i * 3 + 1] = start + i + 1
      batchIndex[batchIN + i * 3 + 2] = start + i + 2
    end
  else
    for i = 0, count - 3, 1 do
      batchIndex[batchIN + i * 3] = start
      batchIndex[batchIN + i * 3 + 1] = start + i + 1
      batchIndex[batchIN + i * 3 + 2] = start + i + 2
    end
  end
  batchIN = batchIN + 3 * (count - 2)
end

function gfx.drawBatch()
  if batchVN == 0 then return end
  -- print("DRAW BATCH:",batched,batchVN,batchIN,gfx.shader.name)
  -- bench.push("drawbatch")
  gfx.batchMesh:setDrawRange(1, batchIN)
  gfx.batchMesh:setVertices(batchVerts, 1, batchVN, batchIndex, batchIN, true)
  gfx.shader:send("has_tex", 0)
  gfx.shader:send("mainColor", white)
  if gfx.finalPass or gfx.canvas.texture then
    updatePipeline(gfx.batchMesh)
    drawMesh(gfx.batchMesh, gfx.identityMatrix)
  end
  batchVN = 0
  batchIN = 0
  batched = 0
  -- bench.pop("drawbatch")
end
function gfx.rectangle(style, x, y, w, h, rx)
  -- if ry then error("ry param deprecated") end
  -- TODO rectangle rounded + speed
  local r = rx and math.min(rx, w / 2, h / 2) or 0
  local start = batchVN
  local line = style == "line"
  local segs = mc(2, (rx or 0) / 2, 16)
  batched = batched + 1

  draw_arc(x + w - r, y + r, r, 0, pi / 2, segs, line)
  draw_arc(x + w - r, y + h - r, r, pi / 2, pi, segs, line)
  draw_arc(x + r, y + h - r, r, pi, pi + pi / 2, segs, line)
  draw_arc(x + r, y + r, r, pi + pi / 2, 2 * pi, segs, line)

  if line then
    batchVN = batchVN + 1
    shapeVerts[batchVN].x, shapeVerts[batchVN].y = shapeVerts[start + 1].x, shapeVerts[start + 1].y
    batchVN = batchVN + 1
    shapeVerts[batchVN].x, shapeVerts[batchVN].y = shapeVerts[start + 2].x, shapeVerts[start + 2].y
  end

  finishShapeBatch(start, line)
end
function gfx.circle(style, x, y, r)
  local start = batchVN
  if r < 1 then return end
  draw_arc(x, y, r, 0, 2 * pi, r + 4, style == "line")
  finishShapeBatch(start, style == "line")
end
function gfx.arc(style, mode, x, y, r, a1, a2, s)
  -- local start = batchVN
  -- draw_arc(x, y, r, a1, a2, s, style=="line")
  -- finishShapeBatch(start,style=="line")
end
function gfx.line(...)
  local sqrt = math.sqrt
  local sv = shapeVerts
  local start = batchVN
  local v = batchVN
  local w = gfx.lineWidth
  local line = { ... }

  line = #line == 1 and line[1] or line
  batched = batched + 1

  local left
  for i = 0, #line - 3, 2 do
    local ax, ay = line[i + 1], line[i + 2]
    local bx, by = line[i + 3], line[i + 4]
    local dx, dy = bx - ax, by - ay
    local len = sqrt(dx * dx + dy * dy)
    dx, dy = (dx / len) * w, (dy / len) * w
    local nx, ny = -dy, dx
    local bnx, bny
    if i > 0 then
      v = v + 1
      sv[v].x, sv[v].y = left and sv[v - 2].x or ax + nx, left and sv[v - 2].y or ay + ny
      v = v + 1
      sv[v].x, sv[v].y = left and ax - nx or sv[v - 2].x, left and ay - ny or sv[v - 2].y
    else
      v = v + 1
      sv[v].x, sv[v].y = ax + nx, ay + ny
      v = v + 1
      sv[v].x, sv[v].y = ax - nx, ay - ny
    end

    if i + 6 <= #line then
      local cx, cy = line[i + 5], line[i + 6]
      local dx, dy = cx - bx, cy - by
      local len = sqrt(dx * dx + dy * dy)
      dx, dy = -(dy / len) * w, (dx / len) * w
      left = ((bx - ax) * (cy - ay) - (by - ay) * (cx - ax)) > 0
      local x, y
      if left then
        x, y = intersect(ax + nx, ay + ny, bx + nx, by + ny, bx + dx, by + dy, cx + dx, cy + dy)
      else
        x, y = intersect(ax - nx, ay - ny, bx - nx, by - ny, bx - dx, by - dy, cx - dx, cy - dy)
      end
      if x then
        bnx, bny = x, y
      end
    end
    if bnx then
      v = v + 1
      sv[v].x, sv[v].y = left and bnx or bx + nx, left and bny or by + ny
      v = v + 1
      sv[v].x, sv[v].y = left and bx - nx or bnx, left and by - ny or bny
    else
      v = v + 1
      sv[v].x, sv[v].y = bx + nx, by + ny
      v = v + 1
      sv[v].x, sv[v].y = bx - nx, by - ny
    end
  end
  batchVN = v
  finishShapeBatch(start, true)
end
function gfx.polygon(style, ...) end
function gfx.getResized()
  return false --engine.window.resized
end

function gfx.getWidth()
  return engine.window.size.width
end
function gfx.getHeight()
  return engine.window.size.height
end
function gfx.isWireframe()
  return gfx.wireframe
end
function gfx.setWireframe(set)
  gfx.wireframe = set and true or false
  ffi.C.gfx_setWireframe(gfx.wireframe)
end

function gfx.translate(x, y, z)
  t_vec:set(x / (engine.window.size.width / engine.window.dpi_scale), -(y or 0) / (engine.window.size.height / engine.window.dpi_scale), z or 0)
  mat4.translate(gfx.transform, gfx.transform, t_vec)
  ffi.C.sgl_translate(x * engine.window.dpi_scale, (y or 0) * engine.window.dpi_scale, z or 0)
end
function gfx.rotate(angle, x, y, z)
  t_vec:set(0, 0, 1)
  mat4.rotate(gfx.transform, gfx.transform, angle, t_vec)
  ffi.C.sgl_rotate(angle, 0, 0, 1)
end
function gfx.scale(x, y, z)
  t_vec:set(
    x, --*(engine.window.size.width/engine.window.dpi_scale),
    y, --*(engine.window.size.height/engine.window.dpi_scale),
    z or 1
  )
  mat4.scale(gfx.transform, gfx.transform, t_vec)
  ffi.C.sgl_scale(x, y, z or 1)
end
function gfx.setTransform(t)
  if t then
    mat4.set(gfx.transform, t)
  else
    -- mat4.identity(gfx.transform)
    mat4.set(gfx.transform, gfx.getScreenMatrix())
  end
end
function gfx.getScreenMatrix()
  mat4.from_ortho(0, engine.window.size.width, 0, engine.window.size.height, -1, 1, screenMat)
  return screenMat
end

function gfx.getCanvas()
  return gfx.canvas
end
function gfx.setCanvas(canvases)
  gfx.drawBatch()

  local canvas = canvases
  if not gfx.finalPass and not canvases then 
    canvas = engine.canvas
    -- error("can't draw to window before final pass") 
  end

  if canvases and gfx.isCanvas(canvases[1]) then
    canvas = canvases[1]

    if canvases[2] then
      local bufferCount = #canvases

      for b, buffer in ipairs(canvases) do
        --TODO gbuffer
      end
      canvas.multiCanvased = true
    end

    if canvases.depth then
      local target = canvas.texture
      local width, height = target.color.width, target.color.height

      -- if not gfx.tempDepthTextures[target.id] then
      --   print("add temp depth", time)
      --   target.depth.img = loadTexture(width, height, SG_PIXELFORMAT.DEPTH, 0, 1)
      --   target.depth.width = width
      --   target.depth.height = height
      --   target.depth.format = SG_PIXELFORMAT.DEPTH
      --   target.depth.mipmaps = 1
      --   gfx.tempDepthTextures[target.id] = target.depth.id
      -- end
    end
  end

  if gfx.canvas and gfx.canvas.texture then
    if gfx.canvas.multiCanvased then gfx.canvas.multiCanvased = false end
  end

  if canvas ~= gfx.canvas then
    gfx.canvas = canvas
    if gfx.state.inPass then 
      gfx.endPass() 
    end
    gfx.beginPass()
  end
  gfx.setView(canvas:getWidth(), canvas:getHeight())
end
function gfx.setView(w, h)
  engine.window.fbo.width = w
  engine.window.fbo.height = h
  -- sgl_ortho(0.0f, w, 0.0f, h,-1.0f, +1.0f);
end

function gfx.clear(r, g, b, a, s, z)
  -- print("CLEAR",gfx.canvas and gfx.canvas.texture.color.id or "window")
  -- print_info(3)
  bench.push("gfx.clear")
  local cr, cg, cb, ca
  if type(r) == "table" then
    cr, cg, cb, ca = r[1] or 0, r[2] or 0, r[3] or 0, r[4] or 0
  else
    cr, cg, cb, ca = r or 0, g or 0, b or 0, a or 1
  end
  clear_col.r, clear_col.g, clear_col.b, clear_col.a = cr, cg, cb, ca
  if gfx.state.inPass then gfx.endPass() end
  gfx.beginPass(false, clear_pass)
  bench.pop("gfx.clear")
end
function gfx.copyCanvas(source, transforms)
  if not pcall(function()
    source:getWidth()
  end) then return end
  local w, h = source:getWidth(), source:getHeight()
  local tf = lume.merge({
    x = 0,
    y = 0,
    nw = w,
    nh = h,
    w = (transforms and transforms.scale or 1) * w,
    h = (transforms and transforms.scale or 1) * h,
    filter = "nearest",
    scale = 1,
  }, transforms or {})

  local dest = newCanvas(
    math.zero(transforms and (transforms.scale and transforms.scale * w or transforms.w) or tf.nw),
    math.zero(transforms and (transforms.scale and transforms.scale * h or transforms.h) or tf.nh),
    {
      format = source:getFormat(),
      msaa = source:getMSAA(),
    }
  )

  gfx.push("all")
  gfx.setCanvas(dest)
  gfx.setShader()
  gfx.clear(0, 0, 0, 0)
  gfx.setColor(1, 1, 1, 1)
  gfx.setBlendMode("replace")

  if tf.nx and tf.ny then
    gfx.draw(source, gfx.newQuad(tf.nx, tf.ny, tf.nw, tf.nh, w, h), tf.x, tf.y)
  else
    gfx.draw(source, math.zero(tf.x), math.zero(tf.y), 0)
  end
  gfx.pop()

  if transforms and transforms.release then
    -- source:getTexture()=nil
  end
  gfx.setFilter(dest, tf.filter)

  return dest
end
function gfx.clipCanvas(source, od, padding, indexed)
  local f = { h = source:getHeight(), w = source:getWidth() }
  local bounds = { t = 1, b = f.h, l = 1, r = f.w }
  local data = source:newImageData()
  local set = false
  local od = od or false
  local padding = padding or 0

  -- loop through pixels Y form top then from bottom
  -- loop through pixels X form left then from right
  for y = 0, f.h - 1 do
    for x = 1, f.w - 1 do
      local r, g, b, a = data:getPixel(x, y)
      local t = indexed and r or a
      if t ~= 0 then
        bounds.t = y
        set = true
        break
      end
    end
    if set then break end
  end
  set = false
  for x = 0, f.w - 1 do
    for y = 1, f.h - 1 do
      local r, g, b, a = data:getPixel(x, y)
      local t = indexed and r or a
      if t ~= 0 then
        bounds.l = x
        set = true
        break
      end
    end
    if set then break end
  end
  set = false
  for x = f.w - 1, 0, -1 do
    for y = 1, f.h - 1 do
      local r, g, b, a = data:getPixel(x, y)
      local t = indexed and r or a
      if t ~= 0 then
        bounds.r = x
        set = true
        break
      end
    end
    if set then break end
  end
  set = false
  for y = f.h - 1, 0, -1 do
    for x = 1, f.w - 1 do
      local r, g, b, a = data:getPixel(x, y)
      local t = indexed and r or a
      if t ~= 0 then
        bounds.b = y
        set = true
        break
      end
    end
    if set then break end
  end

  local offs = {}

  offs.xoff = ((f.w - bounds.r) - bounds.l) / 2
  offs.yoff = ((f.h - bounds.b) - bounds.t) / 2
  offs.pxoff = offs.xoff / f.w
  offs.pyoff = offs.yoff / f.h
  offs.wdiff = f.w / (bounds.r - bounds.l + 2)
  offs.hdiff = f.h / (bounds.b - bounds.t + 2)

  f.w = bounds.r - bounds.l + 2 + padding
  f.h = bounds.b - bounds.t + 2 + padding

  local clipped = newCanvas(f.w, f.h, source:getFormat())
  local oldCanvas = gfx.getCanvas()
  -- gfx.setFilter(clipped, animatePanel.filter)

  gfx.setShader()
  clipped:renderTo(function()
    gfx.setBlendMode("alpha", "premultiplied")
    gfx.setColor(1, 1, 1, 1)
    gfx.draw(source, 1 - bounds.l, 1 - bounds.t)
  end)
  log("booons", bounds.l)

  source = clipped

  if od then
    return clipped, offs, f, bounds
  else
    return clipped
  end
end
function gfx.dataToCanvas(data)
  local dataCanvas = newCanvas(data:getWidth(), data:getHeight())
  dataCanvas:replacePixels(data)
  return dataCanvas
end

function gfx.setDepthMode(mode, write)
  gfx.depthMode = mode or "always" 
  gfx.depthWrite = (write==true)
  gfx.state.pipeNext.depth.write_enabled = gfx.depthWrite
  gfx.state.pipeNext.depth.compare = gfx.depthFunc[gfx.depthMode]
end
function gfx.getDepthMode()
  return gfx.depthMode
end
function gfx.setDepthTest() end

function gfx.isCanvas(canvas)
  return (type(canvas) == "table" and canvas.isCanvas)
end
function gfx.isImage(image)
  return type(image) == "table" and image.isImage
end
function gfx.isMesh(mesh)
  return type(mesh) == "table" and mesh.isMesh
end
function gfx.isTexture(tex)
  return type(tex) == "table" and (tex.isCanvas or tex.isImage)
end

function gfx.setColor(r, g, b, a)
  if g then
    gfx.color[1], gfx.color[2], gfx.color[3], gfx.color[4] = r, g, b, a
  else
    gfx.color[1], gfx.color[2], gfx.color[3], gfx.color[4] = r[1], r[2], r[3], r[4]
  end
end
function gfx.getColor()
  return gfx.color[1], gfx.color[2], gfx.color[3], gfx.color[4]
end

function gfx.push(mode)
  -- local info = debug.getinfo(2, "Sl")
  -- print(string.rep(" ",gfx.pushed)..string.format("%s:%d",info.short_src, info.currentline))

  gfx.pushed = gfx.pushed + 1

  -- print("push "..gfx.pushed)
  if not gfx.stack[gfx.pushed] then gfx.stack[gfx.pushed] = {} end

  gfx.stack[gfx.pushed].mode = mode
  if mode == "all" then
    gfx.stack[gfx.pushed].wireframe = gfx.isWireframe()
    -- gfx.stack[gfx.pushed].compareMode, gfx.stack[gfx.pushed].write = gfx.getDepthTest()
    gfx.stack[gfx.pushed].blendMode, gfx.stack[gfx.pushed].alphaMode = gfx.getBlendMode()
    gfx.stack[gfx.pushed].canvas = gfx.getCanvas()
    gfx.stack[gfx.pushed].shader = gfx.getShader()

    gfx.stack[gfx.pushed].r, gfx.stack[gfx.pushed].g, gfx.stack[gfx.pushed].b, gfx.stack[gfx.pushed].a = gfx.getColor()
  end

  if not matstack[gfx.pushed] then matstack[gfx.pushed] = mat4() end
  ffi.C.sgl_push_matrix()
  gfx.stack[gfx.pushed].transform = matstack[gfx.pushed]
  mat4.set(gfx.stack[gfx.pushed].transform, gfx.transform)
end
function gfx.pop(...)
  -- if gfx.pushed>0 then
  if gfx.pushed == 0 then
    print()
    print(#gfx.stack, gfx.pushed)
    print_info(4)
    error()
    return
  end

  if gfx.stack[gfx.pushed].mode == "all" then
    -- gfx.setDepthTest(gfx.stack[gfx.pushed].compareMode, gfx.stack[gfx.pushed].write)
    if gfx.stack[gfx.pushed].canvas ~= gfx.canvas then 
      gfx.setCanvas(gfx.stack[gfx.pushed].canvas) 
    end
    if gfx.stack[gfx.pushed].shader ~= gfx.shader then 
      gfx.setShader(gfx.stack[gfx.pushed].shader) 
    end

    gfx.setWireframe(gfx.stack[gfx.pushed].wireframe)
    if gfx.stack[gfx.pushed].blendMode ~= gfx.blendMode or gfx.stack[gfx.pushed].alphaMode ~= gfx.alphaMode then
      gfx.setBlendMode(gfx.stack[gfx.pushed].blendMode, gfx.stack[gfx.pushed].alphaMode)
    end

    gfx.setColor(gfx.stack[gfx.pushed].r, gfx.stack[gfx.pushed].g, gfx.stack[gfx.pushed].b, gfx.stack[gfx.pushed].a)
  end
  mat4.set(gfx.transform, gfx.stack[gfx.pushed].transform)
  ffi.C.sgl_pop_matrix()
  -- print("pop "..gfx.pushed)
  lume.clear(gfx.stack[gfx.pushed])
  -- gfx.stack[gfx.pushed] = nil
  gfx.pushed = gfx.pushed - 1
  --    local info = debug.getinfo(2, "Sl")
  -- print(string.rep(".",gfx.pushed)..string.format("[%s]:%d",info.short_src, info.currentline))
  -- if game.state == "Edit" then error("d", 2) end
  -- end
end
function gfx.getStackDepth()
  return gfx.pushed
end

function gfx.setDefaultFilter(filterd, filteru)
  gfx.defaultFilter = filterd
end

local function sendUniform(self, name, value, third)
  local value, column = value, false
  if value == "column" then
    value = third
    column = true
  end
  -- print("set uniform:",name,value)
  for i = 1, 2, 1 do
    local params, params_t
    if i == 1 then
      params, params_t = self.vs_params, self.vs_params_t
    elseif i == 2 then
      params, params_t = self.fs_params, self.fs_params_t
    end
    local u_type = params and params_t and params_t[name]
    -- print(u_type)
    if u_type then
      if type(u_type) == "number" then
        -- u_type here is sampler index
        -- print("send tex", name, u_type, value)
        -- print_info(3)
        self.samplers[u_type] = value
      else
        local param = params[name]
        if u_type == "mat4" then
          setMat(value, param)
        elseif u_type == "vec4" then
          param.x, param.y, param.z, param.w = value[1], value[2], value[3], value[4]
        elseif u_type == "vec3" then
        elseif u_type == "vec2" then
        elseif u_type == "float" then
          params[name] = value
        elseif u_type == "int" then
          params[name] = value
        end
      end
    end
  end
end
-- SHADERs
local shaderMT = { __index = {
  send = sendUniform,
  isShader = true,
} }

function gfx.send(shader, name, value)
  shader:sendUniform(name, value)
end
function gfx.newShader(name)
  print("new shader", name)
  local shader
  if _shader_param_list[name] then -- frag only
    -- print(" frag shader")
    shader = {
      name = name,
      dirty = true,
      samplers = {},
      struct = engine.window.shaders[name],
      desc = engine.window.shaders[name].desc,
      program = engine.window.shaders[name].program,
      vs_params_t = _shader_param_list.defaultShader_vs,
      fs_params_t = _shader_param_list[name],
      vs_params = ffi.new("defaultShader_vs_params_t"),
    }
    shader.struct.vs_uniforms.ptr,shader.struct.vs_uniforms.size =  shader.vs_params, ffi.sizeof(shader.vs_params)
    if _shader_param_list[name] then 
      shader.fs_params = ffi.new(name .. "_params_t") 
      shader.struct.fs_uniforms.ptr,shader.struct.fs_uniforms.size =  shader.fs_params, ffi.sizeof(shader.fs_params)
    end
  elseif _shader_param_list[name .. "_vs"] or _shader_param_list[name .. "_fs"] then
    shader = {
      name = name,
      dirty = true,
      full = true,
      samplers = {},
      struct = engine.window.shaders[name],
      desc = engine.window.shaders[name].desc,
      program = engine.window.shaders[name].program,
      vs_params_t = _shader_param_list[name .. "_vs"],
      fs_params_t = _shader_param_list[name .. "_fs"],
    }
    -- print(" full shader", inspect(shader))
    if shader.vs_params_t then 
      shader.vs_params = ffi.new(name .. "_vs_params_t") 
     shader.struct.vs_uniforms.ptr,shader.struct.vs_uniforms.size =  shader.vs_params, ffi.sizeof(shader.vs_params)
    end
    if shader.fs_params_t then 
      shader.fs_params = ffi.new(name .. "_fs_params_t") 
      shader.struct.fs_uniforms.ptr,shader.struct.fs_uniforms.size =  shader.fs_params, ffi.sizeof(shader.fs_params)
    end

  else
    shader = {}
  end

  return setmetatable(shader, shaderMT)
end
function gfx.getShader()
  return gfx.shader
end
function gfx.setShader(shader)
  local shader = shader or shaders.defaultShader
  if gfx.shader and shader==gfx.shader then return end
  gfx.drawBatch()
  if not shader.program then shader = shaders.defaultShader end
  gfx.shader = shader
  gfx.state.shader = engine.window.shaders[shader.name]
  gfx.state.pipeNext.shader.id = shader.program.id
end

-- ETC
local function getTextWidth(self, text)
  return gfx.currentFontSize*1.1+1
end

local function getFontHeight(self)
  return gfx.currentFontSize
end

function gfx.setFont(font, size)
  local font = font or gfx.currentFont
  local size = math.floor(size or gfx.currentFontSize)

  ffi.C.setFontFace(engine.window.thread, fontIndex[font])
  ffi.C.setFontSize(engine.window.thread, size)

  gfx.currentFont = font
  gfx.currentFontSize = size
  font.getWidth = getTextWidth
  font.getHeight = getFontHeight
end

function gfx.getFont()
  return gfx.currentFont.sizes[gfx.currentFontSize]
end

function gfx.fontSize(size)
  if gfx.currentFontSize ~= size then
    gfx.currentFontSize = size
    gfx.setFont(gfx.currentFont, size)
  end
end

function gfx.print(text, x, y, angle, sx, sy)
  local text = tostring(text)
  textPos.x, textPos.y = mf(x or 0), mf(y or 0)
  ffi.fill(str, #text * char)
  ffi_copy(str, text)
  -- sze[0]=gfx.currentFontSize
  -- rl.DrawTextEx(gfx.currentFont.sizes[gfx.currentFontSize],text,textPos,sze[0],spc[0],gfx.color)
  -- fonsSetColor(fs, white);
  -- fonsSetAlign(fs, FONS_ALIGN_LEFT | FONS_ALIGN_BASELINE);
  ffi.C.drawText(engine.window.thread, x, y, text, 0, gfx.color)
end

function gfx.printf(text, x, y, w, align)
  local text = tostring(text)
  textPos.x, textPos.y = mf(x or 0), mf(y or 0)
  ffi.fill(str, #text * char)
  ffi_copy(str, text)
  sze[0] = gfx.currentFontSize
  local font = gfx.currentFont.sizes[gfx.currentFontSize]
  -- fonsSetAlign(fs, FONS_ALIGN_LEFT | FONS_ALIGN_BASELINE);
  -- fonsVertMetrics(fs, NULL, NULL, &lh);
  -- fonsSetColor(fs, white);
  local center = align == "center"
  ffi.C.drawText(engine.window.thread, x + (center and w / 2 or 0), y, text, center and 1 or 0, gfx.color)
end

function gfx.setLineJoin(style) end

function gfx.setLineWidth(width)
  -- gl.Enable(gle.LINE_SMOOTH)
  -- gl.LineWidth(ffi.new("GLfloat",10))
  gfx.lineWidth = width
end

function gfx.setLineStyle(style) end

function gfx.setScissor(x, y, width, height)
  gfx.drawBatch()
  local sx, sy, sw, sh
  if x then
    sx, sy, sw, sh = x, y, width, height
  else
    local c = gfx.getCanvas()
    if c then
      sx, sy, sw, sh = 0, 0, c:getWidth(), c:getHeight()
    else
      sx, sy, sw, sh = 0, 0, gfx.getWidth(), gfx.getHeight()
    end
  end
  sx, sy, sw, sh = sx * engine.window.dpi_scale, sy * engine.window.dpi_scale, sw * engine.window.dpi_scale, sh * engine.window.dpi_scale

  gfx.scissor.x, gfx.scissor.y, gfx.scissor.width, gfx.scissor.height = sx, sy, sw, sh
  ffi.C.sg_apply_scissor_rectf(sx, sy, sw, sh, true)
  ffi.C.sgl_scissor_rectf(sx, sy, sw, sh, true)
end

function gfx.getScissor()
  return gfx.scissor.x, gfx.scissor.y, gfx.scissor.width, gfx.scissor.height
end

function gfx.reset()
  ffi.C.reset_sgl(engine.window)
  gfx.setTransform()
  gfx.setShader()
  gfx.setColor(1, 1, 1, 1)
end

function gfx.setBlendMode(mode1, mode2)
  gfx.blendMode, gfx.alphaMode = mode1, mode2
end

function gfx.getBlendMode()
  return gfx.blendMode, gfx.alphaMode
end

local function queue(v2, v3, v4, v5, v6, v7, v8, v9, v10)
  
end
local graphics

gfx_copy = gfx
local gfx_post = setmetatable({}, {
  __index = function(mytable, key)
    local value = rawget(graphics, key)
    if type(value) == "function" then
      return queue
    else
      return value
    end
  end,
})
local postingDraws

function isPosting()
  return postingDraws
end

function newCanvas(w, h, s)
  if w < 1 or h < 1 then error("invalid canvas size") end
  -- print("new canvas")
  -- print_info(3)
  if s == "vector" then return end
  local tex, canv

  if type(s) == "string" then s = { format = s } end

  canv = gfx.newCanvas(w, h, s)

  -- table.insert(_canvases,canv)
  -- print("          ",canv)
  local sizes = { normal = 32, rgb565 = 16, rgba16f = 64, depth16 = 16 }
  local fmt = s and s.format or "rgba8"
  local size = sizes[fmt] or 32
  -- print("px",w,h,fmt,(w*h*size)/(1024^2).."mb")
  engine.texmem = engine.texmem + (w * h * size) / (1024 ^ 2)
  return canv
end
