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