GLSL Shaders in BlitzMAX

BlitzMax Forums/BlitzMax Programming/GLSL Shaders in BlitzMAX

TomToad(Posted 2016) [#1]
Been trying to learn shaders and incorporate them into Max2D. Been learning a lot about them. Came up with a type to help with them. The type is far from complete. I'll add to it as I learn more.

Shader Type.bmx
format_codebox('Type TShader
Global init:Int = False 'Set to true if glew is initialized

Field ProgramObject:Int 'Store the program object
Field Name:String 'the name of this shader

'this function will create the shader.
'VertexSource is the GLSL source of the vertex shader
'PixelSource is the GLSL source of the Pixel (Fragment) Shader
'Name is the name you wish to give this shader
Function Create:TShader(VertexSource:String, PixelSource:String, Name:String = "")
Local Shader:TShader = New TShader 'Create the shader type

If Name = ""
Local id:TTypeId = TTypeId.ForObject(Shader)
Name = id.Name()
End If
Shader.Name = Name 'copy the name

If Not init 'Check if glew is initialized
glewInit
init = True
End If

Local VS:Byte Ptr = VertexSource.ToCString() 'CString of Vertex shader source
Local VL:Int = VertexSource.Length
Local PS:Byte Ptr = PixelSource.ToCString() 'CString of Pixel Shader Source
Local PL:Int = PixelSource.Length

Shader.ProgramObject = glCreateProgramObjectARB() 'Create the Shader Program Object
Local VertexShaderObject:Int = glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB) 'Create the Vertex Shader Object
Local PixelShaderObject:Int = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB) 'Create The Pixel Shader Object

glShaderSourceARB(VertexShaderObject,1,Varptr VS, Varptr VL) 'Upload the Vertex Shader source to the compiler
MemFree VS 'No longer need the CString, free the memory
glCompileShaderARB(VertexShaderObject) 'Now compile the source
?debug
LogInfo(VertexShaderObject,name+": Vertex Shader Log~n-----------------") 'Print out the logs
?

glShaderSourceARB(PixelShaderObject,1,Varptr PS, Varptr PL) 'Same thing as before, but for the Pixel Shader Source
MemFree PS
glCompileShaderARB(PixelShaderObject)
?debug
LogInfo(PixelShaderObject,name+": Pixel Shader Log~n--------------------")
?
glAttachObjectARB(Shader.ProgramObject,VertexShaderObject) 'Now we will attach the shaders to the Program Object
glAttachObjectARB(Shader.ProgramObject,PixelShaderObject)
glLinkProgramARB(Shader.ProgramObject) 'And link everything together
?debug
LogInfo(Shader.ProgramObject,name+": Program Object Log~n------------------")
?
glDeleteObjectARB(VertexShaderObject) 'No longer need these objects
glDeleteObjectARB(PixelShaderObject)
Return Shader
End Function

'This will start the shader program running
Method Start()
glUseProgramObjectARB(ProgramObject)
End Method

'Get the location of a shader uniform type
Method GetUniform:Int(UniformName:String)
Local CString:Byte Ptr = UniformName.ToCString()
Local Uniform:Int = glGetUniformLocationARB(ProgramObject,CString)
MemFree CString
Return Uniform
End Method

'Will set a uniform float to specified value
Method SetUniform1f(Uniform:Int,Value:Float)
glUniform1fARB(Uniform,Value)
End Method

'This will stop all shader programs from running
Function Stop()
gluseProgramObjectARB(0)
End Function


?debug
Function LogInfo(Obj:Int, Tag:String)
Local InfoLogLength:Int = 0
Local charsWritten:Int = 0
Local InfoLog:Byte[]
Print "~n"+Tag
glGetObjectParameterivARB(Obj, GL_OBJECT_INFO_LOG_LENGTH_ARB, Varptr infologLength);

If InfoLogLength > 0
InfoLog = New Byte[InfoLogLength]
glGetInfoLogARB(obj, InfoLogLength, Varptr charsWritten, InfoLog )
Print String.FromBytes(InfoLog,charsWritten)
End If
End Function
?

End Type



')
Demo
Circles.bmx
format_codebox('SuperStrict
Include "Shader Type.bmx"
' ****************************************************************
'
' Bouncy Bullseye Shader Demo Thingy
'
' ****************************************************************

Local xres:Int = DesktopWidth(), yres:Int = DesktopHeight()
SetGraphicsDriver GLMax2DDriver()

Graphics xres,yres,32;
HideMouse

' CREATE LINE SHADER
Local LineVertexShaderSource:String = "void main(void) { gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex ; gl_TexCoord[0] = gl_MultiTexCoord0 ; }"
Local LinePixelShaderSource:String = ..
"uniform float Timer; void main(void) { vec4 coord = gl_TexCoord[0] ; ~n" + ..
"float alpha, distance, x, y; x = coord.x - .5; y = coord.y - .5; distance = sqrt(x * x + y * y); if(distance > .5) alpha = 0.0; else alpha = 1.0; " + ..
"float p1 = sin(( Timer + coord.x *360.0 * 4.0)*3.14159/180.0) ; float p2 = sin(( Timer + coord.y *360.0 * 4.0)*3.14159/180.0) ;" + ..
"gl_FragColor = vec4(p1,p2,0.0,alpha) ; }"

Local LineShader:TShader = TShader.Create(LineVertexShaderSource,LinePixelShaderSource)


' CREATE CIRCLE SHADER
Local CircleVertexShaderSource:String = "void main(void) { gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex ; gl_TexCoord[0] = gl_MultiTexCoord0 ; }"
Local CirclePixelShaderSource:String = "uniform float Timer; void main(void) { vec4 coord = gl_TexCoord[0] ; ~n" + ..
"float x = coord.x - .5; float y = coord.y - .5; float d = sqrt(x * x + y * y); " + ..
"float red = sin((d*360.0*2.0+Timer)*3.14159/180.0); float green = sin((d*360.0*3.0+Timer)*3.14159/180.0); float blue = sin((d*360.0*4.0+Timer)*3.14159/180.0);" + ..
"gl_FragColor = vec4(red,green,blue,1.0) ; }"

Local CircleShader:TShader = TShader.Create(CircleVertexShaderSource,CirclePixelShaderSource)

' PROGRAM BEGINS
Const Speed:Int = 4

Local Image:TImage = CreateImage(64,64)
MidHandleImage Image
Local Timer:Float = 0.0
Local x:Int = 0, y:Int = 0, xd:Int = Speed, yd:Int = Speed
Local TimerLocation:String = "Timer"
Local Location:Int

Repeat
Cls ;
CircleShader.Start()
Location = CircleShader.GetUniform(TimerLocation)
CircleShader.SetUniform1f(Location,Timer)
SetRotation Sin(Timer)*120
TileImage Image,0,0
SetScale 8,8
DrawImage image,xres/2,yres/2
SetScale 1,1

LineShader.Start()
Location = LineShader.GetUniform(TimerLocation)
LineShader.SetUniform1f(Location,Timer)
SetRotation Timer
DrawImage Image,x,y;
SetRotation 0
TShader.Stop
Delay 1 ; Flip 1

Timer :+ 4
If Timer >= 360 Then Timer :- 360
x :+ xd
If x > xres-32 Then xd = -Speed
If x < 32 Then xd = Speed
y :+ yd
If y > yres - 32 Then yd = -Speed
If y < 32 Then yd = Speed
Until KeyHit(KEY_ESCAPE)
End
')

Usage is simple. First you must set BM to use OpenGL with
SetGraphicsDriver GLMax2DDriver()

Then set your graphics mode as usual.
Graphics Width, Height

Then you need to create your shader with TShader.Create()
Local Shader:TShader = TShader(VertexShaderSource,PixelShaderSource,Name)
VertexShaderSource and PixelShaderSource are strings containing the GLSL source of your shaders. Name is a name you give to this shader for the debug logging.

Now to use the shader, you just use
Shader.Start()

To stop shader processing, just use
TShader.Stop()

If you need to send data to the shader from BMax, use
Local Location:Int = Shader.GetUniform(UniformName)
Shader.SetUniform1f(Location,Value)


UniformName is the name of the variable in the shader. Currently, I have only implemented sending 1 float value at a time. Other types will be added over time.

That's about it. Look at my demo for an example of how to use the methods. :)

This post will be updated as I learn more and implement more into the Type.


BlitzSupport(Posted 2016) [#2]
That's pretty f-ing awesome, nice work!


TomToad(Posted 2016) [#3]
Getting textures to work. A fluttering flag example. Don't forget to copy the "Shader Type.bmx" file to the directory containing this source (or just Copy/Paste the whole thing where Include "Shader Type.bmx" is located).

format_codebox('SuperStrict
Include "Shader Type.bmx"

SetGraphicsDriver GLMax2DDriver()

Global Width:Int = DesktopWidth()
Global Height:Int = DesktopHeight()

Graphics Width,Height,32
HideMouse

Local VertexSource:String = "void main(void) { gl_Position = ftransform() ; gl_TexCoord[0] = gl_MultiTexCoord0 ; }"
Local PixelSource:String = "uniform float Timer; uniform sampler2D tex; void main(void) { vec4 coord = gl_TexCoord[0]; ~n"+ ..
"float space = sin((coord.x+Timer) * 3.14159 * 10.0)*0.01; ~n"+ ..
"coord.y += space; gl_FragColor = texture2D(tex, coord.xy); }"

Local FlagShader:TShader = TShader.Create(VertexSource, PixelSource, "FlagShader")

Local Flag:TImage = CreateImage(256,256)

Cls
SetColor 255,255,255
DrawRect 0,64,256,128
SetColor 255,0,0
DrawRect 120,72,16,112
DrawRect 72,120,112,16
SetColor 255,255,255
GrabImage Flag,0,0

Local Timer:Float = 0.0
FlagShader.Start()
Local TimerUniform:Int = FlagShader.GetUniform("Timer")
While Not KeyHit(KEY_ESCAPE)
FlagShader.SetUniform1f(TimerUniform,Timer)
Cls
DrawImage Flag,10,10
Flip

Timer :+ .01
If Timer > 1.0 Then Timer :- 1.0
Wend
')


BlitzSupport(Posted 2016) [#4]
Works well here. I realise it's very early days, but do you think it might be possible to do any of:

1) Apply shader to TImage or TPixel;
2) Apply to backbuffer (or copy of same)?
3) Include checks for necessary GL version?

That'd be my little wishlist anyway!


TomToad(Posted 2016) [#5]
Tried adding Klepto's RendertoTexture routine from here codearc_link('2222') to apply a shader to an entire rendered scene, but it seems to only render to half the frame. Any ideas?
format_codebox('SuperStrict
Include "Shader Type.bmx"
Type TImageBuffer
Field Image:TImage
Field rb:Int[1]
Field fb:Int[1]
Field Imageframe:TGLImageframe
Field Frame:Int = 0
Field OrigX:Int
Field OrigY:Int
Field OrigW:Int
Field OrigH:Int

Function SetBuffer:TImageBuffer(Image:TImage,Frame:Int = 0 )
Local IB:TImageBuffer = New TImageBuffer
IB.Image = Image
IB.Frame = Frame
IB.GenerateFBO()
IB.BindBuffer()
Return IB
End Function

Function Init(Width:Int,Height:Int,Bit:Int=0,Mode:Int=60)
SetGraphicsDriver(GLMax2DDriver())
Graphics Width , Height,bit,Mode
glewInit()
End Function

Method GenerateFBO()
ImageFrame = TGLImageFrame(Image.frame(Frame) )

imageframe.v0 = imageframe.v1
imageframe.v1 = 0.0

Local W:Int = Image.width
Local H:Int = Image.Height

AdjustTexSize(W , H)


glGenFramebuffersEXT(1, fb )
glGenRenderbuffersEXT(1 , rb)

glBindTexture(GL_TEXTURE_2D, Imageframe.name);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT , fb[0]) ;


glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, Imageframe.name, 0);
glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, rb[0]);
glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT24, W, H);
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT , GL_DEPTH_ATTACHMENT_EXT , GL_RENDERBUFFER_EXT , rb[0])

Local status:Int = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT)

Select status
Case GL_FRAMEBUFFER_COMPLETE_EXT
Print "all right" + " : " + Status
Case GL_FRAMEBUFFER_UNSUPPORTED_EXT
Print "choose different formats"
Default
End
EndSelect

End Method

Method BindBuffer()
GetViewport(OrigX,OrigY,OrigW,OrigH)
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT , fb[0])
glMatrixMode GL_PROJECTION
glLoadIdentity
glOrtho 0,Image.Width,Image.Width,0,-1,1
glMatrixMode GL_MODELVIEW
glViewport(0 , 0 , Image.Width , Image.Height)
glScissor 0,0, Image.Width , Image.Height
End Method

Method UnBindBuffer()
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT , 0)
glMatrixMode GL_PROJECTION
glLoadIdentity
glOrtho 0,OrigW ,Origh,0,-1,1
glMatrixMode GL_MODELVIEW
glViewport(0 , 0 , OrigW, OrigH)
glScissor 0,0, OrigW ,OrigH
End Method

Method Cls(r#=0.0,g#=0.0,b#=0.0,a#=1.0)
glClearColor r,g,b,a
glClear GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT
End Method

Method BufferWidth:Int()
Return Image.Width
End Method

Method BufferHeight:Int()
Return Image.Height
End Method

End Type


Function AdjustTexSize( width:Int Var,height:Int Var )
'calc texture size
width=Pow2Size( width )
height=Pow2Size( height )
Repeat
Local t:Int
glTexImage2D GL_PROXY_TEXTURE_2D,0,4,width,height,0,GL_RGBA,GL_UNSIGNED_BYTE,Null
glGetTexLevelParameteriv GL_PROXY_TEXTURE_2D,0,GL_TEXTURE_WIDTH,Varptr t
If t Return
If width=1 And height=1 RuntimeError "Unable to calculate tex size"
If width>1 width:/2
If height>1 height:/2
Forever
End Function

Function Pow2Size:Int( n:Int )
Local t:Int=1
While t<n
t:*2
Wend
Return t
End Function


Local Width:Int = DesktopWidth(), Height:Int = DesktopHeight()
TImageBuffer.Init(Width,Height,32)
Local image:TImage = CreateImage(Width,Height)

Local IB:TImagebuffer = TIMageBuffer.Setbuffer(image)

Local VertexSource:String = "void main(void) { gl_Position = ftransform() ; gl_TexCoord[0] = gl_MultiTexCoord0 ; }"
Local PixelSource:String = "uniform float Timer; uniform sampler2D tex; void main(void) { vec4 coord = gl_TexCoord[0]; ~n"+ ..
"float space = sin((coord.x+Timer) * 3.14159 * 10.0)*0.01; ~n"+ ..
"coord.y += space; gl_FragColor = texture2D(tex, coord.xy); }"

Local SineScreenShader:TShader = TShader.Create(VertexSource,PixelSource,"SineScreenShader")

Type TCircle
Field x:Float, y:Float, diam:Float, red:Int, green:Int, blue:Int, xd:Int, yd:Int
End Type
Local List:TList = CreateList()

For Local i:Int = 1 To 50
Local Circle:TCircle = New TCircle
Circle.diam = Rnd(20,100)
Circle.x = Rnd(0,Width-Circle.diam)
Circle.y = Rnd(0,Height - Circle.diam)
Circle.red = Rand(20,255)
Circle.green = Rand(20,255)
Circle.blue = Rand(20,255)
Local d:Int = Rand(0,1)
If d
Circle.xd = 4
Else
Circle.xd = -4
End If
d = Rand(0,1)
If d
Circle.yd = 4
Else
Circle.yd = -4
End If
List.AddLast(Circle)
Next

Local Time:Float = 0.0
While Not KeyHit(KEY_ESCAPE) And Not AppTerminate()
IB.BindBuffer()
IB.Cls()
For Local Circle:TCircle = EachIn List
SetColor Circle.Red, Circle.Green, Circle.Blue
DrawOval Circle.x, Circle.y, Circle.diam, Circle.diam
Circle.x :+ Circle.xd
If Circle.x > Width - Circle.diam Then Circle.xd = -4
If Circle.x < 0 Then Circle.xd = 4
Circle.y :+ Circle.yd
If Circle.y > Height - Circle.diam Then Circle.yd = -4
If Circle.y < 0 Then Circle.yd = 4
Next
SetColor 255,255,255
IB.UnbindBuffer()
SetViewport 0,0,Width,Height
SineScreenShader.Start()
Local Uniform:Int = SineScreenShader.GetUniform("Timer")
SineScreenShader.SetUniform1f(Uniform,Time)
DrawImage Image,0,0
TShader.Stop()
Flip
Time :+ .01
If Time > 1.0 Then Time :- 1.0
Wend
')


TomToad(Posted 2016) [#6]
Ok, if I change line 71 from
glOrtho 0,Image.Width,Image.Width,0,-1,1
to
glOrtho 0,Image.Width,Image.Height,0,-1,1

then it renders as expected. A bug in the Render2Texture routine?


TomToad(Posted 2016) [#7]
Ok, also had to add the glScalef(1,-1,1) and the glScalfef(1,1,1) in the BindBuffer and UnbindBuffer methods respectively to avoid flipping the texture as per reply in the line above.


Endive(Posted 2016) [#8]
This is just wonderful.


Derron(Posted 2016) [#9]
@bug
Yes this is a bug in the routine, I had added the fix to the r2t-examples I gave to dw' in one of his threads (including a bit more "oop" for the types).

I think at least the glscalef-thing was appended to the codearchive-entry of klepto2.


Hope you enjoy the tinker time.


bye
Ron


TomToad(Posted 2016) [#10]
Nice screensaver kinda thing :)
Decided to C&P the TShader type into the source, that way if I change the Type in post #1, I won't risk making all these samples unrunnable.
format_codebox(''*******************************************************
'
' Nice Screensaver Kinda Thing
' by TomToad
'
'*****************************************************
SuperStrict
Type TShader
Global init:Int = False 'Set to true if glew is initialized

Field ProgramObject:Int 'Store the program object
Field Name:String 'the name of this shader

'this function will create the shader.
'VertexSource is the GLSL source of the vertex shader
'PixelSource is the GLSL source of the Pixel (Fragment) Shader
'Name is the name you wish to give this shader
Function Create:TShader(VertexSource:String, PixelSource:String, Name:String = "")
Local Shader:TShader = New TShader 'Create the shader type

If Name = ""
Local id:TTypeId = TTypeId.ForObject(Shader)
Name = id.Name()
End If
Shader.Name = Name 'copy the name

If Not init 'Check if glew is initialized
glewInit
init = True
End If

Local VS:Byte Ptr = VertexSource.ToCString() 'CString of Vertex shader source
Local VL:Int = VertexSource.Length
Local PS:Byte Ptr = PixelSource.ToCString() 'CString of Pixel Shader Source
Local PL:Int = PixelSource.Length

Shader.ProgramObject = glCreateProgramObjectARB() 'Create the Shader Program Object
Local VertexShaderObject:Int = glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB) 'Create the Vertex Shader Object
Local PixelShaderObject:Int = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB) 'Create The Pixel Shader Object

glShaderSourceARB(VertexShaderObject,1,Varptr VS, Varptr VL) 'Upload the Vertex Shader source to the compiler
MemFree VS 'No longer need the CString, free the memory
glCompileShaderARB(VertexShaderObject) 'Now compile the source
?debug
LogInfo(VertexShaderObject,name+": Vertex Shader Log~n-----------------") 'Print out the logs
?

glShaderSourceARB(PixelShaderObject,1,Varptr PS, Varptr PL) 'Same thing as before, but for the Pixel Shader Source
MemFree PS
glCompileShaderARB(PixelShaderObject)
?debug
LogInfo(PixelShaderObject,name+": Pixel Shader Log~n--------------------")
?
glAttachObjectARB(Shader.ProgramObject,VertexShaderObject) 'Now we will attach the shaders to the Program Object
glAttachObjectARB(Shader.ProgramObject,PixelShaderObject)
glLinkProgramARB(Shader.ProgramObject) 'And link everything together
?debug
LogInfo(Shader.ProgramObject,name+": Program Object Log~n------------------")
?
glDeleteObjectARB(VertexShaderObject) 'No longer need these objects
glDeleteObjectARB(PixelShaderObject)
Return Shader
End Function

'This will start the shader program running
Method Start()
glUseProgramObjectARB(ProgramObject)
End Method

'Get the location of a shader uniform type
Method GetUniform:Int(UniformName:String)
Local CString:Byte Ptr = UniformName.ToCString()
Local Uniform:Int = glGetUniformLocationARB(ProgramObject,CString)
MemFree CString
Return Uniform
End Method

'Will set a uniform float to specified value
Method SetUniform1f(Uniform:Int,Value:Float)
glUniform1fARB(Uniform,Value)
End Method

'This will stop all shader programs from running
Function Stop()
gluseProgramObjectARB(0)
End Function


?debug
Function LogInfo(Obj:Int, Tag:String)
Local InfoLogLength:Int = 0
Local charsWritten:Int = 0
Local InfoLog:Byte[]
Print "~n"+Tag
glGetObjectParameterivARB(Obj, GL_OBJECT_INFO_LOG_LENGTH_ARB, Varptr infologLength);

If InfoLogLength > 0
InfoLog = New Byte[InfoLogLength]
glGetInfoLogARB(obj, InfoLogLength, Varptr charsWritten, InfoLog )
Print String.FromBytes(InfoLog,charsWritten)
End If
End Function
?

End Type



Local Width:Int = DesktopWidth(), Height:Int = DesktopHeight()
SetGraphicsDriver GLMax2DDriver()
Graphics Width,Height,32
HideMouse()

Local VertexSource:String = "void main(void) { gl_Position = ftransform() ; gl_TexCoord[0] = gl_MultiTexCoord0 ; }"
Local PixelSource:String = "uniform float Timer; uniform sampler2D tex; void main(void) { vec4 coord = gl_TexCoord[0]; ~n"+ ..
"float spacex = sin((coord.x+Timer) * 3.14159 * 20.0)*0.05; ~n"+ ..
"float spacey = sin((coord.y+Timer) * 3.14159 * 20.0)*0.05; ~n"+ ..
"coord.x += spacex; coord.y += spacey; ~n"+ ..
"gl_FragColor = texture2D(tex, coord.xy); }"

Local SineScreenShader:TShader = TShader.Create(VertexSource,PixelSource,"SineScreenShader")

Local Picture:TImage = CreateImage(512,512)
Cls
For Local y:Int = 0 To 511 Step 64
For Local x:Int = 0 To 511 Step 64
SetColor Rand(128,255),Rand(128,255),Rand(128,255)
DrawOval x,y,64,64
SetColor 0,0,0
DrawOval x+8,y+8,48,48
Next
Next
GrabImage Picture,0,0
SetColor 255,255,255
SineScreenShader.Start()
Local Uniform:Int = SineScreenShader.GetUniform("Timer")

Local scalex:Float = Float(Width)/512.0
Local Scaley:Float = Float(Height)/512.0
Local Time:Float = 0.0
SetScale scalex,scaley
While Not KeyHit(KEY_ESCAPE) And Not AppTerminate()
Cls
SineScreenShader.SetUniform1f(Uniform,Time)
DrawImage Picture,0,0
Flip
Time :+ .001
If Time > 1.0 Then Time :- 1.0
Wend
')


dw817(Posted 2016) [#11]
Great swimmers, TomToad ! This VertexSource and PixelSource is very new to me though.

I'm still waiting for full-screen random pixels with this new GL stuff. I might be able to understand it.


TomToad(Posted 2016) [#12]
full-screen random pixels
format_codebox('SuperStrict
Type TShader
Global init:Int = False 'Set to true if glew is initialized

Field ProgramObject:Int 'Store the program object
Field Name:String 'the name of this shader

'this function will create the shader.
'VertexSource is the GLSL source of the vertex shader
'PixelSource is the GLSL source of the Pixel (Fragment) Shader
'Name is the name you wish to give this shader
Function Create:TShader(VertexSource:String, PixelSource:String, Name:String = "")
Local Shader:TShader = New TShader 'Create the shader type

If Name = ""
Local id:TTypeId = TTypeId.ForObject(Shader)
Name = id.Name()
End If
Shader.Name = Name 'copy the name

If Not init 'Check if glew is initialized
glewInit
init = True
End If

Local VS:Byte Ptr = VertexSource.ToCString() 'CString of Vertex shader source
Local VL:Int = VertexSource.Length
Local PS:Byte Ptr = PixelSource.ToCString() 'CString of Pixel Shader Source
Local PL:Int = PixelSource.Length

Shader.ProgramObject = glCreateProgramObjectARB() 'Create the Shader Program Object
Local VertexShaderObject:Int = glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB) 'Create the Vertex Shader Object
Local PixelShaderObject:Int = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB) 'Create The Pixel Shader Object

glShaderSourceARB(VertexShaderObject,1,Varptr VS, Varptr VL) 'Upload the Vertex Shader source to the compiler
MemFree VS 'No longer need the CString, free the memory
glCompileShaderARB(VertexShaderObject) 'Now compile the source
?debug
LogInfo(VertexShaderObject,name+": Vertex Shader Log~n-----------------") 'Print out the logs
?

glShaderSourceARB(PixelShaderObject,1,Varptr PS, Varptr PL) 'Same thing as before, but for the Pixel Shader Source
MemFree PS
glCompileShaderARB(PixelShaderObject)
?debug
LogInfo(PixelShaderObject,name+": Pixel Shader Log~n--------------------")
?
glAttachObjectARB(Shader.ProgramObject,VertexShaderObject) 'Now we will attach the shaders to the Program Object
glAttachObjectARB(Shader.ProgramObject,PixelShaderObject)
glLinkProgramARB(Shader.ProgramObject) 'And link everything together
?debug
LogInfo(Shader.ProgramObject,name+": Program Object Log~n------------------")
?
glDeleteObjectARB(VertexShaderObject) 'No longer need these objects
glDeleteObjectARB(PixelShaderObject)
Return Shader
End Function

'This will start the shader program running
Method Start()
glUseProgramObjectARB(ProgramObject)
End Method

'Get the location of a shader uniform type
Method GetUniform:Int(UniformName:String)
Local CString:Byte Ptr = UniformName.ToCString()
Local Uniform:Int = glGetUniformLocationARB(ProgramObject,CString)
MemFree CString
Return Uniform
End Method

'Will set a uniform float to specified value
Method SetUniform1f(Uniform:Int,Value:Float)
glUniform1fARB(Uniform,Value)
End Method

'This will stop all shader programs from running
Function Stop()
gluseProgramObjectARB(0)
End Function


?debug
Function LogInfo(Obj:Int, Tag:String)
Local InfoLogLength:Int = 0
Local charsWritten:Int = 0
Local InfoLog:Byte[]
Print "~n"+Tag
glGetObjectParameterivARB(Obj, GL_OBJECT_INFO_LOG_LENGTH_ARB, Varptr infologLength);

If InfoLogLength > 0
InfoLog = New Byte[InfoLogLength]
glGetInfoLogARB(obj, InfoLogLength, Varptr charsWritten, InfoLog )
Print String.FromBytes(InfoLog,charsWritten)
End If
End Function
?

End Type


SetGraphicsDriver GLMax2DDriver()
Local Width:Int = DesktopWidth(), Height:Int = DesktopHeight()

Graphics Width,Height,32
HideMouse

Local VertexSource:String = "void main(void) { gl_Position = ftransform() ; gl_TexCoord[0] = gl_MultiTexCoord0 ; }"
Local PixelSource:String = ..
"uniform float seed;~n" + ..
"float rand(vec2 co){~n" + ..
" co.x += seed;~n" + ..
" return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);~n" + ..
"}~n" + ..
"~n" + ..
"void main(void) {~n" + ..
" vec4 coord = gl_TexCoord[0];~n" + ..
" gl_FragColor = vec4(rand(coord.xy),rand(coord.xy),rand(coord.xy),1.0);~n" + ..
" ~n" + ..
"}~n"

Local Image:TImage = CreateImage(1,1)
Local Shader:TShader = TShader.Create(VertexSource,PixelSource,"NoiseShader")

Shader.Start()
Local Random:Float = Rnd(0,1)
Local Uniform:Int = Shader.GetUniform("seed")
SetScale Width,Height
While Not KeyHit(KEY_ESCAPE) And Not AppTerminate()
Shader.SetUniform1f(Uniform,Random)
DrawImage Image,0,0
Flip
Random = Rnd(0,1)
Wend
')


dw817(Posted 2016) [#13]
Okay, thank you ! Can you make them colored please ? R G B (0-255). Do that and I'll get my magnifying glass out and try to see what's going on there.

Oh, and before you get too busy, is this the fastest way to stuff colored pixels on the screen ? (Benchmarks ?)


Pingus(Posted 2016) [#14]
Awesome stuff there. Is it doable in Dx ?


TomToad(Posted 2016) [#15]
Okay, thank you ! Can you make them colored please ? R G B (0-255). Do that and I'll get my magnifying glass out and try to see what's going on there.

Yeah, a little bug in my program. GLSL doesn't allow for a static type. Globals are reinitialized every frame. So a proper Rnd() function cannot be created on GLSL. I did find one online, but you must provide the seed for every call. I just use the x,y coordinates added to a randomly generated float passed on the CPU side. That is the reason for the Random = Rnd(0,1) in the above code.

The bug comes in to the fact that I pass the exact same seed for red, green, blue components creating various shades of grey. If you replace
" gl_FragColor = vec4(rand(coord.xy),rand(coord.xy),rand(coord.xy),1.0);~n" + ..

with
" gl_FragColor = vec4(rand(coord.xy),rand(coord.xy+.01),rand(coord.xy+.02),1.0);~n" + ..
Then you will get a nice random color screen.


Oh, and before you get too busy, is this the fastest way to stuff colored pixels on the screen ? (Benchmarks ?)

Yes and No. As shown in many examples here, and examples in your Lights Fantastic thread, Doing per pixel calculations and pushing them to the screen can be quite fast. Unfortunately, there is no way to store a pixel table in the shader so pixels can be manipulated on CPU side. You could use a texture for a table, but as a texture is already a type of table anyway, updating them would be no faster than other, non shader versions. Problably some render2Texture code would be your best bet.

I did have an idea to just push a pixel queue (an array full of pixel data) to the shader, so if you are manipulating only a few pixels, there would be no need to push the entire texture across the graphics bus. It theoretically should be at least 2X faster even when changing all pixels in a texture as the transfer would be only one way. Unfortunately, GLSL 1,2 has no way of rendering to the texture directly that I can find. Maybe a higher version of GLSL will allow it, still reading and learning.


dw817(Posted 2016) [#16]
The more I am hearing about pushing pixels in a queue, Tom, it sounds very familiar to getting HAM graphics on the Commodore Amiga.

https://en.wikipedia.org/wiki/Hold-And-Modify

Gosh I hope it doesn't get this complex ! :/


Matty(Posted 2016) [#17]
Ultimately with pushing pixels they have to cross the cpu gpu barrier somewhere....unless you do all or most processing on the gpu itself with minimal instructions sent across.


Pete Rigz(Posted 2016) [#18]
This is really interesting, I've often wanted to dive into shaders, what are your reading/learning sources?


TomToad(Posted 2016) [#19]
[quoteThis is really interesting, I've often wanted to dive into shaders, what are your reading/learning sources?
[/quote]
http://www.lighthouse3d.com/tutorials/glsl-12-tutorial/
http://nehe.gamedev.net/article/glsl_an_introduction/25007/

And a bit of googling.


TomToad(Posted 2016) [#20]
Now messing with the vertex shaders. Made a nice sine scroller.
format_codebox('SuperStrict
Type TShader
Global init:Int = False 'Set to true if glew is initialized

Field ProgramObject:Int 'Store the program object
Field Name:String 'the name of this shader

'this function will create the shader.
'VertexSource is the GLSL source of the vertex shader
'PixelSource is the GLSL source of the Pixel (Fragment) Shader
'Name is the name you wish to give this shader
Function Create:TShader(VertexSource:String, PixelSource:String, Name:String = "")
Local Shader:TShader = New TShader 'Create the shader type

If Name = ""
Local id:TTypeId = TTypeId.ForObject(Shader)
Name = id.Name()
End If
Shader.Name = Name 'copy the name

If Not init 'Check if glew is initialized
glewInit
init = True
End If

Local VS:Byte Ptr = VertexSource.ToCString() 'CString of Vertex shader source
Local VL:Int = VertexSource.Length
Local PS:Byte Ptr = PixelSource.ToCString() 'CString of Pixel Shader Source
Local PL:Int = PixelSource.Length

Shader.ProgramObject = glCreateProgramObjectARB() 'Create the Shader Program Object
Local VertexShaderObject:Int = glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB) 'Create the Vertex Shader Object
Local PixelShaderObject:Int = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB) 'Create The Pixel Shader Object

glShaderSourceARB(VertexShaderObject,1,Varptr VS, Varptr VL) 'Upload the Vertex Shader source to the compiler
MemFree VS 'No longer need the CString, free the memory
glCompileShaderARB(VertexShaderObject) 'Now compile the source
?debug
LogInfo(VertexShaderObject,name+": Vertex Shader Log~n-----------------") 'Print out the logs
?

glShaderSourceARB(PixelShaderObject,1,Varptr PS, Varptr PL) 'Same thing as before, but for the Pixel Shader Source
MemFree PS
glCompileShaderARB(PixelShaderObject)
?debug
LogInfo(PixelShaderObject,name+": Pixel Shader Log~n--------------------")
?
glAttachObjectARB(Shader.ProgramObject,VertexShaderObject) 'Now we will attach the shaders to the Program Object
glAttachObjectARB(Shader.ProgramObject,PixelShaderObject)
glLinkProgramARB(Shader.ProgramObject) 'And link everything together
?debug
LogInfo(Shader.ProgramObject,name+": Program Object Log~n------------------")
?
glDeleteObjectARB(VertexShaderObject) 'No longer need these objects
glDeleteObjectARB(PixelShaderObject)
Return Shader
End Function

'This will start the shader program running
Method Start()
glUseProgramObjectARB(ProgramObject)
End Method

'Get the location of a shader uniform type
Method GetUniform:Int(UniformName:String)
Local CString:Byte Ptr = UniformName.ToCString()
Local Uniform:Int = glGetUniformLocationARB(ProgramObject,CString)
MemFree CString
Return Uniform
End Method

'Will set a uniform float to specified value
Method SetUniform1f(Uniform:Int,Value:Float)
glUniform1fARB(Uniform,Value)
End Method

'This will stop all shader programs from running
Function Stop()
gluseProgramObjectARB(0)
End Function


?debug
Function LogInfo(Obj:Int, Tag:String)
Local InfoLogLength:Int = 0
Local charsWritten:Int = 0
Local InfoLog:Byte[]
Print "~n"+Tag
glGetObjectParameterivARB(Obj, GL_OBJECT_INFO_LOG_LENGTH_ARB, Varptr infologLength);

If InfoLogLength > 0
InfoLog = New Byte[InfoLogLength]
glGetInfoLogARB(obj, InfoLogLength, Varptr charsWritten, InfoLog )
Print String.FromBytes(InfoLog,charsWritten)
End If
End Function
?

End Type



Local VertexShader:String = "void main (void) {~n" + ..
" vec4 VPosition = gl_Vertex;~n" + ..
" VPosition.y += sin(VPosition.x*0.02)*50.0;~n" + ..
" gl_Position = gl_ModelViewProjectionMatrix * VPosition;~n" + ..
" gl_TexCoord[0] = gl_MultiTexCoord0 ;~n" + ..
"}~n"
Local FragShader:String = "uniform sampler2D tex;~n" + ..
"void main (void) {~n" + ..
" vec4 color = texture2D(tex,gl_TexCoord[0].xy);" + ..
" color.r = sin(gl_TexCoord[0].y*5.0*3.14159)*0.5+0.5;" + ..
" gl_FragColor = color.rrba;" + ..
"}~n"

SetGraphicsDriver GLMax2DDriver()
Graphics 800,600
Local Shader:TShader = TShader.Create(VertexShader,FragShader,"Vertex Stuff")
Shader.Start()
Local x:Int = 800
SetScale 3,3
While Not KeyHit(KEY_ESCAPE) And Not AppTerminate()
Cls
DrawText "Hello World! Have a bright day!",X,200
Flip
x = x - 2
If x < -800 Then End
Wend')


dw817(Posted 2016) [#21]
Trying out your code. ?? HOW are you doing this,Tom ? I see DrawText() and no other code in the main.

All I can figure is you somehow managed to reposition all the pixels on the screen to a wave pattern so anything that is plotted will be skewed from its original, meaning of course no straight lines via drawline().

Oddly enough trying to add any graphics around the DrawText() does not appear. Lines, rectangle, or ovals.

Tricky stuff. I remember in S2 I had an icon a picture of a fish and when you used it in your command set, the screen and sprites would become all wavy like that like you were underwater. :)


TomToad(Posted 2016) [#22]
In the Nice Screensaver Kinda Thing above, go to lines 116 and 117 and change the *20.0 in both strings to *2.0. Go to line 147 and change the Time :+ .001 to Time :+ .01.

Now you have a wavy underwater thing. :)

Reason why you don't see anything in the main program is because the code to modify the text is actually being run on the graphics card itself. When a shader is created, you effectively bypass the rendering pipeline. This gives you opportunity to modify the vertices and pixels before they actually reach the screen.

for the wavy text, the shader is moving the corners of each letter up and down on the y axis depending on its position on the x axis. The blue and white stripes are created by modifying the pixels based on its y position on the letter.

Reason why circles and lines are not showing up is because the shaders operate on 3D objects. Since TImages are simply 3D rectangles that are always facing the camera to appear 2D, shaders will work on them. Circles and lines are 2D constructs which shaders don't support (at least I have found none in the research I have done so far). In order to draw the 2D primitives, you must disable the running shaders to allow the normal pipeline to render them. A few small modifications to the main function will do.
format_codebox('SuperStrict
Type TShader
Global init:Int = False 'Set to true if glew is initialized

Field ProgramObject:Int 'Store the program object
Field Name:String 'the name of this shader

'this function will create the shader.
'VertexSource is the GLSL source of the vertex shader
'PixelSource is the GLSL source of the Pixel (Fragment) Shader
'Name is the name you wish to give this shader
Function Create:TShader(VertexSource:String, PixelSource:String, Name:String = "")
Local Shader:TShader = New TShader 'Create the shader type

If Name = ""
Local id:TTypeId = TTypeId.ForObject(Shader)
Name = id.Name()
End If
Shader.Name = Name 'copy the name

If Not init 'Check if glew is initialized
glewInit
init = True
End If

Local VS:Byte Ptr = VertexSource.ToCString() 'CString of Vertex shader source
Local VL:Int = VertexSource.Length
Local PS:Byte Ptr = PixelSource.ToCString() 'CString of Pixel Shader Source
Local PL:Int = PixelSource.Length

Shader.ProgramObject = glCreateProgramObjectARB() 'Create the Shader Program Object
Local VertexShaderObject:Int = glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB) 'Create the Vertex Shader Object
Local PixelShaderObject:Int = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB) 'Create The Pixel Shader Object

glShaderSourceARB(VertexShaderObject,1,Varptr VS, Varptr VL) 'Upload the Vertex Shader source to the compiler
MemFree VS 'No longer need the CString, free the memory
glCompileShaderARB(VertexShaderObject) 'Now compile the source
?debug
LogInfo(VertexShaderObject,name+": Vertex Shader Log~n-----------------") 'Print out the logs
?

glShaderSourceARB(PixelShaderObject,1,Varptr PS, Varptr PL) 'Same thing as before, but for the Pixel Shader Source
MemFree PS
glCompileShaderARB(PixelShaderObject)
?debug
LogInfo(PixelShaderObject,name+": Pixel Shader Log~n--------------------")
?
glAttachObjectARB(Shader.ProgramObject,VertexShaderObject) 'Now we will attach the shaders to the Program Object
glAttachObjectARB(Shader.ProgramObject,PixelShaderObject)
glLinkProgramARB(Shader.ProgramObject) 'And link everything together
?debug
LogInfo(Shader.ProgramObject,name+": Program Object Log~n------------------")
?
glDeleteObjectARB(VertexShaderObject) 'No longer need these objects
glDeleteObjectARB(PixelShaderObject)
Return Shader
End Function

'This will start the shader program running
Method Start()
glUseProgramObjectARB(ProgramObject)
End Method

'Get the location of a shader uniform type
Method GetUniform:Int(UniformName:String)
Local CString:Byte Ptr = UniformName.ToCString()
Local Uniform:Int = glGetUniformLocationARB(ProgramObject,CString)
MemFree CString
Return Uniform
End Method

'Will set a uniform float to specified value
Method SetUniform1f(Uniform:Int,Value:Float)
glUniform1fARB(Uniform,Value)
End Method

'This will stop all shader programs from running
Function Stop()
gluseProgramObjectARB(0)
End Function


?debug
Function LogInfo(Obj:Int, Tag:String)
Local InfoLogLength:Int = 0
Local charsWritten:Int = 0
Local InfoLog:Byte[]
Print "~n"+Tag
glGetObjectParameterivARB(Obj, GL_OBJECT_INFO_LOG_LENGTH_ARB, Varptr infologLength);

If InfoLogLength > 0
InfoLog = New Byte[InfoLogLength]
glGetInfoLogARB(obj, InfoLogLength, Varptr charsWritten, InfoLog )
Print String.FromBytes(InfoLog,charsWritten)
End If
End Function
?

End Type



Local VertexShader:String = "void main (void) {~n" + ..
" vec4 VPosition = gl_Vertex;~n" + ..
" VPosition.y += sin(VPosition.x*0.02)*50.0;~n" + ..
" gl_Position = gl_ModelViewProjectionMatrix * VPosition;~n" + ..
" gl_TexCoord[0] = gl_MultiTexCoord0 ;~n" + ..
"}~n"
Local FragShader:String = "uniform sampler2D tex;~n" + ..
"void main (void) {~n" + ..
" vec4 color = texture2D(tex,gl_TexCoord[0].xy);" + ..
" color.r = sin(gl_TexCoord[0].y*5.0*3.14159)*0.5+0.5;" + ..
" gl_FragColor = color.rrba;" + ..
"}~n"

SetGraphicsDriver GLMax2DDriver()
Graphics 800,600
Local Shader:TShader = TShader.Create(VertexShader,FragShader,"Vertex Stuff")
Local x:Int = 800
While Not KeyHit(KEY_ESCAPE) And Not AppTerminate()
Cls
'Stuff not affected by the shader
SetColor 255,0,0
DrawOval 50,50,200,200
SetColor 0,255,0
DrawOval 250,50,200,200
SetColor 255,255,255
DrawLine 5,5,795,5
DrawLine 5,5,5,595
DrawLine 795,5,795,595
DrawLine 5,595,795,595
Shader.Start() 'Enable shader processing
SetScale 3,3
DrawText "Hello World! Have a bright day!",X,200
SetScale 1,1
Shader.Stop() 'Stop shader processing
SetColor 0,0,255 'some more non shader rendering
DrawOval 450,50,200,200
Flip
x = x - 2
If x < -800 Then End
Wend')


dw817(Posted 2016) [#23]
Well now that you are mentioned it, Tom. Is there a simple way to take a TIMAGE or PIXMAP and display it as a parallelogram.

Where it appears something like this:

[IMAGE]

And would appear as if it were 3-dimensionally tilted away from the camera ?