# Mandelbrot shader using GPGPU techniques
#
# Author: Gabriel Zachmann, June 2007
#
# The code is derived from ../fbo_demo/saxpy.cpp
#
# The original code can be found at:
# http://zach.in.tu-clausthal.de/teaching/cg2_08/downloads/simple_glsl_demos.tar.gz
#
# Modified and extended for Tcl3D by Paul Obermeier 2009/01/04
# See www.tcl3d.org for the Tcl3D extension.
package require Tk
package require Img
package require tcl3d
tcl3dConsoleCreate .tcl3dOutputConsole "# " "Info Messages"
# Font to be used in the Tk listbox.
set g_listFont {-family {Courier} -size 10}
set g_WinWidth 512
set g_WinHeight 512
set g_Stopwatch [tcl3dNewSwatch]
tcl3dStartSwatch $::g_Stopwatch
# command line options
set g_Opts(NumIter) 50 ; # Number of function iterations
set g_Opts(RangeCenter,0) -0.6
set g_Opts(RangeCenter,1) 0.0
set g_Opts(RangeSize) 3.0 ; # Square section of the Mandelbrot set
# Stack for holding zoom parameters.
lappend gBox(stack) [list $g_Opts(RangeCenter,0) $g_Opts(RangeCenter,1) $g_Opts(RangeSize)]
# Will be set to true in CreateCallback, if all needed OpenGL extensions are available.
set g_Opts(haveOglExtensions) false
set g_Opts(TexSize,X) $::g_WinWidth ; # Resolution of the range of the M-set
set g_Opts(TexSize,Y) $::g_WinHeight ; # Resolution of the range of the M-set
set g_ColorMethods [list "Random" "Renorm"]
set g_Opts(ColorMethod) "Renorm"
set g_Opts(BackColor) #00FF00
set g_Opts(InnerColor) #FF0000
set g_Opts(OuterColor) #0000FF
set g_Opts(BandFrequ) 0.02
set gBox(x1) 100
set gBox(x2) 300
set gBox(y1) 100
set gBox(y2) 300
set gBox(draw) 0
set attachmentpoints [list $::GL_COLOR_ATTACHMENT0_EXT $::GL_COLOR_ATTACHMENT1_EXT]
# Determine the directory of this script.
set g_ScriptDir [file dirname [info script]]
# Show errors occuring in the Togl callbacks.
proc bgerror { msg } {
tk_messageBox -icon error -type ok -message "Error: $msg\n\n$::errorInfo"
exit
}
# Print info message into widget a the bottom of the window.
proc PrintGeneralInfo {} {
global tcl_platform
if { ! [winfo exists .fr.info] } {
return
}
if { $::g_Opts(RenderMethod) eq "GLSL" } {
.fr.info configure -text [tcl3dOglGetInfoString]
} elseif { $::g_Opts(RenderMethod) eq "Tcl" } {
.fr.info configure -text \
[format "Running on %s with a Tcl implementation (Tcl %s)" \
$tcl_platform(os) [info patchlevel]]
} else {
.fr.info configure -text \
[format "Running on %s with a C implementation (Tcl %s)" \
$tcl_platform(os) [info patchlevel]]
}
#update
}
proc PrintTiming { secs } {
puts [format "%d x %d (%d pixels) with %d iterations: %.3f secs" \
$::g_Opts(TexSize,X) $::g_Opts(TexSize,Y) \
[expr $::g_Opts(TexSize,X) * $::g_Opts(TexSize,Y)] \
$::g_Opts(NumIter) $secs]
}
proc CheckExtProc { extProc } {
if { ![tcl3dOglHaveFunc $extProc] } {
puts "Extension procedure $extProc not available"
return false
}
return true
}
proc CheckGLErrors { msg } {
set errMsg [tcl3dOglGetError]
if { $errMsg eq "" } {
return
}
puts "$msg: $errMsg"
}
# Check framebuffer status.
# Copied directly out of the spec, modified to deliver a return value.
proc CheckFramebufferStatus {} {
set status [glCheckFramebufferStatusEXT GL_FRAMEBUFFER_EXT]
if { $status == $::GL_FRAMEBUFFER_COMPLETE_EXT } {
return true
} elseif { $status == $::GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT } {
puts "Framebuffer incomplete, incomplete attachment"
return false
} elseif { $status == $::GL_FRAMEBUFFER_UNSUPPORTED_EXT } {
puts "Unsupported framebuffer format"
return false
} elseif { $status == $::GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT } {
puts "Framebuffer incomplete, missing attachment"
return false
} elseif { $status == $::GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT } {
puts "Framebuffer incomplete, attached images must have same dimensions"
return false
} elseif { $status == $::GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT } {
puts "Framebuffer incomplete, attached images must have same format"
return false
} elseif { $status == $::GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT } {
puts "Framebuffer incomplete, missing draw buffer"
return false
} elseif { $status == $::GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT } {
puts "Framebuffer incomplete, missing read buffer"
return false
} else {
puts "Unknown status $status"
return false
}
}
# Set up a floating point texture with NEAREST filtering.
# (mipmaps etc. are unsupported for floating point textures)
proc SetupTexture { texID } {
# make active and bind
glBindTexture GL_TEXTURE_2D $texID
# turn off filtering and wrap modes!
glTexParameteri GL_TEXTURE_2D GL_TEXTURE_MIN_FILTER $::GL_NEAREST
glTexParameteri GL_TEXTURE_2D GL_TEXTURE_MAG_FILTER $::GL_NEAREST
glTexParameteri GL_TEXTURE_2D GL_TEXTURE_WRAP_S $::GL_CLAMP
glTexParameteri GL_TEXTURE_2D GL_TEXTURE_WRAP_T $::GL_CLAMP
# define texture with floating point format
glTexImage2D GL_TEXTURE_2D 0 $::GL_RGBA32F_ARB \
$::g_Opts(TexSize,X) $::g_Opts(TexSize,Y) 0 GL_RGBA GL_FLOAT NULL
# check if that worked
CheckGLErrors "SetupTexture"
}
# Transfer data to texture.
# Check web page for detailed explanation on the difference between ATI and NVIDIA.
proc TransferToTexture { data texID } {
# version (a): HW-accelerated on NVIDIA
glBindTexture GL_TEXTURE_2D $texID
glTexSubImage2D GL_TEXTURE_2D 0 0 0 $::g_Opts(TexSize,X) $::g_Opts(TexSize,Y) \
GL_RGBA GL_FLOAT $data
# version (b): HW-accelerated on ATI
# This version not tested with Tcl3D. Don't have a ATI card.
# glFramebufferTexture2DEXT GL_FRAMEBUFFER_EXT GL_COLOR_ATTACHMENT0_EXT GL_TEXTURE_2D $texID 0
# glDrawBuffer GL_COLOR_ATTACHMENT0_EXT
# glRasterPos2i 0 0
# glDrawPixels $::g_Opts(TexSize,X) $::g_Opts(TexSize,Y) GL_RGBA GL_FLOAT $data
# glFramebufferTexture2DEXT GL_FRAMEBUFFER_EXT GL_COLOR_ATTACHMENT0_EXT GL_TEXTURE_2D 0 0
}
proc CreateTextures {} {
# create textures
glGenTextures 2 $::g_TexId
# set up textures
SetupTexture [$::g_TexId get $::readTex]
SetupTexture [$::g_TexId get $::writeTex]
# set texenv mode from modulate (the default) to replace)
glTexEnvi GL_TEXTURE_ENV GL_TEXTURE_ENV_MODE $::GL_REPLACE
# check if something went completely wrong
CheckGLErrors "CreateTextures"
}
#
# Create framebuffer object, bind it to reroute rendering operations
# from the traditional framebuffer to the off-screen buffer
#
proc InitFBO {} {
# create FBO (off-screen framebuffer)
glGenFramebuffersEXT 1 $::g_FBO
# bind offscreen framebuffer (that is, skip the window-specific render target)
glBindFramebufferEXT GL_FRAMEBUFFER_EXT [$::g_FBO get 0]
# create 'viewport' exactly with same dimensions as the texture(s)
glViewport 0 0 $::g_Opts(TexSize,X) $::g_Opts(TexSize,Y)
# orthographic projection for 1:1 pixel=texture mapping
glMatrixMode GL_PROJECTION
glLoadIdentity
gluOrtho2D 0.0 $::g_Opts(TexSize,X) 0.0 $::g_Opts(TexSize,Y)
glMatrixMode GL_MODELVIEW
glLoadIdentity
# check if something went completely wrong
CheckGLErrors "InitFBO"
}
proc ReadShaderFile { fileName } {
set pathName [file join $::g_ScriptDir $fileName]
set realPath [tcl3dGetExtFile $pathName]
set retVal [catch {open $realPath r} fp]
if { $retVal == 0 } {
set buffer [read $fp]
close $fp
} else {
error "Cannot open shader file $realPath"
}
return $buffer
}
proc LoadAttachShader { program_id shader_id filename } {
set shadersource [ReadShaderFile $filename]
tcl3dOglShaderSource $shader_id $shadersource
glCompileShader $shader_id
glAttachShader $program_id $shader_id
}
proc SetShaders { vert_source frag_source } {
set sh_prog_id [glCreateProgram]
if { $vert_source ne "" } {
set s_id [glCreateShader GL_VERTEX_SHADER]
LoadAttachShader $sh_prog_id $s_id $vert_source
}
if { $frag_source ne "" } {
set s_id [glCreateShader GL_FRAGMENT_SHADER]
LoadAttachShader $sh_prog_id $s_id $frag_source
}
glLinkProgram $sh_prog_id
glUseProgram $sh_prog_id
return $sh_prog_id
}
#
# Performs the actual calculation.
#
proc PerformComputation {} {
# attach two textures to FBO
# because it's much faster to select a new render target via glDrawBuffer(),
# than to attach a different texture
glFramebufferTexture2DEXT GL_FRAMEBUFFER_EXT \
[lindex $::attachmentpoints $::writeTex] \
GL_TEXTURE_2D [$::g_TexId get $::writeTex] 0
CheckGLErrors "perform 0"
glFramebufferTexture2DEXT GL_FRAMEBUFFER_EXT \
[lindex $::attachmentpoints $::readTex] \
GL_TEXTURE_2D [$::g_TexId get $::readTex] 0
CheckGLErrors "perform 1"
# check if that worked
if { ! [CheckFramebufferStatus] } {
puts "glFramebufferTexture2DEXT failed!"
exit
}
# 1st phase: init array with z_1 = c
set sh_prog_id [SetShaders "mandelbrot1.vert" "mandelbrot1.frag"]
set range_center_uni [glGetUniformLocation $sh_prog_id "RangeCenter"]
glUniform2f $range_center_uni $::g_Opts(RangeCenter,0) $::g_Opts(RangeCenter,1)
set range_size_uni [glGetUniformLocation $sh_prog_id "RangeSize"]
glUniform1f $range_size_uni $::g_Opts(RangeSize)
# Calling glFinish() is only neccessary to get accurate timings,
# and we need a high number of iterations to avoid timing noise.
glFinish
tcl3dResetSwatch $::g_Stopwatch
set startTime [tcl3dLookupSwatch $::g_Stopwatch]
# fill texture with initial values (z_1)
RenderScreenSizedQuad
# swap role of the two textures (read-only source becomes
# write-only target and the other way round):
SwapTextures
# 2nd phase: do the iteration to compute z_i
set sh_prog_id [SetShaders "mandelbrot2.vert" "mandelbrot2.frag"]
set zi_minus_1_uni [glGetUniformLocation $sh_prog_id "zi_minus_1"]
glUniform1i $zi_minus_1_uni 0 ; # tex unit 0
set range_center_uni [glGetUniformLocation $sh_prog_id "RangeCenter"]
glUniform2f $range_center_uni $::g_Opts(RangeCenter,0) $::g_Opts(RangeCenter,1)
set range_size_uni [glGetUniformLocation $sh_prog_id "RangeSize"]
glUniform1f $range_size_uni $::g_Opts(RangeSize)
set cur_iteration_uni [glGetUniformLocation $sh_prog_id "curIteration"]
for { set i 1 } { $i <= $::g_Opts(NumIter) } { incr i } {
# pass in new iteration value
glUniform1f $cur_iteration_uni $i
# set render destination
glDrawBuffer [lindex $::attachmentpoints $::writeTex]
# enable "read" texture (read-only), which contains results
# from last iteration
glBindTexture GL_TEXTURE_2D [$::g_TexId get $::readTex]
# make quad filled to hit every pixel/texel
RenderScreenSizedQuad
# swap role of the two textures (read-only source becomes
# write-only target and the other way round):
SwapTextures
}
# 3rd phase: compute nice colors from the iteration count stored in each texel
set sh_prog_id [SetShaders "mandelbrot3.vert" "mandelbrot3.frag"]
set zn_uni [glGetUniformLocation $sh_prog_id "zn"]
glUniform1i $zn_uni 0
set max_iter_uni [glGetUniformLocation $sh_prog_id "MaxIterations"]
glUniform1f $max_iter_uni $::g_Opts(NumIter)
set back_color_uni [glGetUniformLocation $sh_prog_id "BackColor"]
eval glUniform4f $back_color_uni [tcl3dName2rgbaf $::g_Opts(BackColor)]
set inner_color_uni [glGetUniformLocation $sh_prog_id "InnerColor"]
eval glUniform4f $inner_color_uni [tcl3dName2rgbaf $::g_Opts(InnerColor)]
set outer_color_uni [glGetUniformLocation $sh_prog_id "OuterColor"]
eval glUniform4f $outer_color_uni [tcl3dName2rgbaf $::g_Opts(OuterColor)]
set band_frequ_uni [glGetUniformLocation $sh_prog_id "BandFrequ"]
glUniform1f $band_frequ_uni $::g_Opts(BandFrequ)
set colorMethod [glGetUniformLocation $sh_prog_id "ColorMethod"]
if { $::g_Opts(ColorMethod) eq "Random" } {
glUniform1f $colorMethod 0
} else {
glUniform1f $colorMethod 1
}
glDrawBuffer [lindex $::attachmentpoints $::writeTex]
glBindTexture GL_TEXTURE_2D [$::g_TexId get $::readTex]
RenderScreenSizedQuad
SwapTextures
# done, stop timer, calc MFLOP/s if neccessary
glFinish
set endTime [tcl3dLookupSwatch $::g_Stopwatch]
PrintTiming [expr $endTime - $startTime]
# done, just do some checks if everything went smoothly.
CheckFramebufferStatus
CheckGLErrors "PerformComputation"
#puts "[tcl3dOglGetProgramInfoLog $sh_prog_id]"
}
#
# swaps the role of the two y-textures (read-only and write-only)
# Think of "pointer swapping".
#
proc SwapTextures {} {
set h $::writeTex
set ::writeTex $::readTex
set ::readTex $h
}
proc RenderScreenSizedQuad {} {
# make quad filled to hit every pixel/texel
# (should be default but we never know)
glPolygonMode GL_FRONT GL_FILL
# and render the quad with normalized texcoords
set xSize [expr $::g_Opts(TexSize,X) -0]
set ySize [expr $::g_Opts(TexSize,Y) -0]
set xoff 0.5
set yoff 0.5
set xtoff [expr (0.5) / $::g_Opts(TexSize,X)]
set ytoff [expr (0.5 + $::g_Opts(TexSize,Y)) / $::g_Opts(TexSize,Y)]
set xtoff 0.0
set ytoff 0.0
#puts "xtoff = $xtoff ytoff = $ytoff"
glBegin GL_QUADS
glTexCoord2f [expr 0.0 + $xtoff] [expr 0.0 + $ytoff] ; glVertex2f [expr 0.0 + $xoff] [expr 0.0 + $yoff]
glTexCoord2f [expr 1.0 + $xtoff] [expr 0.0 + $ytoff] ; glVertex2f [expr $xSize + $xoff] [expr 0.0 + $yoff]
glTexCoord2f [expr 1.0 + $xtoff] [expr 1.0 + $ytoff] ; glVertex2f [expr $xSize + $xoff] [expr $ySize + $yoff]
glTexCoord2f [expr 0.0 + $xtoff] [expr 1.0 + $ytoff] ; glVertex2f [expr 0.0 + $xoff] [expr $ySize + $yoff]
glEnd
}
proc CalculateWithGLSL {} {
Reset
InitFBO ; # init off-screen framebuffer
CreateTextures ; # create textures for vectors
PerformComputation ; # and start computation
# show final result, which is in the "read" texture
glUseProgram 0 ; # use fixed-function pipeline
}
proc GetRenormalizedColor { re im n } {
if { $n >= $::g_Opts(NumIter) } {
return $::g_Opts(BackColor)
}
set innerColor [tcl3dName2rgb $::g_Opts(InnerColor)]
set outerColor [tcl3dName2rgb $::g_Opts(OuterColor)]
set len [expr {sqrt ($re*$re + $im*$im)}]
set f [expr {$n + 1.0 - log (log ($len)) / log(2) }]
set mix1 [expr {$f * $::g_Opts(BandFrequ)}]
if { $mix1 > 1.0 } {
set mix1 1.0
}
set mix2 [expr {1.0 - $mix1}]
return [tcl3dRgb2Name \
[expr {int ($mix2 * [lindex $innerColor 0] + \
$mix1 * [lindex $outerColor 0])}] \
[expr {int ($mix2 * [lindex $innerColor 1] + \
$mix1 * [lindex $outerColor 1])}] \
[expr {int ($mix2 * [lindex $innerColor 2] + \
$mix1 * [lindex $outerColor 2])}]]
}
proc GetRandomColor { n } {
global g_Colors
return $g_Colors($n)
}
proc SetupRandomColors {} {
global g_Colors
set randomGen [tcl3dNewRandomGen 0]
for { set i 0 } { $i < $::g_Opts(NumIter) } { incr i } {
set g_Colors($i) [tcl3dRgb2Name \
[tcl3dGetRandomInt $randomGen 0 255] \
[tcl3dGetRandomInt $randomGen 0 255] \
[tcl3dGetRandomInt $randomGen 0 255]]
}
set g_Colors($::g_Opts(NumIter)) $::g_Opts(BackColor)
tcl3dDeleteRandomGen $randomGen
}
proc CalculateWithC {} {
PhotoImage blank
PhotoImage config -width $::g_Opts(TexSize,X) -height $::g_Opts(TexSize,Y)
foreach { br bg bb } [tcl3dName2rgb $::g_Opts(BackColor)] { break }
foreach { ir ig ib } [tcl3dName2rgb $::g_Opts(InnerColor)] { break }
foreach { or og ob } [tcl3dName2rgb $::g_Opts(OuterColor)] { break }
tcl3dResetSwatch $::g_Stopwatch
set startTime [tcl3dLookupSwatch $::g_Stopwatch]
tcl3dUtilFractalToPhoto PhotoImage $::g_Opts(ColorMethod) \
$::g_Opts(TexSize,X) $::g_Opts(TexSize,Y) \
$::g_Opts(RangeCenter,0) $::g_Opts(RangeCenter,1) $::g_Opts(RangeSize) \
$::g_Opts(NumIter) $::g_Opts(BandFrequ) \
$br $bg $bb \
$ir $ig $ib \
$or $og $ob
set endTime [tcl3dLookupSwatch $::g_Stopwatch]
PrintTiming [expr $endTime - $startTime]
update
}
proc CalculateWithTcl {} {
PhotoImage blank
PhotoImage config -width $::g_Opts(TexSize,X) -height $::g_Opts(TexSize,Y)
SetupRandomColors
set xScale [expr {$::g_Opts(RangeSize) / $::g_Opts(TexSize,X)}]
set yScale [expr {$::g_Opts(RangeSize) / $::g_Opts(TexSize,Y)}]
set Rmin [expr {$::g_Opts(RangeCenter,0) - $::g_Opts(RangeSize)*0.5}]
set Imin [expr {$::g_Opts(RangeCenter,1) - $::g_Opts(RangeSize)*0.5}]
tcl3dResetSwatch $::g_Stopwatch
set startTime [tcl3dLookupSwatch $::g_Stopwatch]
set step 4 ;# Do interlaced drawing
set numIter $::g_Opts(NumIter)
for {set start 0} {$start < $step} {incr start} {
for {set x $start} {$x < $::g_Opts(TexSize,X)} {incr x $step} {
set c_re [expr {$Rmin + $x * $xScale}]
set data [list]
for {set y [expr {$::g_Opts(TexSize,Y)-1}]} {$y >= 0} {incr y -1} {
set c_im [expr {$Imin + $y * $yScale}]
set z_re $c_re
set z_im $c_im
for {set n 0} {$n < $numIter} {incr n} {
set z_re2 [expr {$z_re * $z_re}] ; # Have we escaped yet?
set z_im2 [expr {$z_im * $z_im}]
if {($z_re2 + $z_im2) > 4} {
break
}
set z_im [expr {2 * $z_re * $z_im + $c_im}]
set z_re [expr {$z_re2 - $z_im2 + $c_re}]
}
if { $::g_Opts(ColorMethod) eq "Random" } {
lappend data [GetRandomColor $n]
} else {
lappend data [GetRenormalizedColor $z_re $z_im $n]
}
}
PhotoImage put $data -to $x 0
}
update
}
set endTime [tcl3dLookupSwatch $::g_Stopwatch]
PrintTiming [expr $endTime - $startTime]
}
proc Update {} {
set ::g_Opts(TexSize,X) $::g_Opts(TexSize)
set ::g_Opts(TexSize,Y) $::g_Opts(TexSize)
if { $::g_Opts(RenderMethod) eq "GLSL" } {
CalculateWithGLSL
DisplayCallback .fr.toglwin
} elseif { $::g_Opts(RenderMethod) eq "Tcl" } {
CalculateWithTcl
} else {
CalculateWithC
}
}
proc CreateCallback { toglwin } {
global g_Opts
set g_Opts(haveOglExtensions) true
if { ![tcl3dOglHaveExtension $toglwin "GL_EXT_framebuffer_object"] } {
puts "Extension GL_EXT_framebuffer_object missing"
set g_Opts(haveOglExtensions) false
}
if { ![tcl3dOglHaveExtension $toglwin "GL_ARB_texture_float"] } {
puts "Extension GL_ARB_texture_float missing"
set g_Opts(haveOglExtensions) false
}
if { ! [tcl3dOglHaveExtension $toglwin "GL_ARB_texture_non_power_of_two"] } {
puts "Extension GL_ARB_texture_non_power_of_two missing"
set g_Opts(haveOglExtensions) false
}
set maxColorAttachments [tcl3dOglGetIntegerState GL_MAX_COLOR_ATTACHMENTS_EXT]
if { $maxColorAttachments < 2 } {
puts "Number of color attachments available is: $maxColorAttachments"
puts "Needed for GLSL fractal generation are 2."
set g_Opts(haveOglExtensions) false
}
set texSizeX $::g_Opts(TexSize,X)
set texSizeY $::g_Opts(TexSize,Y)
# check whether we can actually load textures of that size on the GPU
glTexImage2D GL_PROXY_TEXTURE_2D 0 $::GL_RGBA32F_ARB \
$texSizeX $texSizeY 0 $::GL_RGBA GL_FLOAT NULL
set realWidth [tcl3dVector GLint 1]
glGetTexLevelParameteriv GL_PROXY_TEXTURE_2D 0 GL_TEXTURE_WIDTH $realWidth
if { [$realWidth get 0] == 0 } {
puts [format "Can't load textures of type GL_RGBA32F_ARB (Size %d x %d)." \
$texSizeX $texSizeY]
set g_Opts(haveOglExtensions) false
}
$realWidth delete
if { ! [CheckExtProc "glGenFramebuffersEXT"] } {
set g_Opts(haveOglExtensions) false
}
if { ! [CheckExtProc "glDeleteFramebuffersEXT"] } {
set g_Opts(haveOglExtensions) false
}
if { ! [CheckExtProc "glFramebufferTexture2DEXT"] } {
set g_Opts(haveOglExtensions) false
}
if { ! $g_Opts(haveOglExtensions) } {
puts "GLSL mode disabled because of missing prerequisites."
}
}
proc ReshapeCallback { toglwin { w -1 } { h -1 } } {
global g_Opts
if { $g_Opts(RenderMethod) eq "GLSL" } {
set w [$toglwin width]
set h [$toglwin height]
}
set ::g_WinWidth $w
set ::g_WinHeight $h
if { 0 && $g_Opts(RenderMethod) eq "GLSL" } {
glViewport 0 0 $fractalSize $fractalSize
glMatrixMode GL_PROJECTION
glLoadIdentity
gluOrtho2D 0.0 $fractalSize 0.0 $fractalSize
glMatrixMode GL_MODELVIEW
glLoadIdentity
}
}
proc DisplayCallback { toglwin } {
# no clear necessary, since we draw the whole screen, and without z
glMatrixMode GL_MODELVIEW
glLoadIdentity
# default for camera = 0,0,0; looking at -z; near = -1; far = +1
# restore render destination to regular frame buffer
glBindFramebufferEXT GL_FRAMEBUFFER_EXT 0
glEnable GL_TEXTURE_2D
glBindTexture GL_TEXTURE_2D [$::g_TexId get $::readTex]
# texture states; just to make sure
glTexEnvi GL_TEXTURE_ENV GL_TEXTURE_ENV_MODE $::GL_REPLACE
glTexParameteri GL_TEXTURE_2D GL_TEXTURE_MIN_FILTER $::GL_NEAREST
glTexParameteri GL_TEXTURE_2D GL_TEXTURE_MAG_FILTER $::GL_NEAREST
glTexParameteri GL_TEXTURE_2D GL_TEXTURE_WRAP_S $::GL_CLAMP
glTexParameteri GL_TEXTURE_2D GL_TEXTURE_WRAP_T $::GL_CLAMP
glColor3f 1 1 0
RenderScreenSizedQuad
DrawBoxTogl
# no swap buffers here, because we don't animate anything
glFinish
}
proc Reset {} {
Cleanup false
# Texture identifiers
set ::g_TexId [tcl3dVector GLuint 2]
# Framebuffer object identifier
set ::g_FBO [tcl3dVector GLuint 1]
# ping pong management vars
set ::writeTex 0
set ::readTex 1
}
proc Cleanup { { fullCleanup true } } {
if { [info exists ::g_TexId] } {
glDeleteTextures 2 [$::g_TexId get 0]
$::g_TexId delete
}
if { [info exists ::g_FBO] } {
glDeleteFramebuffersEXT 1 [$::g_FBO get 0]
$::g_FBO delete
}
if { $fullCleanup } {
tcl3dDeleteSwatch $::g_Stopwatch
foreach var [info globals g_*] {
uplevel #0 unset $var
}
}
}
proc ExitProg {} {
exit
}
proc Center2Box {} {
global gBox g_Opts
set cx $g_Opts(RangeCenter,0)
set cy $g_Opts(RangeCenter,1)
set size $g_Opts(RangeSize)
set gBox(x1) [expr {$cx - $size/2}]
set gBox(y1) [expr {$cy - $size/2}]
set gBox(x2) [expr {$cx + $size/2}]
set gBox(y2) [expr {$cy + $size/2}]
}
proc Box2Center {} {
global gBox g_Opts
set x1 $gBox(x1)
set y1 $gBox(y1)
set x2 $gBox(x2)
set y2 $gBox(y2)
set dx [expr {$x2 - $x1}]
set dy [expr {$y2 - $y1}]
set g_Opts(RangeCenter,0) [expr {$x1 + $dx/2}]
set g_Opts(RangeCenter,1) [expr {$y1 + $dy/2}]
set g_Opts(RangeSize) [expr $dx>$dy? $dx : $dy]
}
proc Canvas2Z { winType x y } {
global gBox g_Opts
set xScale [expr {double($g_Opts(RangeSize)) / $::g_Opts(TexSize,X)}]
set yScale [expr {double($g_Opts(RangeSize)) / $::g_Opts(TexSize,Y)}]
set xMin [expr {$g_Opts(RangeCenter,0) - $g_Opts(RangeSize)/2}]
set yMin [expr {$g_Opts(RangeCenter,1) - $g_Opts(RangeSize)/2}]
set re [expr {$xMin + $xScale * $x}]
if { $winType eq "Togl" } {
set im [expr {$yMin + $yScale * $y}]
} else {
set im [expr {$yMin + $yScale * ($::g_WinHeight -1 - $y)}]
}
return [list $re $im]
}
proc ZoomOut { winType w } {
global gBox g_Opts
if { [llength $gBox(stack)] < 2 } {
return
}
set a [lindex $gBox(stack) end-1]
set gBox(stack) [lrange $gBox(stack) 0 end-1]
foreach {g_Opts(RangeCenter,0) g_Opts(RangeCenter,1) g_Opts(RangeSize)} $a break
Update
}
proc ZoomIn { winType w } {
global gBox g_Opts
foreach {Rmin Imin} [Canvas2Z $winType $gBox(x1) $gBox(y1)] break
foreach {Rmax Imax} [Canvas2Z $winType $gBox(x2) $gBox(y2)] break
set g_Opts(RangeCenter,0) [expr $Rmin + ($Rmax - $Rmin)/2]
set g_Opts(RangeCenter,1) [expr $Imin + ($Imax - $Imin)/2]
set g_Opts(RangeSize) [expr $Rmax - $Rmin]
puts "ZoomIn $g_Opts(RangeCenter,0) $g_Opts(RangeCenter,1) $g_Opts(RangeSize)"
lappend gBox(stack) [list $g_Opts(RangeCenter,0) $g_Opts(RangeCenter,1) $g_Opts(RangeSize)]
set gBox(draw) 0
if { $winType eq "Canvas" } {
DrawBoxCanvas $w
}
Update
}
# Draw an interactive zoom box on a Togl window. Used for GLSL render mode.
proc DrawBoxTogl {} {
global gBox
if { ! $gBox(draw) } {
return
}
glDisable GL_LIGHTING
glMatrixMode GL_PROJECTION
glPushMatrix
glLoadIdentity
gluOrtho2D 0 $::g_Opts(TexSize,X) 0 $::g_Opts(TexSize,Y)
glMatrixMode GL_MODELVIEW
glPushMatrix
glLoadIdentity
glColor3f 1 1 1
glBegin GL_LINE_LOOP
glVertex2f $gBox(x1) $gBox(y1)
glVertex2f $gBox(x2) $gBox(y1)
glVertex2f $gBox(x2) $gBox(y2)
glVertex2f $gBox(x1) $gBox(y2)
glEnd
glPopMatrix
glMatrixMode GL_PROJECTION
glPopMatrix
glMatrixMode GL_MODELVIEW
glEnable GL_LIGHTING
}
# Draw an interactive zoom box on a canvas widget. Used for Tcl and C render mode.
proc DrawBoxCanvas { w } {
global gBox
$w delete box
if { $gBox(draw) } {
$w create rect $gBox(x1) $gBox(y1) $gBox(x2) $gBox(y2) \
-outline white -tag box \
-dash 1
}
}
# Handle the interactive zoom box. Used for all render modes.
proc HandleBox { what winType w x y} {
global gBox
if { $what == 0 } { ; # Button down
set gBox(draw) 0
set gBox(x1) $x
set gBox(y1) $y
if { $winType eq "Togl" } {
set gBox(y1) [expr $::g_WinHeight -1 - $y]
}
} else { ; # Button motion
set gBox(draw) 1
set gBox(x2) $x
set gBox(y2) $y
if { $winType eq "Togl" } {
set gBox(y2) [expr $::g_WinHeight -1 - $y]
}
}
if { $winType eq "Togl" } {
$w postredisplay
} else {
DrawBoxCanvas $w
}
}
# Choose a color for
proc GetColor { buttonId which } {
global gPo
set newColor [tk_chooseColor -initialcolor $::g_Opts($which)]
if { $newColor ne "" } {
set ::g_Opts($which) $newColor
$buttonId configure -background $newColor
}
Update
}
proc InitPhoto {} {
catch { image delete PhotoImage }
image create photo PhotoImage -width $::g_Opts(TexSize,X) -height $::g_Opts(TexSize,Y)
}
proc InitCanvas {} {
catch { destroy .fr.toglwin }
if { $::g_Opts(RenderMethod) eq "GLSL" } {
togl .fr.toglwin -width $::g_Opts(TexSize,X) -height $::g_Opts(TexSize,Y) \
-createcommand CreateCallback \
-reshapecommand ReshapeCallback \
-displaycommand DisplayCallback
set mode "Togl"
} else {
InitPhoto
canvas .fr.toglwin -width $::g_Opts(TexSize,X) -height $::g_Opts(TexSize,Y) \
-borderwidth 0 -relief flat -highlightthickness 0
.fr.toglwin create image 0 0 -image PhotoImage -anchor nw -tag Image
bind .fr.toglwin <Configure> "ReshapeCallback .fr.toglwin %w %h"
set mode "Canvas"
}
bind .fr.toglwin <Button-1> [list HandleBox 0 $mode %W %x %y]
bind .fr.toglwin <B1-Motion> [list HandleBox 1 $mode %W %x %y]
bind .fr.toglwin <ButtonRelease-1> [list ZoomIn $mode %W]
bind .fr.toglwin <Button-2> [list ZoomOut $mode %W]
bind .fr.toglwin <Button-3> [list ZoomOut $mode %W]
bind .fr.toglwin <Control-Button-1> [list ZoomOut $mode %W]
bind . <Key-minus> [list ZoomOut $mode %W]
grid .fr.toglwin -row 0 -column 0 -sticky sw
}
proc InitRenderMethods {} {
global g_RenderMethods g_Opts
togl .fr.toglwin -width $::g_WinWidth -height $::g_WinHeight \
-createcommand CreateCallback
set g_RenderMethods [list "Tcl"]
set g_Opts(RenderMethod) [lindex $g_RenderMethods end]
if { [info commands tcl3dUtilFractalToPhoto] ne "" } {
lappend g_RenderMethods "C"
set g_Opts(RenderMethod) [lindex $g_RenderMethods end]
}
if { $g_Opts(haveOglExtensions) } {
lappend g_RenderMethods "GLSL"
set g_Opts(RenderMethod) [lindex $g_RenderMethods end]
}
destroy .fr.toglwin
}
proc ResetCanvas { args } {
InitCanvas
PrintGeneralInfo
Update
}
proc SaveScreenShot { w fileName } {
. configure -cursor watch
update
# Create a name on the file system, if running from within a Starpack.
set fileName [tcl3dGenExtName $fileName]
set imgName [file rootname $fileName]
set imgExt ".ppm"
set imgFmt "PPM"
set imgOpt ""
append imgName $imgExt
after 300
tcl3dWidget2File $w $imgName "" $imgFmt $imgOpt
puts "Screenshot written to: $imgName"
. configure -cursor top_left_arrow
}
proc SaveImg { w } {
global g_Opts
set fileName [format "mandelshot-%s" $g_Opts(RenderMethod)]
SaveScreenShot $w $fileName
}
frame .fr
pack .fr -expand 1 -fill both
InitRenderMethods
Reset
InitCanvas
frame .fr.btns
listbox .fr.usage -font $::g_listFont -height 2
label .fr.info
grid .fr.toglwin -row 0 -column 0 -sticky news
grid .fr.btns -row 0 -column 1 -sticky news
grid .fr.usage -row 1 -column 0 -sticky news -columnspan 2
grid .fr.info -row 2 -column 0 -sticky news -columnspan 2
grid rowconfigure .fr 0 -weight 1
grid columnconfigure .fr 0 -weight 1
.fr.usage insert end "Mouse-L Zoom in"
.fr.usage insert end "Mouse-R Step back"
frame .fr.btns.frTexSize
label .fr.btns.frTexSize.l -text "Texture size:"
tk_optionMenu .fr.btns.frTexSize.m g_Opts(TexSize) 512 256 128
pack .fr.btns.frTexSize -fill x -side top
pack .fr.btns.frTexSize.l .fr.btns.frTexSize.m -side left -expand 1 -fill x
trace add variable g_Opts(TexSize) write ResetCanvas
frame .fr.btns.frIter
label .fr.btns.frIter.l -text "Iterations:" -anchor w
spinbox .fr.btns.frIter.e -textvariable ::g_Opts(NumIter) \
-command Update -width 10 \
-from 10 -to 1000 -increment 10
bind .fr.btns.frIter.e <Key-Return> Update
pack .fr.btns.frIter -side top -fill x
pack .fr.btns.frIter.l -side left -expand 1 -fill x
pack .fr.btns.frIter.e -side left -expand 1 -fill x
frame .fr.btns.frCenterRe
label .fr.btns.frCenterRe.l -text "Center Re:" -anchor w
spinbox .fr.btns.frCenterRe.e -textvariable ::g_Opts(RangeCenter,0) \
-command Update -width 10 \
-from -3.0 -to 3.0 -increment 0.1
bind .fr.btns.frCenterRe.e <Key-Return> Update
pack .fr.btns.frCenterRe -side top -fill x
pack .fr.btns.frCenterRe.l -side left -expand 1 -fill x
pack .fr.btns.frCenterRe.e -side left -expand 1 -fill x
frame .fr.btns.frCenterIm
label .fr.btns.frCenterIm.l -text "Center Im:" -anchor w
spinbox .fr.btns.frCenterIm.e -textvariable ::g_Opts(RangeCenter,1) \
-command Update -width 10 \
-from -3.0 -to 3.0 -increment 0.1
bind .fr.btns.frCenterIm.e <Key-Return> Update
pack .fr.btns.frCenterIm -fill x -side top
pack .fr.btns.frCenterIm.l .fr.btns.frCenterIm.e -side left -expand 1 -fill x
frame .fr.btns.frRangeSize
label .fr.btns.frRangeSize.l -text "Box size:" -anchor w
spinbox .fr.btns.frRangeSize.e -textvariable ::g_Opts(RangeSize) \
-command Update -width 10 \
-from 0.05 -to 5 -increment 0.05
bind .fr.btns.frRangeSize.e <Key-Return> Update
pack .fr.btns.frRangeSize -fill x -side top
pack .fr.btns.frRangeSize.l .fr.btns.frRangeSize.e -side left -expand 1 -fill x
labelframe .fr.btns.frRenderMethod -text "Render Method"
pack .fr.btns.frRenderMethod -fill x -side top -padx 2
foreach cmd $g_RenderMethods {
radiobutton .fr.btns.frRenderMethod.b_$cmd -text $cmd -value $cmd \
-variable g_Opts(RenderMethod) -command ResetCanvas
pack .fr.btns.frRenderMethod.b_$cmd -fill x -side left
}
labelframe .fr.btns.frColorMethod -text "Coloring Method"
pack .fr.btns.frColorMethod -fill x -side top -padx 2
foreach cmd $g_ColorMethods {
radiobutton .fr.btns.frColorMethod.b_$cmd -text $cmd -value $cmd \
-variable g_Opts(ColorMethod) -command ResetCanvas
pack .fr.btns.frColorMethod.b_$cmd -fill x -side left
}
set frRenormOpts .fr.btns.frRenormOpts
set frColor $frRenormOpts.frColor
set frFrequ $frRenormOpts.frFrequ
labelframe $frRenormOpts -text "Renorm Options"
frame $frColor
frame $frFrequ
pack $frRenormOpts -fill x -side top -padx 2
pack $frColor $frFrequ -fill x -side top
label $frColor.l -text "Colors:" -anchor w
button $frColor.bi -text "..." -bg $::g_Opts(InnerColor) \
-command "GetColor $frColor.bi InnerColor"
button $frColor.bo -text "..." -bg $::g_Opts(OuterColor) \
-command "GetColor $frColor.bo OuterColor"
pack $frColor.l $frColor.bi $frColor.bo \
-side left -expand 1 -fill x
label $frFrequ.l -text "Band frequency:" -anchor w
spinbox $frFrequ.e -textvariable ::g_Opts(BandFrequ) \
-command Update -width 10 \
-from 0.001 -to 0.1 -increment 0.005
bind $frFrequ.e <Key-Return> Update
pack $frFrequ.l $frFrequ.e -side left -expand 1 -fill x
frame .fr.btns.frSaveImg
button .fr.btns.frSaveImg.b -text "Save as image" -command [list SaveImg .fr.toglwin]
pack .fr.btns.frSaveImg -fill x -side top
pack .fr.btns.frSaveImg.b -side left -expand 1 -fill x
set appName "Tcl3D demo: Mandelbrot"
wm title . $appName
# Watch For ESC Key And Quit Messages
wm protocol . WM_DELETE_WINDOW "ExitProg"
bind . <Key-Escape> "ExitProg"
#update
Update
PrintGeneralInfo
|