v2 / examples / gg / mandelbrot.v
241 lines · 216 sloc · 6.41 KB · efc8f7fb3ef7e90a088db64ad2c68dd0698b90d5
Raw
1import gg
2import runtime
3import time
4
5const pwidth = 800
6
7const pheight = 600
8
9const chunk_height = 2 // the image is recalculated in chunks, each chunk processed in a separate thread
10
11const zoom_factor = 1.1
12
13const max_iterations = 255
14
15struct ViewRect {
16mut:
17 x_min f64
18 x_max f64
19 y_min f64
20 y_max f64
21}
22
23fn (v &ViewRect) width() f64 {
24 return v.x_max - v.x_min
25}
26
27fn (v &ViewRect) height() f64 {
28 return v.y_max - v.y_min
29}
30
31struct AppState {
32mut:
33 gg &gg.Context = unsafe { nil }
34 iidx int
35 pixels &u32 = unsafe { vcalloc(pwidth * pheight * sizeof(u32)) }
36 npixels &u32 = unsafe { vcalloc(pwidth * pheight * sizeof(u32)) } // all drawing happens here, results are swapped at the end
37 view ViewRect = ViewRect{-3.0773593290970673, 1.4952456603855397, -2.019938598189011, 2.3106642054225945}
38 scale int = 1
39 ntasks int = runtime.nr_jobs()
40}
41
42const colors = [gg.black, gg.blue, gg.red, gg.green, gg.yellow, gg.orange, gg.purple, gg.white,
43 gg.indigo, gg.violet, gg.black, gg.blue, gg.orange, gg.yellow, gg.green].map(u32(it.abgr8()))
44
45struct MandelChunk {
46 cview ViewRect
47 ymin f64
48 ymax f64
49}
50
51fn (mut state AppState) update() {
52 mut chunk_channel := chan MandelChunk{cap: state.ntasks}
53 mut chunk_ready_channel := chan bool{cap: 1000}
54 mut threads := []thread{cap: state.ntasks}
55 defer {
56 chunk_channel.close()
57 threads.wait()
58 }
59 for t in 0 .. state.ntasks {
60 threads << spawn state.worker(t, chunk_channel, chunk_ready_channel)
61 }
62
63 mut oview := ViewRect{}
64 mut sw := time.new_stopwatch()
65 for {
66 sw.restart()
67 cview := state.view
68 if oview == cview {
69 time.sleep(5 * time.millisecond)
70 continue
71 }
72 // schedule chunks, describing the work:
73 mut nchunks := 0
74 for start := 0; start < pheight; start += chunk_height {
75 chunk_channel <- MandelChunk{
76 cview: cview
77 ymin: start
78 ymax: start + chunk_height
79 }
80 nchunks++
81 }
82 // wait for all chunks to be processed:
83 for _ in 0 .. nchunks {
84 _ := <-chunk_ready_channel
85 }
86 // everything is done, swap the buffer pointers
87 state.pixels, state.npixels = state.npixels, state.pixels
88 println('${state.ntasks:2} threads; ${sw.elapsed().milliseconds():3} ms / frame; scale: ${state.scale:4}')
89 oview = cview
90 }
91}
92
93@[direct_array_access]
94fn (mut state AppState) worker(_id int, input chan MandelChunk, ready chan bool) {
95 for {
96 chunk := <-input or { break }
97 yscale := chunk.cview.height() / pheight
98 xscale := chunk.cview.width() / pwidth
99 mut x, mut y, mut iter := 0.0, 0.0, 0
100 mut y0 := chunk.ymin * yscale + chunk.cview.y_min
101 mut x0 := chunk.cview.x_min
102 for y_pixel := chunk.ymin; y_pixel < chunk.ymax && y_pixel < pheight; y_pixel++ {
103 yrow := unsafe { &state.npixels[int(y_pixel * pwidth)] }
104 y0 += yscale
105 x0 = chunk.cview.x_min
106 for x_pixel := 0; x_pixel < pwidth; x_pixel++ {
107 x0 += xscale
108 x, y = x0, y0
109 for iter = 0; iter < max_iterations; iter++ {
110 x, y = x * x - y * y + x0, 2 * x * y + y0
111 if x * x + y * y > 4 {
112 break
113 }
114 }
115 unsafe {
116 yrow[x_pixel] = colors[iter & 15]
117 }
118 }
119 }
120 ready <- true
121 }
122}
123
124fn (mut state AppState) draw() {
125 mut istream_image := state.gg.get_cached_image_by_idx(state.iidx)
126 istream_image.update_pixel_data(unsafe { &u8(state.pixels) })
127 size := gg.window_size()
128 state.gg.draw_image(0, 0, size.width, size.height, istream_image)
129}
130
131fn (mut state AppState) zoom(zoom_factor f64) {
132 c_x, c_y := (state.view.x_max + state.view.x_min) / 2, (state.view.y_max + state.view.y_min) / 2
133 d_x, d_y := c_x - state.view.x_min, c_y - state.view.y_min
134 state.view.x_min = c_x - zoom_factor * d_x
135 state.view.x_max = c_x + zoom_factor * d_x
136 state.view.y_min = c_y - zoom_factor * d_y
137 state.view.y_max = c_y + zoom_factor * d_y
138 state.scale += if zoom_factor < 1 { 1 } else { -1 }
139}
140
141fn (mut state AppState) center(s_x f64, s_y f64) {
142 c_x, c_y := (state.view.x_max + state.view.x_min) / 2, (state.view.y_max + state.view.y_min) / 2
143 d_x, d_y := c_x - state.view.x_min, c_y - state.view.y_min
144 state.view.x_min = s_x - d_x
145 state.view.x_max = s_x + d_x
146 state.view.y_min = s_y - d_y
147 state.view.y_max = s_y + d_y
148}
149
150// gg callbacks:
151
152fn graphics_init(mut state AppState) {
153 state.iidx = state.gg.new_streaming_image(pwidth, pheight, 4, pixel_format: .rgba8)
154}
155
156fn graphics_frame(mut state AppState) {
157 state.gg.begin()
158 state.draw()
159 state.gg.end()
160}
161
162fn graphics_click(x f32, y f32, btn gg.MouseButton, mut state AppState) {
163 if btn == .right {
164 size := gg.window_size()
165 m_x := (x / size.width) * state.view.width() + state.view.x_min
166 m_y := (y / size.height) * state.view.height() + state.view.y_min
167 state.center(m_x, m_y)
168 }
169}
170
171fn graphics_move(_x f32, _y f32, mut state AppState) {
172 if state.gg.mouse_buttons.has(.left) {
173 size := gg.window_size()
174 d_x := (f64(state.gg.mouse_dx) / size.width) * state.view.width()
175 d_y := (f64(state.gg.mouse_dy) / size.height) * state.view.height()
176 state.view.x_min -= d_x
177 state.view.x_max -= d_x
178 state.view.y_min -= d_y
179 state.view.y_max -= d_y
180 }
181}
182
183fn graphics_scroll(e &gg.Event, mut state AppState) {
184 state.zoom(if e.scroll_y < 0 { zoom_factor } else { 1 / zoom_factor })
185}
186
187fn graphics_keydown(code gg.KeyCode, _mod gg.Modifier, mut state AppState) {
188 s_x := state.view.width() / 5
189 s_y := state.view.height() / 5
190 // movement
191 mut d_x, mut d_y := 0.0, 0.0
192 if code == .enter {
193 println('> ViewRect{${state.view.x_min}, ${state.view.x_max}, ${state.view.y_min}, ${state.view.y_max}}')
194 }
195 if state.gg.pressed_keys[int(gg.KeyCode.left)] {
196 d_x -= s_x
197 }
198 if state.gg.pressed_keys[int(gg.KeyCode.right)] {
199 d_x += s_x
200 }
201 if state.gg.pressed_keys[int(gg.KeyCode.up)] {
202 d_y -= s_y
203 }
204 if state.gg.pressed_keys[int(gg.KeyCode.down)] {
205 d_y += s_y
206 }
207 state.view.x_min += d_x
208 state.view.x_max += d_x
209 state.view.y_min += d_y
210 state.view.y_max += d_y
211 // zoom in/out
212 if state.gg.pressed_keys[int(gg.KeyCode.left_bracket)]
213 || state.gg.pressed_keys[int(gg.KeyCode.z)] {
214 state.zoom(1 / zoom_factor)
215 return
216 }
217 if state.gg.pressed_keys[int(gg.KeyCode.right_bracket)]
218 || state.gg.pressed_keys[int(gg.KeyCode.x)] {
219 state.zoom(zoom_factor)
220 return
221 }
222}
223
224fn main() {
225 mut state := &AppState{}
226 state.gg = gg.new_context(
227 width: 800
228 height: 600
229 create_window: true
230 window_title: 'The Mandelbrot Set'
231 init_fn: graphics_init
232 frame_fn: graphics_frame
233 click_fn: graphics_click
234 move_fn: graphics_move
235 keydown_fn: graphics_keydown
236 scroll_fn: graphics_scroll
237 user_data: state
238 )
239 spawn state.update()
240 state.gg.run()
241}
242