package main import ( "fmt" "math" "runtime" "sync" "unsafe" "github.com/veandco/go-sdl2/sdl" ) type PointArgs struct { resX int resY int imageX int imageY int zoom float64 maxIteration int threadNumber int } type Piece struct { pixels []int initX int initY int width int height int } func getX(linearPoint int, width int) int { return linearPoint % width } func getY(linearPoint int, height int) int { return int(math.Floor(float64(linearPoint / height))) } func mapXMandelbrot(x int, width int, zoom float64) float64 { return ((float64(x) / float64(width)) * (3.5 * zoom)) - (2.5 - (1.0 - zoom)) } func mapYMandelbrot(y int, height int, zoom float64) float64 { return ((float64(y) / float64(height)) * (2.0 * zoom)) - (1.00001 - (1.0 - zoom)) } func mandebrotPoint(resX int, resY int, imageX int, imageY int, zoom float64, maxIteration int) int { posX := mapXMandelbrot(imageX, resX, zoom) posY := mapYMandelbrot(imageY, resY, zoom) var x float64 = 0.0 var y float64 = 0.0 iteration := 0 // Period-2 bulb check xTerm := posX + 1.0 posY2 := posY * posY if xTerm*xTerm+posY2 < 0.0625 { return 0 } // Cardioid check xTerm = posX - 0.25 q := xTerm*xTerm + posY2 q = q * (q + xTerm) if q < (0.25 * posY2) { return 0 } var xtemp, xx, yy, xplusy float64 for iteration < maxIteration { xx = x * x yy = y * y xplusy = x + y if (xx)+(yy) > (4.0) { break } y = xplusy*xplusy - xx - yy y = y + posY xtemp = xx - yy + posX x = xtemp iteration++ } if iteration > maxIteration { return 0 } else { return iteration } } func mandelbrotPiece(threads int, numProcess int, width int, height int, maxIteration int, zoom float64, res chan Piece, work *sync.WaitGroup) { defer work.Done() var split int if threads > 2 { split = int(math.Floor(math.Sqrt(float64(threads)))) } else if threads == 2 { split = 2 } else { split = 1 } pieceX := 0 pieceY := 0 if numProcess > 0 { pieceX = numProcess % split pieceY = int(math.Floor(float64(numProcess) / float64(split))) } threadWidth := int(math.Floor(float64(width) / float64(split))) threadHeight := int(math.Floor(float64(height) / float64(split))) initX := threadWidth * pieceX initY := threadHeight * pieceY iterations := make([]int, threadWidth*threadHeight) for y := 0; y < threadHeight; y++ { for x := 0; x < threadWidth; x++ { iterations[x+y*threadWidth] = mandebrotPoint(width, height, x+initX, y+initY, zoom, maxIteration) } } // Return the portion we have calculated res <- Piece{ pixels: iterations, initX: initX, initY: initY, width: threadWidth, height: threadHeight, } } func stitchPiece(iterationPixels *[]uint32, piece Piece, resX int, maxIteration int, format sdl.PixelFormat, rank int) { itPixels := *iterationPixels patchWidth := piece.width + piece.initX patchHeight := piece.height + piece.initY for y := piece.initY; y < patchHeight; y++ { for x := piece.initX; x < patchWidth; x++ { pieceX := x - piece.initX pieceY := y - piece.initY var pixel uint32 iteration := piece.pixels[pieceX+pieceY*piece.width] if (iteration < 128) && (iteration > 0) { value := uint8(20 + iteration) pixel = sdl.MapRGBA(&format, value, 0, value, 255) } else if (iteration >= 128) && (iteration < maxIteration) { pixel = sdl.MapRGBA(&format, uint8(iteration), 148, uint8(iteration), 255) } else { pixel = sdl.MapRGBA(&format, 0, 0, 0, 255) } itPixels[x+y*rank] = pixel } } } func main() { fmt.Println("Starting fractal") if err := sdl.Init(sdl.INIT_EVERYTHING); err != nil { panic(err) } defer sdl.Quit() resX := 1024 resY := 768 window, err := sdl.CreateWindow("fractal", sdl.WINDOWPOS_CENTERED, sdl.WINDOWPOS_CENTERED, int32(resX), int32(resY), sdl.WINDOW_SHOWN) if err != nil { panic(err) } defer window.Destroy() renderer, err := sdl.CreateRenderer(window, -1, 0) if err != nil { panic(err) } // Blank the Window renderer.SetDrawColor(0, 0, 0, 255) renderer.Clear() renderer.Present() textureScreen, err := renderer.CreateTexture(sdl.PIXELFORMAT_ARGB8888, sdl.TEXTUREACCESS_STREAMING, int32(resX), int32(resY)) if err != nil { panic(err) } screen, err := sdl.CreateRGBSurface(0, int32(resX), int32(resY), 32, 0, 0, 0, 0) if err != nil { panic(err) } numCPUs := runtime.NumCPU() // Get how many CPUs the system reports fmt.Println("Using ", numCPUs, " threads") fmt.Println("Rendering and zooming") zoom := 1.0 stopPoint := 0.00001 sizeOfInt32 := int32(unsafe.Sizeof(int32(0))) // Here we write the pixels to be sent to the screen iterationPixels := make([]uint32, resX*resY) pieces := make(chan Piece, numCPUs) running := true for (running) && (zoom > stopPoint) { // Let the user quit any time if event := sdl.PollEvent(); event != nil { switch event.(type) { case *sdl.QuitEvent: fmt.Println("Quit by user") running = false break } } // Draw the fractal // Set the iteration to a nice number to not overdo it maxIteration := 170 if (zoom < -0.02) && (zoom > -1.0) { maxIteration = 100 } var wg sync.WaitGroup wg.Add(numCPUs) // Launch the processes and wait for the results for threadCount := 0; threadCount < numCPUs; threadCount++ { go mandelbrotPiece(numCPUs, threadCount, resX, resY, maxIteration, zoom, pieces, &wg) } wg.Wait() pieceCount := 0 for piece := range pieces { stitchPiece(&iterationPixels, piece, resX, maxIteration, *screen.Format, int(screen.Pitch/sizeOfInt32)) pieceCount++ if pieceCount >= numCPUs { break } } zoom = zoom * 0.99 screenPixels, _, err := textureScreen.Lock(nil) if err != nil { panic(err) } count := 0 for _, pixel := range iterationPixels { for i := uint32(0); i < 4; i++ { screenPixels[count] = byte((pixel >> (8 * i)) & 0xff) count++ } } textureScreen.Unlock() if err != nil { panic(err) } // textureScreen.Update(nil, unsafe.Pointer(&iterationPixels[0]), int(resX*sizeOfInt32)) renderer.Clear() renderer.Copy(textureScreen, nil, nil) renderer.Present() } }