source

고속 메모리 복사 방법(ARGB에서 BGR로)

factcode 2022. 8. 11. 21:55
반응형

고속 메모리 복사 방법(ARGB에서 BGR로)

개요

다른 형식으로 변환해야 하는 이미지 버퍼가 있습니다.원본 이미지 버퍼는 채널당 8비트씩 4개 채널, Alpha, Red, Green 및 Blue입니다.수신처 버퍼는 채널당8비트, 블루, 그린, 레드 3채널입니다.

즉, 무차별 포스 방식은 다음과 같습니다.

// 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.3을 복사하는 것은 사소한 일일 수 있습니다.MB의 이미지 데이터 바이트 수는 정말, 정말 나빠요.

인텔 프로세서(Core 2 이상)에서 실행되므로 스트리밍 및 데이터 처리 명령어가 존재하지만 잘 모르는 경우가 있습니다.특화된 데이터 처리 지침을 찾는 방법에 대한 포인터가 될 수 있습니다.

이것은 OS X 어플리케이션에 들어가 XCode 4를 사용하고 있습니다.조립이 힘들지 않고 확실한 방법이라면 그 길을 따라가는 것도 좋지만, 전에 조립을 하지 않은 걸 보면 너무 많은 시간을 들여 조립하는 것을 경계하게 됩니다.

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

바이트 교환으로 동작하는 4가지 버전을 작성했습니다..2과 gcc 4.2.1을 사용하여 했습니다.-O3 -mssse3평균치를 했습니다.32 .MB의 랜덤 데이터를 10회 실행한 결과 평균이 발견되었습니다.


편집자 주의: 원래 인라인 asm은 안전하지 않은 제약 조건을 사용했습니다. 예를 들어 입력 전용 오퍼랜드를 수정하고 레지스터의 포인터 입력에 의해 지시된 메모리에 대한 부작용에 대해 컴파일러에 알리지 않았습니다.이것은 벤치마크에 적합하다고 합니다.모든 발신자가 안전하게 사용할 수 있도록 제한사항을 수정했습니다.이것은 벤치마크 번호에 영향을 주지 않습니다.주변 코드가 모든 발신자에게 안전한 것을 확인해 주세요.메모리 대역폭이 높은 최신 CPU에서는 SIMD의 속도가 한 번에 4바이트 스칼라보다 더 빨라질 것입니다.그러나 가장 큰 장점은 데이터가 캐시 내에서 핫할 때(작은 블록 또는 작은 총 크기에서 작업)입니다.

2020을 ._mm_loadu_si128https://gcc.gnu.org/wiki/DontUseInlineAsm과 동등한 asm 루프로 컴파일되는 intinctics 버전.

또, 이러한 모든 덮어쓰기 1(스칼라) 또는 4(SIMD) 바이트는 출력의 끝을 지나기 때문에, 문제가 있는 경우는, 마지막 3 바이트를 개별적으로 실시해 주세요.

--- @PeterCordes


번째 C 루프를 각변환합니다.는 C 합니다.OSSwapInt32function(이 함수는 "a"로)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]);
        // warning: strict-aliasing UB.  Use memcpy for unaligned loads/stores
    }
}

두 번째 방법에서는 같은 동작이 실행되지만 C 루프 대신 인라인어셈블리 루프를 사용합니다.

void swap2(ARGB *orig, BGR *dest, unsigned imageSize) {
    asm volatile ( // has to be volatile because the output is a side effect on pointed-to memory
        "0:\n\t"                   // do {
        "movl   (%1),%%eax\n\t"
        "bswapl %%eax\n\t"
        "movl   %%eax,(%0)\n\t"    // copy a dword byte-reversed
        "add    $4,%1\n\t"         // orig += 4 bytes
        "add    $3,%0\n\t"         // dest += 3 bytes
        "dec    %2\n\t"
        "jnz    0b"                // }while(--imageSize)
        : "+r" (dest), "+r" (orig), "+r" (imageSize)
        : // no pure inputs; the asm modifies and dereferences the inputs to use them as read/write outputs.
        : "flags", "eax", "memory"
    );
}

세 번째 버전은 단순한 포저의 대답을 변형한 것입니다.내장 함수를 GCC 등가 기능으로 변환하여 사용하였습니다.lddqu가 내장되어 있습니다 혜택을 .) (편집자 주: P4만 혜택을 받았습니다.lddqu;; 사용해도 좋습니다.movdqu단점은 없습니다.)

typedef char v16qi __attribute__ ((vector_size (16)));
void swap3(uint8_t *orig, uint8_t *dest, size_t imagesize) {
    v16qi mask = {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) {
    static const int8_t mask[16] = {3,2,1,7,6,5,11,10,9,15,14,13,0xFF,0xFF,0xFF,0XFF};
    asm volatile (
        "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)
        : "m" (mask)  // whole array as a memory operand.  "x" would get the compiler to load it
        : "flags", "xmm0", "xmm1", "memory"
    );
}

(이것들은 모두 GCC9.3에서는 정상적으로 컴파일 됩니다만, clang10에서는 알 수 없습니다.__builtin_ia32_pshufb128; ; ;_mm_shuffle_epi8

2010년 MacBook Pro, 2.4Ghz i5(Westmere/Arrandale), 4GB RAM의 평균 시간은 다음과 같습니다.

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

보시다시피 컴파일러는 최적화에 능숙하기 때문에 어셈블리에 쓸 필요가 없습니다.또, 벡터 함수는 32 MB의 데이터에서는 1.5 밀리초 밖에 고속이 되지 않기 때문에, SSE3를 서포트하고 있지 않았던 최초의 인텔 Mac을 서포트하고 싶다고 해도 큰 피해는 없습니다.

편집: liori가 표준 편차 정보를 요청했습니다.안타깝게도 데이터 포인트를 저장하지 않았기 때문에 25회 반복 테스트를 다시 실행했습니다.

평균 | 표준 편차강제력: 18.01956ms | 1.22980ms (6.8%)버전 1: 11.1320 밀리초 | 0.81076 밀리초 (7.3 %)버전 2: 11.27092 ms | 0.66209 ms (5.9%)버전 3: 9.29184 ms | 0.27851 ms (3.0 %)버전 4: 9.40948 ms | 0.32702 ms (3.5%)

그리고, 여기 새로운 테스트의 미가공 데이터가 있습니다.누군가 원하실 경우를 대비해서요.각 반복마다 32MB 데이터 세트가 랜덤으로 생성되어 4가지 기능을 통해 실행되었습니다.각 함수의 실행 시간을 마이크로초 단위로 다음에 나타냅니다.

브루트포스: 22173 18344 17457 17508 19844 17093 17116 19758 17395 18393 17075 17499 19023 19875 17203 16996 17442 17073 17043 18567 17285 1746 17845버전 1: 10508 11042 13432 11892 1257 1187 11281 11281 12601 10451 10441 10444 10455 1034 10452 10452 10454 11458 11601버전 2: 10623 12797 13173 11130 11218 11433 11621 10793 11026 10635 11042 12782 10993 10755 11547 10972 10811 11153 11240 10952 10936버전 3: 9036 9619 9341 8970 9458 9043 10114 9243 90279163 9168 9122 9514 9046 9064 9604 9233 9717 9156버전 4: 9339 10119 9846 9217 95262 9145 10286 9051 9614 9249 9653 979270 9173 9103 9132 9550 9157 9199 9113 96354 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));
    }
}

결합하는 것은 단지 불량자고 Jitamaro의 대답, 만약 여러분이 입력과 출력 16. 그리고 만약 당신이 4한번에 픽셀을 처리하는 당신을시켰습니다 가게를 사용하여 저장할 걸치고, 가면을 쓰고, 변명도 안돼, ors의 조합을 사용할 수 있었으면 정렬되어 있다고 생각한다.주요 아이디어, 4개의 중간 데이터 세트를 생성할 위험이 있거나 함께 마스크 및 픽셀 데이터의 316세트를 작성 관련 픽셀 값을 선택할 수 있다.주의하거나 전혀 실행합니다 이 컴파일하지 않았다.

EDIT2:내부 코드 구조상에 대해 좀 더 세부 사항:.

SSE2를 사용하면 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는 가면을 쓰고 있다는 '을 쓰다' 또는 '탈을 쓰다'입니다.

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

여기서 (1은 왼쪽 픽셀 선택을 의미하고, 0은 오른쪽 픽셀 선택을 의미함): mask(0)= 111 111 111 111 000 00 mask(1)= 1 111 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 mask(2)

그 but but)은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

과 같이 [= & [ 0] + & [ 0 ] + 2 & s [ ] + 1 。
&s[0]+7&s[0]+6&s[0]+5&s[0]+10&s]&s
2 &s1&[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

2개의 셔플 마스크가 겹치지 않기 때문에, 그것들을 조합해 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));
    }
}

나는 파티에 조금 늦게 온다.커뮤니티가 이미 포주르의 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의 이동이 가능한 더프 디바이스를 상상해 보십시오.

여기서의 고속 변환 기능 중 하나와 조합하여 Core 2s에 액세스 할 수 있게 되면, 다음의 psudeocode와 같이, 예를 들면 데이터의 4분의 1에서 동작하는 스레드로 변환을 분할하는 것이 현명할 수 있습니다.

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개의 쓰기 조작을 가지는 brute force 메서드에 비해, 픽셀/패킷 마다 2회(1회의 읽기, 1회의 쓰기) 밖에 메모리에 액세스 할 수 없습니다.방법은 동일하지만 구현이 더 효율적입니다.

4픽셀의 청크로 32비트를 부호 없는 긴 포인터로 이동할 수 있습니다.4개의 32비트 픽셀을 사용하면 다음과 같이 3개의 워드가 4개의 24비트 픽셀을 나타내며 이동 및 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)를 구성하는 가장 빠른 방법도 매우 빠르게 진행되고 있습니다.

다음과 같이 합니다.

//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의 배수가 아닌 이미지로 조정이 필요하지만 어셈블러를 사용하지 않고 가장 빠른 방법일 것입니다.

게다가 구조나 인덱스 액세스를 사용하는 것은 잊어버리세요.데이터 이동 속도가 느린 방법이기 때문에 컴파일된 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에서 하는 방법의 예를 보여 주는 사람은 본 적이 없습니다.

저는 얼마 전에 당신의 문제와 비슷한 글을 썼습니다.YUV 형식의 video4linux2 카메라에서 데이터를 받아 화면(Y 컴포넌트만)에 그레이 레벨로 그리기를 원했습니다.파란색은 너무 어둡고 빨간색은 과포화 된 부분을 그리고 싶었어요.

freeglut 배포의 smooth_opengl3.c 예시로 시작하였습니다.

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

저는 당신이 데이터를 되찾는 방법에 대한 경험이 없습니다.이론적으로 glReadPixels는 데이터를 다시 읽어야 하지만 나는 그것의 성능을 측정해 본 적이 없다.

OpenCL이 더 쉬운 방법일 수도 있지만, 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

반응형