CUDA 프로그래밍 강의를 들은건 작년 1학기 였는데 상당히 매력적인 과목이었다.


예제로 나온 CPU 코드와 GPU 코드를 돌려서 걸리는 시간을 확인했을 때의 충격이 엄청났다.


아무튼, 이번엔 CUDA 과제에 대한 질문을 받았다.


//block과 thread의 개수를 고정 (BLOCK_WIDTH와 TILE_WIDTH 값을 변경하지 말 것)
#define BLOCK_WIDTH 32
#define TILE_WIDTH 32

dim3 dimGrid(BLOCK_WIDTH, BLOCK_WIDTH);//32*32개 블록 dim3 dimBlock(TILE_WIDTH, TILE_WIDTH);//하나의블록당 32 * 32개의 쓰레드

Matrix 간의 병렬곱을 하는 것이 과제였는데, BLOCK_WIDTH와 TILE_WIDTH 값이 고정된 채로 진행하는 것이 과제였다.


dimGrid 안에 들어갈 Block_Width 값을 임의로 변경했었다.

dim3 dimGrid(1+((width-1)/TILE_WIDTH), 1+((width-1)/TILE_WIDTH));
dim3 dimBlock(32, 32);


CUDA를 이용한 병렬 프로그래밍은 사용할 블록의 수와 쓰레드 수에 따라 성능이 결정된다.


상황에 따라 블록 수, 쓰레드 수를 늘리거나 줄일 수도 있다.


고성능 GPU가 사용할 수 있는 블록의 수와 쓰레드 수는 저성능 GPU보다 많다.


동일한 성능을 설정하기 위해서는 블록의 수와 그리드 수를 임의로 설정해야 한다.


위와 같은 이유 때문에 다음과 같은 과제를 제출해준 것이라고 생각했다. 개인적인 추측이다.


처음 교수님이 제출해준 예제 코드는 다음과 같다.


#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "cuda_runtime.h"
#include "device_launch_parameters.h"

// block과 thread의 개수를 고정 (BLOCK_WIDTH와 TILE_WIDTH 값을 변경하지 말 것)
#define BLOCK_WIDTH 32
#define TILE_WIDTH 32

__global__ void MultMatGPU(float *P, float *M, float *N, int width);

void main()
{
   // width: 행렬의 한 변의 크기
   // width를 어떤 값으로 바꾸더라도 작동해야 함
   //int width = TILE_WIDTH * BLOCK_WIDTH;
   //int width = TILE_WIDTH * BLOCK_WIDTH * 2;
   int width = TILE_WIDTH * BLOCK_WIDTH * 3;
   //int width = TILE_WIDTH * BLOCK_WIDTH * 4;
   //int width = TILE_WIDTH * BLOCK_WIDTH * 5;
   float *P = new float[width * width];
   float *M = new float[width * width];
   float *N = new float[width * width];
   for (int i = 0; i < width * width; ++i)
   {
      M[i] = 1.0f;
      N[i] = 1.0f;
      P[i] = 0.0f;
   }

   cudaError_t cudaStatus = cudaSetDevice(0);
   float *dev_P, *dev_M, *dev_N;
   cudaStatus = cudaMalloc((void **)&dev_P, width * width * sizeof(float));
   cudaStatus = cudaMalloc((void **)&dev_M, width * width * sizeof(float));
   cudaStatus = cudaMalloc((void **)&dev_N, width * width * sizeof(float));
   cudaStatus = cudaMemcpy(dev_M, M, width * width * sizeof(float), cudaMemcpyHostToDevice);
   cudaStatus = cudaMemcpy(dev_N, N, width * width * sizeof(float), cudaMemcpyHostToDevice);

   // block과 thread의 개수를 고정 (dimGrid와 dimBlock 변수를 변경하지 말 것)
   dim3 dimGrid(BLOCK_WIDTH, BLOCK_WIDTH);
   dim3 dimBlock(TILE_WIDTH, TILE_WIDTH);
   clock_t st = clock();
   // 이 함수에 입력하는 dimGrid, dimBlock도 변경하지 말 것
   MultMatGPU<<<dimGrid, dimBlock>>>(dev_P, dev_M, dev_N, width);
   cudaDeviceSynchronize();
   clock_t ed = clock();
   printf("Elapsed time = %u ms\n", ed - st);

   cudaStatus = cudaMemcpy(P, dev_P, width * width * sizeof(float), cudaMemcpyDeviceToHost);
   cudaStatus = cudaDeviceReset();

   // 검증 코드
   for(int i = 0; i < width * width; ++i)
   {
      if(P[i] != width)
      {
         printf_s("error!\n");
         break;
      }
   }

   delete[] P;
   delete[] M;
   delete[] N;
   cudaFree(dev_P);
   cudaFree(dev_M);
   cudaFree(dev_N);
}

__global__ void MultMatGPU(float *P, float *M, float *N, int width)
{
   int i = blockIdx.y * TILE_WIDTH + threadIdx.y;
   int j = blockIdx.x * TILE_WIDTH + threadIdx.x;

   if(i < width && j < width)
   {
      float sum = 0.0;

      for (int k = 0; k < width; ++k)
      {
         float a = M[i * width + k];
         float b = N[k * width + j];
         sum += a * b;
      }

      P[i * width + j] = sum;
   }
}


위의 코드는 1 x 1에서 1024 x 1024 크기(최대 BLOCK_WIDTH * TILE_WIDTH)의 배열에 대한 행렬곱을 지원한다.


위의 코드를 수정하여 1024*n이 width인 Matrix에 대한 행렬곱이 지원되는 코드를 작성하는 것이다.


오래되어서 맞는지 모르겠지만


dimGrid는 Grid 내 블록의 개수에 대한 행렬.

dimBlock은 Block 내 쓰레드의 개수에 대한 행렬.


이라고 알고 있다.


일단 MultMatGPU() 함수를 보자.


각 쓰레드는 자신의 위치에서 가로, 세로로 Width 길이만큼의 행렬곱을 수행한다.


다만, BLOCK_WIDTH와 TILE_WIDTH가 크기가 제한되어져 있기 때문에 결과는 일부분만 계산이 된채로 나타난다.


2048 x 2048 행렬이라면 위와 같이 왼쪽 1/4 만큼의 공간만 계산이 될 것이다.


그럼 다음에 해야할 행동은 계산 범위의 변경이다.



파란색 네모가 현재 계산된 부분이라면 위의 사진처럼 구역을 나눠가면서 계산을 하면 전체 행렬이 계산이 된다.



int n = width / 1024;

if (width % 1024 != 0)
	++n;

for (int i = 0; i < n; ++i){
	for (int j = 0; j < n; ++j){
		MultMatGPU <<<dimGrid, dimBlock>>> (dev_P, dev_M, dev_N, width, i, j);
		cudaDeviceSynchronize();
	}
}


한 번에 계산할 수 있는 영역이 1024 x 1024 크기 이므로 width를 1024로 나누어 영역이 얼마나 필요한지 확인한다.


width가 1024보다 작거나 1024의 배수가 아닐 경우를 가정하여 나머지 값이 0이 아니면 n의 값을 1 더 올린다.


나누어진 영역의 크기만큼 for문을 돌려 MultMatGPU 함수를 실행한다.



__global__ void MultMatGPU(float *P, float *M, float *N, int width, int v, int h){
	int i = blockIdx.y * TILE_WIDTH + threadIdx.y + v * 1024;
	int j = blockIdx.x * TILE_WIDTH + threadIdx.x + h * 1024;
 
	if (i < width && j < width) {
		float sum = 0.0;
		for (int k = 0; k < width; ++k){
			float a = M[i * width + k];
			float b = N[k * width + j];
			sum += a * b;
		}
		P[i * width + j] = sum;
	}
}

함수 내에서는 영역의 위치에 따라 인덱스 값을 바꿔준다.


이를 통해 특정 영역 내에서의 행렬곱을 수행함으로써 전체 행렬곱이 가능하게 해준다.






다른 사람이 한 내용을 보았더니 다른 방식으로 작성도 가능하다.


이 경우는 MultMatGPU 내에서 계산을 여러번 실행하는 것으로 함수 내에서 width에 대하여 여러 위치에 대한 계산을 한다.


한 쓰레드에 들어갔을 때, 1024 x 1024 내에서의 해당 위치와 1024*m x 1024*n에서 동일한 위치를 같이 계산한다.


이렇게 할 경우, MultMatGPU 함수를 dimGrid와 dimBlock에 맞춰진 횟수만큼 실행한다.



효율성 면에서는 앞의 코드보다 떨어지지만 main 함수를 수정할 수 없도록 제한된 상황이라면


다음과 같은 코드가 해답이 될 수 있다.

http://devlogue.tistory.com/3

https://www.jbfactory.net/10351#highlightjs


Bresenham's Midpoint Circle Algorithm : https://en.wikipedia.org/wiki/Midpoint_circle_algorithm




아는 형이 코드를 봐달라 해서 잠깐 봤다.


#include <stdio.h>
#include <stdlib.h>

typedef struct COLOR {
	unsigned char r;
	unsigned char g;
	unsigned char b;
} COLOR;

int w = 0;
int h = 0;
int red = 0, green = 0, blue = 0;
int red_f = 0, green_f = 0, blue_f = 0;
COLOR* image;

void pixel(int x, int y) {
	image[x * w + y].r = red;
	image[x * w + y].g = green;
	image[x * w + y].b = blue;
}

void pixel_f(int x, int y) {
	image[x * w + y].r = red_f;
	image[x * w + y].g = green_f;
	image[x * w + y].b = blue_f;
}

void circle_boundary(int x0, int y0, int x, int y) {
	pixel(x0 + x, y0 - y);
	pixel(x0 + x, y0 + y);
	pixel(x0 - x, y0 + y);
	pixel(x0 - x, y0 - y);
	pixel(x0 + y, y0 - x);
	pixel(x0 + y, y0 + x);
	pixel(x0 - y, y0 + x);
	pixel(x0 - y, y0 - x);
}
void circle_filled(int x0, int y0, int x, int y) {
	pixel_f(x0 + x, y0 + y);
	pixel_f(x0 - x, y0 + y);
	pixel_f(x0 + x, y0 - y);
	pixel_f(x0 - x, y0 - y);
	pixel_f(x0 + y, y0 + x);
	pixel_f(x0 + y, y0 - x);
	pixel_f(x0 - y, y0 + x);
	pixel_f(x0 - y, y0 - x);
}

int main() {
	int x0, y0, r, x, y, d, y_f;
	printf("Enter Image Size (Width, Height) : ");
	scanf("%d%d", &w, &h);
	printf("Enter Center Position (x0, y0) : ");
	scanf("%d%d", &x0, &y0);
	printf("Enter Radius (r) : ");
	scanf("%d", &r);
	printf("Enter Boundary Color (R, G, B) : ");
	scanf("%d%d%d", &red, &green, &blue);
	printf("Enter Fill Color (R, G, B) : ");
	scanf("%d%d%d", &red_f, &green_f, &blue_f);

	image = new COLOR[h * w];
	d = 3 - (2 * r);
	x = 0;
	y = r;
	y_f = y - 1;

	//Initializing Background as white color
	for (int i = 0; i < h; ++i) {
		for (int j = 0; j < w; ++j) {
			image[i * w + j].r = 255;
			image[i * w + j].g = 255;
			image[i * w + j].b = 255;
		}
	}
	circle_boundary(x0, y0, x, y);
	while (x < y) {
		if (d < 0) {
			x = x + 1;
			d = d + (4 * x) + 6;
			circle_boundary(x0, y0, x, y);
		}
		else {
			x = x + 1;
			y = y - 1;
			d = d + (4 * (x - y)) + 10;
			circle_boundary(x0, y0, x, y);
		}
	}
	//Save image data as *.ppm file
	FILE *fp = fopen("result.ppm", "wb");
	fprintf(fp, "P6\n");
	fprintf(fp, "%d\n", w);
	fprintf(fp, "%d\n", h);
	fprintf(fp, "%d\n", 255);
	fwrite(image, sizeof(COLOR), h * w, fp);
	fclose(fp);

	free(image);

	return 0;
}



이렇게 원이 나오는데 내부를 채우는게 과제라고 했다.


어떻게 채워줄까 했더니 안에 원을 무수히 많이 그려보자고 한다.


그러면 안이 색으로 가득 차지 않겠냐고 한다.


그래서 안에 무수히 많은 원을 그려보기로 했다.


	for (int i = 0; i < r; ++i) {
		int x = 0;
		int t = i;
		int y = t;
		int d = 3 - (2 * t);
		circle_filled(x0, y0, x, y);
		while (x < y) {
			if (d < 0) {
				x = x + 1;
				d = d + (4 * x) + 6;
				circle_filled(x0, y0, x, y);
			}
			else {
				x = x + 1;
				y = y - 1;
				d = d + (4 * (x - y)) + 10;
				circle_filled(x0, y0, x, y);
			}
		}
	}

모양은 이쁘지만 당연히 이렇게 채우면 안된다. 


픽셀로 그리기 때문에 여백이 계속 일정한 간격으로 남게 된다.


군대 가기 전에 배운 Bresenham 알고리즘이 뭐였는지 기억은 안나고 무식하게 원을 채워보았다.

방법은 간단하다. 그냥 테두리와 테두리 사이를 쭉 그어버리면 된다.



위의 코드에서는 가로, 세로 두 번 그었지만 한 번만 그어도 문제 없이 나타난다.


먼저 내부를 색칠 한 후, 테두리를 그려주는 방식이다. 코드는 되도록 넘겨받았을 당시의 형태를 유지하였다.



보통 사람은 테두리를 그리고 나서 내부를 칠하는 데 이 코드는 반대로 행하는 그림이다. 


완전 엉터리다.


원이니까 이렇게 가능했지, 복잡한 도형은 이렇게 하면 안된다.



옛날 블로그 뒤적거리다가 발견한 사진 3장.


지금은 이렇게 만들려고 해도 못만든다.


포토샵에서 도장 툴로 찍어서 만들었던 것 같다.


'호랑이 담배피던 시절에' 카테고리의 다른 글

포트폴리오(취업 이전)  (0) 2021.06.10
2017.02.18.  (0) 2019.02.18
2014.03 ~ 2014. 06 2D그래픽디자인  (2) 2018.04.19
[Prezi] CG를 꿈꾸다  (0) 2018.03.26



처음 사용했을 땐 나름 신기했던 프레젠테이션 도구였지만 지금은 절대 쓰지 않는다.


대학교 1학년 때 처음 만져봤는데 그 당시 교수님이 이런걸 좋아하니까 사용했지만..


나이 좀 되시는 교수님들 앞에서 이런거 꺼냈다간 큰 화를 면치 못할 것이다.


어떤 교수님께서는 "프레지 사용해서 발표하면 C 줄테다!"라고 하셨으니..


교수님이 좀 젊고 종이로 프레젠테이션 내용을 제출할 필요 없고 화려한걸 좋아한다면 시도해보자.


그러나 ppt 노가다를 열심히 하면 프레지는 상대도 안된다.




'호랑이 담배피던 시절에' 카테고리의 다른 글

포트폴리오(취업 이전)  (0) 2021.06.10
2017.02.18.  (0) 2019.02.18
2014.03 ~ 2014. 06 2D그래픽디자인  (2) 2018.04.19
2011.04.04.  (0) 2018.03.26

http://www.acs.psu.edu/drussell/bats/impulse.htm


http://baseball.physics.illinois.edu/sweetnessGradient.pdf


http://www.physics.usyd.edu.au/~cross/baseball.html


https://ac.els-cdn.com/S1877705812016451/1-s2.0-S1877705812016451-main.pdf?_tid=ad711e14-2a28-442b-84c8-967b44beb358&acdnat=1521779474_d61c7c5f15de62ec92721f079251e73b


http://baseball.physics.illinois.edu/ObliqueCollisionsWSU-AJP.pdf


https://www.science.go.kr/upload/board/EXHIBIT/59/j05920131101.pdf


http://www.riss.kr/search/detail/DetailView.do?p_mat_type=be54d9b8bc7cdb09&control_no=d642664d5ae23397ffe0bdc3ef48d419

참고 페이지 : https://unity3d.college/2016/04/11/baseball-bat-physics-unity/






위 영상에서는 배트의 위치에 따라 각기 다른 velocity를 할당하는 법에 대해 나와있다.

영상을 따라한 후, bat follower의 velocity를 받아온 후, 날아오는 공에 적용하고자 한다면 다음과 같이 진행한다.


1) 배트에 할당된 capsule의 capsule collider를 활성화한다.


isTrigger는 자신이 원하는 대로 한다. OnCollisionEnter를 했을 때, 공의 속도가 감속이 되는지 모르겠지만

공의 기본 속도를 그대로 이용하기 위해서 OnTriggerEnter를 사용하기로 했다.



2) 영상에서는 follower로 작성되어져 있던 변수를 전역변수로 빼준다.

var는 전역변수에서 사용이 불가능하기 때문에 BatCapsuleFollower라는 변수 타입으로 설정한다.


3) OnTriggerEnter에서 batfollower의 velocity값과 angularVelocity값을 받아온다.

이후에 다양한 공식을 이용해서 공의 velocity 값을 변경해주면 된다.


----------------------------------------------------------------------------------------------------------------------------------


여기까지는 대충 하는 부분인데 야구공과 배트의 물리 공식을 찾기가 너무 힘들다.



+ Recent posts