From f2a51336b2af900616e9f743848a2ece2f5bbb23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Carlos=20Cuevas?= Date: Fri, 24 Feb 2023 14:03:43 +0100 Subject: [PATCH] Multiprocessing fractal zoom --- .tool-versions | 1 + go.mod | 5 + go.sum | 2 + main.go | 279 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 287 insertions(+) create mode 100644 .tool-versions create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..58b4461 --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +golang 1.19.5 diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..41db64c --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module fractal + +go 1.19 + +require github.com/veandco/go-sdl2 v0.4.33 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..2dc04ad --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/veandco/go-sdl2 v0.4.33 h1:cxQ0OdUBEByHxvCyrGxy9F8WpL38Ya6hzV4n27QL84M= +github.com/veandco/go-sdl2 v0.4.33/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofep6SNiAjY= diff --git a/main.go b/main.go new file mode 100644 index 0000000..070e4ba --- /dev/null +++ b/main.go @@ -0,0 +1,279 @@ +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() + } +}