Fixed gl crash with bad texture.

Fixed gl crash with bad texture.

Strict

Rem
	bbdoc: OpenGL 1.1 driver for MaxB3D
End Rem
Module MaxB3D.GLDriver
ModuleInfo "Author: Kevin Primm"
ModuleInfo "License: MIT"

Import MaxB3D.Core
Import GFX.GLMax2DEx

Private
Function ModuleLog(message$)
	TMaxB3DLogger.Write "gldriver",message
End Function

Public

Global GL_LIGHT[]=[GL_LIGHT0,GL_LIGHT1,GL_LIGHT2,GL_LIGHT3,GL_LIGHT4,GL_LIGHT5,GL_LIGHT6,GL_LIGHT7]

Type TGLMaxB3DDriver Extends TMaxB3DDriver
	Field _currentdata:TMaxB3DShaderData= New TMaxB3DShaderData

	Method SetGraphics(g:TGraphics)
		Super.SetGraphics g
		If g<>Null Startup
	End Method

	Function BindTexture(index,tex)
		If index=-1
			For Local i=0 To 7
				BindTexture i,tex
			Next
			Return
		EndIf
		Global currenttexture[] = [-1,-1,-1,-1,-1,-1,-1,-1]
		If tex<>currenttexture[index]
			glBindTexture GL_TEXTURE_2D,tex
			currenttexture[index]=tex
		EndIf
	End Function

	Method Startup()
		Global _firsttime=True
		If _firsttime
			Local caps:TGLCaps=TGLCaps(_caps)
			ModuleLog "Initializing GL driver"
			ModuleLog "Vendor:   "+String.FromCString(Byte Ptr(glGetString(GL_VENDOR)))
			ModuleLog "Renderer: "+String.FromCString(Byte Ptr(glGetString(GL_RENDERER)))
			ModuleLog "Version:  "+String.FromCString(Byte Ptr(glGetString(GL_VERSION)))
			ModuleLog "Extensions supported: "+" ".Join(caps.Extensions)
			EndMax2D
			_firsttime=False
		EndIf
		EnableStates
	End Method

	Function EnableStates()
		glEnable GL_LIGHTING
		glEnable GL_DEPTH_TEST
		glEnable GL_CULL_FACE
		glEnable GL_SCISSOR_TEST

		glEnable GL_NORMALIZE

		glEnableClientState GL_VERTEX_ARRAY
		glEnableClientState GL_COLOR_ARRAY
		glEnableClientState GL_NORMAL_ARRAY

		glFrontFace GL_CW

		glLightModeli GL_LIGHT_MODEL_COLOR_CONTROL,GL_SEPARATE_SPECULAR_COLOR
		glLightModeli GL_LIGHT_MODEL_LOCAL_VIEWER,GL_TRUE

		glClearDepth 1.0
		glDepthFunc GL_LEQUAL
		glHint GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST

		glAlphaFunc GL_GEQUAL,0.5

		BindTexture -1,0
	End Function

	Method MakeBuffer:TBuffer(src:Object,width,height,flags)
		Local buffer:TBuffer
		Select True
		Case TTextureFrame(src)<>Null
			buffer=TGLBuffer(TTextureFrame(src)._buffer)
			If buffer=Null
				Local res:TGLTextureRes=UpdateTextureRes(TTextureFrame(src),0)
				buffer=TGLMax2DExDriver(_parent).MakeGLBuffer(res._id,width,height,flags)
			EndIf
		Default
			buffer=TMax2DExDriver(_parent).MakeBuffer(src,width,height,flags)
		End Select
		Return buffer
	End Method

	Method Abbr$()
		Return "gl"
	End Method

	Method GetCaps:TCaps()
		Local caps:TGLCaps=New TGLCaps
		caps.Extensions=String.FromCString(glGetString(GL_EXTENSIONS)).Split(" ")
		If caps.HasExtension("GL_ARB_point_sprite")<>-1
			caps.PointSprites=True
			glGetFloatv GL_POINT_SIZE_MAX_ARB, Varptr caps.MaxPointSize
		EndIf
		Return caps
	End Method

	Method SetMax2D(enable)
		If enable
			glPopClientAttrib
			glPopAttrib
			If _shaderdriver _shaderdriver.Use(Null,Null)
			TGLMax2DExDriver(_parent).ResetGLContext _current
			glMatrixMode GL_TEXTURE
			glLoadIdentity
			glMatrixMode GL_COLOR
			glPopMatrix
		Else
			glPushAttrib GL_ALL_ATTRIB_BITS
			glPushClientAttrib GL_CLIENT_ALL_ATTRIB_BITS
			glMatrixMode GL_TEXTURE
			glPushMatrix
			glMatrixMode GL_COLOR
			glPushMatrix

			EnableStates()
		EndIf
	End Method
	Method SetCamera(camera:TCamera, config:TWorldConfig)
		If config.Dither
			glEnable GL_DITHER
		Else
			glDisable GL_DITHER
		EndIf

		Local vy#=config.Height-camera._viewheight-camera._viewy
		glViewport(camera._viewx,vy,camera._viewwidth,camera._viewheight)
		glScissor(camera._viewx,vy,camera._viewwidth,camera._viewheight)
		glClearColor(camera._brush._r,camera._brush._g,camera._brush._b,1.0)

		If camera._clsmode&CLSMODE_COLOR And camera._clsmode&CLSMODE_DEPTH
			glDepthMask GL_TRUE
			glClear GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT
		Else
			If camera._clsmode&CLSMODE_COLOR
				glClear GL_COLOR_BUFFER_BIT
			Else
				If camera._clsmode&CLSMODE_DEPTH
					glDepthMask GL_TRUE
					glClear GL_DEPTH_BUFFER_BIT
				EndIf
			EndIf
		EndIf

		If camera._fogmode<>FOGMODE_NONE
			glEnable GL_FOG
			glFogi GL_FOG_MODE,GL_LINEAR
			glFogf GL_FOG_START,camera._fognear
			glFogf GL_FOG_END,camera._fogfar
			Local rgb#[]=[camera._fogr,camera._fogg,camera._fogb]
			glFogfv GL_FOG_COLOR,rgb
		Else
			glDisable GL_FOG
		EndIf

		camera.UpdateMatrices()

		glMatrixMode GL_PROJECTION
		glLoadMatrixf camera._projection.ToPtr()
		glMatrixMode GL_MODELVIEW
		glLoadMatrixf camera._modelview.ToPtr()

		' Temporary hack
		'glGetFloatv GL_PROJECTION_MATRIX,camera._projection._m
		'camera._frustum=TFrustum.Extract(camera._modelview,camera._projection)
	End Method

	Method SetLight(light:TLight,index)
		If light=Null
			glDisable GL_LIGHT[index]
			Return
		Else
			If light._hidden=True
				glDisable GL_LIGHT[index]
				Return False
			EndIf
		EndIf

		glEnable GL_LIGHT[index]

		glPushMatrix
		glMultMatrixf light.GetMatrix(True).ToPtr()

		Local white_light#[]=[1.0,1.0,1.0,1.0]
		glLightfv GL_LIGHT[index],GL_SPECULAR,white_light

		Local z#=1.0
		Local w#=0.0
		If light._mode>LIGHT_DIRECTIONAL
			z=0.0
			w=1.0
		EndIf

		Local rgba#[]=[light._brush._r,light._brush._g,light._brush._b,1.0]
		Local pos#[]=[0.0,0.0,-z,w]

		glLightfv GL_LIGHT[index],GL_POSITION,pos
		glLightfv GL_LIGHT[index],GL_DIFFUSE,rgba

		If light._mode<>LIGHT_DIRECTIONAL
			Local light_range#[]=[0.0]
			Local range#[]=[light._range]
			glLightfv GL_LIGHT[index],GL_CONSTANT_ATTENUATION,light_range
			glLightfv GL_LIGHT[index],GL_LINEAR_ATTENUATION,range
		EndIf

		If light._mode=LIGHT_SPOT
			Local dir#[]=[0.0,0.0,-1.0]
			Local outer#[]=[light._outer/2.0]
			Local exponent#[]=[10.0]
			glLightfv GL_LIGHT[index],GL_SPOT_DIRECTION,dir
			glLightfv GL_LIGHT[index],GL_SPOT_CUTOFF,outer
			glLightfv GL_LIGHT[index],GL_SPOT_EXPONENT,exponent
		EndIf

		glPopMatrix
	End Method

	Method SetBrush(brush:TBrush,hasalpha,config:TWorldConfig)
		Local alpha_test

		Local ambient#[]=[config.AmbientRed/255.0,config.AmbientGreen/255.0,config.AmbientBlue/255.0]

		If hasalpha
			glEnable GL_BLEND
			glDepthMask GL_FALSE
		Else
			glDisable GL_BLEND
			glDepthMask GL_TRUE
		EndIf

		Select brush._blend
			Case BLEND_NONE,BLEND_ALPHA
				glBlendFunc GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA
			Case BLEND_MULTIPLY
				glBlendFunc GL_DST_COLOR,GL_ZERO
			Case BLEND_ADD
				glBlendFunc GL_SRC_ALPHA,GL_ONE
		End Select

		If brush._fx&FX_FULLBRIGHT
			ambient[0]=1.0
			ambient[1]=1.0
			ambient[2]=1.0
		EndIf

		If brush._fx&FX_VERTEXCOLOR
			glEnable GL_COLOR_MATERIAL
		Else
			glDisable GL_COLOR_MATERIAL
		EndIf

		If brush._fx&FX_FLATSHADED
			glShadeModel GL_FLAT
		Else
			glShadeModel GL_SMOOTH
		EndIf

		If brush._fx&FX_NOFOG
			glDisable GL_FOG
		EndIf

		If brush._fx&FX_NOCULLING
			glDisable GL_CULL_FACE
		Else
			glEnable GL_CULL_FACE
		EndIf

		If brush._fx&FX_WIREFRAME Or config.Wireframe
			glPolygonMode GL_FRONT_AND_BACK,GL_LINE
		Else
			glPolygonMode GL_FRONT_AND_BACK,GL_FILL
		EndIf

		Local no_mat#[]=[0.0,0.0]
		Local mat_ambient#[]=[brush._r,brush._g,brush._b,brush._a]
		Local mat_diffuse#[]=[brush._r,brush._g,brush._b,brush._a]
		Local mat_specular#[]=[brush._shine,brush._shine,brush._shine,brush._shine]
		Local mat_shininess#[]=[100.0]

		glMaterialfv GL_FRONT,GL_AMBIENT,mat_ambient
		glMaterialfv GL_FRONT,GL_DIFFUSE,mat_diffuse
		glMaterialfv GL_FRONT,GL_SPECULAR,mat_specular
		glMaterialfv GL_FRONT,GL_SHININESS,mat_shininess
		glLightModelfv GL_LIGHT_MODEL_AMBIENT,ambient

		For Local i=0 To 7
			glActiveTextureARB GL_TEXTURE0+i

			Local texture:TTexture=brush._texture[i]
			If texture=Null Or texture._blend=BLEND_NONE
				glDisable GL_TEXTURE_2D
				BindTexture i,0
				Continue
			EndIf

			Local texres:TGLTextureRes=TGLTextureRes(UpdateTextureRes(texture._frame[brush._textureframe[i]],texture._flags))

			glEnable GL_TEXTURE_2D

			BindTexture i,texres._id

			glMatrixMode GL_TEXTURE
			glLoadIdentity
			glTranslatef texture._px,-texture._py,0
			glScalef -texture._sx,texture._sy,1

			alpha_test :| texture._flags&TEXTURE_ALPHA Or texture._flags&TEXTURE_MASKED

			If texture._flags&TEXTURE_MIPMAP
				glTexParameteri GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR
				glTexParameteri GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR
			Else
				glTexParameteri GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR
				glTexParameteri GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR
			EndIf

			If texture._flags&TEXTURE_CLAMPU
				glTexParameteri GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE
			Else
				glTexParameteri GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT
			EndIf

			If texture._flags&TEXTURE_CLAMPV
				glTexParameteri GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE
			Else
				glTexParameteri GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT
			EndIf

			If texture._flags&TEXTURE_SPHEREMAP
				glEnable GL_TEXTURE_GEN_S
				glEnable GL_TEXTURE_GEN_T
				glTexGeni GL_S,GL_TEXTURE_GEN_MODE,GL_SPHERE_MAP
				glTexGeni GL_T,GL_TEXTURE_GEN_MODE,GL_SPHERE_MAP
			Else
				glDisable GL_TEXTURE_GEN_S
				glDisable GL_TEXTURE_GEN_T
			EndIf

			Select texture._blend
			Case BLEND_NONE glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_REPLACE)
			Case BLEND_ALPHA 	glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE)
			Case BLEND_MULTIPLY glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE)
			'Case BLEND_MULTIPLY glTexEnvf(GL_TEXTURE_ENV,GL_COMBINE_RGB_EXT,GL_MODULATE)
			Case BLEND_ADD glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_ADD)
			Case BLEND_DOT3
				glTexEnvf GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT
				glTexEnvf GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_DOT3_RGB_EXT
			Case BLEND_MULTIPLY2
				glTexEnvi GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_COMBINE
				glTexEnvi GL_TEXTURE_ENV,GL_COMBINE_RGB,GL_MODULATE
				glTexEnvi GL_TEXTURE_ENV,GL_RGB_SCALE,2.0
			Default glTexEnvf GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE
			End Select
		Next

		If alpha_test
			glEnable GL_ALPHA_TEST
		Else
			glDisable GL_ALPHA_TEST
		EndIf

		If _shaderdriver _shaderdriver.Use(brush._shader,_currentdata)
	End Method

	Method RenderSurface(resource:TSurfaceRes,brush:TBrush)
		Local res:TGLSurfaceRes=TGLSurfaceRes(resource)

		For Local i=0 To 7
			glClientActiveTextureARB GL_TEXTURE0+i

			Local texture:TTexture=brush._texture[i]
			If texture=Null Or res._vbo[4+texture._coords] = 0
				glDisableClientState GL_TEXTURE_COORD_ARRAY
				Continue
			EndIf

			glEnableClientState GL_TEXTURE_COORD_ARRAY
			glBindBufferARB GL_ARRAY_BUFFER_ARB,res._vbo[4+texture._coords]
			glTexCoordPointer 2,GL_FLOAT,0,Null
		Next

		glBindBufferARB GL_ARRAY_BUFFER_ARB,res._vbo[0]
		glVertexPointer 3,GL_FLOAT,0,Null

		glBindBufferARB GL_ARRAY_BUFFER_ARB,res._vbo[1]
		glNormalPointer GL_FLOAT,0,Null

		glBindBufferARB GL_ARRAY_BUFFER_ARB,res._vbo[2]
		glColorPointer 4,GL_FLOAT,0,Null

		glBindBufferARB GL_ELEMENT_ARRAY_BUFFER_ARB,res._vbo[3]
		glDrawElements GL_TRIANGLES,res._trianglecnt*3,GL_UNSIGNED_INT,Null

		Return res._trianglecnt
	End Method

	Method BeginEntityRender(entity:TEntity)
		If entity._order<>0
			glDisable GL_DEPTH_TEST
			glDepthMask GL_FALSE
		Else
			glEnable GL_DEPTH_TEST
			glDepthMask GL_TRUE
		EndIf

		glMatrixMode GL_MODELVIEW
		glPushMatrix
		glMultMatrixf entity.GetMatrix(True,False).ToPtr()

		Global matrix:TMatrix=New TMatrix
		glGetFloatv GL_MODELVIEW_MATRIX,matrix._m
	End Method
	Method EndEntityRender(entity:TEntity)
		glMatrixMode GL_MODELVIEW
		glPopMatrix
	End Method

	Method RenderFlat(flat:TFlat)
		Local x#,y#,z#
		flat.GetScale x,y,z,True

		glBegin GL_QUADS
			glNormal3f 0,1,0
			glTexCoord2f 0,0
			glVertex3f -1,0, 1
			glTexCoord2f x,0
			glVertex3f 1, 0, 1
			glTexCoord2f x,z
			glVertex3f 1, 0, -1
			glTexCoord2f 0,z
			glVertex3f -1,0, -1
		glEnd
		Return 2
	End Method

	Method RenderSprite(sprite:TSprite)
		If False
			glPointParameterfvARB GL_POINT_DISTANCE_ATTENUATION_ARB, [0.0, 0.0, 1.0]

			glPointSize 1.0

			glPointParameterfARB GL_POINT_FADE_THRESHOLD_SIZE_ARB,60.0
			glPointParameterfARB GL_POINT_SIZE_MIN_ARB,0.0
			glPointParameterfARB GL_POINT_SIZE_MAX_ARB,9999.0

			glTexEnvf GL_POINT_SPRITE_ARB,GL_COORD_REPLACE_ARB,GL_TRUE

			glEnable GL_POINT_SPRITE_ARB

			glBegin GL_POINTS
			glVertex3f 0,0,0
			glEnd

			glDisable GL_POINT_SPRITE_ARB
		Else
			glBegin GL_QUADS
				glNormal3f 0,0,-1
				glTexCoord2f 0,1
				glVertex3f 1, -1, 0
				glTexCoord2f 0,0
				glVertex3f 1, 1, 0
				glTexCoord2f 1,0
				glVertex3f -1,1, 0
				glTexCoord2f 1,1
				glVertex3f -1,-1, 0
			glEnd
		EndIf
	End Method

	Method RenderTerrain(terrain:TTerrain)

		glDisableClientState GL_TEXTURE_COORD_ARRAY
		For Local i=0 To 7
			Local texture:TTexture=terrain._brush._texture[i]
			If texture=Null Continue
			glEnableClientState(GL_TEXTURE_COORD_ARRAY)
			glClientActiveTextureARB(GL_TEXTURE0+i)
			glTexCoordPointer(2,GL_FLOAT,20,terrain._data)
		Next

		glEnableClientState  GL_VERTEX_ARRAY
		glDisableClientState GL_COLOR_ARRAY
		glDisableClientState GL_NORMAL_ARRAY

		glVertexPointer(3,GL_FLOAT, 20, terrain._data+2)
		glDrawArrays(GL_TRIANGLES, 0, 3*terrain._count)

		glEnableClientState GL_VERTEX_ARRAY
		glEnableClientState GL_COLOR_ARRAY
		glEnableClientState GL_NORMAL_ARRAY
	End Method

	Method RenderBSPTree(tree:TBSPTree)
		Local node:TBSPNode=tree.Node
		If node=Null Return
		Local triangles
		triangles:+RenderBSPTree(node.In)

		For Local poly:TBSPPolygon=EachIn node.On
			glBegin GL_POLYGON
			glNormal3f poly.Plane.x,poly.Plane.y,poly.Plane.z
			For Local i=0 To poly.Count()-1
				glTexCoord2f (poly.Plane.x-poly.Center.x)*poly.Point[i].x+(poly.Plane.y-poly.Center.y)*poly.Point[i].y+(poly.Plane.z-poly.Center.z)*poly.Point[i].z, (poly.Plane.x-poly.Center.x)*poly.Point[i].x+(poly.Plane.y-poly.Center.y)*poly.Point[i].y+(poly.Plane.z-poly.Center.z)*poly.Point[i].z
				glVertex3f poly.Point[i].x,poly.Point[i].y,poly.Point[i].z
			Next
			triangles:+poly.Count()-2
			glEnd
		Next

		triangles:+RenderBSPTree(node.Out)
		Return triangles
	End Method

	Method UpdateTextureRes:TGLTextureRes(frame:TTextureFrame,flags)
		If frame=Null Return Null

		Local glres:TGLTextureRes=TGLTextureRes(frame._res)
		If glres=Null
			glres=New TGLTextureRes
			frame._res=glres
			frame._updateres=True
		EndIf
		If Not frame._updateres Return glres

		If glres._id=0 glGenTextures(1,Varptr glres._id)
		BindTexture 0,glres._id
		Local pixmap:TPixmap=frame._pixmap
		gluBuild2DMipmaps(GL_TEXTURE_2D,GL_RGBA8,pixmap.width,pixmap.height,GL_BGRA,GL_UNSIGNED_BYTE,pixmap.pixels)

		frame._updateres=0
		Return glres
	End Method

	Method UpdateSurfaceRes:TGLSurfaceRes(surface:TSurface)
		Local res:TGLSurfaceRes=TGLSurfaceRes(surface._res)
		If res=Null res=New TGLSurfaceRes;surface._res=res
		If surface._reset=0 Return res

		If res._vbo[0]=0 glGenBuffersARB(4,res._vbo)

		If surface._reset=-1 Then surface._reset=1|2|4|8|16|32|64|128|256

		If surface._reset&1 UploadVertexBuffer res._vbo[0],surface._vertexpos
		If surface._reset&2 UploadVertexNormals res._vbo[1],surface._vertexnml
		If surface._reset&4 UploadVertexBuffer res._vbo[2],surface._vertexclr
		If surface._reset&8
			glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB,res._vbo[3])
			glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB,surface._triangle.length*4,surface._triangle,GL_STATIC_DRAW_ARB)
		EndIf

		For Local i=0 To surface._vertextex.length-1
			If res._vbo[4+i]=0 glGenBuffersARB(1,Varptr res._vbo[4+i])
			If surface._reset&Int(2^(4+i)) UploadVertexBuffer res._vbo[4+i],surface._vertextex[i]
		Next

		res._trianglecnt=surface._trianglecnt
		res._vertexcnt=surface._vertexcnt
		surface._reset=0
		Return res
	End Method

	Method MergeSurfaceRes:TGLSurfaceRes(base:TSurface,animation:TSurface,data)
		If animation=Null Return UpdateSurfaceRes(base)
		Local res_base:TGLSurfaceRes=UpdateSurfaceRes(base)
		Local res_anim:TGLSurfaceRes=UpdateSurfaceRes(animation)
		Local res:TGLSurfaceRes=res_base.Copy()
		res._vbo[0]=res_anim._vbo[0]
		Return res
	End Method

	Method UploadVertexBuffer(vbo,data#[])
		glBindBufferARB(GL_ARRAY_BUFFER_ARB,vbo)
		glBufferDataARB(GL_ARRAY_BUFFER_ARB,data.length*4,data,GL_STATIC_DRAW_ARB)
	End Method

	Method UploadVertexNormals(vbo,data#[])
		glBindBufferARB(GL_ARRAY_BUFFER_ARB,vbo)
		glBufferDataARB(GL_ARRAY_BUFFER_ARB,data.length*4,data,GL_STATIC_DRAW_ARB)
	End Method
End Type

Type TGLCaps Extends TCaps
	Field Extensions$[]

	Method HasExtension(ext$)
		For Local extension$=EachIn Extensions
			If extension.Find(ext)<>-1 Return True
		Next
		Return False
	End Method

	Method Copy:TGLCaps()
		Local caps:TGLCaps=New TGLCaps
		caps.CopyBase(Self)
		caps.Extensions=Extensions[..]
		Return caps
	End Method
End Type

Type TGLSurfaceRes Extends TSurfaceRes
	Field _vbo[12]
	Field _texcoord

	Method Copy:TGLSurfaceRes()
		Local res:TGLSurfaceRes=New TGLSurfaceRes
		res._vertexcnt=_vertexcnt;res._trianglecnt=_trianglecnt
		res._vbo=_vbo[..]
		res._texcoord=_texcoord
		Return res
	End Method
End Type

Type TGLTextureRes Extends TTextureRes
	Field _id
End Type

Rem
	bbdoc: Needs documentation. #TODO
End Rem
Function GLMaxB3DDriver:TGLMaxB3DDriver()
	If GLMax2DExDriver()
		Global driver:TGLMaxB3DDriver=New TGLMaxB3DDriver
		driver._parent=GLMax2DExDriver()
		Return driver
	End If
End Function

Rem
	bbdoc: Utility function that sets the MaxB3D GL driver and calls Graphics.
End Rem
Function GLGraphics3D:TGraphics(width,height,depth=0,hertz=60,flags=0)
	SetGraphicsDriver GLMaxB3DDriver(),GRAPHICS_BACKBUFFER|GRAPHICS_DEPTHBUFFER
	Return Graphics(width,height,depth,hertz,flags)
End Function

Local driver:TGLMaxB3DDriver=GLMaxB3DDriver()
If driver SetGraphicsDriver driver,GRAPHICS_BACKBUFFER|GRAPHICS_DEPTHBUFFER

Strict

Rem
	bbdoc: OpenGL 1.1 driver for MaxB3D
End Rem
Module MaxB3D.GLDriver
ModuleInfo "Author: Kevin Primm"
ModuleInfo "License: MIT"

Import MaxB3D.Core
Import GFX.GLMax2DEx

Private
Function ModuleLog(message$)
	TMaxB3DLogger.Write "gldriver",message
End Function

Public

Global GL_LIGHT[]=[GL_LIGHT0,GL_LIGHT1,GL_LIGHT2,GL_LIGHT3,GL_LIGHT4,GL_LIGHT5,GL_LIGHT6,GL_LIGHT7]

Type TGLMaxB3DDriver Extends TMaxB3DDriver
	Field _currentdata:TMaxB3DShaderData= New TMaxB3DShaderData

	Method SetGraphics(g:TGraphics)
		Super.SetGraphics g
		If g<>Null Startup
	End Method

	Function BindTexture(index,tex)
		If index=-1
			For Local i=0 To 7
				BindTexture i,tex
			Next
			Return
		EndIf
		Global currenttexture[] = [-1,-1,-1,-1,-1,-1,-1,-1]
		If tex<>currenttexture[index]
			glBindTexture GL_TEXTURE_2D,tex
			currenttexture[index]=tex
		EndIf
	End Function

	Method Startup()
		Global _firsttime=True
		If _firsttime
			Local caps:TGLCaps=TGLCaps(_caps)
			ModuleLog "Initializing GL driver"
			ModuleLog "Vendor:   "+String.FromCString(Byte Ptr(glGetString(GL_VENDOR)))
			ModuleLog "Renderer: "+String.FromCString(Byte Ptr(glGetString(GL_RENDERER)))
			ModuleLog "Version:  "+String.FromCString(Byte Ptr(glGetString(GL_VERSION)))
			ModuleLog "Extensions supported: "+" ".Join(caps.Extensions)
			EndMax2D
			_firsttime=False
		EndIf
		EnableStates
	End Method

	Function EnableStates()
		glEnable GL_LIGHTING
		glEnable GL_DEPTH_TEST
		glEnable GL_CULL_FACE
		glEnable GL_SCISSOR_TEST

		glEnable GL_NORMALIZE

		glEnableClientState GL_VERTEX_ARRAY
		glEnableClientState GL_COLOR_ARRAY
		glEnableClientState GL_NORMAL_ARRAY

		glFrontFace GL_CW

		glLightModeli GL_LIGHT_MODEL_COLOR_CONTROL,GL_SEPARATE_SPECULAR_COLOR
		glLightModeli GL_LIGHT_MODEL_LOCAL_VIEWER,GL_TRUE

		glClearDepth 1.0
		glDepthFunc GL_LEQUAL
		glHint GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST

		glAlphaFunc GL_GEQUAL,0.5

		BindTexture -1,0
	End Function

	Method MakeBuffer:TBuffer(src:Object,width,height,flags)
		Local buffer:TBuffer
		Select True
		Case TTextureFrame(src)<>Null
			buffer=TGLBuffer(TTextureFrame(src)._buffer)
			If buffer=Null
				Local res:TGLTextureRes=UpdateTextureRes(TTextureFrame(src),0)
				buffer=TGLMax2DExDriver(_parent).MakeGLBuffer(res._id,width,height,flags)
			EndIf
		Default
			buffer=TMax2DExDriver(_parent).MakeBuffer(src,width,height,flags)
		End Select
		Return buffer
	End Method

	Method Abbr$()
		Return "gl"
	End Method

	Method GetCaps:TCaps()
		Local caps:TGLCaps=New TGLCaps
		caps.Extensions=String.FromCString(glGetString(GL_EXTENSIONS)).Split(" ")
		If caps.HasExtension("GL_ARB_point_sprite")<>-1
			caps.PointSprites=True
			glGetFloatv GL_POINT_SIZE_MAX_ARB, Varptr caps.MaxPointSize
		EndIf
		Return caps
	End Method

	Method SetMax2D(enable)
		If enable
			glPopClientAttrib
			glPopAttrib
			If _shaderdriver _shaderdriver.Use(Null,Null)
			TGLMax2DExDriver(_parent).ResetGLContext _current
			glMatrixMode GL_TEXTURE
			glLoadIdentity
			glMatrixMode GL_COLOR
			glPopMatrix
		Else
			glPushAttrib GL_ALL_ATTRIB_BITS
			glPushClientAttrib GL_CLIENT_ALL_ATTRIB_BITS
			glMatrixMode GL_TEXTURE
			glPushMatrix
			glMatrixMode GL_COLOR
			glPushMatrix

			EnableStates()
		EndIf
	End Method
	Method SetCamera(camera:TCamera, config:TWorldConfig)
		If config.Dither
			glEnable GL_DITHER
		Else
			glDisable GL_DITHER
		EndIf

		Local vy#=config.Height-camera._viewheight-camera._viewy
		glViewport(camera._viewx,vy,camera._viewwidth,camera._viewheight)
		glScissor(camera._viewx,vy,camera._viewwidth,camera._viewheight)
		glClearColor(camera._brush._r,camera._brush._g,camera._brush._b,1.0)

		If camera._clsmode&CLSMODE_COLOR And camera._clsmode&CLSMODE_DEPTH
			glDepthMask GL_TRUE
			glClear GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT
		Else
			If camera._clsmode&CLSMODE_COLOR
				glClear GL_COLOR_BUFFER_BIT
			Else
				If camera._clsmode&CLSMODE_DEPTH
					glDepthMask GL_TRUE
					glClear GL_DEPTH_BUFFER_BIT
				EndIf
			EndIf
		EndIf

		If camera._fogmode<>FOGMODE_NONE
			glEnable GL_FOG
			glFogi GL_FOG_MODE,GL_LINEAR
			glFogf GL_FOG_START,camera._fognear
			glFogf GL_FOG_END,camera._fogfar
			Local rgb#[]=[camera._fogr,camera._fogg,camera._fogb]
			glFogfv GL_FOG_COLOR,rgb
		Else
			glDisable GL_FOG
		EndIf

		camera.UpdateMatrices()

		glMatrixMode GL_PROJECTION
		glLoadMatrixf camera._projection.ToPtr()
		glMatrixMode GL_MODELVIEW
		glLoadMatrixf camera._modelview.ToPtr()

		' Temporary hack
		'glGetFloatv GL_PROJECTION_MATRIX,camera._projection._m
		'camera._frustum=TFrustum.Extract(camera._modelview,camera._projection)
	End Method

	Method SetLight(light:TLight,index)
		If light=Null
			glDisable GL_LIGHT[index]
			Return
		Else
			If light._hidden=True
				glDisable GL_LIGHT[index]
				Return False
			EndIf
		EndIf

		glEnable GL_LIGHT[index]

		glPushMatrix
		glMultMatrixf light.GetMatrix(True).ToPtr()

		Local white_light#[]=[1.0,1.0,1.0,1.0]
		glLightfv GL_LIGHT[index],GL_SPECULAR,white_light

		Local z#=1.0
		Local w#=0.0
		If light._mode>LIGHT_DIRECTIONAL
			z=0.0
			w=1.0
		EndIf

		Local rgba#[]=[light._brush._r,light._brush._g,light._brush._b,1.0]
		Local pos#[]=[0.0,0.0,-z,w]

		glLightfv GL_LIGHT[index],GL_POSITION,pos
		glLightfv GL_LIGHT[index],GL_DIFFUSE,rgba

		If light._mode<>LIGHT_DIRECTIONAL
			Local light_range#[]=[0.0]
			Local range#[]=[light._range]
			glLightfv GL_LIGHT[index],GL_CONSTANT_ATTENUATION,light_range
			glLightfv GL_LIGHT[index],GL_LINEAR_ATTENUATION,range
		EndIf

		If light._mode=LIGHT_SPOT
			Local dir#[]=[0.0,0.0,-1.0]
			Local outer#[]=[light._outer/2.0]
			Local exponent#[]=[10.0]
			glLightfv GL_LIGHT[index],GL_SPOT_DIRECTION,dir
			glLightfv GL_LIGHT[index],GL_SPOT_CUTOFF,outer
			glLightfv GL_LIGHT[index],GL_SPOT_EXPONENT,exponent
		EndIf

		glPopMatrix
	End Method

	Method SetBrush(brush:TBrush,hasalpha,config:TWorldConfig)
		Local alpha_test

		Local ambient#[]=[config.AmbientRed/255.0,config.AmbientGreen/255.0,config.AmbientBlue/255.0]

		If hasalpha
			glEnable GL_BLEND
			glDepthMask GL_FALSE
		Else
			glDisable GL_BLEND
			glDepthMask GL_TRUE
		EndIf

		Select brush._blend
			Case BLEND_NONE,BLEND_ALPHA
				glBlendFunc GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA
			Case BLEND_MULTIPLY
				glBlendFunc GL_DST_COLOR,GL_ZERO
			Case BLEND_ADD
				glBlendFunc GL_SRC_ALPHA,GL_ONE
		End Select

		If brush._fx&FX_FULLBRIGHT
			ambient[0]=1.0
			ambient[1]=1.0
			ambient[2]=1.0
		EndIf

		If brush._fx&FX_VERTEXCOLOR
			glEnable GL_COLOR_MATERIAL
		Else
			glDisable GL_COLOR_MATERIAL
		EndIf

		If brush._fx&FX_FLATSHADED
			glShadeModel GL_FLAT
		Else
			glShadeModel GL_SMOOTH
		EndIf

		If brush._fx&FX_NOFOG
			glDisable GL_FOG
		EndIf

		If brush._fx&FX_NOCULLING
			glDisable GL_CULL_FACE
		Else
			glEnable GL_CULL_FACE
		EndIf

		If brush._fx&FX_WIREFRAME Or config.Wireframe
			glPolygonMode GL_FRONT_AND_BACK,GL_LINE
		Else
			glPolygonMode GL_FRONT_AND_BACK,GL_FILL
		EndIf

		Local no_mat#[]=[0.0,0.0]
		Local mat_ambient#[]=[brush._r,brush._g,brush._b,brush._a]
		Local mat_diffuse#[]=[brush._r,brush._g,brush._b,brush._a]
		Local mat_specular#[]=[brush._shine,brush._shine,brush._shine,brush._shine]
		Local mat_shininess#[]=[100.0]

		glMaterialfv GL_FRONT,GL_AMBIENT,mat_ambient
		glMaterialfv GL_FRONT,GL_DIFFUSE,mat_diffuse
		glMaterialfv GL_FRONT,GL_SPECULAR,mat_specular
		glMaterialfv GL_FRONT,GL_SHININESS,mat_shininess
		glLightModelfv GL_LIGHT_MODEL_AMBIENT,ambient

		For Local i=0 To 7
			glActiveTextureARB GL_TEXTURE0+i

			Local texture:TTexture=brush._texture[i]
			If texture=Null Or texture._blend=BLEND_NONE
				glDisable GL_TEXTURE_2D
				BindTexture i,0
				Continue
			EndIf

			Local texres:TGLTextureRes=TGLTextureRes(UpdateTextureRes(texture._frame[brush._textureframe[i]],texture._flags))

			glEnable GL_TEXTURE_2D

			BindTexture i,texres._id

			glMatrixMode GL_TEXTURE
			glLoadIdentity
			glTranslatef texture._px,-texture._py,0
			glScalef -texture._sx,texture._sy,1

			alpha_test :| texture._flags&TEXTURE_ALPHA Or texture._flags&TEXTURE_MASKED

			If texture._flags&TEXTURE_MIPMAP
				glTexParameteri GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR
				glTexParameteri GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR
			Else
				glTexParameteri GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR
				glTexParameteri GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR
			EndIf

			If texture._flags&TEXTURE_CLAMPU
				glTexParameteri GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE
			Else
				glTexParameteri GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT
			EndIf

			If texture._flags&TEXTURE_CLAMPV
				glTexParameteri GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE
			Else
				glTexParameteri GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT
			EndIf

			If texture._flags&TEXTURE_SPHEREMAP
				glEnable GL_TEXTURE_GEN_S
				glEnable GL_TEXTURE_GEN_T
				glTexGeni GL_S,GL_TEXTURE_GEN_MODE,GL_SPHERE_MAP
				glTexGeni GL_T,GL_TEXTURE_GEN_MODE,GL_SPHERE_MAP
			Else
				glDisable GL_TEXTURE_GEN_S
				glDisable GL_TEXTURE_GEN_T
			EndIf

			Select texture._blend
			Case BLEND_NONE glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_REPLACE)
			Case BLEND_ALPHA 	glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE)
			Case BLEND_MULTIPLY glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE)
			'Case BLEND_MULTIPLY glTexEnvf(GL_TEXTURE_ENV,GL_COMBINE_RGB_EXT,GL_MODULATE)
			Case BLEND_ADD glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_ADD)
			Case BLEND_DOT3
				glTexEnvf GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT
				glTexEnvf GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_DOT3_RGB_EXT
			Case BLEND_MULTIPLY2
				glTexEnvi GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_COMBINE
				glTexEnvi GL_TEXTURE_ENV,GL_COMBINE_RGB,GL_MODULATE
				glTexEnvi GL_TEXTURE_ENV,GL_RGB_SCALE,2.0
			Default glTexEnvf GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE
			End Select
		Next

		If alpha_test
			glEnable GL_ALPHA_TEST
		Else
			glDisable GL_ALPHA_TEST
		EndIf

		If _shaderdriver _shaderdriver.Use(brush._shader,_currentdata)
	End Method

	Method RenderSurface(resource:TSurfaceRes,brush:TBrush)
		Local res:TGLSurfaceRes=TGLSurfaceRes(resource)

		For Local i=0 To 7
			glClientActiveTextureARB GL_TEXTURE0+i

			Local texture:TTexture=brush._texture[i]
			If texture=Null
				glDisableClientState GL_TEXTURE_COORD_ARRAY
				Continue
			EndIf

			glEnableClientState GL_TEXTURE_COORD_ARRAY
			glBindBufferARB GL_ARRAY_BUFFER_ARB,res._vbo[4+texture._coords]
			glTexCoordPointer 2,GL_FLOAT,0,Null
		Next

		glBindBufferARB GL_ARRAY_BUFFER_ARB,res._vbo[0]
		glVertexPointer 3,GL_FLOAT,0,Null

		glBindBufferARB GL_ARRAY_BUFFER_ARB,res._vbo[1]
		glNormalPointer GL_FLOAT,0,Null

		glBindBufferARB GL_ARRAY_BUFFER_ARB,res._vbo[2]
		glColorPointer 4,GL_FLOAT,0,Null

		glBindBufferARB GL_ELEMENT_ARRAY_BUFFER_ARB,res._vbo[3]
		glDrawElements GL_TRIANGLES,res._trianglecnt*3,GL_UNSIGNED_INT,Null

		Return res._trianglecnt
	End Method

	Method BeginEntityRender(entity:TEntity)
		If entity._order<>0
			glDisable GL_DEPTH_TEST
			glDepthMask GL_FALSE
		Else
			glEnable GL_DEPTH_TEST
			glDepthMask GL_TRUE
		EndIf

		glMatrixMode GL_MODELVIEW
		glPushMatrix
		glMultMatrixf entity.GetMatrix(True,False).ToPtr()

		Global matrix:TMatrix=New TMatrix
		glGetFloatv GL_MODELVIEW_MATRIX,matrix._m
	End Method
	Method EndEntityRender(entity:TEntity)
		glMatrixMode GL_MODELVIEW
		glPopMatrix
	End Method

	Method RenderFlat(flat:TFlat)
		Local x#,y#,z#
		flat.GetScale x,y,z,True

		glBegin GL_QUADS
			glNormal3f 0,1,0
			glTexCoord2f 0,0
			glVertex3f -1,0, 1
			glTexCoord2f x,0
			glVertex3f 1, 0, 1
			glTexCoord2f x,z
			glVertex3f 1, 0, -1
			glTexCoord2f 0,z
			glVertex3f -1,0, -1
		glEnd
		Return 2
	End Method

	Method RenderSprite(sprite:TSprite)
		If False
			glPointParameterfvARB GL_POINT_DISTANCE_ATTENUATION_ARB, [0.0, 0.0, 1.0]

			glPointSize 1.0

			glPointParameterfARB GL_POINT_FADE_THRESHOLD_SIZE_ARB,60.0
			glPointParameterfARB GL_POINT_SIZE_MIN_ARB,0.0
			glPointParameterfARB GL_POINT_SIZE_MAX_ARB,9999.0

			glTexEnvf GL_POINT_SPRITE_ARB,GL_COORD_REPLACE_ARB,GL_TRUE

			glEnable GL_POINT_SPRITE_ARB

			glBegin GL_POINTS
			glVertex3f 0,0,0
			glEnd

			glDisable GL_POINT_SPRITE_ARB
		Else
			glBegin GL_QUADS
				glNormal3f 0,0,-1
				glTexCoord2f 0,1
				glVertex3f 1, -1, 0
				glTexCoord2f 0,0
				glVertex3f 1, 1, 0
				glTexCoord2f 1,0
				glVertex3f -1,1, 0
				glTexCoord2f 1,1
				glVertex3f -1,-1, 0
			glEnd
		EndIf
	End Method

	Method RenderTerrain(terrain:TTerrain)

		glDisableClientState GL_TEXTURE_COORD_ARRAY
		For Local i=0 To 7
			Local texture:TTexture=terrain._brush._texture[i]
			If texture=Null Continue
			glEnableClientState(GL_TEXTURE_COORD_ARRAY)
			glClientActiveTextureARB(GL_TEXTURE0+i)
			glTexCoordPointer(2,GL_FLOAT,20,terrain._data)
		Next

		glEnableClientState  GL_VERTEX_ARRAY
		glDisableClientState GL_COLOR_ARRAY
		glDisableClientState GL_NORMAL_ARRAY

		glVertexPointer(3,GL_FLOAT, 20, terrain._data+2)
		glDrawArrays(GL_TRIANGLES, 0, 3*terrain._count)

		glEnableClientState GL_VERTEX_ARRAY
		glEnableClientState GL_COLOR_ARRAY
		glEnableClientState GL_NORMAL_ARRAY
	End Method

	Method RenderBSPTree(tree:TBSPTree)
		Local node:TBSPNode=tree.Node
		If node=Null Return
		Local triangles
		triangles:+RenderBSPTree(node.In)

		For Local poly:TBSPPolygon=EachIn node.On
			glBegin GL_POLYGON
			glNormal3f poly.Plane.x,poly.Plane.y,poly.Plane.z
			For Local i=0 To poly.Count()-1
				glTexCoord2f (poly.Plane.x-poly.Center.x)*poly.Point[i].x+(poly.Plane.y-poly.Center.y)*poly.Point[i].y+(poly.Plane.z-poly.Center.z)*poly.Point[i].z, (poly.Plane.x-poly.Center.x)*poly.Point[i].x+(poly.Plane.y-poly.Center.y)*poly.Point[i].y+(poly.Plane.z-poly.Center.z)*poly.Point[i].z
				glVertex3f poly.Point[i].x,poly.Point[i].y,poly.Point[i].z
			Next
			triangles:+poly.Count()-2
			glEnd
		Next

		triangles:+RenderBSPTree(node.Out)
		Return triangles
	End Method

	Method UpdateTextureRes:TGLTextureRes(frame:TTextureFrame,flags)
		If frame=Null Return Null

		Local glres:TGLTextureRes=TGLTextureRes(frame._res)
		If glres=Null
			glres=New TGLTextureRes
			frame._res=glres
			frame._updateres=True
		EndIf
		If Not frame._updateres Return glres

		If glres._id=0 glGenTextures(1,Varptr glres._id)
		BindTexture 0,glres._id
		Local pixmap:TPixmap=frame._pixmap
		gluBuild2DMipmaps(GL_TEXTURE_2D,GL_RGBA8,pixmap.width,pixmap.height,GL_BGRA,GL_UNSIGNED_BYTE,pixmap.pixels)

		frame._updateres=0
		Return glres
	End Method

	Method UpdateSurfaceRes:TGLSurfaceRes(surface:TSurface)
		Local res:TGLSurfaceRes=TGLSurfaceRes(surface._res)
		If res=Null res=New TGLSurfaceRes;surface._res=res
		If surface._reset=0 Return res

		If res._vbo[0]=0 glGenBuffersARB(4,res._vbo)

		If surface._reset=-1 Then surface._reset=1|2|4|8|16|32|64|128|256

		If surface._reset&1 UploadVertexBuffer res._vbo[0],surface._vertexpos
		If surface._reset&2 UploadVertexNormals res._vbo[1],surface._vertexnml
		If surface._reset&4 UploadVertexBuffer res._vbo[2],surface._vertexclr
		If surface._reset&8
			glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB,res._vbo[3])
			glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB,surface._triangle.length*4,surface._triangle,GL_STATIC_DRAW_ARB)
		EndIf

		For Local i=0 To surface._vertextex.length-1
			If res._vbo[4+i]=0 glGenBuffersARB(1,Varptr res._vbo[4+i])
			If surface._reset&Int(2^(4+i)) UploadVertexBuffer res._vbo[4+i],surface._vertextex[i]
		Next

		res._trianglecnt=surface._trianglecnt
		res._vertexcnt=surface._vertexcnt
		surface._reset=0
		Return res
	End Method

	Method MergeSurfaceRes:TGLSurfaceRes(base:TSurface,animation:TSurface,data)
		If animation=Null Return UpdateSurfaceRes(base)
		Local res_base:TGLSurfaceRes=UpdateSurfaceRes(base)
		Local res_anim:TGLSurfaceRes=UpdateSurfaceRes(animation)
		Local res:TGLSurfaceRes=res_base.Copy()
		res._vbo[0]=res_anim._vbo[0]
		Return res
	End Method

	Method UploadVertexBuffer(vbo,data#[])
		glBindBufferARB(GL_ARRAY_BUFFER_ARB,vbo)
		glBufferDataARB(GL_ARRAY_BUFFER_ARB,data.length*4,data,GL_STATIC_DRAW_ARB)
	End Method

	Method UploadVertexNormals(vbo,data#[])
		glBindBufferARB(GL_ARRAY_BUFFER_ARB,vbo)
		glBufferDataARB(GL_ARRAY_BUFFER_ARB,data.length*4,data,GL_STATIC_DRAW_ARB)
	End Method
End Type

Type TGLCaps Extends TCaps
	Field Extensions$[]

	Method HasExtension(ext$)
		For Local extension$=EachIn Extensions
			If extension.Find(ext)<>-1 Return True
		Next
		Return False
	End Method

	Method Copy:TGLCaps()
		Local caps:TGLCaps=New TGLCaps
		caps.CopyBase(Self)
		caps.Extensions=Extensions[..]
		Return caps
	End Method
End Type

Type TGLSurfaceRes Extends TSurfaceRes
	Field _vbo[12]
	Field _texcoord

	Method Copy:TGLSurfaceRes()
		Local res:TGLSurfaceRes=New TGLSurfaceRes
		res._vertexcnt=_vertexcnt;res._trianglecnt=_trianglecnt
		res._vbo=_vbo[..]
		res._texcoord=_texcoord
		Return res
	End Method
End Type

Type TGLTextureRes Extends TTextureRes
	Field _id
End Type

Rem
	bbdoc: Needs documentation. #TODO
End Rem
Function GLMaxB3DDriver:TGLMaxB3DDriver()
	If GLMax2DExDriver()
		Global driver:TGLMaxB3DDriver=New TGLMaxB3DDriver
		driver._parent=GLMax2DExDriver()
		Return driver
	End If
End Function

Rem
	bbdoc: Utility function that sets the MaxB3D GL driver and calls Graphics.
End Rem
Function GLGraphics3D:TGraphics(width,height,depth=0,hertz=60,flags=0)
	SetGraphicsDriver GLMaxB3DDriver(),GRAPHICS_BACKBUFFER|GRAPHICS_DEPTHBUFFER
	Return Graphics(width,height,depth,hertz,flags)
End Function

Local driver:TGLMaxB3DDriver=GLMaxB3DDriver()
If driver SetGraphicsDriver driver,GRAPHICS_BACKBUFFER|GRAPHICS_DEPTHBUFFER


Accessed wrong variables in example2

Accessed wrong variables in example2

SuperStrict
Import htbaapub.xmlrpc

Local client:TXMLRPC_Client = New TXMLRPC_Client.Create()
client.SetTransport(New TXMLRPC_Transport_Http.Create("www.upcdatabase.com", "/rpc"))

Local parameters:TXMLRPC_Call_Parameters = TXMLRPC_Call_Parameters.Create(xmlrpc_vector_array)
parameters.AppendString(Null, "0045496718749")

Local response:TXMLRPC_Response_Data = client.Call("lookupEAN", parameters)

Local description:TXMLRPC_Value_String = TXMLRPC_Value_String(response.data.ValueForKey("description"))
Local issuerCountry:TXMLRPC_Value_String = TXMLRPC_Value_String(response.data.ValueForKey("issuerCountry"))
Local ean:TXMLRPC_Value_String = TXMLRPC_Value_String(response.data.ValueForKey("ean"))

Print description.name + ": " + description.ToString()
Print issuerCountry.name + ": " + issuerCountry.ToString()
Print ean.name + ": " + ean.ToString()

SuperStrict
Import htbaapub.xmlrpc

Local client:TXMLRPC_Client = New TXMLRPC_Client.Create()
client.SetTransport(New TXMLRPC_Transport_Http.Create("www.upcdatabase.com", "/rpc"))

Local parameters:TXMLRPC_Call_Parameters = TXMLRPC_Call_Parameters.Create(xmlrpc_vector_array)
parameters.AppendString(Null, "0045496718749")

Local response:TXMLRPC_Response_Data = client.Call("lookupEAN", parameters)

Local description:TXMLRPC_Value_String = TXMLRPC_Value_String(response.data.ValueForKey("description"))
Local issuerCountry:TXMLRPC_Value_String = TXMLRPC_Value_String(response.data.ValueForKey("issuerCountry"))
Local ean:TXMLRPC_Value_String = TXMLRPC_Value_String(response.data.ValueForKey("ean"))

Print description.name + ": " + description.ToString()
Print description.name + ": " + issuerCountry.ToString()
Print description.name + ": " + ean.ToString()


Changed module import order to include B3D collision before functions module.

Strict

Rem
	bbdoc: MaxB3D driver collection
End Rem
Module MaxB3D.Drivers
ModuleInfo "Author: Kevin Primm"
ModuleInfo "License: MIT"

Import MaxB3D.Core
Import MaxB3D.B3DCollision
Import MaxB3D.Primitives
Import MaxB3D.GLDriver
Import MaxB3D.D3D9Driver
Import MaxB3D.Functions

Strict

Rem
	bbdoc: MaxB3D driver collection
End Rem
Module MaxB3D.Drivers
ModuleInfo "Author: Kevin Primm"
ModuleInfo "License: MIT"

Import MaxB3D.Functions
Import MaxB3D.GLDriver
Import MaxB3D.D3D9Driver
Import MaxB3D.Primitives
Import MaxB3D.B3DCollision


filled out control opcodes

filled out control opcodes

' Blue Moon
' JIT engine/opcodes

' this is the core of the runtime: the machine code generator and opcodes
' it's a halfway blend between JIT and interpreter: a basic JIT generates machine code
' for "unrolled" interpreter loops, theoretically (ha!) providing a speed boost
' it can also "inline" selected operations, coming closer to being a true JIT

' for now this is an Include rather than Import lib due to recursive dependency
' we'll fix that later

Rem
SuperStrict

Import "blueerror.bmx"
Import "bluevm.bmx"
Import etc etc...
End Rem

'Private	'may need to rename and open these (in the shared interface?)
Extern
	Type Stack
		Field retIP:Byte Ptr, prevBase:Stack, varp:Long Ptr, func:Bytecode, _:Long Ptr, argv:Long Ptr, retv:Long Ptr, argc:Short, retc:Short
	End Type
	Type Bytecode
		Field mcode:Byte Ptr, idMod:Int, kcount:Int, pcount:Int, upvars:Int, frameSz:Int, icount:Int, vm:Byte Ptr
	End Type
End Extern
'Public

Type BlueJIT Final
	Const PROLOGUESZ:Int = 25, ISIZE:Int = 5

	Global opTbl:Int(s:Stack, b:Bytecode, r:Byte Ptr)[], opc:BlueOpcode
	Global Prologue:Int[] = [ ..
		$90, $90, ..
		$8b, $44, $24, $04, ..                     'mov 4(%esp), %eax
		$89, $44, $24, $f4, ..                     'mov %eax, -12(%esp)
		$c7, $44, $24, $f8, $FF, $FF, $FF, $FF, .. 'movl ########, -8(%esp)
		$89, $64, $24, $fc, ..                     'mov %esp, -4(%esp)
		$83, $ec, $0c..                            'sub $12, %esp
	]

	Function Compile:Byte Ptr(vm:BlueVM, ins:Byte Ptr, icount:Int, bytecode:Bytecode)
		?Not x86
		RuntimeError "The Blue Moon JIT does not support your platform (x86-32 only at this time)"
		?
		Assert SizeOf(0:Long) = SizeOf(0:Double) And SizeOf(0:Int) = 4 And SizeOf(0:Long) = 8 And SizeOf(Byte Ptr(0)) = 4, ..
			"assumptions about platform datatype sizes are invalid"

		If opTbl = Null Then InitOpTbl()

		' compute executable code size
		Local codesize:Int = PROLOGUESZ, opPos:Int[icount]	'since sizes are irregular, looking up offsets is easiest
		For Local i:Int = 0 Until icount
			opPos[i] = codesize	'finalized below
			Select ins[i * 8]
				Case opc.SETTAB, opc.GETTAB
					codesize :+ ISIZE * 2
				Default
					codesize :+ ISIZE
			End Select
		Next

		' allocate executable space
		Local code:Byte Ptr = vm.mem.AllocCodeBlock(codesize)

		'finalize op offsets se we can look them up
		For Local i:Int = 0 Until icount
			opPos[i] :+ Int(code)	'direct to executable space
		Next

		' emplace prologue (used for calling in from native only)
		For Local p:Int = 0 Until PROLOGUESZ
			code[p] = Prologue[p]
		Next
		Byte Ptr Ptr(code + 14)[0] = Byte Ptr(bytecode)	'replace the ########

		' epilogue (shared location per-vm, probably already set)
		vm.mem.returnToNative[0] = $c30cc483	'add $12, %esp ; ret  - i.e. restore the stack to normal

		Local ktable:Long Ptr = Long Ptr(ins + 8 * bytecode.icount + 8 * bytecode.upvars)	'constant table

		' generate machine code!
		For Local i:Int = 0 Until icount	'emplace opcode calls and supporting bytecode data
			Local codep:Byte Ptr = Byte Ptr(opPos[i]), bytecodep:Byte Ptr = Byte Ptr(Int(codep) + vm.mem.PAGESZ)
			Local bi:Int = i * 8, op:Int = ins[bi], ip:Int Ptr = Int Ptr(ins + bi), func:Byte Ptr = opTbl[op]

			codep[0] = $e8	'call
			Byte Ptr Ptr(codep + 1)[0] = func - Int(codep + ISIZE)

			bytecodep[0] = ins[bi + 1] ; bytecodep[1] = ins[bi + 2]	'used by most, may get overwritten

			Select op
				Case opc.LOADSI, opc.CLOSURE, opc.RET, opc.POSTCALL
					Int Ptr(bytecodep + 1)[0] = ip[1]

				Case opc.LOADK
					Long Ptr Ptr(bytecodep + 1)[0] = ktable + ip[1]

				Case opc.SETTABSI, opc.GETTABSI
					Short Ptr(bytecodep)[1] = ip[1]

				Case opc.SETTAB, opc.GETTAB
					codep[ISIZE] = $90 ; Int Ptr(codep + ISIZE + 1)[0] = $90909090	'nops; the space is needed in the bytecode
					Long Ptr Ptr(bytecodep + 2)[0] = ktable + ip[1]
					Long Ptr Ptr(bytecodep + 2)[1] = Null	'inline cache space

				Case opc.GETUPV
					If Int Ptr(ins + icount * 8)[2 * bytecodep[1]] = -1
						Byte Ptr Ptr(codep + 1)[0] = Byte Ptr(BlueJIT.GETENV) - Int(codep + ISIZE)
						Byte Ptr Ptr(bytecodep + 1)[0] = Varptr(vm._ENV.val)	'note: doesn't allow _ENV to be changed from Max
					EndIf

				Case opc.CALL
					Short Ptr(bytecodep)[1] = ip[1]

				Case opc.JIF, opc.JNOT
					Int Ptr(bytecodep + 1)[0] = opPos[i + ip[1]]

				Case opc.JMP
					codep[0] = $e9	'use a true jump
					Int Ptr(codep + 1)[0] = opPos[i + ip[1]] - (opPos[i] + ISIZE)

				Default	'binary operations A = B op C
					bytecodep[2] = ip[1]
			End Select
		Next

		Return code
	End Function

	Const STACKFRAME_INC:Int = BlueVMMemory.STACKFRAMESZ + 4, BYTECODE_INC:Int = BlueVMMemory.BYTECODESZ + 4
	Const IP_OFFSET:Int = BlueVMMemory.PAGESZ - ISIZE

	'note: extern vars must take into account the vtbl offset, so that converting from a typed pointer can be a simple cast (faster)

	Function MOV(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "MOV      //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		varp[rp[0]] = varp[rp[1]]
	End Function
	Function GETLC(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "GETLC    //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		varp[rp[0]] = Long Ptr Ptr(varp + rp[1])[0][0]
	End Function
	Function SETLC(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "SETLC    //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local convert:BlueVM(p:Byte Ptr) = Byte Ptr(Identity), vm:BlueVM = convert(bc.vm)
		vm.mem.Write(Long Ptr Ptr(varp + rp[0])[0], varp[rp[1]])	'unlikely but technically possible to need this
	End Function
	Function LOADK(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "LOADK    //"
		Local rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET, varp:Long Ptr = stk.varp + rp[0], kp:Long Ptr = Long Ptr Ptr(rp + 1)[0]
		varp[0] = kp[0]
	End Function
	Function LOADSI(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "LOADSI   //"
		Local rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local val:Int = Int Ptr(rp + 1)[0]
		Double Ptr(stk.varp)[rp[0]] = Double(val)
	End Function
	Function LOADBOOL(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "LOADBOOL //"
		Local rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET, vp:Int Ptr = Int Ptr(stk.varp + rp[0])
		vp[1] = BlueTypeTag.NANBOX | BlueTypeTag.BOOL
		vp[0] = Int Ptr(rp + 1)[0]
	End Function
	Function LOADNIL(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "LOADNIL  //"
		Local rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Int Ptr(stk.varp + rp[0])[1] = BlueTypeTag.NANBOX | BlueTypeTag.NIL
	End Function

	Function GETTAB(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "GETTAB   //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET, kp:Long Ptr = Long Ptr Ptr(rp + 2)[0]
		Local tabp:Byte Ptr = Byte Ptr Ptr(varp + rp[1])[0]
		If Int Ptr(varp + rp[1])[1] = BlueTypeTag.TBLBOX
			Local keyslot:Long Ptr = Null, slot:Long Ptr = BlueTable.GetSlot(tabp, kp[0], Varptr(keyslot))
			If (slot = Null) Or (keyslot[0] <> kp[0])
				If keyslot = Long Ptr(1) Then varp[rp[0]] = BlueVMMemory.NIL ; Return	'nil key
			Else
				varp[rp[0]] = slot[0] ; Return
			EndIf
		EndIf
		IndexMetamethod bc, retptr, varp + rp[0], varp[rp[1]], kp[0]	'not in table; invoke metamethod
	End Function
	Function GETTABSI(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "GETTABSI //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local tabp:Byte Ptr = Byte Ptr Ptr(varp + rp[1])[0], key:Long ; Double Ptr(Varptr(key))[0] = Short Ptr(rp)[1]
		If Int Ptr(varp + rp[0])[1] = BlueTypeTag.TBLBOX
			Local keyslot:Long Ptr = Null, slot:Long Ptr = BlueTable.GetSlot(tabp, key, Varptr(keyslot))
			If Not (slot = Null Or (keyslot And keyslot[0] <> key)) Then varp[rp[0]] = slot[0] ; Return
			If keyslot = Long Ptr(1) Then varp[rp[0]] = BlueVMMemory.NIL ; Return
		EndIf
		IndexMetamethod bc, retptr, varp + rp[0], varp[rp[1]], key
	End Function
	Function SETTAB(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "SETTAB   //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local tabvp:Byte Ptr = varp + rp[0], kp:Long Ptr = Long Ptr Ptr(rp + 2)[0]
		If Int Ptr(tabvp)[1] = BlueTypeTag.TBLBOX
			Local keyslot:Long Ptr = Null, slot:Long Ptr = BlueTable.GetSlot(Byte Ptr Ptr(tabvp)[0], kp[0], Varptr(keyslot))
			If Not (slot = Null Or keyslot[0] <> kp[0]) Then slot[0] = varp[rp[1]] ; Return
		EndIf
		NewindexMetamethod bc, retptr, varp[rp[0]], varp[rp[1]], kp[0]	'not in table; invoke metamethod or rawset
	End Function
	Function SETTABSI(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "SETTABSI //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET, tabvp:Byte Ptr = varp + rp[0]
		Local key:Long ; Double Ptr(Varptr(key))[0] = Short Ptr(rp)[1]
		If Int Ptr(tabvp)[1] = BlueTypeTag.TBLBOX
			Local keyslot:Long Ptr = Null, slot:Long Ptr = BlueTable.GetSlot(Byte Ptr Ptr(tabvp)[0], key, Varptr(keyslot))
			If Not (slot = Null Or (keyslot And keyslot[0] <> key)) Then slot[0] = varp[rp[1]] ; Return
		EndIf
		NewindexMetamethod bc, retptr, Long Ptr(tabvp)[0], varp[rp[1]], key
	End Function
	Function GETTABI(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "GETTABI  //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local tabvp:Byte Ptr = varp + rp[1], key:Long = varp[rp[2]]
		If Int Ptr(tabvp)[1] = BlueTypeTag.TBLBOX
			Local keyslot:Long Ptr = Null, slot:Long Ptr = BlueTable.GetSlot(Byte Ptr Ptr(tabvp)[0], key, Varptr(keyslot))
			If Not (slot = Null Or (keyslot And keyslot[0] <> key)) Then varp[rp[0]] = slot[0] ; Return
		EndIf
		IndexMetamethod bc, retptr, varp + rp[0], Long Ptr(tabvp)[0], key
	End Function
	Function SETTABI(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "SETTABI  //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local tabvp:Byte Ptr = varp + rp[0], key:Long = varp[rp[2]]
		If Int Ptr(tabvp)[1] = BlueTypeTag.TBLBOX
			Local keyslot:Long Ptr = Null, slot:Long Ptr = BlueTable.GetSlot(Byte Ptr Ptr(tabvp)[0], key, Varptr(keyslot))
			If Not (slot = Null Or (keyslot And keyslot[0] <> key)) Then slot[0] = varp[rp[1]] ; Return
		EndIf
		NewindexMetamethod bc, retptr, Long Ptr(tabvp)[0], varp[rp[1]], key
	End Function
	Function GETUPV(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "GETUPV   //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local upvp:Byte Ptr Ptr = Byte Ptr Ptr(Byte Ptr(stk) + STACKFRAME_INC), valp:Long Ptr = Long Ptr(upvp[rp[1]])
		varp[rp[0]] = valp[0]
	End Function
	Function GETENV(stk:Stack, bc:Bytecode, retptr:Byte Ptr)	'"unlisted" instruction: GETUPV for _ENV is translated into this at load-time
	'	Print "GETENV   //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		varp[rp[0]] = Long Ptr Ptr(rp + 1)[0][0]	'type check should not be necessary (will be handled by following op)
	End Function
	Function SETUPV(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "SETUPV   //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local convert:BlueVM(p:Byte Ptr) = Byte Ptr(Identity), vm:BlueVM = convert(bc.vm)
		Local upvp:Byte Ptr Ptr = Byte Ptr Ptr(Byte Ptr(stk) + STACKFRAME_INC), valp:Long Ptr = Long Ptr(upvp[rp[0]])
		vm.mem.Write(valp, varp[rp[1]])
	End Function
	Function NEWTAB(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "NEWTAB   //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local convert:BlueVM(p:Byte Ptr) = Byte Ptr(Identity), vm:BlueVM = convert(bc.vm)
		Local tab:Byte Ptr = vm.mem.AllocTable(Null)
		Local d:Int Ptr = Int Ptr(varp + rp[0])
		d[0] = Int(tab) ; d[1] = BlueTypeTag.TBLBOX
	End Function
	Function CLOSURE(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "CLOSURE  //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local convert:BlueVM(p:Byte Ptr) = Byte Ptr(Identity), vm:BlueVM = convert(bc.vm)
		Local d:Int Ptr = Int Ptr(varp + rp[0])
		Local cbytecode:Bytecode = vm.funIndex[Int Ptr(rp + 1)[0]], upvp:Int Ptr = Int Ptr(Byte Ptr(cbytecode) + BYTECODE_INC + 8 * cbytecode.icount)
		Local closure:Byte Ptr = vm.mem.AllocClosure(cbytecode, cbytecode.upvars)	'upvars is always even (alignment)

		'get upvalues off the stack
		For Local u:Int = 0 Until cbytecode.upvars
			If upvp[2 * u]
				Byte Ptr Ptr(closure)[2 + u] = Byte Ptr Ptr(Byte Ptr(stk) + STACKFRAME_INC)[upvp[2 * u + 1]]
			Else
				Byte Ptr Ptr(closure)[2 + u] = Byte Ptr Ptr(varp + upvp[2 * u + 1])[0]
			EndIf
		Next

		d[0] = Int(closure) ; d[1] = BlueTypeTag.FUNBOX
	End Function
	Function NEWUPV(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "NEWUPV   //"
		Local rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local convert:BlueVM(p:Byte Ptr) = Byte Ptr(Identity), vm:BlueVM = convert(bc.vm)
		Local upv:Byte Ptr = vm.mem.AllocObject(8, BlueTypeTag.UPV), d:Int Ptr = Int Ptr(stk.varp + rp[0])
		Long Ptr(upv)[0] = Long Ptr(d)[0]	'promote a value if it existed (useful for parameters)
		d[0] = Int(upv) ; d[1] = BlueTypeTag.NANBOX | BlueTypeTag.UPV
	End Function

	Function ADD(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "ADD      //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local d:Double Ptr = Double Ptr(varp + rp[0])
		Local r:Int Ptr = Int Ptr(varp + rp[1]), l:Int Ptr = Int Ptr(varp + rp[2])
		If l[1] & BlueTypeTag.NANBOX_CHK = BlueTypeTag.NANBOX Or r[1] & BlueTypeTag.NANBOX_CHK = BlueTypeTag.NANBOX
			BinopMetamethod opc.ADD, bc, retptr, Long Ptr(d), Long Ptr(r), Long Ptr(l)
		Else
			Print "  " + Double Ptr(l)[0] + " " + Double Ptr(r)[0]
			d[0] = Double Ptr(l)[0] + Double Ptr(r)[0]
		EndIf
	End Function
	Function SUB(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "SUB      //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local d:Double Ptr = Double Ptr(varp + rp[0])
		Local r:Int Ptr = Int Ptr(varp + rp[1]), l:Int Ptr = Int Ptr(varp + rp[2])
		If l[1] & BlueTypeTag.NANBOX_CHK = BlueTypeTag.NANBOX Or r[1] & BlueTypeTag.NANBOX_CHK = BlueTypeTag.NANBOX
			BinopMetamethod opc.SUB, bc, retptr, Long Ptr(d), Long Ptr(r), Long Ptr(l)
		Else
			d[0] = Double Ptr(l)[0] - Double Ptr(r)[0]
		EndIf
	End Function
	Function MUL(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function DIV(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function NMOD(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function POW(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function CAT(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function IDIV(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function BAND(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function BOR(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function BXOR(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function BSHL(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function BSHR(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function UNM(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function LNOT(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function ALEN(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function BNOT(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function UNP(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function EQ(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "EQ       //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local d:Int Ptr = Int Ptr(varp + rp[0]), r:Int Ptr = Int Ptr(varp + rp[1]), l:Int Ptr = Int Ptr(varp + rp[2])
		If l[1] & BlueTypeTag.NANBOX_CHK = BlueTypeTag.NANBOX Or r[1] & BlueTypeTag.NANBOX_CHK = BlueTypeTag.NANBOX
			BinopMetamethod opc.EQ, bc, retptr, Long Ptr(d), Long Ptr(r), Long Ptr(l)
		Else
			d[0] = Double Ptr(l)[0] = Double Ptr(r)[0] ; d[1] = BlueTypeTag.BOOLBOX
		EndIf
	End Function
	Function LT(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "LT       //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local d:Int Ptr = Int Ptr(varp + rp[0]), r:Int Ptr = Int Ptr(varp + rp[1]), l:Int Ptr = Int Ptr(varp + rp[2])
		If l[1] & BlueTypeTag.NANBOX_CHK = BlueTypeTag.NANBOX Or r[1] & BlueTypeTag.NANBOX_CHK = BlueTypeTag.NANBOX
			BinopMetamethod opc.LT, bc, retptr, Long Ptr(d), Long Ptr(r), Long Ptr(l)
		Else
			d[0] = Double Ptr(l)[0] < Double Ptr(r)[0] ; d[1] = BlueTypeTag.BOOLBOX
		EndIf
	End Function
	Function LEQ(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "LEQ      //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local d:Int Ptr = Int Ptr(varp + rp[0]), r:Int Ptr = Int Ptr(varp + rp[1]), l:Int Ptr = Int Ptr(varp + rp[2])
		If l[1] & BlueTypeTag.NANBOX_CHK = BlueTypeTag.NANBOX Or r[1] & BlueTypeTag.NANBOX_CHK = BlueTypeTag.NANBOX
			BinopMetamethod opc.LEQ, bc, retptr, Long Ptr(d), Long Ptr(r), Long Ptr(l)
		Else
			d[0] = Double Ptr(l)[0] <= Double Ptr(r)[0] ; d[1] = BlueTypeTag.BOOLBOX
		EndIf
	End Function

	Function JMP(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
		RuntimeError "wait, why are we here?"
	End Function
	Function JIF(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "JIF      //"
		Local rp:Byte Ptr = (Byte Ptr Ptr(retptr) - 4)[0] + IP_OFFSET, valp:Int Ptr = Int Ptr(stk.varp + rp[0])
		If valp[1] <> BlueTypeTag.NILBOX And Not(valp[1] = BlueTypeTag.BOOLBOX And valp[0] = 0)
			Local target:Int = Int Ptr(rp + 1)[0] ; Int Ptr(retptr)[-4] = target
		EndIf
	End Function
	Function JNOT(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "JNOT     //"
		Local rp:Byte Ptr = (Byte Ptr Ptr(retptr) - 4)[0] + IP_OFFSET, valp:Int Ptr = Int Ptr(stk.varp + rp[0])
		If valp[1] = BlueTypeTag.NILBOX Or (valp[1] = BlueTypeTag.BOOLBOX And valp[0] = 0)
			Local target:Int = Int Ptr(rp + 1)[0] ; Int Ptr(retptr)[-4] = target
		EndIf
	End Function
	Function CALL(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "CALL     //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local fp:Int Ptr = Int Ptr(varp + rp[0])
		If Not PrepareCall(fp, stk, rp, varp, retptr)	'PrepareCall sets everything up so there's nothing else to do to make the call happen
			'__call metamethod
		EndIf
	End Function
	Function TCALL(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
		'implement this properly
		CALL(stk, bc, retptr)
	End Function
	Function RET:Byte Ptr(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "RET      //"
		Local varp:Long Ptr = stk.varp
		Local rp0:Byte Ptr = Byte Ptr Ptr(retptr)[-4], rp:Byte Ptr = rp0 + IP_OFFSET

		Local oldStk:Stack = stk.prevBase
		Local retv:Long Ptr = varp + rp[0]
		If oldStk
			oldStk.retv = retv
			oldStk.retc = Int Ptr(rp + 1)[0]
			Byte Ptr Ptr(retptr)[-4] = stk.retIP
			Byte Ptr Ptr(retptr)[-3] = Byte Ptr(oldStk)
			Byte Ptr Ptr(retptr)[-2] = Byte Ptr(oldStk.func) + BYTECODE_INC
		Else	'return to native code
			Local codePage:Byte Ptr = Byte Ptr(Int(rp0) & ((Int(2^12)-1) Shl 20))
			Byte Ptr Ptr(retptr)[-4] = codePage + 4
			Return retv
		EndIf
	End Function
	Function RETVA(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
		Local convert:BlueVM(p:Byte Ptr) = Byte Ptr(Identity), vm:BlueVM = convert(stk.func.vm)
		vm.Error("vararg return not yet implemented")
	End Function
	Function POSTCALL(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "POSTCALL //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local rneed:Int = Int Ptr(rp + 1)[0], rmin:Int = Min(rneed, stk.retc)
		For Local r:Int = 0 Until rmin
			(varp + rp[0])[r] = stk.retv[r]
		'	Print "  return " + r + ": " + Double Ptr(stk.retv)[r]
		Next
		For Local r:Int = rmin Until rneed	'nil the rest
			(varp + rp[0])[r] = BlueVMMemory.NIL
		Next
	End Function
	Function VARARG(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
		Local convert:BlueVM(p:Byte Ptr) = Byte Ptr(Identity), vm:BlueVM = convert(stk.func.vm)
		vm.Error("varargs not yet implemented")
	End Function
	Function VAINIT(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
		Local convert:BlueVM(p:Byte Ptr) = Byte Ptr(Identity), vm:BlueVM = convert(stk.func.vm)
		vm.Error("vararg initialization not yet implemented")
	End Function
	Function CALLINIT(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
		Local convert:BlueVM(p:Byte Ptr) = Byte Ptr(Identity), vm:BlueVM = convert(stk.func.vm)
		vm.Error("variable-length call initialization not yet implemented")
	End Function

	Function PrepareCall:Int(fp:Int Ptr, stk:Stack, rp:Byte Ptr, varp:Long Ptr, retptr:Byte Ptr)
		If fp[1] = BlueTypeTag.NANBOX | BlueTypeTag.FUN
			Local newStk0:Stack ; Byte Ptr Ptr(Varptr(newStk0))[0] = Byte Ptr(stk) + stk.func.frameSz
			Local newStk:Stack = newStk0	'micro-opt: newStk0 can't be a register because of the unwieldy conversion

			newStk.retIP = Byte Ptr Ptr(retptr)[-4]
			newStk.prevBase = stk
			Local closure:Byte Ptr = Byte Ptr(fp[0])
			Local newBC:Bytecode = Bytecode Ptr(closure)[0]
			Local voff:Int = STACKFRAME_INC + 4 * newBC.upvars
			newStk.varp = Long Ptr(Byte Ptr(newStk) + voff)
			newStk.func = newBC

			Local argc_actual:Int = Short Ptr(rp)[1], argc_required:Int = newBC.pcount
		'	Local argc_min:Int ; If argc_actual < argc_required Then argc_min = argc_actual Else argc_min = argc_required
			Local argc_min:Int = argc_actual - argc_required ; argc_min = (argc_min & (argc_min Shr 31)) + argc_required 'branchless 32-bit Min

			Local argv:Long Ptr = varp + rp[1], destv:Long Ptr = newStk.varp
			For Local a:Int = 0 Until argc_min
				destv[a] = argv[a]
			'	Print "  arg " + a + ": " + argv[a]
			Next
			For Local a:Int = argc_min Until argc_required	'nil any unfilled parameters
				Int Ptr(destv + a)[1] = BlueTypeTag.NANBOX | BlueTypeTag.NIL
			Next
			newStk.argv = argv + argc_min	'argv should be to the varargs (if any)
			newStk.argc = argc_actual - argc_min	'argc is the number of varargs

			Local destup:Byte Ptr Ptr = Byte Ptr Ptr(Byte Ptr(newStk) + STACKFRAME_INC)
			Local upv:Byte Ptr Ptr = Byte Ptr Ptr(closure + 8)
			For Local up:Int = 0 Until newBC.upvars	'emplace upvars
				destup[up] = upv[up]
			'	Print "  upv " + up + ": " + destv[up]
			'	Print "  val: " + Double Ptr Ptr(Byte Ptr Ptr(closure) + 2 + up)[0][0]
			Next

			'note that the following OVERWRITE THE PARAMETERS (in release mode), so no touching stk from here on
			Byte Ptr Ptr(retptr)[-4] = newBC.mcode
			Byte Ptr Ptr(retptr)[-3] = Byte Ptr(newStk)
			Byte Ptr Ptr(retptr)[-2] = Byte Ptr(newBC) + BYTECODE_INC

		ElseIf fp[1] = BlueTypeTag.NANBOX | BlueTypeTag.NATFUN	'native call
			Local fun:Int(vm:BlueVM, ac:Int, av:Long Ptr, rv:Long Ptr) = Byte Ptr(fp[0])
			Local argc:Int = Short Ptr(rp)[1], argv:Long Ptr = varp + rp[1], retv:Long Ptr = Long Ptr(Byte Ptr(stk) + stk.func.frameSz)
			Local convert:BlueVM(p:Byte Ptr) = Byte Ptr(Identity), vm:BlueVM = convert(stk.func.vm)
			stk.retc = fun(vm, argc, argv, retv)
			stk.retv = retv
		Else
			Return False	'not a function; take appropriate action
		EndIf
		Return True	'all good and will auto-call when the caller returns
	End Function

	Function IndexMetamethod(bc:Bytecode, retptr:Byte Ptr, d:Long Ptr, tbl:Long, key:Long)	'prep but not execute a call
		'need to work out a way to return values from this without POSTCALL (maybe can co-opt RET to handle singles by itself)
		Local convert:BlueVM(p:Byte Ptr) = Byte Ptr(Identity), vm:BlueVM = convert(bc.vm)
		vm.Error("metamethod access isn't implemented yet")
	End Function
	Function NewindexMetamethod(bc:Bytecode, retptr:Byte Ptr, d:Long, val:Long, key:Long)	'as above
		Local convert:BlueVM(p:Byte Ptr) = Byte Ptr(Identity), vm:BlueVM = convert(bc.vm)
	'	vm.Error("metamethod access isn't implemented yet")
		'just rawset it for now
		'remember to handle nil key (what is correct?)
		Local tabp:Byte Ptr = Byte Ptr(Int(d))
		BlueTable.RawSet(vm.mem, tabp, key, val)
	End Function

	Function BinopMetamethod(op:Int, bc:Bytecode, retptr:Byte Ptr, d:Long Ptr, r:Long Ptr, l:Long Ptr)	'as above
		Local convert:BlueVM(p:Byte Ptr) = Byte Ptr(Identity), vm:BlueVM = convert(bc.vm)
		vm.Error("metamethod access isn't implemented yet")
	End Function

	Function InitOpTbl()
		opc = New BlueOpcode
		opTbl = [MOV, GETLC, SETLC, LOADK, LOADSI, LOADBOOL, LOADNIL, ..
			GETTAB, GETTABSI, SETTAB, SETTABSI, GETTABI, SETTABI, GETUPV, SETUPV, ..
			NEWTAB, CLOSURE, NEWUPV, ..
			ADD, SUB, MUL, DIV, NMOD, POW, CAT, ..
			IDIV, BAND, BOR, BXOR, BSHL, BSHR, ..
			UNM, LNOT, ALEN, BNOT, UNP, ..
			EQ, LT, LEQ, ..
			JMP, JIF, JNOT, CALL, TCALL, RETVA, RETVA, ..	'not a mistake: RET doesn't fit but we need the spacer
			POSTCALL, VARARG, VAINIT, CALLINIT]

		opTbl[opc.RET] = Byte Ptr(RET)
	End Function

	Function Identity:Byte Ptr(b:Byte Ptr)
		Return b
	End Function
	Function PointerToExtType:Byte Ptr(p:Byte Ptr)	'horrible pointer abuse (increment down so we can ignore the nonexistent vtbl)
		Return p - SizeOf(Byte Ptr(0))
	End Function
	Global BPtoS:Stack(p:Byte Ptr) = Byte Ptr(BlueJIT.PointerToExtType)
End Type

' Blue Moon
' JIT engine/opcodes

' this is the core of the runtime: the machine code generator and opcodes
' it's a halfway blend between JIT and interpreter: a basic JIT generates machine code
' for "unrolled" interpreter loops, theoretically (ha!) providing a speed boost
' it can also "inline" selected operations, coming closer to being a true JIT

' for now this is an Include rather than Import lib due to recursive dependency
' we'll fix that later

Rem
SuperStrict

Import "blueerror.bmx"
Import "bluevm.bmx"
Import etc etc...
End Rem

'Private	'may need to rename and open these (in the shared interface?)
Extern
	Type Stack
		Field retIP:Byte Ptr, prevBase:Stack, varp:Long Ptr, func:Bytecode, _:Long Ptr, argv:Long Ptr, retv:Long Ptr, argc:Short, retc:Short
	End Type
	Type Bytecode
		Field mcode:Byte Ptr, idMod:Int, kcount:Int, pcount:Int, upvars:Int, frameSz:Int, icount:Int, vm:Byte Ptr
	End Type
End Extern
'Public

Type BlueJIT Final
	Const PROLOGUESZ:Int = 25, ISIZE:Int = 5

	Global opTbl:Int(s:Stack, b:Bytecode, r:Byte Ptr)[], opc:BlueOpcode
	Global Prologue:Int[] = [ ..
		$90, $90, ..
		$8b, $44, $24, $04, ..                     'mov 4(%esp), %eax
		$89, $44, $24, $f4, ..                     'mov %eax, -12(%esp)
		$c7, $44, $24, $f8, $FF, $FF, $FF, $FF, .. 'movl ########, -8(%esp)
		$89, $64, $24, $fc, ..                     'mov %esp, -4(%esp)
		$83, $ec, $0c..                            'sub $12, %esp
	]

	Function Compile:Byte Ptr(vm:BlueVM, ins:Byte Ptr, icount:Int, bytecode:Bytecode)
		?Not x86
		RuntimeError "The Blue Moon JIT does not support your platform (x86-32 only at this time)"
		?
		Assert SizeOf(0:Long) = SizeOf(0:Double) And SizeOf(0:Int) = 4 And SizeOf(0:Long) = 8 And SizeOf(Byte Ptr(0)) = 4, ..
			"assumptions about platform datatype sizes are invalid"

		If opTbl = Null Then InitOpTbl()

		' compute executable code size
		Local codesize:Int = PROLOGUESZ, opPos:Int[icount]	'since sizes are irregular, looking up offsets is easiest
		For Local i:Int = 0 Until icount
			opPos[i] = codesize	'finalized below
			Select ins[i * 8]
				Case opc.SETTAB, opc.GETTAB
					codesize :+ ISIZE * 2
				Default
					codesize :+ ISIZE
			End Select
		Next

		' allocate executable space
		Local code:Byte Ptr = vm.mem.AllocCodeBlock(codesize)

		'finalize op offsets se we can look them up
		For Local i:Int = 0 Until icount
			opPos[i] :+ Int(code)	'direct to executable space
		Next

		' emplace prologue (used for calling in from native only)
		For Local p:Int = 0 Until PROLOGUESZ
			code[p] = Prologue[p]
		Next
		Byte Ptr Ptr(code + 14)[0] = Byte Ptr(bytecode)	'replace the ########

		' epilogue (shared location per-vm, probably already set)
		vm.mem.returnToNative[0] = $c30cc483	'add $12, %esp ; ret  - i.e. restore the stack to normal

		Local ktable:Long Ptr = Long Ptr(ins + 8 * bytecode.icount + 8 * bytecode.upvars)	'constant table

		' generate machine code!
		For Local i:Int = 0 Until icount	'emplace opcode calls and supporting bytecode data
			Local codep:Byte Ptr = Byte Ptr(opPos[i]), bytecodep:Byte Ptr = Byte Ptr(Int(codep) + vm.mem.PAGESZ)
			Local bi:Int = i * 8, op:Int = ins[bi], ip:Int Ptr = Int Ptr(ins + bi), func:Byte Ptr = opTbl[op]

			codep[0] = $e8	'call
			Byte Ptr Ptr(codep + 1)[0] = func - Int(codep + ISIZE)

			bytecodep[0] = ins[bi + 1] ; bytecodep[1] = ins[bi + 2]	'used by most, may get overwritten

			Select op
				Case opc.LOADSI, opc.CLOSURE, opc.RET, opc.POSTCALL
					Int Ptr(bytecodep + 1)[0] = ip[1]

				Case opc.LOADK
					Long Ptr Ptr(bytecodep + 1)[0] = ktable + ip[1]

				Case opc.SETTABSI, opc.GETTABSI
					Short Ptr(bytecodep)[1] = ip[1]

				Case opc.SETTAB, opc.GETTAB
					codep[ISIZE] = $90 ; Int Ptr(codep + ISIZE + 1)[0] = $90909090	'nops; the space is needed in the bytecode
					Long Ptr Ptr(bytecodep + 2)[0] = ktable + ip[1]
					Long Ptr Ptr(bytecodep + 2)[1] = Null	'inline cache space

				Case opc.GETUPV
					If Int Ptr(ins + icount * 8)[2 * bytecodep[1]] = -1
						Byte Ptr Ptr(codep + 1)[0] = Byte Ptr(BlueJIT.GETENV) - Int(codep + ISIZE)
						Byte Ptr Ptr(bytecodep + 1)[0] = Varptr(vm._ENV.val)	'note: doesn't allow _ENV to be changed from Max
					EndIf

				Case opc.CALL
					Short Ptr(bytecodep)[1] = ip[1]

				Case opc.JIF, opc.JNOT
					Int Ptr(bytecodep + 1)[0] = opPos[i + ip[1]]

				Case opc.JMP
					codep[0] = $e9	'use a true jump
					Int Ptr(codep + 1)[0] = opPos[i + ip[1]] - (opPos[i] + ISIZE)

				Default	'binary operations A = B op C
					bytecodep[2] = ip[1]
			End Select
		Next

		Return code
	End Function

	Const STACKFRAME_INC:Int = BlueVMMemory.STACKFRAMESZ + 4, BYTECODE_INC:Int = BlueVMMemory.BYTECODESZ + 4
	Const IP_OFFSET:Int = BlueVMMemory.PAGESZ - ISIZE

	'note: extern vars must take into account the vtbl offset, so that converting from a typed pointer can be a simple cast (faster)

	Function MOV(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "MOV      //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		varp[rp[0]] = varp[rp[1]]
	End Function
	Function GETLC(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "GETLC    //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		varp[rp[0]] = Long Ptr Ptr(varp + rp[1])[0][0]
	End Function
	Function SETLC(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "SETLC    //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local convert:BlueVM(p:Byte Ptr) = Byte Ptr(Identity), vm:BlueVM = convert(bc.vm)
		vm.mem.Write(Long Ptr Ptr(varp + rp[0])[0], varp[rp[1]])	'unlikely but technically possible to need this
	End Function
	Function LOADK(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "LOADK    //"
		Local rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET, varp:Long Ptr = stk.varp + rp[0], kp:Long Ptr = Long Ptr Ptr(rp + 1)[0]
		varp[0] = kp[0]
	End Function
	Function LOADSI(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "LOADSI   //"
		Local rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local val:Int = Int Ptr(rp + 1)[0]
		Double Ptr(stk.varp)[rp[0]] = Double(val)
	End Function
	Function LOADBOOL(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "LOADBOOL //"
		Local rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET, vp:Int Ptr = Int Ptr(stk.varp + rp[0])
		vp[1] = BlueTypeTag.NANBOX | BlueTypeTag.BOOL
		vp[0] = Int Ptr(rp + 1)[0]
	End Function
	Function LOADNIL(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "LOADNIL  //"
		Local rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Int Ptr(stk.varp + rp[0])[1] = BlueTypeTag.NANBOX | BlueTypeTag.NIL
	End Function

	Function GETTAB(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "GETTAB   //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET, kp:Long Ptr = Long Ptr Ptr(rp + 2)[0]
		Local tabp:Byte Ptr = Byte Ptr Ptr(varp + rp[1])[0]
		If Int Ptr(varp + rp[1])[1] = BlueTypeTag.TBLBOX
			Local keyslot:Long Ptr = Null, slot:Long Ptr = BlueTable.GetSlot(tabp, kp[0], Varptr(keyslot))
			If (slot = Null) Or (keyslot[0] <> kp[0])
				If keyslot = Long Ptr(1) Then varp[rp[0]] = BlueVMMemory.NIL ; Return	'nil key
			Else
				varp[rp[0]] = slot[0] ; Return
			EndIf
		EndIf
		IndexMetamethod bc, retptr, varp + rp[0], varp[rp[1]], kp[0]	'not in table; invoke metamethod
	End Function
	Function GETTABSI(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "GETTABSI //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local tabp:Byte Ptr = Byte Ptr Ptr(varp + rp[1])[0], key:Long ; Double Ptr(Varptr(key))[0] = Short Ptr(rp)[1]
		If Int Ptr(varp + rp[0])[1] = BlueTypeTag.TBLBOX
			Local keyslot:Long Ptr = Null, slot:Long Ptr = BlueTable.GetSlot(tabp, key, Varptr(keyslot))
			If Not (slot = Null Or (keyslot And keyslot[0] <> key)) Then varp[rp[0]] = slot[0] ; Return
			If keyslot = Long Ptr(1) Then varp[rp[0]] = BlueVMMemory.NIL ; Return
		EndIf
		IndexMetamethod bc, retptr, varp + rp[0], varp[rp[1]], key
	End Function
	Function SETTAB(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "SETTAB   //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local tabvp:Byte Ptr = varp + rp[0], kp:Long Ptr = Long Ptr Ptr(rp + 2)[0]
		If Int Ptr(tabvp)[1] = BlueTypeTag.TBLBOX
			Local keyslot:Long Ptr = Null, slot:Long Ptr = BlueTable.GetSlot(Byte Ptr Ptr(tabvp)[0], kp[0], Varptr(keyslot))
			If Not (slot = Null Or keyslot[0] <> kp[0]) Then slot[0] = varp[rp[1]] ; Return
		EndIf
		NewindexMetamethod bc, retptr, varp[rp[0]], varp[rp[1]], kp[0]	'not in table; invoke metamethod or rawset
	End Function
	Function SETTABSI(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "SETTABSI //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET, tabvp:Byte Ptr = varp + rp[0]
		Local key:Long ; Double Ptr(Varptr(key))[0] = Short Ptr(rp)[1]
		If Int Ptr(tabvp)[1] = BlueTypeTag.TBLBOX
			Local keyslot:Long Ptr = Null, slot:Long Ptr = BlueTable.GetSlot(Byte Ptr Ptr(tabvp)[0], key, Varptr(keyslot))
			If Not (slot = Null Or (keyslot And keyslot[0] <> key)) Then slot[0] = varp[rp[1]] ; Return
		EndIf
		NewindexMetamethod bc, retptr, Long Ptr(tabvp)[0], varp[rp[1]], key
	End Function
	Function GETTABI(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "GETTABI  //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local tabvp:Byte Ptr = varp + rp[1], key:Long = varp[rp[2]]
		If Int Ptr(tabvp)[1] = BlueTypeTag.TBLBOX
			Local keyslot:Long Ptr = Null, slot:Long Ptr = BlueTable.GetSlot(Byte Ptr Ptr(tabvp)[0], key, Varptr(keyslot))
			If Not (slot = Null Or (keyslot And keyslot[0] <> key)) Then varp[rp[0]] = slot[0] ; Return
		EndIf
		IndexMetamethod bc, retptr, varp + rp[0], Long Ptr(tabvp)[0], key
	End Function
	Function SETTABI(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "SETTABI  //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local tabvp:Byte Ptr = varp + rp[0], key:Long = varp[rp[2]]
		If Int Ptr(tabvp)[1] = BlueTypeTag.TBLBOX
			Local keyslot:Long Ptr = Null, slot:Long Ptr = BlueTable.GetSlot(Byte Ptr Ptr(tabvp)[0], key, Varptr(keyslot))
			If Not (slot = Null Or (keyslot And keyslot[0] <> key)) Then slot[0] = varp[rp[1]] ; Return
		EndIf
		NewindexMetamethod bc, retptr, Long Ptr(tabvp)[0], varp[rp[1]], key
	End Function
	Function GETUPV(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "GETUPV   //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local upvp:Byte Ptr Ptr = Byte Ptr Ptr(Byte Ptr(stk) + STACKFRAME_INC), valp:Long Ptr = Long Ptr(upvp[rp[1]])
		varp[rp[0]] = valp[0]
	End Function
	Function GETENV(stk:Stack, bc:Bytecode, retptr:Byte Ptr)	'"unlisted" instruction: GETUPV for _ENV is translated into this at load-time
	'	Print "GETENV   //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		varp[rp[0]] = Long Ptr Ptr(rp + 1)[0][0]	'type check should not be necessary (will be handled by following op)
	End Function
	Function SETUPV(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "SETUPV   //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local convert:BlueVM(p:Byte Ptr) = Byte Ptr(Identity), vm:BlueVM = convert(bc.vm)
		Local upvp:Byte Ptr Ptr = Byte Ptr Ptr(Byte Ptr(stk) + STACKFRAME_INC), valp:Long Ptr = Long Ptr(upvp[rp[0]])
		vm.mem.Write(valp, varp[rp[1]])
	End Function
	Function NEWTAB(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "NEWTAB   //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local convert:BlueVM(p:Byte Ptr) = Byte Ptr(Identity), vm:BlueVM = convert(bc.vm)
		Local tab:Byte Ptr = vm.mem.AllocTable(Null)
		Local d:Int Ptr = Int Ptr(varp + rp[0])
		d[0] = Int(tab) ; d[1] = BlueTypeTag.TBLBOX
	End Function
	Function CLOSURE(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "CLOSURE  //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local convert:BlueVM(p:Byte Ptr) = Byte Ptr(Identity), vm:BlueVM = convert(bc.vm)
		Local d:Int Ptr = Int Ptr(varp + rp[0])
		Local cbytecode:Bytecode = vm.funIndex[Int Ptr(rp + 1)[0]], upvp:Int Ptr = Int Ptr(Byte Ptr(cbytecode) + BYTECODE_INC + 8 * cbytecode.icount)
		Local closure:Byte Ptr = vm.mem.AllocClosure(cbytecode, cbytecode.upvars)	'upvars is always even (alignment)

		'get upvalues off the stack
		For Local u:Int = 0 Until cbytecode.upvars
			If upvp[2 * u]
				Byte Ptr Ptr(closure)[2 + u] = Byte Ptr Ptr(Byte Ptr(stk) + STACKFRAME_INC)[upvp[2 * u + 1]]
			Else
				Byte Ptr Ptr(closure)[2 + u] = Byte Ptr Ptr(varp + upvp[2 * u + 1])[0]
			EndIf
		Next

		d[0] = Int(closure) ; d[1] = BlueTypeTag.FUNBOX
	End Function
	Function NEWUPV(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "NEWUPV   //"
		Local rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local convert:BlueVM(p:Byte Ptr) = Byte Ptr(Identity), vm:BlueVM = convert(bc.vm)
		Local upv:Byte Ptr = vm.mem.AllocObject(8, BlueTypeTag.UPV), d:Int Ptr = Int Ptr(stk.varp + rp[0])
		Long Ptr(upv)[0] = Long Ptr(d)[0]	'promote a value if it existed (useful for parameters)
		d[0] = Int(upv) ; d[1] = BlueTypeTag.NANBOX | BlueTypeTag.UPV
	End Function

	Function ADD(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
		Print "ADD      //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local d:Double Ptr = Double Ptr(varp + rp[0])
		Local r:Int Ptr = Int Ptr(varp + rp[1]), l:Int Ptr = Int Ptr(varp + rp[2])
		If l[1] & BlueTypeTag.NANBOX_CHK = BlueTypeTag.NANBOX Or r[1] & BlueTypeTag.NANBOX_CHK = BlueTypeTag.NANBOX
			DebugStop
		Else
			Print "  " + Double Ptr(l)[0] + " " + Double Ptr(r)[0]
			d[0] = Double Ptr(l)[0] + Double Ptr(r)[0]
		EndIf
	End Function
	Function SUB(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function MUL(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function DIV(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function NMOD(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function POW(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function CAT(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function IDIV(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function BAND(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function BOR(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function BXOR(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function BSHL(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function BSHR(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function UNM(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function LNOT(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function ALEN(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function BNOT(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function UNP(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function EQ(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "EQ       //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = (Byte Ptr Ptr(retptr) - 4)[0] + IP_OFFSET
		Local r:Double = Double Ptr(varp)[rp[1]]
		Local l:Double = Double Ptr(varp)[rp[2]]
		Int Ptr(varp + rp[0])[0] = l = r
	End Function
	Function LT(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function LEQ(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function

	Function JMP(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
		RuntimeError "wait, why are we here?"
	End Function
	Function JIF(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "JIF      //"
		Local rp:Byte Ptr = (Byte Ptr Ptr(retptr) - 4)[0] + IP_OFFSET
		If Int Ptr(stk.varp + rp[0])[0]
			Local target:Int = Int Ptr(rp + 1)[0]
			Int Ptr(retptr)[-4] = target
		EndIf
	End Function
	Function JNOT(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function CALL(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "CALL     //"
		Local varp:Long Ptr = stk.varp, rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET
		Local fp:Int Ptr = Int Ptr(varp + rp[0])
		If Not PrepareCall(fp, stk, rp, varp, retptr)	'PrepareCall sets everything up so there's nothing else to do to make the call happen
			'__call metamethod
		EndIf
	End Function
	Function TCALL(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function RET:Byte Ptr(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "RET      //"
		Local varp:Long Ptr = stk.varp
		Local rp0:Byte Ptr = Byte Ptr Ptr(retptr)[-4], rp:Byte Ptr = rp0 + IP_OFFSET

		Local oldStk:Stack = stk.prevBase
		Local retv:Long Ptr = varp + rp[0]
		If oldStk
			oldStk.retv = retv
			oldStk.retc = Int Ptr(rp + 1)[0]
			Byte Ptr Ptr(retptr)[-4] = stk.retIP
			Byte Ptr Ptr(retptr)[-3] = Byte Ptr(oldStk)
			Byte Ptr Ptr(retptr)[-2] = Byte Ptr(oldStk.func) + BYTECODE_INC
		Else	'return to native code
			Local codePage:Byte Ptr = Byte Ptr(Int(rp0) & ((Int(2^12)-1) Shl 20))
			Byte Ptr Ptr(retptr)[-4] = codePage + 4
			Return retv
		EndIf
	End Function
	Function RETVA(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function POSTCALL(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	'	Print "POSTCALL //"
		Local varp:Long Ptr = stk.varp
		Local rp:Byte Ptr = Byte Ptr Ptr(retptr)[-4] + IP_OFFSET

		For Local r:Int = 0 Until Min(Int Ptr(rp + 1)[0], stk.retc)
			(varp + rp[0])[r] = stk.retv[r]
		'	Print "  return " + r + ": " + Double Ptr(stk.retv)[r]
		Next
		'nil the rest
	End Function
	Function VARARG(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function VAINIT(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function
	Function CALLINIT(stk:Stack, bc:Bytecode, retptr:Byte Ptr)
	End Function

	Function PrepareCall:Int(fp:Int Ptr, stk:Stack, rp:Byte Ptr, varp:Long Ptr, retptr:Byte Ptr)
		If fp[1] = BlueTypeTag.NANBOX | BlueTypeTag.FUN
			Local newStk0:Stack ; Byte Ptr Ptr(Varptr(newStk0))[0] = Byte Ptr(stk) + stk.func.frameSz
			Local newStk:Stack = newStk0	'micro-opt: newStk0 can't be a register because of the unwieldy conversion

			newStk.retIP = Byte Ptr Ptr(retptr)[-4]
			newStk.prevBase = stk
			Local closure:Byte Ptr = Byte Ptr(fp[0])
			Local newBC:Bytecode = Bytecode Ptr(closure)[0]
			Local voff:Int = STACKFRAME_INC + 4 * newBC.upvars
			newStk.varp = Long Ptr(Byte Ptr(newStk) + voff)
			newStk.func = newBC

			Local argc_actual:Int = Short Ptr(rp)[1], argc_required:Int = newBC.pcount
		'	Local argc_min:Int ; If argc_actual < argc_required Then argc_min = argc_actual Else argc_min = argc_required
			Local argc_min:Int = argc_actual - argc_required ; argc_min = (argc_min & (argc_min Shr 31)) + argc_required 'branchless 32-bit Min

			Local argv:Long Ptr = varp + rp[1], destv:Long Ptr = newStk.varp
			For Local a:Int = 0 Until argc_min
				destv[a] = argv[a]
			'	Print "  arg " + a + ": " + argv[a]
			Next
			For Local a:Int = argc_min Until argc_required	'nil any unfilled parameters
				Int Ptr(destv + a)[1] = BlueTypeTag.NANBOX | BlueTypeTag.NIL
			Next
			newStk.argv = argv + argc_min	'argv should be to the varargs (if any)
			newStk.argc = argc_actual - argc_min	'argc is the number of varargs

			Local destup:Byte Ptr Ptr = Byte Ptr Ptr(Byte Ptr(newStk) + STACKFRAME_INC)
			Local upv:Byte Ptr Ptr = Byte Ptr Ptr(closure + 8)
			For Local up:Int = 0 Until newBC.upvars	'emplace upvars
				destup[up] = upv[up]
			'	Print "  upv " + up + ": " + destv[up]
			'	Print "  val: " + Double Ptr Ptr(Byte Ptr Ptr(closure) + 2 + up)[0][0]
			Next

			'note that the following OVERWRITE THE PARAMETERS (in release mode), so no touching stk from here on
			Byte Ptr Ptr(retptr)[-4] = newBC.mcode
			Byte Ptr Ptr(retptr)[-3] = Byte Ptr(newStk)
			Byte Ptr Ptr(retptr)[-2] = Byte Ptr(newBC) + BYTECODE_INC

		ElseIf fp[1] = BlueTypeTag.NANBOX | BlueTypeTag.NATFUN	'native call
			Local fun:Int(vm:BlueVM, ac:Int, av:Long Ptr, rv:Long Ptr) = Byte Ptr(fp[0])
			Local argc:Int = Short Ptr(rp)[1], argv:Long Ptr = varp + rp[1], retv:Long Ptr = Long Ptr(Byte Ptr(stk) + stk.func.frameSz)
			Local convert:BlueVM(p:Byte Ptr) = Byte Ptr(Identity), vm:BlueVM = convert(stk.func.vm)
			stk.retc = fun(vm, argc, argv, retv)
			stk.retv = retv
		Else
			Return False	'not a function; take appropriate action
		EndIf
		Return True	'all good and will auto-call when the caller returns
	End Function

	Function IndexMetamethod(bc:Bytecode, retptr:Byte Ptr, d:Long Ptr, tbl:Long, key:Long)	'prep but not execute a call
		'need to work out a way to return values from this without POSTCALL (maybe can co-opt RET to handle singles by itself)
		Local convert:BlueVM(p:Byte Ptr) = Byte Ptr(Identity), vm:BlueVM = convert(bc.vm)
		vm.Error("metamethod access isn't implemented yet")
	End Function
	Function NewindexMetamethod(bc:Bytecode, retptr:Byte Ptr, d:Long, val:Long, key:Long)	'as above
		Local convert:BlueVM(p:Byte Ptr) = Byte Ptr(Identity), vm:BlueVM = convert(bc.vm)
	'	vm.Error("metamethod access isn't implemented yet")
		'just rawset it for now
		'remember to handle nil key (what is correct?)
		Local tabp:Byte Ptr = Byte Ptr(Int(d))
		BlueTable.RawSet(vm.mem, tabp, key, val)
	End Function

	Function BinopMetamethod(op:Int, bc:Bytecode, retptr:Byte Ptr, d:Long Ptr, r:Long Ptr, l:Long Ptr)	'as above
		Local convert:BlueVM(p:Byte Ptr) = Byte Ptr(Identity), vm:BlueVM = convert(bc.vm)
		vm.Error("metamethod access isn't implemented yet")
	End Function

	Function InitOpTbl()
		opc = New BlueOpcode
		opTbl = [MOV, GETLC, SETLC, LOADK, LOADSI, LOADBOOL, LOADNIL, ..
			GETTAB, GETTABSI, SETTAB, SETTABSI, GETTABI, SETTABI, GETUPV, SETUPV, ..
			NEWTAB, CLOSURE, NEWUPV, ..
			ADD, SUB, MUL, DIV, NMOD, POW, CAT, ..
			IDIV, BAND, BOR, BXOR, BSHL, BSHR, ..
			UNM, LNOT, ALEN, BNOT, UNP, ..
			EQ, LT, LEQ, ..
			JMP, JIF, JNOT, CALL, TCALL, RETVA, RETVA, ..	'not a mistake: RET doesn't fit but we need the spacer
			POSTCALL, VARARG, VAINIT, CALLINIT]

		opTbl[opc.RET] = Byte Ptr(RET)
	End Function

	Function Identity:Byte Ptr(b:Byte Ptr)
		Return b
	End Function
	Function PointerToExtType:Byte Ptr(p:Byte Ptr)	'horrible pointer abuse (increment down so we can ignore the nonexistent vtbl)
		Return p - SizeOf(Byte Ptr(0))
	End Function
	Global BPtoS:Stack(p:Byte Ptr) = Byte Ptr(BlueJIT.PointerToExtType)
End Type