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

Frame Buffer 이야기 (5)

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”를 누르고 메일을 보내기 바랍니다.

== 시작
예고한대로 mmap을 이용한 무식하지 않은(?) pixel 찍는 법을 알아 볼 차례입니다. 방법은 아주 간단합니다. frame buffer device를 open하고 open이 리턴한 file descriptor를 이용하여 mmap을 합니다. 당연히 offset은 0이고 length는 화면의 해상도와 bpp를 통해 계산된 byte수 만큼이죠. mmap이 리턴한 address와 pixel을 찍기를 원하는 좌표를 이용하여 새로운 address를 계산하고 그 address에 값을 쓰면 pixel이 찍히게 되고 그 address에서 값을 읽으면 화면상에 있는 pixel 값을 읽을 수 있는 겁니다. 자 준비되었으면 소스를 보도록 하죠.

== 다시 나온 pixel 찍기 소스
===============================
/*
* fbpixel2.c : Frame buffer draw pixel example(using mmap)
*
* Copyright(C) 2002 holelee
*
*/

#include <stdio.h>
#include <stdlib.h> /* for exit */
#include <unistd.h> /* for open/close .. */
#include <fcntl.h> /* for O_RDWR */
#include <sys/ioctl.h> /* for ioctl */
#include <sys/mman.h> /* for mmap */
#include <linux/fb.h> /* for fb_var_screeninfo, FBIOGET_VSCREENINFO */

#define FBDEVFILE "/dev/fb"

typedef unsigned char ubyte;

unsigned short makepixel(ubyte r, ubyte g, ubyte b)
{
return (unsigned short)(((r>>3)<<11)|((g>>2)<<5)|(b>>3));
}

int main()
{
int fbfd;
int ret;
struct fb_var_screeninfo fbvar;
unsigned short pixel;
int offset;
unsigned short *pfbdata;

fbfd = open(FBDEVFILE, O_RDWR);
if(fbfd < 0)
{
perror("fbdev open");
exit(1);
}

ret = ioctl(fbfd, FBIOGET_VSCREENINFO, &fbvar);
if(ret < 0)
{
perror("fbdev ioctl");
exit(1);
}

if(fbvar.bits_per_pixel != 16)
{
fprintf(stderr, "bpp is not 16\n");
exit(1);
}

pfbdata = (unsigned short *)
mmap(0, fbvar.xres*fbvar.yres*16/8,
PROT_READ|PROT_WRITE, MAP_SHARED, fbfd, 0);

if((unsigned)pfbdata == (unsigned)-1)
{
perror("fbdev mmap");
exit(1);
}

/* red pixel @ (0,0) */
pixel = makepixel(255,0,0); /* red pixel */
*pfbdata = pixel;

/* green pixel @ (100,50) */
offset = 50*fbvar.xres+100;
pixel = makepixel(0,255,0); /* green pixel */
*(pfbdata+offset) = pixel; /* draw pixel */


/* blue pixel @ (50,100) */
offset = 100*fbvar.xres+50;
pixel = makepixel(0,0,255); /* blue pixel */
*(pfbdata+offset) = pixel; /* draw pixel */

/* white pixel @ (100,100) */
offset = 100*fbvar.xres+100;
pixel = makepixel(255,255,255); /* white pixel */
*(pfbdata+offset) = pixel; /* draw pixel */

munmap(pfbdata, fbvar.xres*fbvar.yres*16/8);
close(fbfd);
exit(0);
return 0;
}
===============================
먼저 소개한 fbpixel.c처럼 (0,0)에 빨간 pixel, (100, 50)에 녹색 pixel, (50, 100)에 파란 pixel, (100, 100)에 하얀 pixel을 순서대로 찍는 간단한 프로그램입니다. fbvar.xres*fbvar.yres*(16/8)만큼의 바이트만큼이 mmap으로 virtual address에 매핑되고 있습니다.(당연히 mmap을 지원하지 않는 특이한 frame buffer에서는 에러를 내고 죽게 됩니다.) 그 address로부터 픽셀이 저장된(될) address라고 보면 됩니다. 픽셀이 저장된 순서는 (0,0)부터 시작하여 (1,0), (2,0)의 순서로 (xres-1, 0)까지 진행되고 그 다음 address는 (0, 1)이 되고 다시 (1,1), (2,1),… 이런 식으로 진행됩니다. 쉽게 이해할 수 있겠죠. lseek 때의 경우와 똑같습니다. 당연히 픽셀 offset은 바이트 단위로는 먼저 소개한 fbpixel.c와 같지만 unsigned short 포인터 pfbdata와 더하므로 offset을 바이트 단위로 계산하지 않고 2byte(sizeof(unsigned short))단위로 하고 있습니다. 따라서 더 이상 (18/6)과 같은 것은 offset 계산에 필요가 없습니다. 그럼 좌표 (xpos, ypos)에 pixel을 찍는 함수를 한번 작성해 보죠.
===============================
1: void put_pixel(fb_var_screeninfo *fbvar, unsigned short *pfbdata, int xpos, int ypos, unsigned short pixel)
2: {
3: int offset = ypos*fbvar->xres+xpos;
4: pfbdata[offset] = pixel;
5: }
===============================
pfbdata에 mmap이 리턴한 address를 주면 제대로 동작할 것으로 기대됩니다.


== address에 값을 쓰면 어떤 일이 벌어지나?
mmap이 리턴한 address는 virtual address로 실제 물리적인 것과는 전혀 관계가 없음을 저번 글에서 언급했습니다. 그럼 위의 fbpixel2.c 소스 코드에서 *(fbdata+offset) = pixel이라는 수식(expression)이 수행되면 실제 PC에서는 어떤 일이 벌어질까요? 알고 있다시피 실제 화면에 나올 픽셀이 저장된 버퍼는 PC에서는 그래픽카드에 있는 RAM에 존재합니다. 그럼 C 언어의 수식하나로 그 그래픽 카드에 있는 RAM 값이 update되는 셈인데 어떤 일이 벌어지는 지 궁금하지 않나요? 제가 어떤 일이 벌어지는지 구성해 보도록 하겠습니다.
우선 CPU에서 저 수식이 수행되면 address 계산(fbdata+offset)을 하게 됩니다. 그 address에 pixel 값을 쓰는 instruction 수행되겠죠.(보통 RISC는 그런 instruction을 store라고 부르고 x86에서는 mov입니다.) 그럼 address를 CPU의 MMU가 살펴보고 physical address로 바꾸어서 CPU의 버스에 그 physical address에 pixel이라는 값을 쓰겠다고 신호를 보냅니다. 그럼 CPU 버스에 물려있는 North bridge라는 녀석이 그 address를 살펴보고 시스템에 설치된 DRAM의 어드레스가 아님을 판단하고 PCI 버스(혹은 AGP포트)에 address와 pixel 데이터로 다시 신호를 보내게 됩니다.(이 때 address translation이 다시 일어날 수도 있습니다. PCI버스와 CPU버스가 addressing이 다르다면.) 그럼 PCI 버스(혹은 AGP포트)에 달려있는 그래픽 카드가 그 address를 보고 자기 자신에 관한 일임을 알아채고 그에 상응하는 일을 해주게 됩니다. ATI mach 64 CT처럼 PCI 버스에서 동작하는 그래픽카드를 가정하고 환상적인 그림솜씨(?)를 뽐내보면 다음과 같은 그림으로 시스템을 나타내 볼 수 있습니다.

CPU(MMU)
|/
|/ <CPU버스>
|/
North Bridge --- DRAM
|/
|/ <PCI버스>
|/
그래픽카드

당연히 버스에는 latency가 존재하므로 DRAM에 접근하는 것보다는 훨씬 오래 걸립니다. 또한 Cache에 저장되면 문제가 일어나게 되므로(Cache만 update되고 화면은 바뀌지 않는 식으로) Cache를 이용할 수도 없어서 latency가 상당하죠.

(실제로 요즘 최신 PC 시스템에서는 North Bridge에 직접 PCI버스가 물려있는 것이 아니고 North Bridge에는 Point-to-Point 버스로 South Bridge가 물려있고 South Bridge에 PCI 버스가 물려 있는 것으로 알고 있습니다. 그러한 구조상의 변화 때문에 Intel에서는 Bridge라는 말을 더 이상 사용하지 않고 HUB라는 말을 쓴다고 하는군요.)


== 마치며
이번 글에서는 mmap을 이용한 깔끔한 pixel 찍기에 대해서 살펴봤습니다. 뭐 별로 어려울 것은 없을 것으로 생각됩니다. 그럼 다음 글에서는 왜 그 전에 lseek/write를 이용한 pixel 찍기가 “다소 무식한(?)” 방법이었는지 생각해 보는 시간을 갖도록 하겠습니다.

 

728x90
반응형

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

Frame Buffer 이야기 (7)  (0) 2008.11.26
Frame Buffer 이야기 (6)  (0) 2008.11.26
Frame Buffer 이야기 (4)  (0) 2008.11.26
Frame Buffer 이야기 (3)  (0) 2008.11.26
Frame Buffer 이야기 (2)  (0) 2008.11.26