# trislam.tcl
#
# Purpose: Determine performance curves for various methods of pushing
#          triangles and quads through the OpenGL pipeline
#
# Copyright (c) 2004-2006, Geoff Broadwell; this script is released
# as open source and may be distributed and modified under the terms
# of either the Artistic License or the GNU General Public License,
# in the same manner as Perl itself.  These licenses should have been
# distributed to you as part of your Perl distribution, and can be
# read using `perldoc perlartistic` and `perldoc perlgpl` respectively.
#
# Rewritten in Python by Bob Free
#
# Rewritten and extended for Tcl3D by Paul Obermeier, 2008

package require Tk
package require tcl3d

tcl3dConsoleCreate .tcl3dOutputConsole "# " "Console of TrislamTcl3D"

### USER CONFIG

# Primitive sizes (and therefore counts) are integer divisors of
# (A^i * B^j * C^k ...) where good A, B, C, ... are relatively prime;
# this number is used for the draw area height and width and defaults to:
#     2^4 * 3^2 * 5 = 720
# You may also want to get fewer data points across the same range by
# directly using higher powers; for example:
#     16  * 9   * 5 = 720
#
# max_powers = (16 => 1, 9 => 1, 5 => 1);
set max_powers [list 2 4 3 2 5 1]

# Maximum quads along each axis for known slow versus usually fast tests;
# chosen to be somewhat reasonable for most common settings of @max_powers
# my $max_count_slow = 60;
set max_count_slow 154
set max_count_fast 154

# Font to use to label graphs
set fontName {-family {Helvetica} -size 10}

### MISC GLOBALS

set VERSION "0.1.24"

set MIN_FRAMES  0
set MIN_SECONDS 0

set w 0
set h 0

set slow [list]
set fast [list]

set test  0
set run   0
set done  0
set ready 0

set showing_graph 0
set empty_time    0
set empty_frames  0

set optLineMode 1

# Create a stop watch for time measurement.
set stopwatch [tcl3dNewSwatch]
tcl3dStartSwatch $stopwatch 

### BENCHMARK TYPES

array set va_types {
    q  make_quads_va
    t  make_tris_va
    qs make_qs_va
    ts make_ts_va
}

array set dl_types {
    qs  draw_qs
    ts  draw_ts
    qsv draw_qs_va
    tsv draw_ts_va
}

    # Nick Draw Routine  Stats Calc  Type   Graph         Color
set testTemplates { \
    {empty draw_empty    stats_empty single {1.0 1.0 1.0} 0xFFFF} \
    {t     draw_tris     stats_tris  slow   {1.0 0.0 0.0} 0xAAAA} \
    {q     draw_quads    stats_quads slow   {1.0 0.5 0.0} 0xAAAA} \
    {ts    draw_ts       stats_ts    slow   {1.0 1.0 0.0} 0xAAAA} \
    {qs    draw_qs       stats_qs    slow   {0.0 1.0 0.0} 0xAAAA} \
    {tsd   draw_ts_dl    stats_ts    fast   {0.0 1.0 1.0} 0xAAAA} \
    {qsd   draw_qs_dl    stats_qs    fast   {0.0 0.0 1.0} 0xAAAA} \
    {tv    draw_tris_va  stats_tris  fast   {0.8 0.0 0.0} 0xFFFF} \
    {qv    draw_quads_va stats_quads fast   {0.8 0.4 0.0} 0xFFFF} \
    {tsv   draw_ts_va    stats_ts    fast   {0.8 0.8 0.0} 0xFFFF} \
    {qsv   draw_qs_va    stats_qs    fast   {0.0 0.8 0.0} 0xFFFF} \
    {tsvd  draw_ts_va_dl stats_ts    fast   {0.0 0.8 0.8} 0xFFFF} \
    {qsvd  draw_qs_va_dl stats_qs    fast   {0.0 0.0 0.8} 0xFFFF} \
}

# Utility procedures for output to console and text widget.

proc PrintTextMsg { msg newline } {
    append msg $newline
    puts -nonewline $msg ; flush stdout
}

proc OUTS { msg } {
    PrintTextMsg $msg "\n"
}

proc OUT { msg } {
    PrintTextMsg $msg "\n"
}

proc OUTN { msg } {
    PrintTextMsg $msg ""
}

# Show errors occuring in the Togl callbacks.
proc bgerror { msg } {
    tk_messageBox -icon error -type ok -message "Error: $msg"
    exit
}

# Print info message into widget at the bottom of the window.
proc PrintInfo { msg } {
    if { [winfo exists .fr.info] } {
        .fr.info configure -text $msg
    }
}

### BENCHMARK INITS

proc SelectionChanged {} {
    global labels

    set labels(selectionChanged) 1
}

proc Reset {} {
    global test run done ready
    global tests testTemplates stats
    global max total
    global labels

    set test  0
    set run   0
    set done  0
    set ready 0
    ClearTestLabels
    ClearCountLabels

    set stats [list]
    set total [list]
    set max   [list]

    # Fill the tests list with the tests as selected in the GUI.
    set tests [list]
    foreach t $testTemplates {
        set name [lindex $t 0]
        if { $labels(usetest,$name) } {
            lappend tests $t
        }
    }
    set labels(selectionChanged) 0
}

proc make_quads_va { count size } {
    for { set y 0 } { $y < $count } { incr y } {
        set y0 [expr {$y * $size}] 
        set ys [expr {$y0 + $size}] 
        for { set x 0 } { $x < $count } { incr x } {
            set x0 [expr {$x * $size}] 
            set xs [expr {$x0 + $size}] 
            lappend l $x0 $ys  $x0 $y0  $xs $y0  $xs $ys
        }
    }
    return [tcl3dVectorFromList GLfloat $l]
}

proc make_qs_va { count size } {
    for { set y 0 } { $y < $count } { incr y } {
        set y0 [expr {$y * $size}] 
        set ys [expr {$y0 + $size}] 
        for { set x 0 } { $x <= $count } { incr x } {
            set x0 [expr {$x * $size}] 
            lappend l $x0 $ys  $x0 $y0
        }
    }
    return [tcl3dVectorFromList GLfloat $l]
}

proc make_tris_va { count size } {
    for { set y 0 } { $y < $count } { incr y } {
        set y0 [expr {$y * $size}] 
        set ys [expr {$y0 + $size}] 
        for { set x 0 } { $x < $count } { incr x } {
            set x0 [expr {$x * $size}] 
            set xs [expr {$x0 + $size}] 
            lappend l $x0 $ys  $x0 $y0  $xs $ys
            lappend l $xs $ys  $x0 $y0  $xs $y0
        }
    }
    return [tcl3dVectorFromList GLfloat $l]
}

proc make_ts_va { count size } {
    for { set y 0 } { $y < $count } { incr y } {
        set y0 [expr {$y * $size}] 
        set ys [expr {$y0 + $size}] 
        for { set x 0 } { $x <= $count } { incr x } {
            set x0 [expr {$x * $size}] 
            lappend l $x0 $ys  $x0 $y0
        }
    }
    return [tcl3dVectorFromList GLfloat $l]
}

### BENCHMARK METHODS

proc draw_empty { count size } {
    return
}

proc stats_empty { count size } {
    return [list 0 0 0 0]
}

proc draw_quads { count size } {
    glBegin GL_QUADS
    for { set y 0 } { $y < $count } { incr y } {
        set y0 [expr {$y * $size}] 
        set ys [expr {$y0 + $size}] 
        for { set x 0 } { $x < $count } { incr x } {
            set x0 [expr {$x * $size}] 
            set xs [expr {$x0 + $size}] 
            glVertex2f $x0 $ys
            glVertex2f $x0 $y0
            glVertex2f $xs $y0
            glVertex2f $xs $ys
        }
    }
    glEnd
}

proc draw_quads_va { count size } {
    global vas

    set va $vas(q_$count)
    glVertexPointer 2 GL_FLOAT 0 $va
    glEnableClientState GL_VERTEX_ARRAY
    glDrawArrays GL_QUADS 0 [expr {4 * $count * $count}]
    glDisableClientState GL_VERTEX_ARRAY
}

proc stats_quads { count size } {
    set length  [expr {$size * $count}]
    set area    [expr {$length * $length}]
    set prims   [expr {$count * $count}]
    set tris    [expr {2 * $prims}]
    set verts   [expr {4 * $prims}]
    return [list $area $prims $tris $verts]
}

proc draw_qs { count size } {
    for { set y 0 } { $y < $count } { incr y } {
        set y0 [expr {$y * $size}] 
        set ys [expr {$y0 + $size}] 
        glBegin GL_QUAD_STRIP
        for { set x 0 } { $x <= $count } { incr x } {
            set x0 [expr {$x * $size}] 
            glVertex2f $x0 $ys
            glVertex2f $x0 $y0
        }
        glEnd
    }
}

proc draw_qs_va { count size } {
    global vas

    set va $vas(qs_$count)
    set row [expr {2 * ($count + 1)}]

    glVertexPointer 2 GL_FLOAT 0 $va

    glEnableClientState GL_VERTEX_ARRAY
    for { set y 0 } { $y < $count } { incr y } {
        glDrawArrays GL_QUAD_STRIP [expr {$y * $row}] $row
    }
    glDisableClientState GL_VERTEX_ARRAY
}

proc draw_qs_dl { count size } {
    global dls

    glCallList $dls(qs_$count)
}

proc draw_qs_va_dl { count size } {
    global vas dls

    set va $vas(qs_$count)

    glVertexPointer 2 GL_FLOAT 0 $va

    glEnableClientState GL_VERTEX_ARRAY
    glCallList $dls(qsv_$count)
    glDisableClientState GL_VERTEX_ARRAY
} 

proc stats_qs { count size } {
    set length [expr {$size * $count}]
    set area   [expr {$length * $length}]
    set prims  $count
    set tris   [expr {2 *  $count * $prims}]
    set verts  [expr {2 * ($count + 1) * $prims}]
    return [list $area $prims $tris $verts]
}

proc draw_tris { count size } {
    glBegin GL_TRIANGLES
    for { set y 0 } { $y < $count } { incr y } {
        set y0 [expr {$y * $size}] 
        set ys [expr {$y0 + $size}] 
        for { set x 0 } { $x < $count } { incr x } {
            set x0 [expr {$x * $size}] 
            set xs [expr {$x0 + $size}] 

            glVertex2f $x0 $ys
            glVertex2f $x0 $y0
            glVertex2f $xs $ys

            glVertex2f $xs $ys
            glVertex2f $x0 $y0
            glVertex2f $xs $y0
        }
    }
    glEnd
}

proc draw_tris_va { count size } {
    global vas

    set va $vas(t_$count)

    glVertexPointer 2 GL_FLOAT 0 $va

    glEnableClientState GL_VERTEX_ARRAY
    glDrawArrays GL_TRIANGLES 0 [expr {6 * $count * $count}]
    glDisableClientState GL_VERTEX_ARRAY
} 

proc stats_tris { count size } {
    set length [expr {$size * $count}]
    set area   [expr {$length * $length}]
    set prims  [expr {2 * $count * $count}]
    set tris   $prims
    set verts  [expr {3 * $prims}]
    return [list $area $prims $tris $verts]
}

proc draw_ts { count size } {
    for { set y 0 } { $y < $count } { incr y } {
        set y0 [expr {$y * $size}] 
        set ys [expr {$y0 + $size}] 
        glBegin GL_TRIANGLE_STRIP
        for { set x 0 } { $x <= $count } { incr x } {
            set x0 [expr {$x * $size}] 
            glVertex2f $x0 $ys
            glVertex2f $x0 $y0
        }
        glEnd
    }
} 

proc draw_ts_va { count size } {
    global vas

    set va $vas(ts_$count)
    set row [expr {2 * ($count + 1)}]

    glVertexPointer 2 GL_FLOAT 0 $va

    glEnableClientState GL_VERTEX_ARRAY
    for { set y 0 } { $y < $count } { incr y } {
        glDrawArrays GL_TRIANGLE_STRIP [expr {$y * $row}] $row
    }
    glDisableClientState GL_VERTEX_ARRAY
}

proc draw_ts_dl { count size } {
    global dls

    glCallList $dls(ts_$count)
} 

proc draw_ts_va_dl { count size } {
    global vas dls

    set va $vas(ts_$count)

    glVertexPointer 2 GL_FLOAT 0 $va

    glEnableClientState GL_VERTEX_ARRAY
    glCallList $dls(tsv_$count)
    glDisableClientState GL_VERTEX_ARRAY
}

proc stats_ts { count size } {
    set length [expr {$size * $count}]
    set area   [expr {$length * $length}]
    set prims  $count
    set tris   [expr {2 *  $count * $prims}]
    set verts  [expr {2 * ($count + 1) * $prims}]
    return [list $area $prims $tris $verts]
}

# STATISTICS

proc fixup_stats {} {
    global ready
    global stats total
    global max
    global empty_time empty_frames

    set stat [lindex $stats 0]
    if { [lindex $stat 0] eq "empty" } {
        set empty_time [lindex $stat 2]
        set empty_frames [lindex $stat 3]
        set empty_tpf [expr $empty_time / $empty_frames]
        #puts "Found empty stat: $empty_time $empty_frames $empty_tpf"
        while { [lindex $stat 0] eq "empty" } {
            set stats [lrange $stats 1 end]
            set stat [lindex $stats 0]
        }
    } else {
        set empty_time   0
        set empty_frames 0
        set empty_tpf    0
    }

    lappend total "totl,"
    lappend total 0
    for { set i 0 } { $i < 12 } { incr i } {
        lappend total 0.0
    }

    lappend max "max"
    lappend max 0
    for { set i 0 } { $i < 12 } { incr i } {
        lappend max 0.0
    }

    set newstats [list]
    foreach stat $stats {
        foreach {name count secs frames pixpf prmpf tpf vpf} $stat { break }

        # Subtract out empty loop time, and loop if negative result
        # $time -= $empty_tpf * $frames;
        if { $secs <= 0 } {
            for { set i 0 } { $i < 5 } { incr i } {
                lappend stat 0
            }
            continue
        }

        # Calc "work", the geometric mean of pixels and vertices
        set workpf [expr sqrt (double ($pixpf) * $vpf)]

        # Calc fps
        set fps [expr double($frames) / $secs]

        # Calc other perf stats
        set pixps [expr $pixpf * $fps]
        set prmps [expr $prmpf * $fps]
        set tps   [expr $tpf * $fps]
        set vps   [expr $vpf * $fps]
        set wps   [expr $workpf * $fps]
        
        # Add them to stat row
        lappend stat $fps $pixps $prmps $tps $vps $wps

        # Convert per frame counts to totals
        for { set i 4 } { $i < 8 } { incr i } {
            lset stat $i [expr [lindex $stat $i] * double($frames)]
        }

        # Update running totals
        for { set i 2 } { $i < 8 } { incr i } {
            lset total $i [expr [lindex $total $i] + [lindex $stat $i]]
        }

        # Update running maximums
        for { set i 2 } { $i < 14 } { incr i } {
            if { [lindex $max $i] < [lindex $stat $i] } {
                lset max $i [lindex $stat $i]
            }
        }
        lappend newstats $stat
    }
    set stats $newstats

    # Calc averages for totals line
    for { set i 8 } { $i < 14 } { incr i } {
        if { [lindex $total 2] == 0 } {
            lset total $i 0
        } else {
            lset total $i [expr [lindex $total [expr $i-5]] / [lindex $total 2]]
        }
    }
    lset total 1 "avg"

    incr ready
}

proc show_stats {} {
    global total stats
    global empty_time empty_frames

    set basic {Name Cnt Time}
    set raw   {Frms Mpix Kprim Ktri Kvert}
    set calc $raw
    set header [concat $basic $raw $calc]

    set mags {0 6 3 3 3 0 6 3 3 3}
    foreach i $mags {
        lappend scale [expr pow (10, $i)]
    }

    set g_form "%9s%-*s%s"
    set h_form "%-5s%3s %6s"
    append h_form [string repeat " %5s" [llength $raw]]
    append h_form [string repeat " %5s" [llength $calc]]
    set fmt "%-5s%3s %6.3f"
    append fmt [string repeat " %5d" [llength $raw]]
    append fmt [string repeat " %5d" [llength $calc]]

    OUT [format "Line mode %s" [expr $::optLineMode ? "ON" : "OFF"]]
    OUT [format $g_form "" [expr 6*[llength $raw] + 8] "MEASURED" "PER SECOND"]
    set cmd "format \"$h_form\" [join $header]"
    OUTS [eval $cmd]

    set empty_stat { \
        "empty" "1" $empty_time $empty_frames \
        0 0 0 0 0 0 0 0 0 \
    }
    set cmd "format \"$fmt\" [join $empty_stat]"
    OUTS [eval $cmd]

    lappend stats $total

    foreach stat $stats {
        set count 0
        foreach val $stat {
            set tstat($count) $val
            incr count
        }

        for { set i 0 } { $i < [llength $scale] } { incr i } {
            set tstat([expr $i+3]) [expr int ($tstat([expr $i+3]) / [lindex $scale $i])]
        }

        OUTS [format $fmt \
              $tstat(0) $tstat(1) $tstat(2) $tstat(3) $tstat(4)  $tstat(5) \
              $tstat(6) $tstat(7) $tstat(8) $tstat(9) $tstat(10) $tstat(11) \
              $tstat(12)]
    }
}

proc kilo_mag { num } {
    set mag [expr {int(log($num) / log(10))}]
    return [expr {int($mag / 3)}]
}

proc mag_char { num } {
    return [lindex {"" "K" "M" "G" "T" "P" "E" "Z" "Y"} [kilo_mag $num]]
}

proc mag_scale { num } {
    return [expr {pow (10, [expr 3*[kilo_mag $num]])}]
}

proc tick_inc { max { parts 5 } } {
    if { $max < 1 } {
        return [expr {$max / $parts}]
    }

    set mag [expr {int(log($max) / log(10))}]
    set scl [expr {pow (10, ($mag - 1))}]
    set inc [expr {$max / ($scl * $parts)}]

    if { $inc > 7.5 } {
        set inc 10
    } elseif { $inc > 3.5 } {
        set inc 5
    } elseif { $inc > 1.5 } {
        set inc 2
    } else {
        set inc 1
    }

    return [expr {$inc * $scl}]
}

proc draw_one_stat { x_loc y_loc y_off x_scale num } {
    global tests stats
    global max h

    set y_max [lindex $max $num]
    set y_scale [expr {($h - 4.0*$y_off) / (2.0*$y_max)}]

    foreach item $tests {
        set name [lindex $item 0]
        set colors($name)  [lindex $item end-1]
        set stipple($name) [lindex $item end]
    }
    set last ""

    glEnable GL_LINE_STIPPLE
    glBegin GL_LINE_STRIP
    for { set run 0 } { $run < [llength $stats] -1 } { incr run } {
        set stat [lindex $stats $run]
        set name  [lindex $stat 0]
        set count [lindex $stat 1]
        set value [lindex $stat $num]

        if { $name ne $last } {
            glEnd
            glLineStipple 3 $stipple($name)
            glBegin GL_LINE_STRIP
            set color $colors($name)
            glColor3f [lindex $color 0] [lindex $color 1] [lindex $color 2]
            set last $name
        }

        glVertex2f [expr {$count*$x_scale + $x_loc}] [expr {$value*$y_scale + $y_loc}]
    }
    glEnd

    glDisable GL_LINE_STIPPLE
}

proc draw_string { font str x y } {
    set len [string length $str]
    if { $len > 0 } {
        glRasterPos2f $x $y
        glListBase $font
        set sa [tcl3dVectorFromString GLubyte $str]
        glCallLists $len GL_UNSIGNED_BYTE $sa
        $sa delete
    }
}

proc draw_stats {} {
    global ready
    global w h
    global slow fast
    global tests
    global max
    global font_style

    if { ! $ready } {
        return
    }

    # Graph config
    set x_off 10
    set y_off 10
    set tick_size 3
    set val_space 50
    set key_size  20
    # OPA set x_count  len(fast) and fast[-1] or slow[-1]
    set x_count  [lindex $slow end]
    set x_scale   [expr {($w - 4.0 * $x_off) / (2.0 * $x_count)}]
    set key_scale [expr {($h - 4.0 * $y_off) / (2.0 * [llength $tests])}]

    # Get a fresh black frame for graphing
    glClearColor 0 0 0 1
    start_frame

    # Use antialiased lines
    glEnable GL_BLEND
    glBlendFunc GL_SRC_ALPHA GL_ONE_MINUS_SRC_ALPHA
    glEnable GL_LINE_SMOOTH
    glHint GL_LINE_SMOOTH_HINT GL_NICEST

    # Draw axis ticks
    glColor3f 1 1 1
    glBegin GL_LINES

    foreach count [concat 0 $slow $fast] {
        set x_tick [expr {$count * $x_scale + $x_off}]

        glVertex2f $x_tick $y_off
        glVertex2f $x_tick [expr {$y_off - $tick_size}]
        glVertex2f $x_tick [expr {$y_off + $h / 2.0}]
        glVertex2f $x_tick [expr {$y_off + $h / 2.0 - $tick_size}]
        glVertex2f [expr {$x_tick + $w / 2.0}] [expr {$y_off + $h / 2.0}]
        glVertex2f [expr {$x_tick + $w / 2.0}] [expr {$y_off + $h / 2.0 - $tick_size}]
    }
    glEnd

    set x_tick  [expr {$x_off + 3}]
    set val_max [expr {int(($h / 2.0 - 2.0 * $y_off) / $val_space)}]

    # Work
    for { set value 0 } { $value < $val_max } { incr value } {
        set y_tick [expr {$value * $val_space + $y_off}]

        glBegin GL_LINES
        glVertex2f $x_off $y_tick
        glVertex2f [expr {$x_off - $tick_size}] $y_tick
        glEnd
    }

    # Pixels
    set value 0
    set val_max [expr {[lindex $max 9] / [mag_scale [lindex $max 9]] }]
    set y_scale [expr {($h - 4.0 * $y_off) / (2.0 * $val_max)}]
    set val_inc [tick_inc $val_max 5]
    while { $value < $val_max } {
        set y_tick [expr {($value * $y_scale) + $y_off}]

        glBegin GL_LINES
        glVertex2f $x_off [expr {$y_tick + $h / 2.0}]
        glVertex2f [expr {$x_off - $tick_size}] [expr {$y_tick + $h / 2.0}]
        glEnd
        if { $value } {
            draw_string $font_style $value $x_tick [expr {$y_tick + $h / 2.0}]
        }
        set value [expr {$value + $val_inc}]
    }

    # Vertices
    set value 0
    set val_max [expr {[lindex $max 12] / [mag_scale [lindex $max 12]] }]
    set y_scale [expr {($h - 4.0 * $y_off) / (2.0 * $val_max)}]
    set val_inc [tick_inc $val_max 5]

    while { $value < $val_max } {
        set y_tick [expr {($value * $y_scale) + $y_off}]

        glBegin GL_LINES
        glVertex2f [expr {$x_off + $w / 2.0}] [expr {$y_tick + $h / 2.0}]
        glVertex2f [expr {$x_off + $w / 2.0 - $tick_size}] [expr {$y_tick + $h / 2.0}]
        glEnd
        if { $value } {
            draw_string $font_style $value [expr {$x_tick + $w/2.0}] [expr {$y_tick + $h/2.0}]
        }
        set value [expr {$value + $val_inc}]
    }

    # Draw axes
    glBegin GL_LINE_STRIP
    glVertex2f $x_off [expr {$h / 2.0 - $y_off}]
    glVertex2f $x_off $y_off
    glVertex2f [expr {$w / 2.0 - $x_off}] $y_off
    glEnd
    glBegin GL_LINE_STRIP
    glVertex2f $x_off [expr {$h - $y_off}]
    glVertex2f $x_off [expr {$h / 2.0 + $y_off}]
    glVertex2f [expr {$w / 2.0 - $x_off}] [expr {$h / 2.0 + $y_off}]
    glEnd
    glBegin GL_LINE_STRIP
    glVertex2f [expr {$w / 2.0 + $x_off}] [expr {$h - $y_off}]
    glVertex2f [expr {$w / 2.0 + $x_off}] [expr {$h / 2.0 + $y_off}]
    glVertex2f [expr {$w - $x_off}] [expr {$h / 2.0 + $y_off}]
    glEnd

    # Draw color key
    for { set num 0 } { $num < [llength $tests] } { incr num } {
        set test [lindex $tests $num]
        set name    [lindex $test 0]
        set color   [lindex $test end-1]
        set stipple [lindex $test end]

        glEnable GL_LINE_STIPPLE
        glLineStipple 3 $stipple

        glBegin GL_LINES
        glColor3f [lindex $color 0] [lindex $color 1] [lindex $color 2]
        glVertex2f [expr {$x_off + $w / 2.0}] [expr {$y_off + $num * $key_scale}]
        glVertex2f [expr {$x_off + $w / 2.0 + $key_size}] [expr {$y_off + $num * $key_scale}]
        glEnd

        glDisable GL_LINE_STIPPLE

        draw_string $font_style $name \
                    [expr {$x_off+$w/2.0+$key_size*2.0}] \
                    [expr {$y_off+$num*$key_scale}]
    }

    # Draw performance graph lines

    # Pixels per second
    draw_one_stat $x_off [expr {$y_off + $h / 2.0}] $y_off $x_scale 9
    glColor3f 1.0 1.0 1.0
    draw_string $font_style "[mag_char [lindex $max 9]] Pixels/Sec" \
                [expr {$w/4.0}] [expr {$h-2.0*$y_off}]

    # Vertices per second
    draw_one_stat [expr {$x_off + $w/2.0}] [expr {$y_off + $h/2.0}] $y_off $x_scale 12
    glColor3f 1.0 1.0 1.0
    draw_string $font_style "[mag_char [lindex $max 12]] Vertices/Sec" \
                [expr {3.0*$w/4.0}] [expr {$h-2.0*$y_off}]

    # "Work" per second, the geometric mean of pixels and vertices
    draw_one_stat $x_off $y_off $y_off $x_scale 13
    glColor3f 1.0 1.0 1.0
    draw_string $font_style "Work/Sec" [expr {$w/4.0}] [expr {$h/2.0 - 2.0*$y_off}]

    # Show our graph
    end_frame
    set showing_graph 1
}

proc RedrawStats {} {
    fixup_stats
    show_stats
    draw_stats
} 

proc display { toglwin } {
    global done ready
    global tests

    if { ! [info exists tests] || [llength $tests] == 0 } {
        start_frame
        end_frame
        return
    }

    if { ! $done && [info exists ::animateId] } {
        benchmark
    } elseif { ! $ready && [info exists ::animateId] } {
        RedrawStats
    } elseif { $done && $ready } {
        StopAnimation
        set ::startStop 0
        draw_stats
    }
}

proc Animate {} {
    .fr.toglwin postredisplay
    set ::animateId [tcl3dAfterIdle Animate]
}

# This procedure should not be named StartAnimation, as this a reserved
# name of the Tcl3D presentation framework for demos which should be
# automatically animated after startup, which we don't want for this demo.
proc StartAnimationTest {} {
    global labels

    if { ! [info exists ::animateId] } {
        if { $labels(selectionChanged) } {
            Reset
        }
        Animate
    }
}

proc StopAnimation {} {
    if { [info exists ::animateId] } {
        after cancel $::animateId
        unset ::animateId
    }
}

proc StartStopAnimation {} {
    if { [info exists ::animateId] } {
        StopAnimation
    } else {
        StartAnimationTest
    }
}

proc start_frame {} {
    glClear [expr $::GL_COLOR_BUFFER_BIT | $::GL_DEPTH_BUFFER_BIT]
}

proc end_frame {} {
    glFinish
}

proc fade_to_white { frac } {
    glColor4f $frac $frac $frac 1
    glClearColor $frac $frac $frac 1
    glClear [expr $::GL_COLOR_BUFFER_BIT | $::GL_DEPTH_BUFFER_BIT]
    glFinish
}

proc benchmark {} {
    global MIN_FRAMES MIN_SECONDS
    global w h
    global stats tests test
    global done run
    global slow fast
    global labels

    if { $test >= [llength $tests] } {
        if { ! $done } {
            OUT "."
        }
        incr done
        return
    }

    set currentTest [lindex $tests $test]
    foreach { name draw do_stats class } $currentTest { break }

    if { $class eq "single" } {
        set counts [list 1]
    } elseif { $class eq "slow" } {
        set counts $slow
    } else {
        set counts [concat $slow $fast]
    }
    $labels(test,$name) configure -background yellow

    if { ! $run } {
        OUTN " $name" ; flush stdout
    }

    set count [lindex $counts $run]
    set size  [expr $w / $count]
    $labels(count,$count) configure -background yellow

    fade_to_white [expr ($test + (double($run)/[llength $counts])) / \
                   [llength $tests]]

    # Set polygon mode.
    if { $::optLineMode } {
        glColor3f 0 1 0
        glPolygonMode GL_FRONT_AND_BACK GL_LINE
    } else {
        glPolygonMode GL_FRONT_AND_BACK GL_POLYGON
    }

    set run_done 0
    set frames 0
    tcl3dResetSwatch $::stopwatch

    while { ! $run_done } {
        start_frame
        $draw $count $size
        end_frame

        incr frames
        if { $MIN_FRAMES <= $frames && \
             $MIN_SECONDS <= [tcl3dLookupSwatch $::stopwatch] } {
            set run_done 1
        }
    }

    glFinish
    set secs [tcl3dLookupSwatch $::stopwatch]

    lappend stats [join [list $name $count $secs $frames \
                        [$do_stats $count $size]]]

    incr run
    if { $run >= [llength $counts] } {
        $labels(test,$name) configure -background green
        ClearCountLabels
        incr test
        set run 0
    }
}

proc init_display_lists {} {
    global w h
    global dl_types dls
    global slow fast

    OUTN "Init display lists:"
    set l [concat $slow $fast]
    set num_lists [expr [array size dl_types] * [llength $l]]
    set current [glGenLists $num_lists]

    set types [lsort [array names dl_types]]
    tcl3dResetSwatch $::stopwatch
    foreach type $types {
        OUTN " $type" ; flush stdout
        foreach count $l {
            set dls(${type}_${count}) $current
            glNewList $current GL_COMPILE
            incr current
            set drawFunc $dl_types($type)
            $drawFunc $count [expr $w / $count]
            glEndList
        }
    }
    set secs [format " (%.2f secs)" [tcl3dLookupSwatch $::stopwatch]]
    OUT "$secs"
}

proc init_vertex_arrays {} {
    global w h
    global va_types vas
    global slow fast

    OUTN "Init vertex arrays:"
    set types [lsort [array names va_types]]
    tcl3dResetSwatch $::stopwatch
    foreach type $types {
        OUTN " $type" ; flush stdout
        set l [concat $slow $fast]
        foreach count $l {
            set dataFunc $va_types($type)
            set data [$dataFunc $count [expr $w / $count]]
            set vas(${type}_${count}) $data
        }
    }
    set secs [format " (%.2f secs)" [tcl3dLookupSwatch $::stopwatch]]
    OUT "$secs"
}

proc show_basic_config { version } {
    global conf

    OUT "$conf(title), version $version\n"

    OUT "Operating system: $::tcl_platform(os)"
    OUT "Tcl version:      [info patchlevel]"
    OUT "Graphic card:     [glGetString GL_RENDERER]"
    OUT "OpenGL version:   [glGetString GL_VERSION]"
    OUT ""

    OUT "window size:      $conf(width) x $conf(height)"
    OUT "min frames/test:  $conf(frames)"
    OUT "min seconds/test: $conf(seconds)"
}

proc show_user_message {} {
    global slow fast

    set msg {
TRISLAM benchmarks several methods of pushing OpenGL primitives,
testing each method with various primitive counts and sizes.
During the benchmark, the test window will start out black, slowly
brightening to white as testing progresses.  Once benchmarking is
complete, the collected data will be dumped in tabular form.

The configuration for this series of tests will be as follows:
}
    OUT $msg

    show_basic_config $::VERSION

    OUT "standard runs:   "
    foreach i $slow {
        OUTN " $i"
    }
    OUT ""
    OUT "extra fast runs: "
    foreach i $fast {
        OUTN " $i"
    }
    OUT ""
    OUT [string repeat "-" 80]
    OUT ""
}

proc recurse_combos { argList } {
    if { [llength $argList] == 0 } {
        return [list 1]
    }
    set base [lindex $argList 0]
    set max_power [lindex $argList 1]

    set combos [list]
    for { set power 0 } { $power <= $max_power } { incr power } {
        set multiplier [expr int (pow ($base, $power))]
        foreach item [recurse_combos [lrange $argList 2 end]] {
            lappend combos [expr {$item * $multiplier}]
        }
    }
    return $combos
}

proc ClearTestLabels {} {
    global labels

    foreach key [array names labels "test,*"] {
        $labels($key) configure -background white
    }
}

proc ClearCountLabels {} {
    global labels

    foreach key [array names labels "count,*"] {
        $labels($key) configure -background white
    }
}

proc SetAllTests { allTestsOn } {
    global labels

    foreach key [array names labels "usetest,*"] {
        set labels($key) $allTestsOn
    }
    SelectionChanged
}

proc AllTestsOn {} {
    SetAllTests 1
}

proc AllTestsOff {} {
    SetAllTests 0
}

proc SnapShot { fileName } {
    . configure -cursor watch
    update
    if { $fileName ne "" } {
        # Create a name on the file system, if running from within a Starpack.
        set fileName [tcl3dGenExtName $fileName]
        set imgName [file rootname $fileName]
        set imgExt ".png"
        set imgFmt "PNG"
        append imgName $imgExt
        tcl3dWidget2File . $imgName "*Console*" $imgFmt
        puts "Screenshot written to: $imgName"
    }
    . configure -cursor top_left_arrow
}

proc SaveSnapshot {} {
    global ready done run
    global test tests

    set fileName ""
    if { $done && $ready } {
        set fileName "trislam-stat"
    } elseif { ! $done } {
        set currentTest [lindex $tests $test]
        set testName [lindex $currentTest 0]
        set fileName "trislam-$testName-$run"
    }
    if { $fileName ne "" } {
        SnapShot $fileName
    }
}

proc init_opengl { title w h } {
    global testTemplates
    global slow fast
    global labels

    frame .fr
    pack .fr -expand 1 -fill both
    set toglwin .fr.toglwin
    togl $toglwin -width $w -height $h \
                  -double false -depth true \
                  -displaycommand display
    frame .fr.btns
    label .fr.info
    grid $toglwin  -row 0 -column 0 -sticky news
    grid .fr.btns  -row 0 -column 1 -sticky new
    grid .fr.info  -row 1 -column 0 -sticky news -columnspan 2
    grid rowconfigure .fr 0 -weight 1
    grid columnconfigure .fr 0 -weight 1

    set actionFrame .fr.btns.actionFr
    labelframe $actionFrame -text "Actions"
    pack $actionFrame -side top -fill x -expand 1 -pady 5 -padx 2

    checkbutton $actionFrame.start -indicatoron false -text "Start" \
                -variable ::startStop -command StartStopAnimation
    pack $actionFrame.start -side top -fill x -expand 1
    button $actionFrame.reset -text "Reset" -command Reset
    pack $actionFrame.reset -side top -fill x -expand 1
    button $actionFrame.snap -text "Snapshot" -command SaveSnapshot
    pack $actionFrame.snap -side top -fill x -expand 1

    set optionFrame .fr.btns.optionFr
    labelframe $optionFrame -text "Options"
    pack $optionFrame -side top -fill x -expand 1 -pady 5 -padx 2

    checkbutton $optionFrame.line -text "Line Mode" -variable ::optLineMode
    pack $optionFrame.line -side top -fill x -expand 1


    set testFrame .fr.btns.testFr
    labelframe $testFrame -text "Test Selection"
    pack $testFrame -side top -fill x -expand 1 -pady 5 -padx 2

    set labels(selectionChanged) 0
    foreach t $testTemplates {
        set name [lindex $t 0]
        set labels(usetest,$name) 0
        set labels(test,$name) [checkbutton $testFrame.cb_$name \
                                            -text $name -bg white -anchor w \
                                            -command SelectionChanged \
                                            -variable labels(usetest,$name)]
        pack $testFrame.cb_$name -side top -fill x
    }
    set labels(usetest,t) 1
    SelectionChanged

    button $testFrame.allOn -text "All On" -command AllTestsOn
    pack $testFrame.allOn -side top -fill x -expand 1
    button $testFrame.allOff -text "All Off" -command AllTestsOff
    pack $testFrame.allOff -side top -fill x -expand 1


    set infoFrame .fr.btns.infoFr
    labelframe $infoFrame -text "Count Info"
    pack $infoFrame -side top -fill x -expand 1 -pady 5 -padx 2

    frame $infoFrame.fr0
    frame $infoFrame.fr1
    frame $infoFrame.fr2
    pack $infoFrame.fr0 $infoFrame.fr1 $infoFrame.fr2 \
         -side left -fill both -expand 1

    set ind 0
    foreach c [concat $slow $fast] {
        set frInd [expr $ind % 3]
        set labels(count,$c) [label $infoFrame.fr$frInd.l_$c -text $c -bg white]
        pack $infoFrame.fr$frInd.l_$c -side top -fill x
        incr ind
    }

    wm title . $title
    return $toglwin
}

proc init {} {
    global MIN_FRAMES MIN_SECONDS
    global w h
    global conf
    global slow fast
    global max_powers max_count_slow max_count_fast
    global font_style

    # Figure out primitive counts for each run of each test type
    catch { unset ::combo_hash }

    foreach item [recurse_combos $max_powers] {
        set ::combo_hash($item) [list]
    }

    set combos [lsort -integer [array names ::combo_hash]]

    foreach item $combos {
        set i [expr int($item)]
        if { $i <= $max_count_slow } {
            lappend slow $i
        }
        if { $i > $max_count_slow && $i <= $max_count_fast } {
            lappend fast $i
        }
    }

    # Choose drawing area size to match counts
    set h [expr int([lindex $combos end])]
    set w $h

    # Do the standard init stuff, including command line processing,
    # window creation, and so on
    array set conf [list \
        "title" "Tcl3D demo: Triangle Slammer OpenGL Benchmark" \
        "width" $w \
        "height" $h \
        "geometry" "${w}x${h}" \
        "frames" 10 \
        "seconds" 1 \
    ]

    set toglwin [init_opengl $conf(title) $w $h]

    # Reduce indirections in inner loops
    set MIN_FRAMES  $conf(frames)
    set MIN_SECONDS $conf(seconds)

    # Let user know what's going on
    show_user_message

    # Change projection to integer-pixel ortho
    glMatrixMode GL_PROJECTION
    glOrtho 0 [expr $w +1] 0 [expr $h +1]  -1 1
    glMatrixMode GL_MODELVIEW

    # Load font for graph labels
    set retVal [catch { $toglwin loadbitmapfont $::fontName } font_style]
    if { $retVal != 0 } {
        set font_style [$toglwin loadbitmapfont]
    }

    # Make sure GL state is consistent for VA and DL creation
    start_frame

    # Create vertex arrays and display lists outside timing loop
    init_vertex_arrays
    init_display_lists

    # Clean up GL state
    end_frame
}

# Cleanup procedure needed only, when this script is used from
# the presentation framework.
proc Cleanup {} {
    uplevel #0 unset max_powers
    uplevel #0 unset slow
    uplevel #0 unset fast
    uplevel #0 unset va_types
    uplevel #0 unset dl_types
    uplevel #0 unset testTemplates
    uplevel #0 unset labels
    uplevel #0 unset stats
    uplevel #0 unset total
    uplevel #0 unset max
    uplevel #0 unset tests
    uplevel #0 unset vas
    uplevel #0 unset dls
    uplevel #0 unset conf
    uplevel #0 unset combo_hash

    $::stopwatch -delete
}

proc ExitProg {} {
    exit
}

# Main app

init
Reset
OUT "Benchmarks: "

wm protocol . WM_DELETE_WINDOW "ExitProg"
bind . <Key-Escape> "ExitProg"

PrintInfo [tcl3dOglGetInfoString]

if { [file tail [info script]] eq [file tail $::argv0] } {
    # If started directly from tclsh or wish, then start animation.
    update
    set ::startStop 1
    StartStopAnimation
}
