본문 바로가기
공부하는 하스씨/Graphics

Frame Buffer 이야기 (3)

by 박하스. 2008. 11. 26.
728x90
반응형

출처: KELP 이규명님의 강좌 http://kelp.or.kr/korweblog/stories.php?story=02/11/09/8557035&topic=29

 

== 시작하기에 앞서
이 글에 있는 모든 내용은 글쓴이가 가지고 있는 ATI mach 64(2MB) 그래픽 카드가 달려있고 RedHat 8.0이 설치된 PC(또는 Permedia2(8MB) 그래픽 카드가 달려 있는 RedHat 7.3이 설치된 PC)에서 제대로 동작하지만 이 글에 있는 모든 내용이 정확하다고 말씀 드릴 수 없습니다. 이 글에 있는 내용을 따라 했을 때 혹 생길 지 모르는 모든 문제에 대해서 글쓴이는 책임을 지지 않습니다. 글의 내용 중에 잘못된 내용이 있거나 질문할 것이 있으면 위쪽의 “holelee”를 누르고 메일을 보내기 바랍니다.

== 시작
이번 글에서는 드디어 점(pixel) 찍기를 해보도록 하겠습니다. 점 찍기는 당연히 모든 그래픽을 표현하는 기본이 됩니다. 점을 어떻게 찍는 지만 알면 선이나 네모를 그리거나 bmp를 화면에 출력하는 일은 그냥 그것의 응용일 뿐입니다.(물론 선을 그리거나 원이나 호를 그리는 것은 약간의 알고리즘 지식이 필요하기는 합니다만 이 글의 목적상 그런 부분을 다루지는 않겠습니다.) 저번 글에서 언급한대로 16bpp환경에서 진행합니다.
글을 진행하기에 앞서 한가지 밝혀 둘 것이 있는데 이번 글에서 나오는 모든 소스는 좀 무식한(?) 방법으로 구현되었습니다. 좀더 깔끔하고 빠르게 동작하는 소스 코드는 글이 좀더 진행되면 만날 수 있을 겁니다.

== 16bpp에서 한 pixel 데이터
우선 한 pixel의 데이터가 어떤 식으로 표현되는지 이해하고 있어야 합니다. 당연히 한 pixel은 Red, Green, Blue(RGB)의 값을 가지고 있어야 합니다. Red=0,Green=0,Blue=0인 경우는 검정색이 되고 모두 maximum 값을 가지고 있으면 하얀색 점이 됩니다. 그럼 16bpp에서 RGB값은 묶여 있는 형식은 다음 그림(?)과 같습니다.
-------------------------------
| R(5bit) | G(6bit) | B(5bit) |
-------------------------------
MSB LSB
Red와 Blue는 각각 5비트씩이고 G는 6비트 정보를 가지게 됩니다. 그리고 MSB(Most Significant Bit)에서 LSB(Least Significant Bit)쪽으로 R, G, B 순으로 저장됩니다.
(이 부분에 다소 문제가 있음 나중에 밝혀졌습니다. 문제가 되는 부분은 "frame buffer 이야기(7)"에서 확인할 수 있습니다.

== 좌표계
사실 frame buffer 에서 좌표를 따진다는 것은 사실 별로 무의미할지도 모릅니다. 그러나 이미 fb_var_screeninfo에서 xres, yres라는 좌표형식이 등장하였고 앞으로 있을 설명의 편의상 그냥 좌표계를 정하겠습니다.
좌표의 origin은 당연히 화면 왼쪽 상단에 있고 좌우축은 x축에 해당하고 상하축은 y축에 해당한다고 사회적 통념상 무리가 없고 fb_var_screeninfo의 xres, yres의 결과와도 부합합니다. 좌표계를 그림(?)으로 표현하면 다음과 같겠죠.
화면 상단쪽
(0,0)...............(x,0)
.........................
.........................
...
.........................
.........................
(0,y)...............(x,y)
화면 하단쪽

== pixel 찍는 방법(?)
이번에 소개할 pixel을 찍는 방법은 아주 간단합니다. 그냥 device를 열고 write하면 pixel이 찍히게 됩니다. pixel을 원하는 위치에 찍기 위해서는 그냥 lseek 시스템 호출을 이용하여 offset을 바꾸면 됩니다. 640x480@16bpp frame buffer라고 하면 640*480*(16/8)개의 바이트가 들어있는 파일이라고 생각하면 쉽습니다. 좌표 (0, 0)에서부터 시작하여 (1, 0), (2, 0), (3, 0)의 순으로 (639, 0)까지 진행되다가 (0, 1)로 계속 되는 순서를 가지는 파일이라고 보면 딱 맞습니다. 그럼 pixel 찍는 예제 소스 코드를 살펴보도록 하죠.

== pixel 찍는 프로그램 소스코드
다시 한번 얘기하지만 좀 원초적인 방법으로 구현된 소스 코드입니다. 그냥 동작한다는 것이 의미가 있을 뿐 실제로 쓰기에는 조금 무리가 있는 소스 코드입니다.
16bpp가 아니면 실행이 되지 않도록 체크 코드가 들어 있습니다.
===============================
1 /*
2 * fbpixel.c : Frame buffer draw pixel example
3 *
4 * Copyright(C) 2002 holelee
5 *
6 */
7
8 #include <stdio.h>
9 #include <stdlib.h> /* for exit */
10 #include <unistd.h> /* for open/close .. */
11 #include <fcntl.h> /* for O_RDONLY */
12 #include <sys/ioctl.h> /* for ioctl */
13 #include <sys/types.h>
14 #include <linux/fb.h> /* for fb_var_screeninfo, FBIOGET_VSCREENINFO */
15
16 #define FBDEVFILE "/dev/fb"
17
18 typedef unsigned char ubyte;
19
20 unsigned short makepixel(ubyte r, ubyte g, ubyte b)
21 {
22 return (unsigned short)(((r>>3)<<11)|((g>>2)<<5)|(b>>3));
23 }
24
25 int main()
26 {
27 int fbfd;
28 int ret;
29 struct fb_var_screeninfo fbvar;
30 unsigned short pixel;
31 int offset;
32
33 fbfd = open(FBDEVFILE, O_RDWR);
34 if(fbfd < 0)
35 {
36 perror("fbdev open");
37 exit(1);
38 }
39
40 ret = ioctl(fbfd, FBIOGET_VSCREENINFO, &fbvar);
41 if(ret < 0)
42 {
43 perror("fbdev ioctl");
44 exit(1);
45 }
46
47 if(fbvar.bits_per_pixel != 16)
48 {
49 fprintf(stderr, "bpp is not 16\n");
50 exit(1);
51 }
52
53 /* red pixel @ (0,0) */
54 if(lseek(fbfd, 0, SEEK_SET) < 0)
55 {
56 perror("fbdev lseek");
57 exit(1);
58 }
59 pixel = makepixel(255,0,0); /* red pixel */
60 write(fbfd, &pixel, 2); /* write 2byte(16bit) */
61
62 /* green pixel @ (100,50) */
63 offset = 50*fbvar.xres*(16/8)+100*(16/8);
64 if(lseek(fbfd, offset, SEEK_SET) < 0)
65 {
66 perror("fbdev lseek");
67 exit(1);
68 }
69 pixel = makepixel(0,255,0); /* green pixel */
70 write(fbfd, &pixel, 2); /* write 2byte(16bit) */
71
72
73 /* blue pixel @ (50,100) */
74 offset = 100*fbvar.xres*(16/8)+50*(16/8);
75 if(lseek(fbfd, offset, SEEK_SET) < 0)
76 {
77 perror("fbdev lseek");
78 exit(1);
79 }
80 pixel = makepixel(0,0,255); /* blue pixel */
81 write(fbfd, &pixel, 2); /* write 2byte(16bit) */
82
83 /* white pixel @ (100,100) */
84 offset = 100*fbvar.xres*(16/8)+100*(16/8);
85 if(lseek(fbfd, offset, SEEK_SET) < 0)
86 {
87 perror("fbdev lseek");
88 exit(1);
89 }
90 pixel = makepixel(255,255,255); /* white pixel */
91 write(fbfd, &pixel, 2); /* write 2byte(16bit) */
92
93 close(fbfd);
94 exit(0);
95 return 0;
96 }
===============================
(0,0)에 빨간 pixel, (100, 50)에 녹색 pixel, (50, 100)에 파란 pixel, (100, 100)에 하얀 pixel을 순서대로 찍는 프로그램입니다. 실행시켜 보면 네 개의 점을 볼 수 있습니다. 간단히 설명하면 makepixel() 함수는 r,g,b 세 개의 byte값(0~255)을 받아서 16bit pixel 값을 만들어 내는 함수 입니다. 원하는 위치에 pixel을 찍기 위해 사용하는 방법은 단순히 file descriptor의 처음(SEEK_SET)에 해당 위치의 offset을 계산한 후 lseek()을 호출하여 file offset을 바꾸고 2 byte pixel 데이터를 write하는 것 뿐입니다. 당연히 offset은 바이트 단위로 계산합니다. (xpos, ypos)에 점을 찍고 싶으면 오프셋 계산 식은 다음과 같습니다.
offset = ypos*{한줄의바이트수} + xpos*{한픽셀당바이트수}
한픽셀당바이트수 = 16/8(16bpp이므로)
한줄의바이트수 = {한줄의픽셀수}*{한픽셀당바이트수} = xres*(16/8)
결국, offset = ypos*xres*(16/8)+xpos*(16/8) 이 됩니다.
그럼 원하는 위치에 pixel을 찍는 함수를 다음과 같이 구현해 볼 수 있겠습니다.
===============================
1: void put_pixel(struct fb_var_screeninfo *fbvar, int fbfd, int xpos, int ypos, unsigned short pixel)
2: {
3: int offset = ypos*fbvar->xres*(16/8)+xpos*(16/8);
4: lseek(fbfd, offset, SEEK_SET);
5: write(fbfd, &pixel, 2);
6: }
===============================
한 가지 기억해 둘 것은 한 개의 점을 원하는 위치에 찍기 위해서 두 개의 시스템 호출(lseek, write)을 해야 한다는 것입니다. 이것이 원초적이고 무식한(?) 이유입니다. 나중에 배울 새로운 점 찍기 루틴을 보고, 성능 비교를 해보면 왜 무식한지 알 수 있을 겁니다.

== pixel 읽기
화면에 있는 pixel을 읽으려면 어떻게 해야 할까요? 그냥 유추해 볼 수 있는 가장 간단한 방법은 똑같이 offset을 계산해서 write를 하지 않고 이번에는 read를 하는 것입니다. 뭐 마땅한 예제를 만들기가 힘들어서 넘어가지만 예상대로라면 제대로 pixel을 읽을 수 있을 겁니다.

== random number 찍기
혹 점이 몇 개 없어서 심심하게 생각하는 사람들을 고려해 서비스 차원에서 다음 소스코드를 작성했습니다. Ctrl+C를 누르면 중지하는 무한 루프입니다. 특별히 설명할 내용은 당연히 없습니다.
===============================
1 /*
2 * fbrandpixel.c : Frame buffer draw random pixel example
3 *
4 * Copyright(C) 2002 holelee
5 *
6 */
7
8 #include <stdio.h>
9 #include <stdlib.h> /* for exit */
10 #include <unistd.h> /* for open/close .. */
11 #include <fcntl.h> /* for O_RDONLY */
12 #include <sys/ioctl.h> /* for ioctl */
13 #include <sys/types.h>
14 #include <linux/fb.h> /* for fb_var_screeninfo, FBIOGET_VSCREENINFO */
15
16 #define FBDEVFILE "/dev/fb"
17
18 int main()
19 {
20 int fbfd;
21 int ret;
22 struct fb_var_screeninfo fbvar;
23
24 fbfd = open(FBDEVFILE, O_RDWR);
25 if(fbfd < 0)
26 {
27 perror("fbdev open");
28 exit(1);
29 }
30
31 ret = ioctl(fbfd, FBIOGET_VSCREENINFO, &fbvar);
32 if(ret < 0)
33 {
34 perror("fbdev ioctl");
35 exit(1);
36 }
37
38 if(fbvar.bits_per_pixel != 16)
39 {
40 fprintf(stderr, "bpp is not 16\n");
41 exit(1);
42 }
43
44 while(1)
45 {
46 int xpos, ypos;
47 int offset;
48 int rpixel;
49
50 /* random number between 0 and xres-1 */
51 xpos = (int)((fbvar.xres*1.0*rand())/(RAND_MAX+1.0));
52 /* random number between 0 and yres-1 */
53 ypos = (int)((fbvar.yres*1.0*rand())/(RAND_MAX+1.0));
54
55 offset = ypos*fbvar.xres*(16/8)+xpos*(16/8);
56
57 /* random number between 0 and 65535(2^16-1) */
58 rpixel = (int)(65536.0*rand()/(RAND_MAX+1.0));
59
60 if(lseek(fbfd, offset, SEEK_SET) < 0)
61 {
62 perror("fbdev lseek");
63 exit(1);
64 }
65 write(fbfd, &rpixel, 2); /* write 2byte(16bit) */
66 }
67
68 return 0;
69 }
===============================

== random 네모 그리기
똑같이 그냥 데모용 random 네모 그리기 소스 코드입니다.
===============================
1 /*
2 * fbrandrect.c : Frame buffer draw random rectangular example
3 *
4 * Copyright(C) 2002 holelee
5 *
6 */
7
8 #include <stdio.h>
9 #include <stdlib.h> /* for exit */
10 #include <unistd.h> /* for open/close .. */
11 #include <fcntl.h> /* for O_RDONLY */
12 #include <sys/ioctl.h> /* for ioctl */
13 #include <sys/types.h>
14 #include <linux/fb.h> /* for fb_var_screeninfo, FBIOGET_VSCREENINFO */
15
16 #define FBDEVFILE "/dev/fb"
17
18 int main()
19 {
20 int fbfd;
21 int ret;
22 struct fb_var_screeninfo fbvar;
23
24 fbfd = open(FBDEVFILE, O_RDWR);
25 if(fbfd < 0)
26 {
27 perror("fbdev open");
28 exit(1);
29 }
30
31 ret = ioctl(fbfd, FBIOGET_VSCREENINFO, &fbvar);
32 if(ret < 0)
33 {
34 perror("fbdev ioctl");
35 exit(1);
36 }
37
38 if(fbvar.bits_per_pixel != 16)
39 {
40 fprintf(stderr, "bpp is not 16\n");
41 exit(1);
42 }
43
44 while(1)
45 {
46 int xpos1, ypos1;
47 int xpos2, ypos2;
48 int offset;
49 int rpixel;
50 int t, tt;
51
52 /* random number between 0 and xres-1 */
53 xpos1 = (int)((fbvar.xres*1.0*rand())/(RAND_MAX+1.0));
54 xpos2 = (int)((fbvar.xres*1.0*rand())/(RAND_MAX+1.0));
55
56 /* random number between 0 and yres */
57 ypos1 = (int)((fbvar.yres*1.0*rand())/(RAND_MAX+1.0));
58 ypos2 = (int)((fbvar.yres*1.0*rand())/(RAND_MAX+1.0));
59
60 if(xpos1 > xpos2)
61 {
62 t = xpos1;
63 xpos1 = xpos2;
64 xpos2 = t;
65 }
66
67 if(ypos1 > ypos2)
68 {
69 t = ypos1;
70 ypos1 = ypos2;
71 ypos2 = t;
72 }
73
74 /* random number between 0 and 65535(2^16-1) */
75 rpixel = (int)(65536.0*rand()/(RAND_MAX+1.0));
76
77 for(t = ypos1; t <= ypos2; t++)
78 {
79 offset = t*fbvar.xres*(16/8)+xpos1*(16/8);
80
81 if(lseek(fbfd, offset, SEEK_SET) < 0)
82 {
83 perror("fbdev lseek");
84 exit(1);
85 }
86 for(tt = xpos1; tt <= xpos2; tt++)
87 write(fbfd, &rpixel, 2);
88 }
89 }
90
91 return 0;
92 }
===============================

== 마치며
이번 글에서는 16bpp 픽셀이 어떤 형식으로 구성되는 지 알아보았고 lseek, write를 이용하여 원하는 위치에 pixel을 찍는 법을 살펴보았습니다. lseek, read를 이용하면 원하는 위치의 pixel값을 읽어올 수도 있다는 예측도 해보았습니다. 다음 글에서는 mmap() 시스템 호출(system call)에 대해서 알아볼 예정입니다.

== 글을 올리고 나서
어떤 방식으로 글을 올리던지 이 KELP 사이트의 게시판에서는 소스코드가 이쁘게 나오지 않네요.

 

728x90
반응형

'공부하는 하스씨 > Graphics' 카테고리의 다른 글

Frame Buffer 이야기 (5)  (0) 2008.11.26
Frame Buffer 이야기 (4)  (0) 2008.11.26
Frame Buffer 이야기 (2)  (0) 2008.11.26
Frame Buffer 이야기 (1)  (0) 2008.11.26
게임 개발의 꽃 DirectX10과 3D 그래픽스  (0) 2008.10.11