Portál AbcLinuxu, 1. května 2025 22:34
#define RGB_TO_YCbCrJPEG_Y(Rd, Gd, Bd) \ ( + ((306 * (Rd) + 601 * (Gd) + 117 * (Bd)) >> 10)) #define RGB_TO_YCbCrJPEG_Cb(Rd, Gd, Bd) \ (128 - ((173 * (Rd) + 339 * (Gd) - 512 * (Bd)) >> 10)) #define RGB_TO_YCbCrJPEG_Cr(Rd, Gd, Bd) \ (128 + ((512 * (Rd) - 429 * (Gd) - 83 * (Bd)) >> 10)) void ycbcr_bgr_to_jpeg420(save_thread_t *th, unsigned char *from) { unsigned int Ypix; unsigned int op1, op2, op3, op4; unsigned char Rd, Gd, Bd; unsigned int ox, oy, Yy, Yx; unsigned char *Y, *Cb, *Cr; Y = th->yuv_buffer; Cb = &th->yuv_buffer[th->yw * th->yh]; Cr = &th->yuv_buffer[th->yw * th->yh + th->cw * th->ch]; oy = (th->yh - 2) * th->stride; ox = 0; for (Yy = 0; Yy < th->yh; Yy += 2) { for (Yx = 0; Yx < th->yw; Yx += 2) { op1 = ox + oy; op2 = op1 + 4; op3 = op1 + th->stride; op4 = op2 + th->stride; Rd = (from[op1 + 2] + from[op2 + 2] + from[op3 + 2] + from[op4 + 2]) >> 2; Gd = (from[op1 + 1] + from[op2 + 1] + from[op3 + 1] + from[op4 + 1]) >> 2; Bd = (from[op1 + 0] + from[op2 + 0] + from[op3 + 0] + from[op4 + 0]) >> 2; /* CbCr */ *Cb++ = RGB_TO_YCbCrJPEG_Cb(Rd, Gd, Bd); *Cr++ = RGB_TO_YCbCrJPEG_Cr(Rd, Gd, Bd); /* Y' */ Ypix = Yx + Yy * th->yw; Y[Ypix] = RGB_TO_YCbCrJPEG_Y(from[op3 + 2], from[op3 + 1], from[op3 + 0]); Y[Ypix + 1] = RGB_TO_YCbCrJPEG_Y(from[op4 + 2], from[op4 + 1], from[op4 + 0]); Y[Ypix + th->yw] = RGB_TO_YCbCrJPEG_Y(from[op1 + 2], from[op1 + 1], from[op1 + 0]); Y[Ypix + 1 + th->yw] = RGB_TO_YCbCrJPEG_Y(from[op2 + 2], from[op2 + 1], from[op2 + 0]); ox += 4 * 2; } ox = 0; oy -= 2 * th->stride; } }
Šoupnul jsem to do cyklu s počtem iterací 30*20 (tj. 20s video při 30 snímcích za sekundu), rozlišení 1280x1024. Konverze je hotová za 6.243s.
Zdálo se mi to dlouho, tak jsem zkoušel optimalizovat a upatlal jsem toto:
void ycbcr_bgr_to_jpeg420_mine(save_thread_t *th, unsigned char *from) { unsigned bytes_per_row; unsigned extra_per_row; unsigned char *row; unsigned char *end_pix; unsigned char *end_row; unsigned char *Y, *Cb, *Cr; unsigned char R, G, B; Y = th->yuv_buffer + (th->yh - 2) * th->yw; Cb = &th->yuv_buffer[th->yw * th->yh + (th->ch - 1) * th->cw]; Cr = &th->yuv_buffer[th->yw * th->yh + th->cw * th->ch + (th->ch - 1) * th->cw]; bytes_per_row = th->width * 4; extra_per_row = th->stride - bytes_per_row; end_row = from + th->stride * th->height; for (; from != end_row; from += th->stride + extra_per_row) { end_pix = from + bytes_per_row; for (; from != end_pix; from += 2*4) { B = (from[0 + 0] + from[4 + 0] + from[th->stride + 0] + from[th->stride + 4 + 0]) >> 2; G = (from[0 + 1] + from[4 + 1] + from[th->stride + 1] + from[th->stride + 4 + 1]) >> 2; R = (from[0 + 2] + from[4 + 2] + from[th->stride + 2] + from[th->stride + 4 + 2]) >> 2; *Cb = RGB_TO_YCbCrJPEG_Cb(R, G, B); *Cr = RGB_TO_YCbCrJPEG_Cr(R, G, B); Y[0] = RGB_TO_YCbCrJPEG_Y(from[th->stride + 2], from[th->stride + 1], from[th->stride + 0]); Y[1] = RGB_TO_YCbCrJPEG_Y(from[th->stride + 4 + 2], from[th->stride + 4 + 1], from[th->stride + 4 + 0]); Y[th->yw + 0] = RGB_TO_YCbCrJPEG_Y(from[0 + 2], from[0 + 1], from[0 + 0]); Y[th->yw + 1] = RGB_TO_YCbCrJPEG_Y(from[4 + 2], from[4 + 1], from[4 + 0]); Y += 2; ++Cb; ++Cr; } Y -= th->yw; Y -= th->yw; Y -= th->yw; Cb -= th->cw; Cb -= th->cw; Cr -= th->cw; Cr -= th->cw; } }
Prohodil jsem směr (btw řádky vrácené z opengl jsou v rgb bufferu opačně (horní řádek dole)), čtu odshora dolů a zapisuju odspoda nahoru. No nevím jestli to zlepšilo to, nebo jestli to zlepšilo použítí pointerů na některých místech, každopádně dostal jsem se na 4.854s.
Ale protože Jardíkovi se to pořád zdálo hodně a ten obraz se zdál být tmavý, tak jsem našel jiný vzorec na převod a zkoušel jsem to spatlat ... v assembleru.
global bgr_to_jpeg420_sse ; Y = ( ( 66 * R + 129 * G + 25 * B + 128) >> 8) + 16 ; U = ( ( -38 * R - 74 * G + 112 * B + 128) >> 8) + 128 ; V = ( ( 112 * R - 94 * G - 18 * B + 128) >> 8) + 128 section .data align=16 vec_alpha_1_and: align 16 dd 0x00FFFFFF, 0x00FFFFFF, 0x00FFFFFF, 0x00FFFFFF vec_alpha_1_or: align 16 dd 0x01000000, 0x01000000, 0x01000000, 0x01000000 vec_dd_16: align 16 dd 16, 16, 16, 16 vec_dd_128: align 16 dd 128, 128, 128, 128 vec_y_const: align 16 dw 25, 129, 66, 128, 25, 129, 66, 128 vec_u_const: align 16 dw 112, -74, -38, 128, 112, -74, -38, 128 vec_v_const: align 16 dw -18, -94, 112, 128, -18, -94, 112, 128 section .text ; this expects these xmm registers to be set: ; xmm4 to vec_alpha_1_and ; xmm5 to vec_alpha_1_or ; xmm6 to vec_y_const ; xmm7 to vec_dd_16 ; input vector in xmm0 as (argb3, argb2, argb1, argb0) ; returns result as dwords in xmm1 (y3,y2,y1,y0) ; destroys content of xmm0, xmm2 %macro bgr_to_jpeg420_sse_get_Y 0 pand xmm0, xmm4 ; set alpha of each pixel to 1 por xmm0, xmm5 movaps xmm1, xmm0 ; copy to xmm1 pxor xmm2, xmm2 ; zero xmm2 punpckhbw xmm0, xmm2 ; interleave xmm2 with first 2 pixels, xmm = (a3,r3,g3,b3,a2,r2,g2,b2) punpcklbw xmm1, xmm2 ; interleave xmm2 with second 2 pixels, xmm = (a1,r1,g1,b1,a0,r0,g0,b0) pmaddwd xmm0, xmm6 ; xmm = (128a3+66r3 = A3,129g3+25b3 = B3,128a2+66r2 = A2,129g2+25b2 = B2) pmaddwd xmm1, xmm6 ; xmm = (128a1+66r1 = A1,129g1+25b1 = B1,128a0+66r0 = A0,129g0+25b0 = B0) pshufd xmm0, xmm0, 0b11011000 ; (A3, A2, B3, B2) pshufd xmm1, xmm1, 0b11011000 ; (A1, A0, B1, B0) movaps xmm2, xmm1 ; save xmm1 punpckhqdq xmm1, xmm0 ; xmm1 = (A3, A2, A1, A0) punpcklqdq xmm2, xmm0 ; xmm2 = (B3, B2, B1, B0) paddd xmm1, xmm2 ; xmm1 = (A3+B3, A2+B2, A1+B1, A0+B0) psrld xmm1, 8 ; xmm1 /= 256 paddd xmm1, xmm7 ; xmm1 += 16 ; xmm1 = ( y3, y2, y1, y0) %endmacro ; void bgr_to_jpeg420_sse( ; unsigned char *yuv_buffer, // rdi, must be 16B aligned ; const unsigned char *bgr_buffer, // rsi, must be 16B aligned ; unsigned int width, // rdx, must be multiple of 4 ; unsigned int height // rcx, must be multiple of 2 ; ) bgr_to_jpeg420_sse: push rax push rbx push rcx push rdx push rsi push rdi push rbp mov rbx, rdx ; rbx = width, mul will overwrite rdx later mov rax, rdx mul rcx ; rax = width * height mov rdx, rbx ; move back width to rdx mov rbp, rdi add rbp, rax ; rbp = pointer to U part mov rbx, rax shr rbx, 2 ; rbp+rbx = pointer to V part shl rax, 2 ; rax = rgb buffer size (width * height * 4) mov r9, rdx ; r9 = width shl r9, 2 ; r9 = width*4 = rgb_buffer stride add rsi, rax ; make rsi point to the last but one row sub rsi, r9 ; of rgb_buffer sub rsi, r9 shr rcx, 1 ; we process 2 rows in one loop,so make height half movaps xmm4, [rel vec_alpha_1_and] movaps xmm5, [rel vec_alpha_1_or] movaps xmm6, [rel vec_y_const] movaps xmm7, [rel vec_dd_16] movaps xmm3, [rel vec_dd_128] movaps xmm10, [rel vec_u_const] movaps xmm11, [rel vec_v_const] .__outer_loop: mov r8, rdx ; r8 = width shr r8, 2 ; r8 = width / 4 (4 pixels in one loop) .__inner_loop: movaps xmm8, [rsi] ; load the 4 pixels in first row, xmm = (argb3,argb2,argb1,argb0) movaps xmm9, [rsi+r9] ; load the 4 pixels in second row, xmm = (argb3,argb2,argb1,argb0) movaps xmm0, xmm9 ; get Y value for each pixel in the vector bgr_to_jpeg420_sse_get_Y ; xmm1 = ( y3, y2, y1, y0) pextrw eax, xmm1, 0 ; store it in yuv_buffer mov BYTE [rdi], al pextrw eax, xmm1, 2 mov BYTE [rdi+1], al pextrw eax, xmm1, 4 mov BYTE [rdi+2], al pextrw eax, xmm1, 6 mov BYTE [rdi+3], al movaps xmm0, xmm8 ; get Y value for each pixel in the vector bgr_to_jpeg420_sse_get_Y ; xmm1 = ( y3, y2, y1, y0) pextrw eax, xmm1, 0 ; store it in yuv_buffer mov BYTE [rdi+rdx], al pextrw eax, xmm1, 2 mov BYTE [rdi+rdx+1], al pextrw eax, xmm1, 4 mov BYTE [rdi+rdx+2], al pextrw eax, xmm1, 6 mov BYTE [rdi+rdx+3], al ; Calc U and V here movaps xmm0, xmm8 pavgb xmm0, xmm9 pshufd xmm1, xmm0, 0b10000000 pshufd xmm0, xmm0, 0b11010000 pavgb xmm0, xmm1 pand xmm0, xmm4 ; set alpha of each pixel to 1 por xmm0, xmm5 pxor xmm2, xmm2 punpckhbw xmm0, xmm2 ; xmm0 now contains words (a1,r1,g1,b1,a0,r0,g0,b0) movaps xmm8, xmm0 ; save it to xmm8 as U calculation will overwrite xmm0 ; calc U pmaddwd xmm0, xmm10 ; xmm = (128a1-38r1 = A1,-74g1+112b1 = B1,128a0-38r0 = A0,-74g0+112b0 = B0) pshufd xmm1, xmm0, 0b10110001 paddd xmm0, xmm1 ; xmm = (A1+B1, A1+B1, A0+B0, A0+B0) psrld xmm0, 8 ; xmm /= 256 paddd xmm0, xmm3 ; xmm += 128 pextrw eax, xmm0, 0 mov BYTE [rbp], al pextrw eax, xmm0, 4 mov BYTE [rbp+1], al ; calc V movaps xmm0, xmm8 pmaddwd xmm0, xmm11 ; xmm = (128a1-38r1 = A1,-74g1+112b1 = B1,128a0-38r0 = A0,-74g0+112b0 = B0) pshufd xmm1, xmm0, 0b10110001 paddd xmm0, xmm1 ; xmm = (A1+B1, A1+B1, A0+B0, A0+B0) psrld xmm0, 8 ; xmm /= 256 paddd xmm0, xmm3 ; xmm += 128 pextrw eax, xmm0, 0 mov BYTE [rbp+rbx], al pextrw eax, xmm0, 4 mov BYTE [rbp+rbx+1], al add rbp, 2 add rsi, 16 ; rgb_buffer += 16 add rdi, 4 ; Y += 4 dec r8 ; remaining iterations for inner loop jnz .__inner_loop add rdi, rdx ; skip the row sub rsi, r9 ; make rsi point to the 2 previous rows sub rsi, r9 sub rsi, r9 dec rcx ; decrement number of remaining rows jnz .__outer_loop ; if not zero, continue .__end: pop rbp pop rdi pop rsi pop rdx pop rcx pop rbx pop rax ret
S tímto jsem se dostal na 3.509s. Kód je docela ošklivý a patlal jsem to s instrukcema, co jsem našel v různých seznamech instrukcí po netu se válících. No a proč to sem házím ... kdyby se našel nějaký expert co by to rád zoptimalizoval, nebo poradil, kde a co tam zlepšit, .. byl bych moc vděčný.
Používám tam x86-64 registry, s xmm registry jsem se prostě nevešel a když ty konstanty nebudu mít přednačtené a budu je pak z paměti načítat v cyklu, tak to zdržuje a algoritmus to zpomalí. A když už jsem se nevešel s xmm registry, tak už jsem rovnou použil i x86-64 GPR.
Tiskni
Sdílej:
Kromě šumu je analogová technika ve všech vlastnostech o mnoho řádů napřed. To co se dá v analogové technice vyřešit součástkami za 10 Kč, na vyřešení stejné operace v digitální technice je potřeba stroj za sto tisíc dolarů a ještě bude pokulhávat za analogem.Pokud si vyberete jednu z těch několika málo operací, které se dají analogově snadno realizovat, tak ano
Mým výsledkem má být yuv420, pro každé 4 pixely (2x2) je tam jeden U a jeden V. Takže když mám pro 4 pixely už 4 Y', tak abych mohl použít ten vzorec řekněme pro U, musel bych nejdříve zprůměrovat B hodnoty pro ty 4 pixely, zprůměrovat Y hodnoty pro ty pixely a použít vzorec.
Momentálně to dělám tak, pro každý pixel vypočítám Y, zprůměruju rgb hodnoty pro 2x2 sousední pixely a spočtu U. Pravděpodobně by mi použítí "jednodušší" vzorce v tomto případě moc nepomohlo. Leda bych U nepočítal z průměrů ale pouze z jednoho pixelu, ale pak bych asi nedostal hezký výsledek.
Jak jsem psal, nerozumím tomu do detailu. Ale jednu věc vím zcela určitě. Volba lepšího algoritmu a pohrání si s matematikou dává mnohem větší zrychlení, než ďábelsky kódovat nanosekundy pomocí asm. Možná se ukáže, že to není k ničemu.Jenže Jardík obvykle jde cestou předčasné optimalizace a ukazuje nám proč se nemá dělat. (viz. jeho starší blog kde ďábelsky optimalizoval v ASM, až se dostal na stejný čas jako naivní C program)
>> 2
" u onoho supsamplingu se mi také moc nelíbí, protože to se může bez ztráty kytičky udělat až na konci a získámé tím trochu vyšší přenost.
No a ještě bych nejspíš držel dva ukazatele do RGB pole from
, pošouplé o th->stride
a jenom je inkrementoval, protože i když z toho překladač udělá nepřímé adresování, stále to bude sahat do paměti. To samé i u výsledného Y a th->yw
.
ISSN 1214-1267, (c) 1999-2007 Stickfish s.r.o.