-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.go
More file actions
176 lines (153 loc) · 4.41 KB
/
main.go
File metadata and controls
176 lines (153 loc) · 4.41 KB
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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
/*
Package parallelBandelbrot implements a parallel rendering of the Mandelbrot set
The Mandelbrot set is defined by the set of complex numbers "c"
for which the complex numbers of the sequence "Zn"
remain bounded in absolute value. The sequence "Zn" is defined by:
Z(0) = 0
Z(n+1) = Z(n)^2 + c
The task of rendering the image is split into regions of the image(WorkRange)
and each task is given to a new goroutine(calculatorWorker) to calculate. When a
calculatorWorker has finished calculating a region a new regoin gets assigned to it.
*/
package main
import (
"fmt"
"image"
"image/color"
"image/png"
"math"
"os"
"sync"
)
// Struct for work item
type workRange struct {
minX, maxX, minY, maxY int
}
type pixel struct {
x, y int
col color.RGBA
}
// Image and concurrency parameters
var (
MAX_ITER int = 255
IMG_SCALE int = 10
RE_START float64 = -2.
RE_END float64 = 1.
IM_START float64 = -1.
IM_END float64 = 1.
bound float64 = 2
width int = 600 * IMG_SCALE
height int = 400 * IMG_SCALE
numOfPixels = width * height
numWorkTasks int = 64
numThreads int = 12
printCalculatingProgress bool = true
printDrawingProgress bool = true
printWorkItems bool = false
)
func mandelbrotCalc(c complex64) int {
z := complex64(0)
n := 0
for math.Abs(float64(real(z))) <= bound && n < MAX_ITER {
z = z*z + c
n += 1
}
return n
}
func calculatorWorker(work workRange, pixelBuffer chan pixel, freeThreads chan bool) {
for x := work.minX; x < work.maxX; x++ {
for y := work.minY; y < work.maxY; y++ {
complNum := complex(RE_START+float64(x)/float64(width)*(RE_END-RE_START),
IM_START+float64(y)/float64(height)*(IM_END-IM_START))
val := mandelbrotCalc(complex64(complNum))
val = 255 - int(val*255/MAX_ITER)
col := color.RGBA{uint8(val), uint8(val), uint8(val), 254}
pixelBuffer <- pixel{x, y, col}
}
}
freeThreads <- true
}
func drawingWorker(pixelBuffer chan pixel, image *image.RGBA, pixelCount *int, wg *sync.WaitGroup) {
for p := range pixelBuffer {
image.Set(p.x, p.y, p.col)
*pixelCount++
if printDrawingProgress && *pixelCount%(numOfPixels/10) == 0 {
fmt.Print("█")
}
if *pixelCount == numOfPixels {
break
}
}
fmt.Println()
wg.Done()
}
func workCreator(workBuffer chan workRange) {
r := int(math.Sqrt(float64(numWorkTasks)))
work_width := width / r
work_height := height / r
for i := 0; i < r; i++ {
for j := 0; j < r; j++ {
w := workRange{i * work_width, (i + 1) * work_width,
j * work_height, (j + 1) * work_height}
if printWorkItems {
fmt.Println(w)
}
workBuffer <- w
}
}
}
func calculatorWorkerStarter(workBuffer chan workRange, pixelBuffer chan pixel, freeThreads chan bool) {
for t := 0; t < numThreads; t++ {
freeThreads <- true
}
workCounter := numWorkTasks
for w := range workBuffer { // For every work task(workRange) start a goroutine
<-freeThreads // continue execution when theres a free thread
go calculatorWorker(w, pixelBuffer, freeThreads)
workCounter--
if workCounter == 0 {
return
}
if printCalculatingProgress && workCounter%(workCounter/10+1) == 0 {
fmt.Print("█")
}
}
if printCalculatingProgress {
fmt.Println()
}
}
func main() {
// Image initialization
upLeft := image.Point{0, 0}
lowRight := image.Point{width, height}
image := image.NewRGBA(image.Rectangle{upLeft, lowRight})
// How many pixels have been rendered
pixelCount := 0
// Initialize channels to pass work items, pixels to-be-rendered and threads who are free to start working
freeThreads := make(chan bool, numThreads)
pixelBuffer := make(chan pixel, numOfPixels)
workBuffer := make(chan workRange, numWorkTasks)
// Create work tasks to be distributed to threads to calculate
workCreator(workBuffer)
// Give work tasks to workers
go calculatorWorkerStarter(workBuffer, pixelBuffer, freeThreads)
// Sync group is used to signal when the drawing thread has finished rendering the image
var wg sync.WaitGroup
wg.Add(1)
// Start drawing thread
go drawingWorker(pixelBuffer, image, &pixelCount, &wg)
// Wait for drawing thread to render image
wg.Wait()
fmt.Println("Pixels rendered:", pixelCount)
// Create file
imageName := fmt.Sprintf("mandelbrot_%d_%d_%d.png", width, height, MAX_ITER)
f, err := os.Create(imageName)
if err != nil {
// Print error if creating file failed
fmt.Println("err:", err)
} else {
// Encode file as PNG
png.Encode(f, image)
fmt.Println("Image created:", imageName)
}
}