summaryrefslogtreecommitdiffstats
path: root/virtual-programs/display/image.folk
blob: b654f84618f7c7c72bd16de10dd638cb3fcc1714 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
On process "display" {
    set invBilinear $::invBilinear
    set rotate $::rotate
    # TODO: Do this with a wish, instead of hard-coding the global dict.
    dict set ::pipelines "image" [Gpu::pipeline {sampler2D image vec2 imageSize
                              vec2 pos float radians vec2 scale
                              fn rotate} {
        vec2 a = pos + rotate(scale*-imageSize/2, -radians);
        vec2 b = pos + rotate(scale*vec2(imageSize.x, -imageSize.y)/2, -radians);
        vec2 c = pos + rotate(scale*imageSize/2, -radians);
        vec2 d = pos + rotate(scale*vec2(-imageSize.x, imageSize.y)/2, -radians);
        vec2 vertices[4] = vec2[4](a, b, d, c);
        return vertices[gl_VertexIndex];
    } {fn invBilinear fn rotate} {
        vec2 a = pos + rotate(scale*-imageSize/2, -radians);
        vec2 b = pos + rotate(scale*vec2(imageSize.x, -imageSize.y)/2, -radians);
        vec2 c = pos + rotate(scale*imageSize/2, -radians);
        vec2 d = pos + rotate(scale*vec2(-imageSize.x, imageSize.y)/2, -radians);
        vec2 p = gl_FragCoord.xy;
        vec2 uv = invBilinear(p, a, b, c, d);
        if( max( abs(uv.x-0.5), abs(uv.y-0.5))<0.5 ) {
            return texture(image, uv);
        }
        return vec4(0.0, 0.0, 0.0, 0.0);
    }]

    When the GPU has loaded /nfonts/ fonts {
        namespace eval ::ImageCache {
            # Backing store: stores pairs of (GPU image handle, heap slot version).
            variable cache [dict create]
            variable CACHE_MAX_SIZE [- $Gpu::ImageManager::GPU_MAX_IMAGES [uplevel {set nfonts}]]

            proc getOrInsert {im} {
                variable cache
                variable CACHE_MAX_SIZE
                if {[dict exists $cache $im]} {
                    lassign [dict get $cache $im] gim cachedVersion
                    set version [Heap::folkHeapGetVersion [string map {uint8_t void} [::image_t data_ptr $im]]]
                    if {$version == $cachedVersion} {
                        # Bump this image to end of cache since it's
                        # most-recently-accessed.
                        dict unset cache $im
                        dict set cache $im [list $gim $cachedVersion]
                        return $gim
                    } else {
                        # This image is stale. Don't retain it.
                        remove $im
                    }
                }
                if {[dict size $cache] >= $CACHE_MAX_SIZE} {
                    evict
                }
                if {[dict size $cache] >= $CACHE_MAX_SIZE} {
                    puts stderr "image: Warning: Out of slots in GPU image cache."
                }
                set version [Heap::folkHeapGetVersion [string map {uint8_t void} [::image_t data_ptr $im]]]
                set gim [Gpu::ImageManager::copyImageToGpu $im]
                dict set cache $im [list $gim $version]
                return $gim
            }

            proc evict {} {
                variable cache
                variable CACHE_MAX_SIZE
                set numToEvict [expr {([dict size $cache] + 1) - $CACHE_MAX_SIZE}]
                set numEvicted [list]
                # Evict stale.
                dict for {im v} $cache {
                    lassign $v gim expectedVersion
                    set version [Heap::folkHeapGetVersion [string map {uint8_t void} [::image_t data_ptr $im]]]
                    if {$expectedVersion != $version} {
                        Gpu::ImageManager::freeGpuImage $gim
                        lappend numEvicted $im
                    }
                }
                foreach im $numEvicted { dict unset cache $im }
                # Evict old.
                dict for {im v} $cache {
                    if {$numToEvict - [llength $numEvicted] <= 0} {
                        break
                    }
                    lassign $v gim
                    Gpu::ImageManager::freeGpuImage $gim
                    lappend numEvicted $im
                }
                foreach im $numEvicted { dict unset cache $im }
            }

            proc remove {im} {
                variable cache
                if {[dict exists $cache $im]} {
                    lassign [dict get $cache $im] gim
                    Gpu::ImageManager::freeGpuImage $gim
                }
                dict unset cache $im
            }
        }
    }

    Wish $::thisProcess receives statements like \
        [list /someone/ wishes to draw an image with /...options/]
    When /someone/ wishes to draw an image with /...options/ {
        if {[dict exists $options center]} {
            set center [dict get $options center]
        } else {
            set center [list [dict get $options x] [dict get $options y]]
        }
        set im [dict get $options image]
        set radians [dict get $options radians]
        set scale [dict_getdef $options scale 1.0]
        if {[llength $scale] == 1} {
          set scale [list $scale $scale] 
        }

        set gim [ImageCache::getOrInsert $im]

        Wish the GPU draws pipeline "image" with arguments \
            [list $gim [list [image_t width $im] [image_t height $im]] \
                 $center $radians $scale]
    }
}