You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							213 lines
						
					
					
						
							7.3 KiB
						
					
					
				
			
		
		
		
			
			
			
				
					
				
				
					
				
			
		
		
	
	
							213 lines
						
					
					
						
							7.3 KiB
						
					
					
				| // This file is part of Eigen, a lightweight C++ template library | |
| // for linear algebra. | |
| // | |
| // Copyright (C) 2008 Benoit Jacob <jacob.benoit.1@gmail.com> | |
| // | |
| // This Source Code Form is subject to the terms of the Mozilla | |
| // Public License v. 2.0. If a copy of the MPL was not distributed | |
| // with this file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
|  | |
| #include "mandelbrot.h" | |
| #include <iostream> | |
| #include<QtGui/QPainter> | |
| #include<QtGui/QImage> | |
| #include<QtGui/QMouseEvent> | |
| #include<QtCore/QTime> | |
|  | |
| void MandelbrotWidget::resizeEvent(QResizeEvent *) | |
| { | |
|   if(size < width() * height()) | |
|   { | |
|     std::cout << "reallocate buffer" << std::endl; | |
|     size = width() * height(); | |
|     if(buffer) delete[]buffer; | |
|     buffer = new unsigned char[4*size]; | |
|   } | |
| } | |
| 
 | |
| template<typename T> struct iters_before_test { enum { ret = 8 }; }; | |
| template<> struct iters_before_test<double> { enum { ret = 16 }; }; | |
| 
 | |
| template<typename Real> void MandelbrotThread::render(int img_width, int img_height) | |
| { | |
|   enum { packetSize = Eigen::internal::packet_traits<Real>::size }; // number of reals in a Packet | |
|   typedef Eigen::Array<Real, packetSize, 1> Packet; // wrap a Packet as a vector | |
|  | |
|   enum { iters_before_test = iters_before_test<Real>::ret }; | |
|   max_iter = (max_iter / iters_before_test) * iters_before_test; | |
|   const int alignedWidth = (img_width/packetSize)*packetSize; | |
|   unsigned char *const buffer = widget->buffer; | |
|   const double xradius = widget->xradius; | |
|   const double yradius = xradius * img_height / img_width; | |
|   const int threadcount = widget->threadcount; | |
|   typedef Eigen::Array<Real, 2, 1> Vector2; | |
|   Vector2 start(widget->center.x() - widget->xradius, widget->center.y() - yradius); | |
|   Vector2 step(2*widget->xradius/img_width, 2*yradius/img_height); | |
|   total_iter = 0; | |
| 
 | |
|   for(int y = id; y < img_height; y += threadcount) | |
|   { | |
|     int pix = y * img_width; | |
| 
 | |
|     // for each pixel, we're going to do the iteration z := z^2 + c where z and c are complex numbers,  | |
|     // starting with z = c = complex coord of the pixel. pzi and pzr denote the real and imaginary parts of z. | |
|     // pci and pcr denote the real and imaginary parts of c. | |
|  | |
|     Packet pzi_start, pci_start; | |
|     for(int i = 0; i < packetSize; i++) pzi_start[i] = pci_start[i] = start.y() + y * step.y(); | |
| 
 | |
|     for(int x = 0; x < alignedWidth; x += packetSize, pix += packetSize) | |
|     { | |
|       Packet pcr, pci = pci_start, pzr, pzi = pzi_start, pzr_buf; | |
|       for(int i = 0; i < packetSize; i++) pzr[i] = pcr[i] = start.x() + (x+i) * step.x(); | |
| 
 | |
|       // do the iterations. Every iters_before_test iterations we check for divergence, | |
|       // in which case we can stop iterating. | |
|       int j = 0; | |
|       typedef Eigen::Matrix<int, packetSize, 1> Packeti; | |
|       Packeti pix_iter = Packeti::Zero(), // number of iteration per pixel in the packet | |
|               pix_dont_diverge; // whether or not each pixel has already diverged | |
|       do | |
|       { | |
|         for(int i = 0; i < iters_before_test/4; i++) // peel the inner loop by 4 | |
|         { | |
| #         define ITERATE \ | |
|             pzr_buf = pzr; \ | |
|             pzr = pzr.square(); \ | |
|             pzr -= pzi.square(); \ | |
|             pzr += pcr; \ | |
|             pzi = (2*pzr_buf)*pzi; \ | |
|             pzi += pci; | |
|           ITERATE ITERATE ITERATE ITERATE | |
|         } | |
|         pix_dont_diverge = ((pzr.square() + pzi.square()) | |
|                            .eval() // temporary fix as what follows is not yet vectorized by Eigen | |
|                            <= Packet::Constant(4)) | |
|                                 // the 4 here is not a magic value, it's a math fact that if | |
|                                 // the square modulus is >4 then divergence is inevitable. | |
|                            .template cast<int>(); | |
|         pix_iter += iters_before_test * pix_dont_diverge; | |
|         j++; | |
|         total_iter += iters_before_test * packetSize; | |
|       } | |
|       while(j < max_iter/iters_before_test && pix_dont_diverge.any()); // any() is not yet vectorized by Eigen | |
|  | |
|       // compute pixel colors | |
|       for(int i = 0; i < packetSize; i++) | |
|       { | |
|         buffer[4*(pix+i)] = 255*pix_iter[i]/max_iter; | |
|         buffer[4*(pix+i)+1] = 0; | |
|         buffer[4*(pix+i)+2] = 0; | |
|       } | |
|     } | |
| 
 | |
|     // if the width is not a multiple of packetSize, fill the remainder in black | |
|     for(int x = alignedWidth; x < img_width; x++, pix++) | |
|       buffer[4*pix] = buffer[4*pix+1] = buffer[4*pix+2] = 0; | |
|   } | |
|   return; | |
| } | |
| 
 | |
| void MandelbrotThread::run() | |
| { | |
|   setTerminationEnabled(true); | |
|   double resolution = widget->xradius*2/widget->width(); | |
|   max_iter = 128; | |
|   if(resolution < 1e-4f) max_iter += 128 * ( - 4 - std::log10(resolution)); | |
|   int img_width = widget->width()/widget->draft; | |
|   int img_height = widget->height()/widget->draft; | |
|   single_precision = resolution > 1e-7f; | |
| 
 | |
|   if(single_precision) | |
|     render<float>(img_width, img_height); | |
|   else | |
|     render<double>(img_width, img_height); | |
| } | |
| 
 | |
| void MandelbrotWidget::paintEvent(QPaintEvent *) | |
| { | |
|   static float max_speed = 0; | |
|   long long total_iter = 0; | |
| 
 | |
|   QTime time; | |
|   time.start(); | |
|   for(int th = 0; th < threadcount; th++) | |
|     threads[th]->start(QThread::LowPriority); | |
|   for(int th = 0; th < threadcount; th++) | |
|   { | |
|     threads[th]->wait(); | |
|     total_iter += threads[th]->total_iter; | |
|   } | |
|   int elapsed = time.elapsed(); | |
| 
 | |
|   if(draft == 1) | |
|   { | |
|     float speed = elapsed ? float(total_iter)*1000/elapsed : 0; | |
|     max_speed = std::max(max_speed, speed); | |
|     std::cout << threadcount << " threads, " | |
|               << elapsed << " ms, " | |
|               << speed << " iters/s (max " << max_speed << ")" << std::endl; | |
|     int packetSize = threads[0]->single_precision | |
|                    ? int(Eigen::internal::packet_traits<float>::size) | |
|                    : int(Eigen::internal::packet_traits<double>::size); | |
|     setWindowTitle(QString("resolution ")+QString::number(xradius*2/width(), 'e', 2) | |
|                   +QString(", %1 iterations per pixel, ").arg(threads[0]->max_iter) | |
|                   +(threads[0]->single_precision ? QString("single ") : QString("double ")) | |
|                   +QString("precision, ") | |
|                   +(packetSize==1 ? QString("no vectorization") | |
|                                   : QString("vectorized (%1 per packet)").arg(packetSize))); | |
|   } | |
|    | |
|   QImage image(buffer, width()/draft, height()/draft, QImage::Format_RGB32); | |
|   QPainter painter(this); | |
|   painter.drawImage(QPoint(0, 0), image.scaled(width(), height())); | |
| 
 | |
|   if(draft>1) | |
|   { | |
|     draft /= 2; | |
|     setWindowTitle(QString("recomputing at 1/%1 resolution...").arg(draft)); | |
|     update(); | |
|   } | |
| } | |
| 
 | |
| void MandelbrotWidget::mousePressEvent(QMouseEvent *event) | |
| { | |
|   if( event->buttons() & Qt::LeftButton ) | |
|   { | |
|     lastpos = event->pos(); | |
|     double yradius = xradius * height() / width(); | |
|     center = Eigen::Vector2d(center.x() + (event->pos().x() - width()/2) * xradius * 2 / width(), | |
|                              center.y() + (event->pos().y() - height()/2) * yradius * 2 / height()); | |
|     draft = 16; | |
|     for(int th = 0; th < threadcount; th++) | |
|       threads[th]->terminate(); | |
|     update(); | |
|   } | |
| } | |
| 
 | |
| void MandelbrotWidget::mouseMoveEvent(QMouseEvent *event) | |
| { | |
|   QPoint delta = event->pos() - lastpos; | |
|   lastpos = event->pos(); | |
|   if( event->buttons() & Qt::LeftButton ) | |
|   { | |
|     double t = 1 + 5 * double(delta.y()) / height(); | |
|     if(t < 0.5) t = 0.5; | |
|     if(t > 2) t = 2; | |
|     xradius *= t; | |
|     draft = 16; | |
|     for(int th = 0; th < threadcount; th++) | |
|       threads[th]->terminate(); | |
|     update(); | |
|   } | |
| } | |
| 
 | |
| int main(int argc, char *argv[]) | |
| { | |
|   QApplication app(argc, argv); | |
|   MandelbrotWidget w; | |
|   w.show(); | |
|   return app.exec(); | |
| } | |
| 
 | |
| #include "mandelbrot.moc"
 |