developer tip

번역과 함께 메모리를 복사하는 빠른 방법-ARGB에서 BGR로

copycodes 2020. 11. 17. 08:20
반응형

번역과 함께 메모리를 복사하는 빠른 방법-ARGB에서 BGR로


개요

다른 형식으로 변환해야하는 이미지 버퍼가 있습니다. 원본 이미지 버퍼는 4 개 채널, 채널당 8 비트, 알파, 빨간색, 녹색 및 파란색입니다. 대상 버퍼는 3 개 채널, 채널당 8 비트, 파란색, 녹색 및 빨간색입니다.

따라서 무차별 대입 방법은 다음과 같습니다.

// Assume a 32 x 32 pixel image
#define IMAGESIZE (32*32)

typedef struct{ UInt8 Alpha; UInt8 Red; UInt8 Green; UInt8 Blue; } ARGB;
typedef struct{ UInt8 Blue; UInt8 Green; UInt8 Red; } BGR;

ARGB orig[IMAGESIZE];
BGR  dest[IMAGESIZE];

for(x = 0; x < IMAGESIZE; x++)
{
     dest[x].Red = orig[x].Red;
     dest[x].Green = orig[x].Green;
     dest[x].Blue = orig[x].Blue;
}

그러나 루프와 3 바이트 복사본이 제공하는 것보다 더 빠른 속도가 필요합니다. 32 비트 컴퓨터에서 실행 중이라는 점을 감안할 때 메모리 읽기 및 쓰기 수를 줄이는 데 사용할 수있는 몇 가지 트릭이 있기를 바랍니다.

추가 정보

모든 이미지는 최소 4 픽셀의 배수입니다. 따라서 16 ARGB 바이트를 처리하고 루프 당 12 RGB 바이트로 이동할 수 있습니다. 아마도이 사실은 특히 32 비트 경계에 잘 빠지기 때문에 속도를 높이는 데 사용할 수 있습니다.

OpenCL에 액세스 할 수 있습니다. 전체 버퍼를 GPU 메모리로 이동 한 다음 결과를 다시 이동해야하지만 OpenCL이 이미지의 여러 부분에서 동시에 작동 할 수 있다는 사실과 큰 메모리 블록 이동이 실제로 매우 효율적으로 이것을 가치있는 탐색으로 만들 수 있습니다.

위의 작은 버퍼의 예를 들었지만 실제로는 HD 비디오 (1920x1080)를 이동하고 때로는 더 크고 대부분 더 작은 버퍼를 이동하고 있습니다. 따라서 32x32 상황은 사소 할 수 있지만 8.3MB의 이미지 데이터를 바이트 단위로 복사하는 것은 정말, 정말 나쁩니다.

Intel 프로세서 (Core 2 이상)에서 실행되므로 내가 알고있는 스트리밍 및 데이터 처리 명령이 있지만 잘 모르겠습니다. 전문 데이터 처리 지침을 찾을 위치에 대한 포인터가 좋을 것입니다.

이것은 OS X 응용 프로그램에 들어가고 저는 XCode 4를 사용하고 있습니다. 어셈블리가 고통스럽지 않고 명백한 방법이라면 그 경로를 따라가는 것이 좋지만 이전에이 설정을 수행하지 않았기 때문에주의해야합니다. 그것에 너무 많은 시간을 가라 앉히는 것.

의사 코드는 괜찮습니다. 완전한 솔루션을 찾고있는 것이 아니라 알고리즘과 즉시 명확하지 않을 수있는 속임수에 대한 설명뿐입니다.


나는 바이트를 교환하여 작동하는 4 가지 버전을 작성했습니다. 나는 gcc 4.2.1을 사용하여 컴파일하고 -O3 -mssse332MB의 임의 데이터에 대해 10 번 실행하여 평균을 찾았습니다.

첫 번째 버전은 C 루프를 사용하여 OSSwapInt32함수 (를 사용하여 bswap명령어로 컴파일 됨)를 사용하여 각 픽셀을 개별적으로 변환 합니다 -O3.

void swap1(ARGB *orig, BGR *dest, unsigned imageSize) {
    unsigned x;
    for(x = 0; x < imageSize; x++) {
        *((uint32_t*)(((uint8_t*)dest)+x*3)) = OSSwapInt32(((uint32_t*)orig)[x]);
    }
}

두 번째 방법은 동일한 작업을 수행하지만 C 루프 대신 인라인 어셈블리 루프를 사용합니다.

void swap2(ARGB *orig, BGR *dest, unsigned imageSize) {
    asm (
        "0:\n\t"
        "movl   (%1),%%eax\n\t"
        "bswapl %%eax\n\t"
        "movl   %%eax,(%0)\n\t"
        "addl   $4,%1\n\t"
        "addl   $3,%0\n\t"
        "decl   %2\n\t"
        "jnz    0b"
        :: "D" (dest), "S" (orig), "c" (imageSize)
        : "flags", "eax"
    );
}

세 번째 버전은 단지 포저의 대답을 수정 한 버전입니다 . 내장 함수를 GCC 등가물로 변환하고 내장 함수를 사용 lddqu하여 입력 인수를 정렬 할 필요가 없도록했습니다.

typedef uint8_t v16qi __attribute__ ((vector_size (16)));
void swap3(uint8_t *orig, uint8_t *dest, size_t imagesize) {
    v16qi mask = __builtin_ia32_lddqu((const char[]){3,2,1,7,6,5,11,10,9,15,14,13,0xFF,0xFF,0xFF,0XFF});
    uint8_t *end = orig + imagesize * 4;
    for (; orig != end; orig += 16, dest += 12) {
        __builtin_ia32_storedqu(dest,__builtin_ia32_pshufb128(__builtin_ia32_lddqu(orig),mask));
    }
}

마지막으로 네 번째 버전은 세 번째 버전과 동일한 인라인 어셈블리입니다.

void swap2_2(uint8_t *orig, uint8_t *dest, size_t imagesize) {
    int8_t mask[16] = {3,2,1,7,6,5,11,10,9,15,14,13,0xFF,0xFF,0xFF,0XFF};//{0xFF, 0xFF, 0xFF, 0xFF, 13, 14, 15, 9, 10, 11, 5, 6, 7, 1, 2, 3};
    asm (
        "lddqu  (%3),%%xmm1\n\t"
        "0:\n\t"
        "lddqu  (%1),%%xmm0\n\t"
        "pshufb %%xmm1,%%xmm0\n\t"
        "movdqu %%xmm0,(%0)\n\t"
        "add    $16,%1\n\t"
        "add    $12,%0\n\t"
        "sub    $4,%2\n\t"
        "jnz    0b"
        :: "r" (dest), "r" (orig), "r" (imagesize), "r" (mask)
        : "flags", "xmm0", "xmm1"
    );
}

내 2010 MacBook Pro, 2.4Ghz i5, 4GB RAM에서 각각의 평균 시간은 다음과 같습니다.

버전 1 : 10.8630 밀리 초
버전 2 : 11.3254 밀리 초
버전 3 : 9.3163 밀리 초
버전 4 : 9.3584 밀리 초

보시다시피 컴파일러는 어셈블리를 작성할 필요가없는 최적화에 충분합니다. 또한 벡터 함수는 32MB 데이터에서 1.5 밀리 초 더 빠르기 때문에 SSSE3를 지원하지 않는 초기 Intel Mac을 지원하려는 경우 큰 해를 끼치 지 않습니다.

편집 : liori는 표준 편차 정보를 요청했습니다. 불행히도 데이터 포인트를 저장하지 않았기 때문에 25 번의 반복으로 다른 테스트를 실행했습니다.

              평균 | 표준 편차
무차별 대입 : 18.01956ms | 1.22980ms (6.8 %)
버전 1 : 11.13120ms | 0.81076ms (7.3 %)
버전 2 : 11.27092ms | 0.66209ms (5.9 %)
버전 3 : 9.29184ms | 0.27851ms (3.0 %)
버전 4 : 9.40948ms | 0.32702ms (3.5 %)

또한, 누군가가 원할 경우를 대비하여 새로운 테스트의 원시 데이터가 있습니다. 각 반복에 대해 32MB 데이터 세트가 무작위로 생성되어 네 가지 기능을 통해 실행되었습니다. 각 함수의 실행 시간 (마이크로 초)은 다음과 같습니다.

무차별 대입 : 22173 18344 17458 17277 17508 19844 17093 17116 19758 17395 18393 17075 17499 19023 19875 17203 16996 17442 17458 17073 17043 18567 17285 17746 17845
버전 1 : 10508 11042 13432 11892 12577 10587 11281 11912 12500 10601 10551 10444 11655 10421 11285 10554 10334 10452 10490 10554 10419 11458 11682 11048 10601
버전 2 : 10623 12797 13173 11130 11218 11433 11621 10793 11026 10635 11042 11328 12782 10943 10693 10755 11547 11028 10972 10811 11152 11143 11240 10952 10936
버전 3 : 9036 9619 9341 8970 9453 9758 9043 10114 9243 9027 9163 9176 9168 9122 9514 9049 9161 9086 9064 9604 9178 9233 9301 9717 9156
버전 4 : 9339 10119 9846 9217 9526 9182 9145 10286 9051 9614 9249 9653 9799 9270 9173 9103 9132 9550 9147 9157 9199 9113 9699 9354 9314

pshufb를 사용하는 것이 분명합니다.

#include <assert.h>
#include <inttypes.h>
#include <tmmintrin.h>

// needs:
// orig is 16-byte aligned
// imagesize is a multiple of 4
// dest has 4 trailing scratch bytes
void convert(uint8_t *orig, size_t imagesize, uint8_t *dest) {
    assert((uintptr_t)orig % 16 == 0);
    assert(imagesize % 4 == 0);
    __m128i mask = _mm_set_epi8(-128, -128, -128, -128, 13, 14, 15, 9, 10, 11, 5, 6, 7, 1, 2, 3);
    uint8_t *end = orig + imagesize * 4;
    for (; orig != end; orig += 16, dest += 12) {
        _mm_storeu_si128((__m128i *)dest, _mm_shuffle_epi8(_mm_load_si128((__m128i *)orig), mask));
    }
}

포저와 지 타마로의 답변 만 결합하여 입력과 출력이 16 바이트로 정렬되어 있다고 가정하고 한 번에 4 개의 픽셀을 처리하는 경우 셔플, 마스크 및 ors의 조합을 사용하여 정렬을 사용하여 저장할 수 있습니다. 백화점. 주요 아이디어는 4 개의 중간 데이터 세트를 생성 한 다음 마스크와 함께 해당 픽셀 값을 선택하고 3 개의 16 바이트 세트의 픽셀 데이터를 작성하는 것입니다. 나는 이것을 컴파일하거나 전혀 실행하지 않았다는 점에 유의하십시오.

EDIT2 : 기본 코드 구조에 대한 자세한 정보 :

SSE2를 사용하면 16 바이트로 정렬 된 16 바이트의 읽기 및 쓰기로 더 나은 성능을 얻을 수 있습니다. 3 바이트 픽셀은 16 픽셀마다 16 바이트로만 정렬 할 수 있으므로 셔플과 마스크의 조합을 사용하여 한 번에 16 픽셀을 일괄 처리하고 한 번에 16 개의 입력 픽셀을 사용합니다.

LSB에서 MSB까지 입력은 다음과 같으며 특정 구성 요소를 무시합니다.

s[0]: 0000 0000 0000 0000
s[1]: 1111 1111 1111 1111
s[2]: 2222 2222 2222 2222
s[3]: 3333 3333 3333 3333

ouptuts는 다음과 같습니다.

d[0]: 000 000 000 000 111 1
d[1]:  11 111 111 222 222 22
d[2]:   2 222 333 333 333 333

따라서 이러한 출력을 생성하려면 다음을 수행해야합니다 (실제 변환은 나중에 지정하겠습니다).

d[0]= combine_0(f_0_low(s[0]), f_0_high(s[1]))
d[1]= combine_1(f_1_low(s[1]), f_1_high(s[2]))
d[2]= combine_2(f_1_low(s[2]), f_1_high(s[3]))

이제 어떻게 combine_<x>생겼을까 요? d단순히 s함께 압축 된다고 가정 s하면 마스크와 or로 두 개의를 연결할 수 있습니다 .

combine_x(left, right)= (left & mask(x)) | (right & ~mask(x))

여기서 (1은 왼쪽 픽셀 선택, 0은 오른쪽 픽셀 선택) : mask (0) = 111111111111111000 0 mask (1) = 11111111000000000 00 mask (2) = 1111000000000000

그러나 실제 변환 ( f_<x>_low, f_<x>_high)은 실제로 그렇게 간단하지 않습니다. 소스 픽셀에서 바이트를 반전하고 제거하므로 실제 변환은 다음과 같습니다 (간결성을 위해 첫 번째 대상).

d[0]= 
    s[0][0].Blue s[0][0].Green s[0][0].Red 
    s[0][1].Blue s[0][1].Green s[0][1].Red 
    s[0][2].Blue s[0][2].Green s[0][2].Red 
    s[0][3].Blue s[0][3].Green s[0][3].Red
    s[1][0].Blue s[1][0].Green s[1][0].Red
    s[1][1].Blue

위의 값을 소스에서 목적지까지의 바이트 오프셋으로 변환하면 다음과 같이됩니다. d [0] = & s [0] +3 & s [0] +2 & s [0] +1
& s [0] +7 & s [0] + 6 & s [0] +5 & s [0] +11 & s [0] +10 & s [0] +9 & s [0] +15 & s [0] +14 & s [0] +13
& s [1] +3 & s [1] +2 및 s [1] +1
및 s [1] +7

(모든 s [0] 오프셋을 살펴보면 포저의 셔플 마스크와 역순으로 일치합니다.)

이제 각 소스 바이트를 대상 바이트에 매핑하는 셔플 마스크를 생성 할 수 있습니다 ( X이 값이 무엇인지는 신경 쓰지 않는다는 의미).

f_0_low=  3 2 1  7 6 5  11 10 9  15 14 13  X X X  X
f_0_high= X X X  X X X   X  X X   X  X  X  3 2 1  7

f_1_low=    6 5  11 10 9  15 14 13  X X X   X X X  X  X
f_1_high=   X X   X  X X   X  X  X  3 2 1   7 6 5  11 10

f_2_low=      9  15 14 13  X  X  X  X X X   X  X  X  X  X  X
f_2_high=     X   X  X  X  3  2  1  7 6 5   11 10 9  15 14 13

각 소스 픽셀에 사용하는 마스크를 살펴봄으로써이를 더욱 최적화 할 수 있습니다. s [1]에 사용하는 셔플 마스크를 살펴보면 :

f_0_high=  X  X  X  X  X  X  X  X  X  X  X  X  3  2  1  7
f_1_low=   6  5 11 10  9 15 14 13  X  X  X  X  X  X  X  X

두 개의 셔플 마스크가 겹치지 않기 때문에, 우리는 그것들을 결합하고 우리가 이미했던 Combine_에서 관련없는 픽셀을 간단히 마스킹 할 수 있습니다! 다음 코드는 이러한 모든 최적화를 수행합니다 (또한 소스 및 대상 주소가 16 바이트로 정렬되어 있다고 가정). 또한 마스크는 순서에 대해 혼란 스러울 경우를 대비하여 MSB-> LSB 순서로 코드로 작성됩니다.

편집 : _mm_stream_si128많은 쓰기 작업을 수행하고 캐시를 반드시 플러시하고 싶지 않기 때문에 저장소를로 변경했습니다 . 또한 어쨌든 정렬되어야 무료 성능을 얻을 수 있습니다!

#include <assert.h>
#include <inttypes.h>
#include <tmmintrin.h>

// needs:
// orig is 16-byte aligned
// imagesize is a multiple of 4
// dest has 4 trailing scratch bytes
void convert(uint8_t *orig, size_t imagesize, uint8_t *dest) {
    assert((uintptr_t)orig % 16 == 0);
    assert(imagesize % 16 == 0);

    __m128i shuf0 = _mm_set_epi8(
        -128, -128, -128, -128, // top 4 bytes are not used
        13, 14, 15, 9, 10, 11, 5, 6, 7, 1, 2, 3); // bottom 12 go to the first pixel

    __m128i shuf1 = _mm_set_epi8(
        7, 1, 2, 3, // top 4 bytes go to the first pixel
    -128, -128, -128, -128, // unused
        13, 14, 15, 9, 10, 11, 5, 6); // bottom 8 go to second pixel

    __m128i shuf2 = _mm_set_epi8(
        10, 11, 5, 6, 7, 1, 2, 3, // top 8 go to second pixel
    -128, -128, -128, -128, // unused
        13, 14, 15, 9); // bottom 4 go to third pixel

    __m128i shuf3 = _mm_set_epi8(
        13, 14, 15, 9, 10, 11, 5, 6, 7, 1, 2, 3, // top 12 go to third pixel
        -128, -128, -128, -128); // unused

    __m128i mask0 = _mm_set_epi32(0, -1, -1, -1);
    __m128i mask1 = _mm_set_epi32(0,  0, -1, -1);
    __m128i mask2 = _mm_set_epi32(0,  0,  0, -1);

    uint8_t *end = orig + imagesize * 4;
    for (; orig != end; orig += 64, dest += 48) {
        __m128i a= _mm_shuffle_epi8(_mm_load_si128((__m128i *)orig), shuf0);
        __m128i b= _mm_shuffle_epi8(_mm_load_si128((__m128i *)orig + 1), shuf1);
        __m128i c= _mm_shuffle_epi8(_mm_load_si128((__m128i *)orig + 2), shuf2);
        __m128i d= _mm_shuffle_epi8(_mm_load_si128((__m128i *)orig + 3), shuf3);

        _mm_stream_si128((__m128i *)dest, _mm_or_si128(_mm_and_si128(a, mask0), _mm_andnot_si128(b, mask0));
        _mm_stream_si128((__m128i *)dest + 1, _mm_or_si128(_mm_and_si128(b, mask1), _mm_andnot_si128(c, mask1));
        _mm_stream_si128((__m128i *)dest + 2, _mm_or_si128(_mm_and_si128(c, mask2), _mm_andnot_si128(d, mask2));
    }
}

나는 파티에 조금 늦게오고 있는데, 커뮤니티가 이미 poseur의 pshufb-answer를 결정했지만 2000 개의 평판을 배포하고있는 것 같아서 너무 관 대해서 한 번 시도 해봐야합니다.

여기에 플랫폼 별 내장 함수 나 기계 별 asm이없는 버전이 있습니다. 같이 비트 트위들 링을 수행하고 컴파일러 최적화 (레지스터 최적화, 루프 풀기)를 활성화 하면 4 배의 속도 향상을 보여주는 크로스 플랫폼 타이밍 코드를 포함했습니다. :

#include "stdlib.h"
#include "stdio.h"
#include "time.h"

#define UInt8 unsigned char

#define IMAGESIZE (1920*1080) 
int main() {
    time_t  t0, t1;
    int frames;
    int frame; 
    typedef struct{ UInt8 Alpha; UInt8 Red; UInt8 Green; UInt8 Blue; } ARGB;
    typedef struct{ UInt8 Blue; UInt8 Green; UInt8 Red; } BGR;

    ARGB* orig = malloc(IMAGESIZE*sizeof(ARGB));
    if(!orig) {printf("nomem1");}
    BGR* dest = malloc(IMAGESIZE*sizeof(BGR));
    if(!dest) {printf("nomem2");}

    printf("to start original hit a key\n");
    getch();
    t0 = time(0);
    frames = 1200;
    for(frame = 0; frame<frames; frame++) {
        int x; for(x = 0; x < IMAGESIZE; x++) {
            dest[x].Red = orig[x].Red;
            dest[x].Green = orig[x].Green;
            dest[x].Blue = orig[x].Blue;
            x++;
        }
    }
    t1 = time(0);
    printf("finished original of %u frames in %u seconds\n", frames, t1-t0);

    // on my core 2 subnotebook the original took 16 sec 
    // (8 sec with compiler optimization -O3) so at 60 FPS 
    // (instead of the 1200) this would be faster than realtime 
    // (if you disregard any other rendering you have to do). 
    // However if you either want to do other/more processing 
    // OR want faster than realtime processing for e.g. a video-conversion 
    // program then this would have to be a lot faster still.

    printf("to start alternative hit a key\n");
    getch();
    t0 = time(0);
    frames = 1200;
    unsigned int* reader;
    unsigned int* end = reader+IMAGESIZE;
    unsigned int cur; // your question guarantees 32 bit cpu
    unsigned int next;
    unsigned int temp;
    unsigned int* writer;
    for(frame = 0; frame<frames; frame++) {
        reader = (void*)orig;
        writer = (void*)dest;
        next = *reader;
        reader++;
        while(reader<end) {
            cur = next;
            next = *reader;         
            // in the following the numbers are of course the bitmasks for 
            // 0-7 bits, 8-15 bits and 16-23 bits out of the 32
            temp = (cur&255)<<24 | (cur&65280)<<16|(cur&16711680)<<8|(next&255); 
            *writer = temp;
            reader++;
            writer++;
            cur = next;
            next = *reader;
            temp = (cur&65280)<<24|(cur&16711680)<<16|(next&255)<<8|(next&65280);
            *writer = temp;
            reader++;
            writer++;
            cur = next;
            next = *reader;
            temp = (cur&16711680)<<24|(next&255)<<16|(next&65280)<<8|(next&16711680);
            *writer = temp;
            reader++;
            writer++;
        }
    }
    t1 = time(0);
    printf("finished alternative of %u frames in %u seconds\n", frames, t1-t0);

    // on my core 2 subnotebook this alternative took 10 sec 
    // (4 sec with compiler optimization -O3)

}

결과는 다음과 같습니다 (내 핵심 2 서브 노트북).

F:\>gcc b.c -o b.exe

F:\>b
to start original hit a key
finished original of 1200 frames in 16 seconds
to start alternative hit a key
finished alternative of 1200 frames in 10 seconds

F:\>gcc b.c -O3 -o b.exe

F:\>b
to start original hit a key
finished original of 1200 frames in 8 seconds
to start alternative hit a key
finished alternative of 1200 frames in 4 seconds

Duff의 장치를 사용하려고합니다 : http://en.wikipedia.org/wiki/Duff%27s_device . JavaScript에서도 작동합니다. 그러나이 게시물은 http://lkml.indiana.edu/hypermail/linux/kernel/0008.2/0171.html 을 읽는 것이 약간 재미 있습니다. 512KB의 움직임을 가진 Duff 장치를 상상해보십시오.


여기에서 빠른 변환 기능 중 하나와 결합하여 Core 2에 대한 액세스 권한이 주어지면 다음 psudeocode에서와 같이 데이터의 네 번째 데이터에 대해 작동하는 스레드로 변환을 분할하는 것이 현명 할 수 있습니다.

void bulk_bgrFromArgb(byte[] dest, byte[] src, int n)
{
       thread threads[] = {
           create_thread(bgrFromArgb, dest, src, n/4),
           create_thread(bgrFromArgb, dest+n/4, src+n/4, n/4),
           create_thread(bgrFromArgb, dest+n/2, src+n/2, n/4),
           create_thread(bgrFromArgb, dest+3*n/4, src+3*n/4, n/4),
       }
       join_threads(threads);
}

이 어셈블리 함수는 수행해야하지만 이전 데이터를 유지 하려는지 여부는 모르겠지만이 함수는이를 재정의합니다.

이 코드는 인텔 어셈블리 플레이버가있는 MinGW GCC 용 이므로 컴파일러 / 어셈블러에 맞게 수정해야합니다.

extern "C" {
    int convertARGBtoBGR(uint buffer, uint size);
    __asm(
        ".globl _convertARGBtoBGR\n"
        "_convertARGBtoBGR:\n"
        "  push ebp\n"
        "  mov ebp, esp\n"
        "  sub esp, 4\n"
        "  mov esi, [ebp + 8]\n"
        "  mov edi, esi\n"
        "  mov ecx, [ebp + 12]\n"
        "  cld\n"
        "  convertARGBtoBGR_loop:\n"
        "    lodsd          ; load value from [esi] (4byte) to eax, increment esi by 4\n"
        "    bswap eax ; swap eax ( A R G B ) to ( B G R A )\n"
        "    stosd          ; store 4 bytes to [edi], increment  edi by 4\n"
        "    sub edi, 1; move edi 1 back down, next time we will write over A byte\n"
        "    loop convertARGBtoBGR_loop\n"
        "  leave\n"
        "  ret\n"
    );
}

다음과 같이 호출해야합니다.

convertARGBtoBGR( &buffer, IMAGESIZE );

이 함수는 3 개의 읽기 및 3 개의 쓰기 작업을 수행 한 (적어도 / 등록하기 위해 컴파일되었다고 가정 할 때) 무차별 대입 방법 과 비교하여 픽셀 / 패킷 당 두 번만 메모리에 액세스합니다 (1 읽기, 1 쓰기) . 방법은 동일하지만 구현하면 더 효율적입니다.


부호없는 긴 포인터를 사용하여 32 비트를 이동하면서 4 픽셀 단위로 수행 할 수 있습니다. 다음과 같이 4 개의 32 비트 픽셀로 4 개의 24 비트 픽셀을 나타내는 3 개의 단어를 시프트 및 OR / AND로 구성 할 수 있다고 생각하십시오.

//col0 col1 col2 col3
//ARGB ARGB ARGB ARGB 32bits reading (4 pixels)
//BGRB GRBG RBGR  32 bits writing (4 pixels)

이동 연산은 모든 최신 32/64 비트 프로세서 (배럴 이동 기술)에서 항상 1 개의 명령 주기로 수행되므로 쓰기, 비트 AND 및 OR을 위해 3 개 단어를 구성하는 가장 빠른 방법도 엄청나게 빠릅니다.

이렇게 :

//assuming we have 4 ARGB1 ... ARGB4 pixels and  3 32 bits words,  W1, W2 and W3 to write
// and *dest  its an unsigned long pointer for destination
W1 = ((ARGB1 & 0x000f) << 24) | ((ARGB1 & 0x00f0) << 8) | ((ARGB1 & 0x0f00) >> 8) | (ARGB2 & 0x000f);
*dest++ = W1;

등등 .... 루프의 다음 픽셀로.

4의 배수가 아닌 이미지로 약간의 조정이 필요하지만 어셈블러를 사용하지 않고 가장 빠른 방법이라고 확신합니다.

그리고 btw, 구조체와 인덱싱 된 액세스를 사용하는 것을 잊으십시오. 데이터를 이동하는 데있어 모두 느린 방법입니다. 컴파일 된 C ++ 프로그램의 디스 어셈블리 목록을 살펴보면 저와 동의 할 것입니다.


typedef struct{ UInt8 Alpha; UInt8 Red; UInt8 Green; UInt8 Blue; } ARGB;
typedef struct{ UInt8 Blue; UInt8 Green; UInt8 Red; } BGR;

어셈블리 또는 컴파일러 내장 함수를 제외하고는 다음을 시도 할 수 있습니다. 그 중 일부 (유니온이 관련된 경우)가 컴파일러 구현에 종속 될 가능성이 있기 때문에 최종 동작매우 신중하게 확인합니다 .

union uARGB
{
   struct ARGB argb;
   UInt32 x;
};
union uBGRA
{
   struct 
   {
     BGR bgr;
     UInt8 Alpha;
   } bgra;
   UInt32 x;
};

그런 다음 코드 커널에 대해 루프 언 롤링이 적절합니다.

inline void argb2bgr(BGR* pbgr, ARGB* pargb)
{
    uARGB* puargb = (uARGB*)pargb;
    uBGRA ubgra;
    ubgra.x = __byte_reverse_32(pargb->x);
    *pbgr = ubgra.bgra.bgr;
}

여기서는 __byte_reverse_32()32 비트 워드의 바이트를 반전시키는 컴파일러 내장 함수가 있다고 가정합니다.

기본 접근 방식을 요약하려면 :

  • ARGB 구조를 32 비트 정수로보기
  • 32 비트 정수 반전
  • 반전 된 32 비트 정수를 (BGR) A 구조로보기
  • 컴파일러가 (BGR) A 구조의 (BGR) 부분을 복사하도록합니다.

CPU 사용량에 따라 몇 가지 트릭을 사용할 수 있지만

This kind of operations can be done fasted with GPU.

C / C ++를 사용하는 것 같습니다. 따라서 GPU 프로그래밍에 대한 대안은 (Windows 플랫폼에서)

빠른 계산을 위해 이러한 종류의 배열 작업에 GPU를 잠시 사용하십시오. 그들은 그것을 위해 설계되었습니다.


GPU에서 수행하는 방법에 대한 예를 보여주는 사람을 본 적이 없습니다.

얼마 전에 나는 당신의 문제와 비슷한 것을 썼습니다. video4linux2 카메라에서 YUV 형식으로 데이터를 받았으며 화면에 회색 수준으로 그리려고했습니다 (Y 구성 요소 만). 또한 너무 어두운 부분은 파란색으로, 과포화 부분은 빨간색으로 그리고 싶었습니다.

freeglut 배포판 의 smooth_opengl3.c 예제로 시작했습니다 .

데이터는 텍스처에 YUV로 복사되고 다음 GLSL 셰이더 프로그램이 적용됩니다. 요즘 GLSL 코드는 모든 Mac에서 실행되며 모든 CPU 접근 방식보다 훨씬 빠를 것입니다.

데이터를 다시 얻는 방법에 대한 경험이 없습니다. 이론적으로 glReadPixels는 데이터를 다시 읽어야하지만 성능을 측정 한 적이 없습니다.

OpenCL이 더 쉬운 접근 방식 일 수 있지만이를 지원하는 노트북이있을 때만 개발을 시작합니다.

(defparameter *vertex-shader*
"void main(){
    gl_Position    = gl_ModelViewProjectionMatrix * gl_Vertex;
    gl_FrontColor  = gl_Color;
    gl_TexCoord[0] = gl_MultiTexCoord0;
}
")

(progn
 (defparameter *fragment-shader*
   "uniform sampler2D textureImage;
void main()
{
  vec4 q=texture2D( textureImage, gl_TexCoord[0].st);
  float v=q.z;
  if(int(gl_FragCoord.x)%2 == 0)
     v=q.x; 
  float x=0; // 1./255.;
  v-=.278431;
  v*=1.7;
  if(v>=(1.0-x))
    gl_FragColor = vec4(255,0,0,255);
  else if (v<=x)
    gl_FragColor = vec4(0,0,255,255);
  else
    gl_FragColor = vec4(v,v,v,255); 
}
")

여기에 이미지 설명 입력

참고 URL : https://stackoverflow.com/questions/6804101/fast-method-to-copy-memory-with-translation-argb-to-bgr

반응형