This example is a slight twist to djpeters 'offline shadertoy'
the twist being the shader output is bound to an sdl window.
Also demonstrated is a hybrid usage of opengl with a regular
sdl window, some basic proportional placement and the pitfalls
with using multiple windows in sdl2.
Note: things do seem to have improved in sdl3 regrettably
I could not use sdl3 for this example.
snags
Well taking heed to the Chinese proverb;
man walk over mountain, trips over pebble,
webgl aka shadertoy shaders are not exactly
easy to utilize (similar to .mp3 or .avi for example)
This means you will need to do some creative
cut and pasting from shadertoy:
https://www.shadertoy.com
Which in turn means you will most likely have
to familiarize your self with:
https://www.shadertoy.com/howto
As mentioned earlier there is some funky behavior
when combining multiple windows.
navigation
f11 - fullscreen
esc - close app
arrow keys - navigate mouse pointer
return - select button (fake left mouse click)
left click lower right corner, under shader,
toggles size shader.
Compiling
Place sdl2.dll in the same folder as the code
https://github.com/libsdl-org/SDL/releases
curently v2.30.9 (windows 32bit)
also add the following three shaders, in the
same folder as the code, as seperate files
for demonstration purposes.
Save as 'theeye.gls'
Code: Select all
//courtesy https://www.shadertoy.com/view/llVczd
void mainImage( out vec4 f, in vec2 w ) {
vec2 r = iResolution.xy, p = w - r*.5;
float d = length(p) / r.y, c=1., x = pow(d, .1), y = atan(p.x, p.y) / 6.28;
for (float i = 0.; i < 3.; ++i)
c = min(c, length(fract(vec2(x - iTime*i*.003, fract(y + i*.003)*.10)*20.)*2.-1.));
f = vec4(d+20.*c*d*d*(.6-d));
}
Code: Select all
// courtesy https://www.shadertoy.com/view/td3Sz8
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 p=(3.3*fragCoord.xy-iResolution.xy)/max(iResolution.x,iResolution.y);
for(float i=3.3;i<33.;i++)
{
p+= .33/i * cos(i*p.yx+iTime*vec2(.33,.33) + vec2(.33,3.3));
}
vec3 col=vec3(.33*sin(3.3*p.x)+.33,.33*sin(3.3*p.y)+.33,sin(3.3*p.x+3.3*p.y));
fragColor=(3.3/(3.3-(3.3/3.3)))*vec4(col, 3.3);
}
Code: Select all
// courtesy https://www.shadertoy.com/view/dtSXWV
void mainImage(out vec4 C, in vec2 U)
{
vec2 R = iResolution.xy,
v = (U+U-R)/R.y, // coords
u = v*10./(1.-iMouse.y/R.y), // coords with zoom & scale
o = vec2(.5, .866), // hex offset
a = mod(u, o+o) - o, // grid 1
b = mod(u-o, o+o) - o, // grid 2
h = dot(a,a) < dot(b,b) ? a : b, // combine grids for hex tile
k = abs(h);
float p = length(u-h) * 4.0 *iTime/60., // color & shadow pattern
n = 6.2832; // pi2
vec3 c = (1.-max(k.x, dot(k, o))*2.) // hex tile
* (sin(p*n)/2.+1.) // brightness pattern
* (cos(radians(vec3(-95, -135, 170)) + p*n)*.5+.5) // color
* (2.-length(v)); // darken screen edges
C = vec4(c, 1);
}
edit: ouch playback shader was reversed, time issue, now fixed 27/11/2024
main
Code: Select all
' tweaks to shadertoy related code based on
' https://www.freebasic.net/forum/viewtopic.php?t=24462&hilit=shadertoy
' by djpeters
#include once "SDL2/SDL.bi"
#include once "GL/gl.bi"
#include once "GL/glext.bi"
' dir function and provides constants to use for the attrib_mask parameter and dates
#include once "vbcompat.bi"
dim screenwidth as integer = 1280
dim screenheight as integer = 720
dim fullscreen as boolean = false
dim glfullscreen as boolean = false
dim desktopw as integer
dim desktoph as integer
dim desktopr as integer
' get desktop info
ScreenInfo desktopw, desktoph,,,desktopr
' init hitbox button
dim boundbox as sdl_rect
dim offsety as single = 0.325f
' init mouse feedback
Type mousecoord
x As integer
y As integer
End Type
dim mouse as mousecoord
dim mousebutton as string
Dim As Integer x, y, buttons, buttonsup, res
mouse.x = 0
mouse.y = 0
' setup shadertoy
dim w2 as integer
dim h2 as integer
type vec3
as GLfloat x,y,z
end type
' internal screen dimension gl shader
dim as vec3 v3
' define opengl proc
#define glDefine(n) dim shared as PFN##n##PROC n
' shader
glDefine(glCreateShader)
glDefine(glDeleteShader)
glDefine(glShaderSource)
glDefine(glCompileShader)
glDefine(glGetShaderiv)
' program
glDefine(glCreateProgram)
glDefine(glDeleteProgram)
glDefine(glAttachShader)
glDefine(glDetachShader)
glDefine(glLinkProgram)
glDefine(glGetProgramiv)
glDefine(glUseProgram)
' uniform
glDefine(glGetUniformLocation)
glDefine(glUniform1f)
glDefine(glUniform2f)
glDefine(glUniform3f)
glDefine(glUniform4f)
glDefine(glUniform1i)
#undef glDefine
type ShaderToy
declare destructor
declare function CompileFile(Filename as string) as boolean
declare function CompileCode(Code as string) as boolean
as GLuint FragmentShader
as GLuint ProgramObject
end type
destructor ShaderToy
if ProgramObject then
glUseprogram(0)
if FragmentShader then
glDetachShader(ProgramObject,FragmentShader)
glDeleteShader(FragmentShader)
end if
glDeleteProgram(ProgramObject)
end if
end destructor
dim as ShaderToy Shader
dim as integer mx,my,mb
dim as double tStart = Timer()
dim as double tLast = tStart
dim as double tNow = Timer()
Dim As integer epoch
dim as string filename
function ShaderToy.CompileCode(UserCode as string) as boolean
dim as GLint logSize
dim as GLint status
dim as string FragmentProlog
FragmentProlog & =!"uniform float iGlobalTime; // shader playback time (in seconds) deprecated in 2017\n"
FragmentProlog & =!"uniform float iTime; // shader playback time (in seconds)\n"
FragmentProlog & =!"uniform vec3 iResolution; // viewport resolution (in pixels)\n"
FragmentProlog & =!"uniform vec4 iMouse; // mouse pixel coords. xy: current (if MLB down), zw: click\n"
FragmentProlog & =!"uniform vec4 iDate; // (year, month, day, time in seconds)\n"
FragmentProlog & =!"uniform sampler2D iChannel0;\n"
FragmentProlog & =!"uniform sampler2D iChannel1;\n"
FragmentProlog & =!"uniform sampler2D iChannel2;\n"
FragmentProlog & =!"uniform sampler2D iChannel3;\n"
dim as string FragmentEpilog
FragmentEpilog &= !"void main() {\n"
FragmentEpilog &= !" vec4 color;\n"
FragmentEpilog &= !" // call user shader\n"
FragmentEpilog &= !" mainImage(color, gl_FragCoord.xy);\n"
FragmentEpilog &= !" color.w = 1.0;\n"
FragmentEpilog &= !" gl_FragColor = color;\n"
FragmentEpilog &= !"}\n"
dim as string FragmentCode = FragmentProlog & UserCode & FragmentEpilog
' cleanup previous shader
glUseprogram(0)
glDeleteProgram(ProgramObject) : ProgramObject = 0
FragmentShader = glCreateShader(GL_FRAGMENT_SHADER)
if FragmentShader=0 then
'logentry("error", "glCreateShader(GL_FRAGMENT_SHADER) failed.")
return false
end if
dim as GLchar ptr pCode=strptr(FragmentCode)
glShaderSource (FragmentShader, 1, @pCode, NULL)
glCompileShader(FragmentShader)
glGetShaderiv (FragmentShader, GL_COMPILE_STATUS, @status)
if status = GL_FALSE then
glGetShaderiv(FragmentShader, GL_INFO_LOG_LENGTH, @logSize)
'logentry("error", "glCompileShader(FragmentShader) failed ")
glDeleteShader(FragmentShader) : FragmentShader = 0
return false
end if
ProgramObject = glCreateProgram()
if ProgramObject = 0 then
'logentry("error", "glCreateProgram() failed.")
glDeleteShader(FragmentShader) : FragmentShader = 0
return false
end if
glAttachShader(ProgramObject,FragmentShader)
glLinkProgram (ProgramObject)
glGetProgramiv(ProgramObject, GL_LINK_STATUS, @status)
if (status = GL_FALSE) then
glGetProgramiv(ProgramObject, GL_INFO_LOG_LENGTH, @logSize)
'logentry("error", "glLinkProgram() failed.")
glDeleteShader(FragmentShader) : FragmentShader = 0
return false
end if
' cleanup aka detach shader
glDeleteShader (FragmentShader) : FragmentShader = 0
logSize = 0
return true
end function
function ShaderToy.CompileFile(filename as string) as boolean
dim as string code
var hFile = FreeFile()
if open(filename,for input, as #hFile) then
'logentry("error", filename + " shader path or file not found.")
return false
end if
while not eof(hFile)
dim as string aLine
line input #hFile,aLine
code &= aLine & !"\n"
wend
close #hFile
return CompileCode(code)
end function
' setup sdl windows
SDL_Init(SDL_INIT_VIDEO)
' disable specific subsytems sdl
SDL_QuitSubSystem(SDL_INIT_AUDIO)
SDL_QuitSubSystem(SDL_INIT_HAPTIC)
' filter non used events
SDL_EventState(SDL_FINGERMOTION, SDL_IGNORE)
SDL_EventState(SDL_FINGERDOWN, SDL_IGNORE)
SDL_EventState(SDL_FINGERUP, SDL_IGNORE)
SDL_EventState(SDL_MULTIGESTURE, SDL_IGNORE)
SDL_EventState(SDL_DOLLARGESTURE, SDL_IGNORE)
SDL_EventState(SDL_JOYBALLMOTION, SDL_IGNORE)
SDL_EventState(SDL_DROPFILE, SDL_IGNORE)
' set this to capture clicks normaly going to focused innerwindow
SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1")
Dim glass as SDL_Window ptr
Dim glglass as SDL_Window ptr
Dim As SDL_Renderer Ptr renderer
Dim As SDL_Color backgrondcolor = (75, 85, 95, 0)
Dim As SDL_Event event
initsdl:
if fullscreen then
glass = SDL_CreateWindow("sdl2", null, null, screenwidth, screenheight, SDL_WINDOW_BORDERLESS)
else
glass = SDL_CreateWindow("sdl2", 100, 100, screenwidth, screenheight, SDL_WINDOW_RESIZABLE)
end if
' create a window with an opengl context
if glfullscreen then
glglass = SDL_CreateWindow("sdl2 opengl", null, null, screenwidth, screenheight, SDL_WINDOW_OPENGL _
or SDL_WINDOW_BORDERLESS or SDL_WINDOW_SHOWN)
else
glglass = SDL_CreateWindow("sdl2 opengl", 100, 100, screenwidth * 0.35f, screenheight * 0.35f, SDL_WINDOW_OPENGL _
or SDL_WINDOW_BORDERLESS or SDL_WINDOW_SHOWN)
end if
' needed to force focus on regular sdl window
SDL_RaiseWindow(glass)
SDL_SetWindowInputFocus(glass)
renderer = SDL_CreateRenderer(glass, -1, SDL_RENDERER_ACCELERATED Or SDL_RENDERER_PRESENTVSYNC)
if (renderer = NULL) Then
'logentry("error", "sdl2 could not create renderer")
SDL_Quit()
EndIf
' create opengl context bound to sdl
Dim As SDL_GLContext glContext = SDL_GL_CreateContext(glglass)
#define glProc(n) n = SDL_GL_GetProcAddress(#n) : if n = 0 then print "shadertoy opengl proc issue"
' shader
glProc(glCreateShader)
glProc(glDeleteShader)
glProc(glShaderSource)
glProc(glCompileShader)
glProc(glGetShaderiv)
' program
glProc(glCreateProgram)
glProc(glDeleteProgram)
glProc(glAttachShader)
glProc(glDetachShader)
glProc(glLinkProgram)
glProc(glGetProgramiv)
glProc(glUseProgram)
' uniform
glProc(glGetUniformLocation)
glProc(glUniform1f)
glProc(glUniform2f)
glProc(glUniform3f)
glProc(glUniform4f)
glProc(glUniform1i)
#undef glProc
' load shader code and compile it
if filename = "" then
filename = exepath + "\theeye.gls"
end if
shader.CompileFile(filename)
' main
Dim running As Boolean = True
While running
' make sure gl window is on top
SDL_RaiseWindow(glglass)
' keep gl window in place relative to regular sdl window
SDL_GetWindowPosition(glass, @w2, @h2)
if glfullscreen then
sdl_setwindowposition(glglass, w2, h2)
' set vec3 iResolution
v3.x = screenwidth
v3.y = screenheight
v3.z = v3.x/v3.y
else
SDL_SetWindowPosition(glglass,_
((w2 + screenwidth) - screenwidth * 0.35f) - (screenwidth * 0.05f),_
((h2 + screenheight) - screenheight * 0.25f) - (screenheight * 0.4f))
' set vec3 iResolution
v3.x = screenwidth * 0.35f
v3.y = screenheight * 0.35f
v3.z = v3.x/v3.y
end if
' enable shader
glUseProgram(Shader.ProgramObject)
tNow = Timer()
' get uniforms locations in shader program
var iGlobalTime = glGetUniformLocation(Shader.ProgramObject,"iGlobalTime")
var iTime = glGetUniformLocation(Shader.ProgramObject,"iTime")
var iResolution = glGetUniformLocation(Shader.ProgramObject,"iResolution")
var iMouse = glGetUniformLocation(Shader.ProgramObject,"iMouse")
var iDate = glGetUniformLocation(Shader.ProgramObject,"iDate")
glUniform3f(iResolution, v3.x, v3.y, v3.z)
glUniform4f(idate, year(now), month(now), day(now), (hour(now) * 60 * 60) + (minute(now) * 60) + second(now) + (epoch - fix(epoch)))
glUniform1f(iGlobalTime, tNow-tStart)
glUniform1f(iTime,tNow - tStart)
glRectf (-1.0, -1.0, 1.0, 1.0)
' Update the screen
SDL_GL_SwapWindow(glglass)
' render main sdl window
SDL_SetRenderDrawColor(renderer, backgrondcolor.r, backgrondcolor.g, backgrondcolor.b, backgrondcolor.a )
SDL_RenderClear(renderer)
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 0)
boundbox.x = screenwidth * 0.015f
boundbox.y = screenheight * offsety
boundbox.w = 400
boundbox.h = 100
SDL_SetRenderDrawColor(renderer, 0, 255, 0, 0)
SDL_RenderDrawRect(renderer, @boundbox)
SDL_RenderFillRect(renderer, @boundbox)
boundbox.y = screenheight * offsety + 120
SDL_SetRenderDrawColor(renderer, 0, 0, 255, 0)
SDL_RenderDrawRect(renderer, @boundbox)
SDL_RenderFillRect(renderer, @boundbox)
boundbox.y = screenheight * offsety + 240
SDL_SetRenderDrawColor(renderer, 255, 0, 0, 0)
SDL_RenderDrawRect(renderer, @boundbox)
SDL_RenderFillRect(renderer, @boundbox)
SDL_RenderPresent(renderer)
While SDL_PollEvent(@event)
select case event.type
case SDL_KEYDOWN and event.key.keysym.sym = SDLK_ESCAPE
running = False
case SDL_WINDOWEVENT and event.window.event = SDL_WINDOWEVENT_CLOSE
running = False
case SDL_WINDOWEVENT and event.window.event = SDL_WINDOWEVENT_MINIMIZED
SDL_HideWindow(glglass)
SDL_MinimizeWindow(glass)
case SDL_WINDOWEVENT and event.window.event = SDL_WINDOWEVENT_RESTORED
SDL_ShowWindow(glglass)
SDL_RestoreWindow(glass)
case SDL_WINDOWEVENT and event.window.event = SDL_WINDOWEVENT_RESIZED
SDL_GetWindowPosition(glass, @w2, @h2)
sdl_setwindowposition(glglass, w2, h2)
case SDL_KEYDOWN and event.key.keysym.sym = SDLK_F11
SDL_GL_DeleteContext(glContext)
SDL_DestroyRenderer(renderer)
SDL_DestroyWindow(glass)
SDL_DestroyWindow(glglass)
select case fullscreen
case true
' enable or disable mouse cursor in window
screenwidth = 1280
screenheight = 720
fullscreen = false
goto initsdl
case false
screenwidth = desktopw
screenheight = desktoph
fullscreen = true
sdl_setwindowposition(glglass, 0, 0)
goto initsdl
end select
' navigation keyboard arrow keys and select
case SDL_KEYDOWN and event.key.keysym.sym = SDLK_LEFT
SDL_WarpMouseInWindow(glass, mouse.x - 50, mouse.y)
case SDL_KEYDOWN and event.key.keysym.sym = SDLK_RIGHT
SDL_WarpMouseInWindow(glass, mouse.x + 50, mouse.y)
case SDL_KEYDOWN and event.key.keysym.sym = SDLK_DOWN
SDL_WarpMouseInWindow(glass, mouse.x, mouse.y + 50)
case SDL_KEYDOWN and event.key.keysym.sym = SDLK_UP
SDL_WarpMouseInWindow(glass, mouse.x, mouse.y - 50)
case SDL_KEYDOWN and event.key.keysym.sym = SDLK_RETURN
' fake mouse button left
mousebutton = "left"
case SDL_KEYUP and event.key.keysym.sym = SDLK_RETURN
' fake mouse button left
mousebutton = "left"
event.type = SDL_MOUSEBUTTONDOWN
event.button.button = SDL_BUTTON_LEFT
SDL_PushEvent(@event)
case SDL_MOUSEMOTION
mouse.x = event.motion.x
mouse.y = event.motion.y
if (mouse.y > screenheight * offsety and mouse.y < boundbox.y + 340 and mouse.x < boundbox.x + 400)_
or (mouse.x > screenwidth * 0.35f and mouse.y > screenheight * 0.5f) then
SDL_SetCursor(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND))
else
SDL_SetCursor(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW))
end if
case SDL_MOUSEBUTTONDOWN
select case event.button.button
case SDL_BUTTON_LEFT
mousebutton = "left"
if (mouse.y > screenheight * offsety and mouse.y < boundbox.y + 100 and mouse.x < boundbox.x + 400) then
filename = exepath + "\hextile.gls"
end if
if (mouse.y > screenheight * offsety + 120 and mouse.y < boundbox.y + 220 and mouse.x < boundbox.x + 400) then
filename = exepath + "\aflowof33.gls"
end if
if (mouse.y > screenheight * offsety + 240 and mouse.y < boundbox.y + 340 and mouse.x < boundbox.x + 400) then
filename = exepath + "\theeye.gls"
end if
' toggle fullscreen shader
if mouse.x > screenwidth * 0.35f and mouse.y > screenheight * 0.5f then
if glfullscreen then
glfullscreen = false
else
glfullscreen = true
end if
SDL_GL_DeleteContext(glContext)
SDL_DestroyRenderer(renderer)
SDL_DestroyWindow(glass)
SDL_DestroyWindow(glglass)
goto initsdl
end if
Shader.CompileFile(filename)
case SDL_BUTTON_MIDDLE
mousebutton = "middle"
case SDL_BUTTON_RIGHT
mousebutton = "right"
case else
end select
end select
Wend
SDL_SetWindowTitle(glass, "shadertoy sdl2 | coord: " & mouse.x & ":" & mouse.y & " | button: " + mousebutton & " | file: " & filename)
' reduce cpu usage affects shader animation
SDL_Delay(25)
Wend
' clean up
SDL_GL_DeleteContext(glContext)
SDL_DestroyRenderer(renderer)
SDL_DestroyWindow(glass)
SDL_DestroyWindow(glglass)
SDL_Quit()
end
djpeters: https://www.freebasic.net/forum/viewtop ... =shadertoy
shaders: gbirbilis, pik33 and ChunderFPV
A integrated example of the sdl shadertoy
code can be found at:
https://github.com/thrive4/app.fb.slideshow/releases