이것은 1999년 하이텔 게임 제작 동호회에 올렸던 강좌입니다.

                                              바이너리 데이터

#####################################################

안녕하세요.. 안영기입니다.

다름 아니라 이번에는 MMX Technology 를 이용하여 DX 상에서 고속 스프라이트를
찍는 법을 설명하고자 합니다.

흔히 DX 256 컬러 모드에서 표면을 Lock 하여 그곳에 스프라이트를 쓰고자 한다면
압축 스프라이트를 쓰던지 아니면 직접 1 byte 씩 투명색인지 아닌지를 체크해
가며 점 단위 연산을 하는 것이 보통이었습니다.

이에, 제가 소개 하고자 하는 팁은 투명색인지 판별조차 하지않고서도 8 개의
스프라이트 픽셀을 동시에 전송하는 방법입니다.

일단 예전처럼 투명색인지 아닌지 판별하는 부분이 없어졌으므로 'je', 'jz' 라는
명령어로 인한 분기의 부하를 줄이고 1 byte 씩 하던 전송을 한번에 8 bytes 씩
가능하게 해준다는 것에 그 의의를 둘 수가 있습니다.


풀소스에 들어가기 전에 핵심 부분만 추려보면 다음과 같습니다.

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


 >> 전제...

   esi     : 스프라이트 데이터
   edi     : Lock 된 Direct Draw Surface 의 미리 계산된 주소
   pMask   : char pMask[8] 로 정의되어 있으며 8개의 byte 가 모두 투명색
             Index 로 채워져 있다.


 >> 코드

   mov   ebx, pMask
   movq  mm4, [ebx]     // mm4 에 투명색 번호를 넣는다

   mov   ecx, Size.X    // Size.X 에는 X 스프라이트의 x 사이즈가 들어감
   shr   ecx, 3         // 동시에 8개의 점을 연산하므로

LOOP_X:
   movq  mm0, [edi]     // mm0 은 Destination
   movq  mm1, [esi]     // mm1 은 Source
   movq  mm2, mm1       // mm2 에 Source 데이터를 복사
   pcmpeqb mm2, mm4     // mm2 에 투명색에 따른 마스크를 생성
   movq  mm3, mm2       // mm3 에 마스크를 하나 더복사
   pandn mm2, mm1       // Source 스프라이트 부분만 복사
   pand  mm3, mm0       // Destination 의 갱신될 부분만 제거
   por   mm2, mm3       // Source 와 Destination 을 결합
   movq  [edi], mm2     // Destination 에 결과를 씀

   add   esi, 8         //  한번에 8 bytes 를 동시에 처리했으므로
   add   edi, 8

   loop  LOOP_X


 >> 설명

   MMX Technology 는 FPU 의 스택 프레임을 절대 번지로 사용하여 8 bytes 짜리
   레지스터인 mm0 에서 mm7 까지의 8 개의 레지스터를 사용하는 기술입니다.
   그리고 DSP 적인 요소가 많아서 ( 곱하고 동시에 더한다던지.. ) 멀티미디어
   계산에 적합하다고들 하죠.. 하지만 넓은 의미의 멀티미디어의 한분야인
   게임에서 역시 MMX Technology 는 아주 유용하게 쓰이는데 그 중 한 부분이
   바로 이제 설명하려는 '무분기 스프라이트 출력'입니다.
                        ~~~~~~~~~~~~~~~~~~~~~~~~
   위의 소스에서 보시다시피 8 bytes 를 동시에 전송하면서도 한점 한점이
   투명색인지 아닌지를 체크하지 않고 바로 연산을 합니다.

   예전 같으면,

      mov  ecx, Size.X

   LOOP_X:

      cmp  ptr byte [esi], TRANSPARENCY_COLOR
      je   SKIP

      mov  ptr byte [edi], al

   SKIP:
      inc  esi

      loop LOOP_X

   이런 식이 되어서 점의 수만큼 'cmp'를 해야만 했었습니다.


 >> 코드분석

   가장 코어 부분만 보면 다음과 같습니다.

   - movq  mm0, [edi]

     mm0 레지스터에 edi 가 가리키는 데이터중 8 bytes 를 가져 옵니다.

     예>   mm0   =>   43 2D 1A E4 54 78 20 CA

   - movq  mm1, [esi]

     mm0 레지스터에 esi 가 가리키는 데이터중 8 bytes 를 가져 옵니다.

     예>   mm2   =>  10 2F F8 80 80 3D 80 CE

   - movq  mm2, mm1

     mm2 레지스터에 mm1 레지스터의 내용을 카피합니다.
     소스 데이터에서 투명색에 대한 정보를 얻기 위해서입니다.

   - pcmpeqb mm2, mm4

     위에 보시면 이미 mm4 에는 1byte 짜리 마스크로 8bytes 가 채워져 있습니다.
     'pcmpeqb' 명령은 byte 단위로 8 개의 각 byte 가 같은지 같지 않은지를
     판별합니다. 그래서 만약 값이 같다면 그 byte는 모두 1로 채우고 그렇지
     않다면 모두 0으로 채웁니다/

     예>     mm2   =>   10 2F F8 80 80 3D 80 CE
             mm4   =>   80 80 80 80 80 80 80 80   ( 투명색이 0x80 일때 )

      결과   mm2   =>   00 00 00 FF FF 00 FF 00

   - movq  mm3, mm2

     이렇게 만들어진 마스크를 mm3 에 하나 더 복사합니다.

   - pandn mm2, mm1       // Source 스프라이트 부분만 복사

     'pandn' 명령어는 mm2 를 반전한후 그 mm2 와 mm1 를 and 해서 mm2 로
     넣습니다.

     예>     mm2    =>   00 00 00 FF FF 00 FF 00
             mm2    =>   FF FF FF 00 00 FF 00 FF  ( 반전 )
             mm1    =>   10 2F F8 80 80 3D80 CE

      결과   mm2    =>   10 2F F8 00 00 3D 00 CE  ( mm2 & mm1 )

     결과에서 보시면 아시겠지만 원 스프라이트 데이터에서 투명색 부분만
     '00' 이 되었다는 것을 알 것입니다.

   - pand  mm3, mm0

     'pand' 명령어는 mm3 와 mm0 를 and 연산하여 mm3 에 넣습니다.

     예>     mm3    =>   00 00 00 FF FF 00 FF 00
             mm0    =>   43 2D 1A E4 54 78 20 CA

      결과   mm3    =>   00 00 00 E4 54 00 20 00

   - por   mm2, mm3

     'por' 명령어는 mm2 와 mm3 를 or 연산하여 mm2 에 넣습니다.

     예>     mm3    =>   00 00 00 E4 54 00 20 00
             mm2    =>   10 2F F8 00 00 3D 00 CE

      결과   mm2    =>   10 2F F8 E4 54 3D 20 CE

   - movq  [edi], mm2

     edi 가 가리키는 주소에 mm2 의 내용 8 bytes 를 쓴다.


   이렇게 해서 결국은 아래의 결과를 냅니다.

             [edi]   =>   43 2D 1A E4 54 78 20 CA ( 배경 )
             [esi]   =>   10 2F F8 80 80 3D 80 CE ( 스프라이트, 투명색 80 )

             [edi]   =>   10 2F F8 E4 54 3D 20 CE
                                   ~~ ~~    ~~    ( 투명색이었던 부분만
                                                    배경으로 치환         )

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

제 나름대로는 열심히 설명을 드린다고 하긴 했는데 제대로 이해가 되셨는지
모르겠네요...

그리고 제가 8 bit 컬러 모드에 대해서만 설명했는데.. 16bit 모드나 트루컬러
모드에서도 똑같이 적용됩니다. 단 'pcmpeqb' 라는 명령어가 2bytes, 4bytes 씩
비교하는 'pcmpeqw', 'pcmpeqd' 로 수정하시면 됩니다.

그리고 아래는 소스입니다. 제가 도스 때는 종종 C 버전도 같이 올리곤 했는데
윈도우로 가면서부터는 제가 테스트해볼 C 컴파일러가 없어서 그냥 '델파이'
소스로만 올립니다. 뜻이 있으신 C 유저 분들은 번역해서 잘 사용해 보세요..

그리고 델파이로된 실행 테스트 파일과 풀 소스는 자료실에 올리겠습니다.

시간이 된다면 MMX 코드로 3D 행렬 한번에 계산 하는 거나 아니면 MMX 고속
알파 블렌딩 기술도 써 보겠습니다.... 아니면 Direct Music 쪽이 될런지도..

그럼.. SMgal.


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

여기는 각각 서피스에서 직접 읽었지만 실제론 스프라이트가 유저 데이터형으로
들어가게 되겠죠... ( 이건 테스트라서 Surface 사용했음.. )

procedure   SpriteCopy(DestX, DestY : integer;
                       SourX, SourY : integer;
                       Size         : TPoint;
                       Sour, Dest   : IDirectDrawSurface);
const
   TRANSPARENCY_VALUE  = 80; // 투명색이 80번 인덱스이다.
var
   SourDesc, DestDesc  : TDDSurfaceDesc;
   pSour, pDest, pMask : PByte;
   Transparency        : array[1..8] of byte;
begin

   FillChar(Transparency,8,TRANSPARENCY_VALUE);

   SourDesc.dwSize := SizeOf(TDDSurfaceDesc);
   with Basic do repeat
   until MakeItSo(Sour.Lock(PRect(nil)^,SourDesc,DDLOCK_SURFACEMEMORYPTR +
                                                 DDLOCK_WAIT,0));

   DestDesc.dwSize := SizeOf(TDDSurfaceDesc);
   with Basic do repeat
   until MakeItSo(Dest.Lock(PRect(nil)^,DestDesc,DDLOCK_SURFACEMEMORYPTR +
             DDLOCK_WAIT,0));

   pSour := PByte(DWORD(SourDesc.lpSurface) + SourY*SourDesc.lPitch + SourX);
   pDest := PByte(DWORD(DestDesc.lpSurface) + DestY*DestDesc.lPitch + DestX);
   pMask := Pointer(@Transparency);

   asm
         push  esi
         push  edi

         mov   esi, pMask
         db $0F,$6F,$26       /// movq  mm4, [esi]
                              //  mm4 에 투명색 번호를 넣는다
         mov   esi, pSour
         mov   edi, pDest

         mov   ecx, Size.Y

   @@LOOP_Y:

         push  ecx

         mov   ecx, Size.X
         shr   ecx, 3         // 동시에 8개의 점을 연산하므로


   @@LOOP_X:

         db $0F,$6F,$07       /// movq  mm0, [edi]
                              //  mm0 은 Destination
         db $0F,$6F,$0E       /// movq  mm1, [esi]
                              //  mm1 은 Source
         db $0F,$6F,$D1       /// movq  mm2, mm1
                              //  mm2 에 Source 데이터를 복사
         db $0F,$74,$D4       /// pcmpeqb mm2, mm4
                              //  mm2 에 투명색에 따른 마스크를 생성
         db $0F,$6F,$DA       /// movq  mm3, mm2
                              //  mm3 에 마스크를 하나 더 복사
         db $0F,$DF,$D1       /// pandn mm2, mm1
                              //  Source 스프라이트 부분만을 남김
         db $0F,$DB,$D8       /// pand  mm3, mm0
                              //  Destination 의 갱신될 부분만 제거
         db $0F,$EB,$D3       /// por   mm2, mm3
                              //  Source 와 Destination 을 결합
         db $0F,$7F,$17       /// movq  [edi], mm2
                              //  Destination 에 결과를 씀

         add   esi, 8
                              //  한번에 8 bytes 를 동시에 처리했으므로
         add   edi, 8

         loop  @@LOOP_X

         add   esi, SourDesc.lPitch
         sub   esi, Size.X
         add   edi, DestDesc.lPitch
         sub   edi, Size.X

         pop   ecx
         loop  @@LOOP_Y

         db $0F,$77              /// emms

         pop   edi
         pop   esi

   end;

   Sour.UnLock(SourDesc.lpSurface);
   Dest.UnLock(DestDesc.lpSurface);

end;

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

             " A Mountain is a mountain, Water is water "


                                              copyright SMgal 1999/05/24