ddongstudy

Newton Fractal 본문

수업 과제 & 실습/병렬프로그래밍

Newton Fractal

ddongyeonn 2020. 7. 4. 16:15

1. Newton Fractal 정의

Newton Fractal 정의

2. CPU 구현

// CPU(점&픽셀) 최종
#include "..\usr\include\GL\freeglut.h"
#include <math.h>
#include <stdio.h>
#include <time.h>
#include<stdlib.h>
#include<complex>
#include<memory.h>

struct ucomplex {
	float real;
	float imag;
};

const int width = 1024;
float scope = 0.8; // 확대,축소
float pre_scope = 0.0;
float* z = (float*)malloc(width * width * sizeof(float));
unsigned char Image[width * width * 3];
float dx = 0.0, dy = 0.0; // mouse move
bool TimeDir = true, Mode = true; // 확대 축소 방향 . time/scroll 
int key_s = 0; // 방정식 바꾸는 key
int key_d = 0; // 차수 늘리는 key
float Zoom = -50.0;
int StartPt[2];

// 콜백 함수
void Keyboard(unsigned char key, int x, int y);
void Mouse(int button, int state, int x, int y);
void MouseMove(int x, int y);
void MouseWheel(int button, int dir, int x, int y);
void Reshape(int w, int h);
void Timer(int id);


// 연산자 오버라이딩
////////////////////////////////////////////////////////////////////////////////////////

float abs(ucomplex x) {
	return sqrt(x.real * x.real + x.imag * x.imag);
}

ucomplex operator*(ucomplex x, ucomplex y) {
	ucomplex c = {
	(x.real * y.real - x.imag * y.imag),
	(x.imag * y.real + x.real * y.imag)
	};
	return c;
}

ucomplex operator*(ucomplex x, float dy) {
	ucomplex y = { dy, 0.0 };
	return x * y;
}

ucomplex operator/(ucomplex x, ucomplex y) {
	ucomplex c = {
	(x.real * y.real + x.imag * y.imag) / (y.real * y.real + y.imag * y.imag),
	(x.imag * y.real - x.real * y.imag) / (y.real * y.real + y.imag * y.imag)
	};
	return c;
}

ucomplex operator+(ucomplex x, ucomplex y) {
	ucomplex c = {
	(x.real + y.real),
	(x.imag + y.imag)
	};
	return c;
}

ucomplex operator+(ucomplex x, float dy) {
	ucomplex y = { dy, 0.0 };
	return x + y;
}

ucomplex operator-(ucomplex x, ucomplex y) {
	ucomplex c = {
	(x.real - y.real),
	(x.imag - y.imag)
	};
	return c;
}

ucomplex operator-(ucomplex x, float dy) {
	ucomplex y = { dy, 0.0 };
	return x - y;
}
/////////////////////////////////////////////////////////////////////////////////////////

ucomplex f(ucomplex x)
{
	// 1. 초기 방정식: x^3 - 1
	ucomplex d = x * x * x - 1.0;
	if (key_s == 0)
	{
		ucomplex temp_d = x * x * x;
		if (key_d > 0) {
			for (int i = 0; i < key_d; i++)
				temp_d = temp_d * x;
		}
		d = temp_d - 1.0;
	}
	// 2. x^3 - 2x^2 + 2
	else if (key_s == 1)
	{
		d = x * x * x - x * x * 2.0 + 2.0;
	}
	// 3. x^8 + 15x^4 - 16
	else if (key_s == 2)
	{
		d = x * x * x * x * x * x * x * x + x * x * x * x * 15.0 - 16.0;
	}
	// 4. sin(x)
	else if (key_s == 3)
	{
		typedef std::complex<float> dcomplex;
		dcomplex a(x.real, x.imag);
		d = {
		   sin(a).real(),
		   sin(a).imag()
		};
	}
	return d;
}

ucomplex df(ucomplex x) // f 미분값
{
	ucomplex d = x * x * 3;
	// 1. 3x^2
	if (key_s == 0)
	{
		ucomplex temp_d = x * x;
		float de = 3.0;
		if (key_d > 0) {
			for (int i = 0; i < key_d; i++) {
				temp_d = temp_d * x;
				de = de + 1.0;
			}
		}
		d = temp_d * de;

	}
	// 2. 3x^2 - 4x
	else if (key_s == 1)
	{
		d = x * x * 3 - x * 4.0;
	}
	// 3. 8x^7 +60x^3
	else if (key_s == 2)
	{
		d = x * x * x * x * x * x * x * 8.0 + x * x * x * 60.0;
	}
	// 4. cos(x)
	else if (key_s == 3)
	{
		typedef std::complex<float> dcomplex;
		dcomplex a(x.real, x.imag);
		d = {
		   cos(a).real(),
		   cos(a).imag()
		};
	}
	return d;
}

//사용자 정의함수
__int64 GetMicroSecond();
float newton(ucomplex x0, float eps, int maxiter);
void MathCPU();
void Display();

int main(int argc, char** argv) {

	// opengl 초기화
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_RGB);
	//glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);

	// 윈도우 설정
	glutInitWindowSize(width, width);
	//glutInitWindowSize(700, 700);
	glutInitWindowPosition(0, 0);
	glutCreateWindow("Newton");
	glClearColor(1.0, 1.0, 1.0, 1.0);

	// 관측설정
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(-2, 2, 2, -2, 1, -1);

	// 콜백함수 등록
	glutDisplayFunc(Display);
	glutTimerFunc(30, Timer, 0);
	glutReshapeFunc(Reshape);
	glutKeyboardFunc(Keyboard);
	glutMouseFunc(Mouse);
	glutMotionFunc(MouseMove);
	glutMouseWheelFunc(MouseWheel);

	glutMainLoop();
}

void Keyboard(unsigned char key, int x, int y)
{
	if (key == 27)
		exit(-1);
	// 방정식 바꾸기
	if (key == 's')
	{
		key_d = 0;
		key_s = (key_s + 1) % 4;
	}
	// 차수 늘리기
	if (key == 'd')
	{
		key_d++;
	}
	if (key == 'e')
	{
		if (key_d > 0)
			key_d--;
	}
	// 모드 바꾸기
	if (key == 'f') {
		Mode = !Mode;
	}
	glutPostRedisplay();
}

void Mouse(int button, int state, int x, int y)
{
	// 마우스 버튼을 누른 경우,
	if (state == GLUT_DOWN)
	{
		StartPt[0] = x;
		StartPt[1] = y;
	}

	// 마우스 버튼을 땐 경우,
	if (state == GLUT_UP)
		StartPt[0] = StartPt[1] = 0;
}

void MouseMove(int x, int y) {
	dx += -(x - StartPt[0]) * 0.01; // x 이동값
	dy += -(StartPt[1] - y) * 0.01; // y 이동값
}

void MouseWheel(int button, int dir, int x, int y)
{
	// Mode = false 일때
	if (!Mode) {
		if (dir > 0)
		{
			pre_scope = scope;
			scope -= 0.01;
			if (scope <= 0.00001)
				scope = 0.00001;
		}
		else
		{
			pre_scope = scope;
			scope += 0.01;
			if (scope >= 1.57)
				scope = 1.57;
		}
	}
	glutPostRedisplay();
}

float newton(ucomplex x0, float eps, int maxiter) {
	ucomplex x = x0;
	int iter = 0;
	// 픽셀의 고유 iter값 계산
	while (abs(f(x)) > eps && iter <= maxiter) {
		iter++;
		x = x - f(x) / df(x); // 점화식
	}
	return iter;
}

void Timer(int id)
{
	// Mode = true 일때 시간에따라 확대,축소
	if (Mode) {
		if (TimeDir == true) {
			scope -= 0.05;
			if (scope <= 0.00001) {
				scope = 0.00001;
				TimeDir = false;
			}
		}
		else {
			scope += 0.05;
			if (scope >= 1.57) {
				scope = 1.57;
				TimeDir = true;
			}
		}
	}
	glutPostRedisplay();
	glutTimerFunc(30, Timer, 0); //30ms마다 Timer함수 호출
}

void Reshape(int w, int h)
{
	glViewport(0, 0, w, h);
}

void MathCPU() {

	// 범위 설정
	float xmin = -2, xmax = 2;
	float ymin = -2, ymax = 2;

	// 각 픽셀간의 간격을 설정 
	int xsteps = width, ysteps = width;
	float hx = (xmax - xmin) / xsteps, hy = (ymax - ymin) / ysteps;
	float eps = 0.0001; // eps보다 작아지면 더이상 진행하지 않음
	int maxiter = 255; // 최대 iter값

	float scopenum = 15 * sin(scope); // 확대 축소 범위: -15 ~ 15
	float x, y;
	y = ymin;
	// 픽셀별 뉴턴값 계산
	for (int i = 0; i < ysteps; i++) {
		x = xmin;
		for (int j = 0; j < xsteps; j++) {
			ucomplex  xy = { x * scopenum + dx,y * scopenum + dy }; // 복소수값 할당
			z[i * width + j] = newton(xy, eps, maxiter); // 뉴턴값 계산
			x += hx; // + 1열
		}
		y += hy; // +1행
	}
}

// width * width 픽셀에 색상을 지정
void Display() {
	glClearColor(1, 1, 1, 1);
	glClear(GL_COLOR_BUFFER_BIT);

	__int64 _st = GetMicroSecond();
	MathCPU();

	for (int i = 0; i < width; i++) {
		for (int j = 0; j < width; j++) {
			float color = (z[i * width + j] / (float)256);
			int offset = (i * width + j) * 3;
			Image[offset] = fmod(color * 13, 1.0f) * 255;
			Image[offset + 1] = fmod(color * 33, 1.0f) * 255;
			Image[offset + 2] = fmod(color * 49, 1.0f) * 255;
		}
	}
	glDrawPixels(width, width, GL_RGB, GL_UNSIGNED_BYTE, Image);

	printf("Elapsed time: %I64d mico sec \n", GetMicroSecond() - _st);

	glFinish();
}


__int64 GetMicroSecond()
{
	LARGE_INTEGER frequency;
	LARGE_INTEGER now;

	if (!QueryPerformanceFrequency(&frequency))
		return (__int64)GetTickCount();

	if (!QueryPerformanceCounter(&now))
		return (__int64)GetTickCount();

	return((now.QuadPart) / (frequency.QuadPart / 1000000));
}
/*
//width * width 점을 찍어서 렌더링
void Display() {
	glClear(GL_COLOR_BUFFER_BIT);
	glBegin(GL_POINTS);

	__int64 _st = GetMicroSecond();
	MathCPU();

   float xmin = -2, xmax = 2;
   float ymin = -2, ymax = 2;

   int xsteps = width, ysteps = width;
   float hx = (xmax - xmin) / xsteps, hy = (ymax - ymin) / ysteps;

   float x, y;
   float max = z[0];
   float min = z[0];

   for (int i = 0; i < width; i++) {
	  for (int j = 0; j < width; j++) {
		 if (z[i * width + j] >= max) max = z[i * width + j];
		 if (z[i * width + j] <= min) min = z[i * width + j];
	  }
   }

   y = ymin;
	for (int i = 0; i < width; i++) {
	  x = xmin;
		for (int j = 0; j < width; j++) {
			float color = (z[i * width + j]) / (256);
			glColor3d(fmod(color * 13, 1.0f),
				fmod(color * 33, 1.0f),
				fmod(color * 49, 1.0f));
			glVertex2d(x, y);
		 x += hx;
		}
	  y += hy;
	}

   printf("Elapsed time: %I64d mico sec \n", GetMicroSecond() - _st);
	glEnd();
	glutSwapBuffers();
}
*/
  • newton() 

    • 임의의 복소수값을 전달받으면 newton점화식을 수렴할때까지 반복하고 반복된 횟수를 반환한다.

  • MathCPU()

    • 픽셀별로 복소수를 할당한다.

    • 복소수를 할당할 x, y범위를 설정하고 범위 내에서 픽셀의 개수(width*width)만큼 복소수 값을 설정한다.

    • 생성된 복소수 별로 newton점화식을 계산하고 결과값을 z배열에 저장한다.

  • Display()

    • z배열에 저장된 픽셀별 newton점화식 값을 이용해 픽셀의 색상을 결정한다.

3. GPU 구현

// GPU - PBO 이용
#include <math.h>
//#include <glut.h>
#include <stdio.h>
#include <time.h>
#include <cuda.h>
#include <iostream>
#include <complex>
#include <cuComplex.h>
#include <thrust/complex.h>
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include "..\usr\include\GL\glew.h"
#include "..\usr\include\GL\freeglut.h"
#include "cuda_gl_interop.h"
using namespace std;

// 복소수 구조체
struct ucomplex {
	float real;
	float imag;
};

const int width = 1024; // 넓이
float scope = 0.8; // 확대,축소
float pre_scope = 0.0;
float Dx = 0.0, Dy = 0.0; // mouse move
bool TimeDir = true, Mode = true; // 확대 축소 방향 . time/scroll 
int key_s = 0; // 방정식 바꾸는 key
int key_d = 0; // 차수 늘리는 key
float Zoom = -50.0;
int StartPt[2];

//GPU 관련 변수
dim3 dimGrid;
dim3 dimBlock;
float* zD; // 뉴턴방정식 계산 결과를 저장할 배열
float p = 0;
GLuint gl_pbo; // 픽셀 버퍼를 가르키는 OpenGL 핸들 
cudaGraphicsResource* cuda_pbo; // 픽셀 버퍼를 가르키는 CUDA 핸들 
uchar4* pDevImage; // 픽셀 버퍼에 대한 실제 메모리 주소

// 콜백 함수
void Keyboard(unsigned char key, int x, int y);
void Mouse(int button, int state, int x, int y);
void MouseMove(int x, int y);
void MouseWheel(int button, int dir, int x, int y);
void Reshape(int w, int h);
void Timer(int id);
void Close();
void Display();

//사용자 정의함수
__global__ void MathGPUKernel(float* zD, float scopenum, float dx, float dy, int Key_d, int Key_s, uchar4* DevImage);
__int64 GetMicroSecond();
__host__ __device__ float abs(ucomplex x) {
	return sqrt(x.real * x.real + x.imag * x.imag);
}
__host__ __device__ ucomplex operator*(ucomplex x, ucomplex y) {
	ucomplex c = {
	(x.real * y.real - x.imag * y.imag),
	(x.imag * y.real + x.real * y.imag)
	};
	return c;
}
__host__ __device__ ucomplex operator*(ucomplex x, float dy) {
	ucomplex y = { dy, 0.0 };
	return x * y;
}
__host__ __device__ ucomplex operator/(ucomplex x, ucomplex y) {
	ucomplex c = {
	(x.real * y.real + x.imag * y.imag) / (y.real * y.real + y.imag * y.imag),
	(x.imag * y.real - x.real * y.imag) / (y.real * y.real + y.imag * y.imag)
	};
	return c;
}
__host__ __device__ ucomplex operator+(ucomplex x, ucomplex y) {
	ucomplex c = {
	(x.real + y.real),
	(x.imag + y.imag)
	};
	return c;
}
__host__ __device__ ucomplex operator+(ucomplex x, float dy) {
	ucomplex y = { dy, 0.0 };
	return x + y;
}
__host__ __device__ ucomplex operator-(ucomplex x, ucomplex y) {
	ucomplex c = {
	(x.real - y.real),
	(x.imag - y.imag)
	};
	return c;
}
__host__ __device__ ucomplex operator-(ucomplex x, float dy) {
	ucomplex y = { dy, 0.0 };
	return x - y;
}
__host__ __device__ ucomplex f(ucomplex x, int Key_d, int Key_s) {
	// 1. 초기 방정식: x^3 - 1
	ucomplex d = x * x * x - 1.0;
	if (Key_s == 0)
	{
		ucomplex temp_d = x * x * x;
		if (Key_d > 0) {
			for (int i = 0; i < Key_d; i++)
				temp_d = temp_d * x;
		}
		d = temp_d - 1.0;
	}
	// 2. x^3 - 2x^2 + 2
	else if (Key_s == 1)
	{
		d = x * x * x - x * x * 2.0 + 2.0;
	}
	// 3. x^8 + 15x^4 - 16
	else if (Key_s == 2)
	{
		d = x * x * x * x * x * x * x * x + x * x * x * x * 15.0 - 16.0;
	}
	// 4. sin(x)
	else if (Key_s == 3)
	{
		thrust::complex<float> c = thrust::complex<float>(x.real, x.imag);
		d = {
		   sin(c).real(),
		   sin(c).imag()
		};
	}
	return d;
}
__host__ __device__ ucomplex df(ucomplex x, int Key_d, int Key_s) {
	ucomplex d = x * x * 3;
	// 1. 3x^2
	if (Key_s == 0)
	{
		ucomplex temp_d = x * x;
		float de = 3.0;
		if (Key_d > 0) {
			for (int i = 0; i < Key_d; i++) {
				temp_d = temp_d * x;
				de = de + 1.0;
			}
		}
		d = temp_d * de;

	}
	// 2. 3x^2 - 4x
	else if (Key_s == 1)
	{
		d = x * x * 3 - x * 4.0;
	}
	// 3. 8x^7 +60x^3
	else if (Key_s == 2)
	{
		d = x * x * x * x * x * x * x * 8.0 + x * x * x * 60.0;
	}
	// 4. cos(x)
	else if (Key_s == 3)
	{
		thrust::complex<float> c = thrust::complex<float>(x.real, x.imag);
		d = {
		   cos(c).real(),
		   cos(c).imag()
		};
	}
	return d;
}
__host__ __device__ float newton(ucomplex x0, float eps, int maxiter, int Key_d, int Key_s) {
	ucomplex x = x0;
	int iter = 0;
	// 뉴턴식 계산 - 픽셀의 고유 iter값을 얻는다
	while (abs(f(x, Key_d, Key_s)) > eps && iter <= maxiter) {
		iter++;
		x = x - f(x, Key_d, Key_s) / df(x, Key_d, Key_s); // 점화식
	}
	return iter;
}

int main(int argc, char** argv) {

	// opengl 초기화
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_RGBA);
	//glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);

	// 윈도우 설정
	glutInitWindowSize(width, width);
	//glutInitWindowSize(700, 700);
	glutInitWindowPosition(0, 0);
	glutCreateWindow("Newton");


	// 관측설정
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(-2, 2, 2, -2, 1, -1);

	// 콜백함수 등록
	glutDisplayFunc(Display);
	glutTimerFunc(30, Timer, 0);
	glutReshapeFunc(Reshape);
	glutKeyboardFunc(Keyboard);
	glutMouseFunc(Mouse);
	glutMotionFunc(MouseMove);
	glutMouseWheelFunc(MouseWheel);
	glutCloseFunc(Close);

	// grid, block 만들기
	const int block_width = 16;
	cudaMalloc((void**)&zD, width * width * sizeof(float));
	dimGrid = dim3((width - 1 / block_width) + 1, (width - 1 / block_width) + 1);
	dimBlock = dim3(block_width, block_width);



	// glew 초기화
	glewInit();

	// GPU Device 세팅
	cudaSetDevice(0);
	cudaGLSetGLDevice(0);
	glGenBuffers(1, &gl_pbo);
	glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, gl_pbo);

	// 픽셀 버퍼 할당
	glBufferData(GL_PIXEL_UNPACK_BUFFER_ARB, width * width * sizeof(uchar4), NULL, GL_DYNAMIC_DRAW_ARB);

	// 이벤트 처리 루프 진입
	glutMainLoop();

	// 동적할당 해제
	cudaFree(zD);
}

void MathGPU() {

	// 화면 비율에 맞춰 확대축소 비율 계산
	if (width < 1000)
		p = 1;
	else
		p = (width / 999);
	float scopenum = p * 15 * sin(scope);

	float dx = Dx, dy = Dy;
	MathGPUKernel << <dimGrid, dimBlock >> > (zD, scopenum, dx, dy, key_d, key_s, pDevImage); // 커널함수 호출
	cudaDeviceSynchronize(); // 동기화
}

__global__ void MathGPUKernel(float* zD, float scopenum, float dx, float dy, int Key_d, int Key_s, uchar4* DevImage) {
	float xi = blockIdx.x * blockDim.x + threadIdx.x;
	float yi = blockIdx.y * blockDim.y + threadIdx.y;
	int maxiter = 255;
	float eps = 0.0001;
	// 뉴턴 점화식 계산
	uchar4 C;
	if (xi < width && yi < width) {

		// 각 픽셀별 복소수값 할당 후 뉴턴값 계산
		// 마우스 스크롤(scope) -> 전체 범위가 확대 축소(곱 연산)
		// 마우스 드래그(dx,dy) -> 범위의 크기는 그대로이고 범위가 평행이동(+연산)
		ucomplex xy = { (xi / width * 4 - 2) * scopenum + dx, (yi / width * 4 - 2) * scopenum + dy };
		zD[(int)yi * width + (int)xi] = newton(xy, eps, maxiter, Key_d, Key_s);

		// 색상 계산
		float color = zD[(int)yi * width + (int)xi] / maxiter;
		int offset = ((int)yi * width + (int)xi);
		C.x = color * 13 * 255;
		C.y = color * 33 * 255;
		C.z = color * 49 * 255;
		C.w = 255;
		DevImage[offset] = C;
	}
}

// width * width 픽셀에 색상을 지정
void Display() {

	// 배경 초기화
	__int64 _st = GetMicroSecond();
	glClearColor(1, 1, 1, 1);
	glClear(GL_COLOR_BUFFER_BIT);

	// 픽셀 버퍼를 CUDA에 등록
	cudaGraphicsGLRegisterBuffer(&cuda_pbo, gl_pbo, cudaGraphicsMapFlagsNone);

	// 픽셀 버퍼를 CUDA 시스템에 mapping
	size_t size;
	cudaGraphicsMapResources(1, &cuda_pbo, NULL);
	cudaGraphicsResourceGetMappedPointer((void**)&pDevImage, &size, cuda_pbo);

	MathGPU();
	glDrawPixels(width, width, GL_RGBA, GL_UNSIGNED_BYTE, 0);
	printf("Elapsed time: %I64d mico sec \n", GetMicroSecond() - _st);
	glFinish();

	cudaGraphicsUnmapResources(1, &cuda_pbo, NULL);
	cudaGraphicsUnregisterResource(cuda_pbo);
}

void Close() {
	// GPU 리셋
	cudaDeviceReset();

	//픽셀 버퍼의 바인딩 해제 및 핸들 제거
	glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, 0);
	glDeleteBuffers(1, &gl_pbo);
}

void Timer(int id)
{
	// Mode = true 일때 시간에따라 자동 확대, 축소 (key = f)
	// TimeDir = true 이면 축소 false이면 확대
	if (Mode) {
		if (TimeDir == true) {
			scope -= 0.05;
			if (scope <= 0.00001) {
				scope = 0.00001;
				TimeDir = false;
			}
		}
		else {
			scope += 0.05;
			if (scope >= 1.57) {
				scope = 1.57;
				TimeDir = true;
			}
		}
	}
	glutPostRedisplay();
	glutTimerFunc(30, Timer, 0); //30ms마다 Timer함수 호출
}

void Reshape(int w, int h)
{
	glViewport(0, 0, w, h);
}

void Keyboard(unsigned char key, int x, int y)
{
	if (key == 27)
		exit(-1);
	// 방정식 바꾸기
	if (key == 's')
	{
		key_d = 0;
		key_s = (key_s + 1) % 4;
	}
	// 차수 늘리기
	if (key == 'd')
	{
		key_d++;
	}
	// 차수 낮추기
	if (key == 'e')
	{
		if (key_d > 0)
			key_d--;
	}
	// 자동 확대 모드 바꾸기
	if (key == 'f') {
		Mode = !Mode;
	}
	glutPostRedisplay();
}

void Mouse(int button, int state, int x, int y)
{
	// 마우스 버튼을 누른 경우,
	if (state == GLUT_DOWN)
	{
		StartPt[0] = x;
		StartPt[1] = y;
	}

	// 마우스 버튼을 땐 경우,
	if (state == GLUT_UP)
		StartPt[0] = StartPt[1] = 0;
}

void MouseMove(int x, int y) {
	Dx += -(x - StartPt[0]) * 0.01; // x 이동값
	Dy += -(StartPt[1] - y) * 0.01; // y 이동값
}

void MouseWheel(int button, int dir, int x, int y)
{
	// Mode = false 일때
	// 수동 확대 축소
	if (!Mode) {
		if (dir > 0)
		{
			pre_scope = scope;
			scope -= 0.01;
			if (scope <= 0.00001)
				scope = 0.00001;
		}
		else
		{
			pre_scope = scope;
			scope += 0.01;
			if (scope >= 1.57)
				scope = 1.57;
		}
	}
	glutPostRedisplay();
}

__int64 GetMicroSecond()
{
	LARGE_INTEGER frequency;
	LARGE_INTEGER now;

	if (!QueryPerformanceFrequency(&frequency))
		return (__int64)GetTickCount();

	if (!QueryPerformanceCounter(&now))
		return (__int64)GetTickCount();

	return((now.QuadPart) / (frequency.QuadPart / 1000000));
}

// GPU - 픽셀에 색상을 지정
/*
#include "..\usr\include\GL\freeglut.h"
#include <math.h>
//#include <glut.h>
#include <stdio.h>
#include <time.h>
#include <cuda.h>
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <iostream>
#include <complex>
#include <cuComplex.h>
#include <thrust/complex.h>
using namespace std;

struct ucomplex {
	float real;
	float imag;
};

const int width = 1024;
float scope = 0.8; // 확대,축소
float pre_scope = 0.0;
float* z = (float*)malloc(width * width * sizeof(float));
unsigned char Image[width * width * 3];
float Dx = 0.0, Dy = 0.0; // mouse move
bool TimeDir = true, Mode = true; // 확대 축소 방향 . time/scroll
int key_s = 0; // 방정식 바꾸는 key
int key_d = 0; // 차수 늘리는 key
float Zoom = -50.0;
int StartPt[2];

//GPU 관련 변수
dim3 dimGrid;
dim3 dimBlock;
float* zD;
unsigned char* DevImage;
int Size;
float p = 0;

// 콜백 함수
void Keyboard(unsigned char key, int x, int y);
void Mouse(int button, int state, int x, int y);
void MouseMove(int x, int y);
void MouseWheel(int button, int dir, int x, int y);
void Reshape(int w, int h);
void Timer(int id);

//사용자 정의함수
void MathCPU();
void Display();
__int64 GetMicroSecond();
__global__ void MathGPUKernel(float* zD, float scopenum, float dx, float dy, int Key_d, int Key_s, unsigned char* DevImage);
__host__ __device__ float abs(ucomplex x) {
	return sqrt(x.real * x.real + x.imag * x.imag);
}
__host__ __device__ ucomplex operator*(ucomplex x, ucomplex y) {
	ucomplex c = {
	(x.real * y.real - x.imag * y.imag),
	(x.imag * y.real + x.real * y.imag)
	};
	return c;
}
__host__ __device__ ucomplex operator*(ucomplex x, float dy) {
	ucomplex y = { dy, 0.0 };
	return x * y;
}
__host__ __device__ ucomplex operator/(ucomplex x, ucomplex y) {
	ucomplex c = {
	(x.real * y.real + x.imag * y.imag) / (y.real * y.real + y.imag * y.imag),
	(x.imag * y.real - x.real * y.imag) / (y.real * y.real + y.imag * y.imag)
	};
	return c;
}
__host__ __device__ ucomplex operator+(ucomplex x, ucomplex y) {
	ucomplex c = {
	(x.real + y.real),
	(x.imag + y.imag)
	};
	return c;
}
__host__ __device__ ucomplex operator+(ucomplex x, float dy) {
	ucomplex y = { dy, 0.0 };
	return x + y;
}
__host__ __device__ ucomplex operator-(ucomplex x, ucomplex y) {
	ucomplex c = {
	(x.real - y.real),
	(x.imag - y.imag)
	};
	return c;
}
__host__ __device__ ucomplex operator-(ucomplex x, float dy) {
	ucomplex y = { dy, 0.0 };
	return x - y;
}
__host__ __device__ ucomplex f(ucomplex x, int Key_d, int Key_s) {
	// 1. 초기 방정식: x^3 - 1
	ucomplex d = x * x * x - 1.0;
	if (Key_s == 0)
	{
		ucomplex temp_d = x * x * x;
		if (Key_d > 0) {
			for (int i = 0; i < Key_d; i++)
				temp_d = temp_d * x;
		}
		d = temp_d - 1.0;
	}
	// 2. x^3 - 2x^2 + 2
	else if (Key_s == 1)
	{
		d = x * x * x - x * x * 2.0 + 2.0;
	}
	// 3. x^8 + 15x^4 - 16
	else if (Key_s == 2)
	{
		d = x * x * x * x * x * x * x * x + x * x * x * x * 15.0 - 16.0;
	}
	// 4. sin(x)
	else if (Key_s == 3)
	{
		thrust::complex<float> c = thrust::complex<float>(x.real, x.imag);
		d = {
		   sin(c).real(),
		   sin(c).imag()
		};
	}
	return d;
}
__host__ __device__ ucomplex df(ucomplex x, int Key_d, int Key_s) {
	ucomplex d = x * x * 3;
	// 1. 3x^2
	if (Key_s == 0)
	{
		ucomplex temp_d = x * x;
		float de = 3.0;
		if (Key_d > 0) {
			for (int i = 0; i < Key_d; i++) {
				temp_d = temp_d * x;
				de = de + 1.0;
			}
		}
		d = temp_d * de;

	}
	// 2. 3x^2 - 4x
	else if (Key_s == 1)
	{
		d = x * x * 3 - x * 4.0;
	}
	// 3. 8x^7 +60x^3
	else if (Key_s == 2)
	{
		d = x * x * x * x * x * x * x * 8.0 + x * x * x * 60.0;
	}
	// 4. cos(x)
	else if (Key_s == 3)
	{
		thrust::complex<float> c = thrust::complex<float>(x.real, x.imag);
		d = {
		   cos(c).real(),
		   cos(c).imag()
		};
	}
	return d;
}
__host__ __device__ float newton(ucomplex x0, float eps, int maxiter, int Key_d, int Key_s) {
	ucomplex x = x0;
	int iter = 0;
	// 픽셀의 고유 iter값 계산
	while (abs(f(x, Key_d, Key_s)) > eps && iter <= maxiter) {
		iter++;
		x = x - f(x, Key_d, Key_s) / df(x, Key_d, Key_s); // 점화식
	}
	return iter;
}

int main(int argc, char** argv) {
	// opengl 초기화
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_RGB);
	//glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);

	// 윈도우 설정
	glutInitWindowSize(width, width);
	//glutInitWindowSize(700, 700);
	glutInitWindowPosition(0, 0);
	glutCreateWindow("Newton");
	glClearColor(1.0, 1.0, 1.0, 1.0);

	// 관측설정
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(-2, 2, 2, -2, 1, -1);

	// 콜백함수 등록
	glutDisplayFunc(Display);
	glutTimerFunc(30, Timer, 0);
	glutReshapeFunc(Reshape);
	glutKeyboardFunc(Keyboard);
	glutMouseFunc(Mouse);
	glutMotionFunc(MouseMove);
	glutMouseWheelFunc(MouseWheel);

	// grid, block 만들기
	const int block_width = 16;
	Size = width * width * sizeof(float);
	cudaMalloc((void**)&zD, Size);
	cudaMalloc((void**)&DevImage, width * width * 3);
	dimGrid = dim3((width - 1 / block_width) + 1, (width - 1 / block_width) + 1);
	dimBlock = dim3(block_width, block_width);
	p = (width / 1024);

	glutMainLoop();

	// 동적할당 해제
	delete[] z;
	cudaFree(DevImage);
	cudaFree(zD);
}

void MathGPU() {
	float scopenum = p * 15 * sin(scope);
	float dx = Dx, dy = Dy;

	cudaError_t cudaStatus = cudaSetDevice(0);
	MathGPUKernel << <dimGrid, dimBlock >> > (zD, scopenum, dx, dy, key_d, key_s, DevImage);
	cudaDeviceSynchronize();
	cudaMemcpy(Image, DevImage, width * width * 3, cudaMemcpyDeviceToHost);
}

__global__ void MathGPUKernel(float* zD, float scopenum, float dx, float dy, int Key_d, int Key_s, unsigned char* DevImage) {
	float xi = blockIdx.x * blockDim.x + threadIdx.x;
	float yi = blockIdx.y * blockDim.y + threadIdx.y;
	int maxiter = 255;
	float eps = 0.0001;
	// 뉴턴 점화식 계산
	if (xi < width && yi < width) {
		ucomplex xy = { (xi / width * 4 - 2) * scopenum + dx,(yi / width * 4 - 2) * scopenum + dy };
		zD[(int)yi * width + (int)xi] = newton(xy, eps, maxiter, Key_d, Key_s);

		// 색상 계산
		float color = zD[(int)yi * width + (int)xi] / maxiter;
		int offset = ((int)yi * width + (int)xi) * 3;
		DevImage[offset] = color * 13 * 255;
		DevImage[offset + 1] = color * 33 * 255;
		DevImage[offset + 2] = color * 49 * 255;
		//printf("%f\n", DevImage[offset]);
	}
}

// width * width 픽셀에 색상을 지정
void Display() {
	glClearColor(1, 1, 1, 1);
	glClear(GL_COLOR_BUFFER_BIT);

	__int64 _st = GetMicroSecond();
	MathGPU();
	//for (int i = 0; i < width * width * 3; i++)
	//   printf("%f\n", Image[i]);
	glDrawPixels(width, width, GL_RGB, GL_UNSIGNED_BYTE, Image);
	printf("Elapsed time: %I64d mico sec \n", GetMicroSecond() - _st);
	glFinish();
}

void Timer(int id)
{
	// Mode = true 일때 시간에따라 확대, 축소
	if (Mode) {
		if (TimeDir == true) {
			scope -= 0.05;
			if (scope <= 0.00001) {
				scope = 0.00001;
				TimeDir = false;
			}
		}
		else {
			scope += 0.05;
			if (scope >= 1.57) {
				scope = 1.57;
				TimeDir = true;
			}
		}
	}
	glutPostRedisplay();
	glutTimerFunc(30, Timer, 0); //30ms마다 Timer함수 호출
}

void Reshape(int w, int h)
{
	glViewport(0, 0, w, h);
}

void Keyboard(unsigned char key, int x, int y)
{
	if (key == 27)
		exit(-1);
	// 방정식 바꾸기
	if (key == 's')
	{
		key_d = 0;
		key_s = (key_s + 1) % 4;
	}
	// 차수 늘리기
	if (key == 'd')
	{
		// 삼각함수는 차수 늘리지 않음
		if (key_s != 3)
			key_d++;
	}
	// 모드 바꾸기
	if (key == 'f') {
		Mode = !Mode;
	}
	glutPostRedisplay();
}

void Mouse(int button, int state, int x, int y)
{
	// 마우스 버튼을 누른 경우,
	if (state == GLUT_DOWN)
	{
		StartPt[0] = x;
		StartPt[1] = y;
	}

	// 마우스 버튼을 땐 경우,
	if (state == GLUT_UP)
		StartPt[0] = StartPt[1] = 0;
}

void MouseMove(int x, int y) {
	Dx += -(x - StartPt[0]) * 0.01; // x 이동값
	Dy += -(StartPt[1] - y) * 0.01; // y 이동값
}

void MouseWheel(int button, int dir, int x, int y)
{
	// Mode = false 일때
	if (!Mode) {
		if (dir > 0)
		{
			pre_scope = scope;
			scope -= 0.01;
			if (scope <= 0.00001)
				scope = 0.00001;
		}
		else
		{
			pre_scope = scope;
			scope += 0.01;
			if (scope >= 1.57)
				scope = 1.57;
		}
	}
	glutPostRedisplay();
}

__int64 GetMicroSecond()
{
	LARGE_INTEGER frequency;
	LARGE_INTEGER now;

	if (!QueryPerformanceFrequency(&frequency))
		return (__int64)GetTickCount();

	if (!QueryPerformanceCounter(&now))
		return (__int64)GetTickCount();

	return((now.QuadPart) / (frequency.QuadPart / 1000000));
}
*/

 

  • CPU버전과 달리 2중 for문을 사용하지 않고 커널함수를 호출하여 width*width 만큼의 스레드를 생성하였다.

  • 각 스레드별로 MathGpuKernel()함수 내에서 복소수 할당 -> newton점화식 계산 -> 색상 지정 까지의 과정이 한번에 진행된다.

  • PBO를 이용하여 구현하여 결과값을 CPU로 전달할 필요없이 GPU에서 바로 색상을 출력하여 성능개선을 하였다.

  • 정점을 이용하여 출력 VS 픽셀을 이용하여 출력 과정을 비교해본 결과 픽셀을 이용해 출력하는 과정이 시간이 덜 걸리는 것을 확인 할 수 있었다. (주석 처리된 부분이 정점을 이용해 출력한 부분)

 

4. 결과 영상

Newton Fractal CPU

 

Newton Fractal GPU

 

5. 성능 비교

CPU / GPU 실행시간 비교

 

 

 

 

'수업 과제 & 실습 > 병렬프로그래밍' 카테고리의 다른 글

GPU를 이용한 Julia Set 구현  (0) 2020.07.04