Amestecați corect culorile sau optimizați alfabetul

Amestecați corect culorile sau optimizați alfabele
Eu scriu un mesager multiprotocol (dar nu multi-platformă, din păcate, acum doar Windows), care până acum acceptă doar protocolul TOX. Dar nu este vorba despre mesager, ci despre interfața sa, și mai exact despre funcția sa principală - AlphaBlend. Da, am decis să scriu GUI pentru motociclete. Ei bine, și ce GUI modern, fără elemente semi-transparente și rotunde netede? Prin urmare, a devenit necesar să se amestece imaginile ținând cont de translucență, adică amestecarea alfa sau amestecarea alfa. Din fericire, în ferestrele GDI o astfel de funcție este disponibilă - AlphaBlend. Funcționează așa cum ar trebui, face ceea ce este necesar. Dar eu sunt încă un constructor de biciclete și mă întrebam dacă aș putea scrie aceeași funcție, dar mai repede. Rezultatul muncii mele sub tăiere.






Teoria amestecării alfa

Probabil că știți această teorie, așa că nu o voi picta în detaliu, voi observa numai punctele principale.

Deci, avem 2 pixeli - pixelul sursă și pixelul de destinație. Ei trebuie să fie amestecați și să obțină un nou pixel de destinație. Fiecare pixel este reprezentat de 4 octeți A, R, G, B, unde A este valoarea de transparență a pixelilor (0 - complet transparent, 255 - complet opac), RGB - componente de culoare. Formula de amestec clasică este după cum urmează:

Un punct important! Unitatea este în formula. În viață, avem valoarea 255 pentru unitate. Pentru a aplica formula, trebuie mai întâi să divizăm valoarea fiecărui octet cu 255. Deoarece este ușor de văzut, 255 și 256 sunt valori destul de apropiate, iar împărțirea cu 256 este doar o schimbare corectă de 8 biți. Prin urmare, există o astfel de simplificare: în loc de operațiune

Acest lucru funcționează bine (și cel mai important, mult mai rapid decât diviziunea onestă), dar, în cazul amestecării alfa, rezultatul nu este complet corect, și anume, pixelul rezultat devine puțin mai întunecat. În continuare, vă voi arăta cum să efectuați calculele cu exactitate și fără a pierde viteza.

Un alt punct important! Uită-te la formula. A doua parte are SRC_COLOR * SRC_ALPHA. Astfel de acceleratoare 3D de multiplicare funcționează în milioane și chiar miliarde, fără a bate o pleoapă. Dar încercăm să rezolvăm problema folosind CPU-ul, iar multiplicarea inutilă (mai exact 4 multiplicări suplimentare) pe pixel nu este foarte bună. De ce nu este necesar? Da, deoarece această multiplicare se poate face în avans prin transformarea imaginii originale. Astfel de imagini au chiar un titlu: premultiplied. Nu cunosc termenul în limba rusă, dar traduce cuvântul cu cuvânt "prefinat". Și sigur, funcția GDI a lui AlphaBlend necesită strict premultiplied ca imagine sursă. E rezonabil.

Ei bine, teoria sa terminat. În practică, vom lucra cu culoare pe 32 de biți. Un pixel este reprezentat de un număr de 32 de biți, în care 4 octeți, începând cu cel minor, înseamnă: B (lue), G (reen), R (ed), A (lpha). Să mergem.

Prima implementare

Prima mea implementare a fost următoarea:

Sunt de acord, nu arata foarte bine. 4 multiplicare reală (mai exact 5) și 4 rotunjire pe pixel - este prea mult. Nu este surprinzător că viteza acestui monstru a pierdut AlphaBlend'u de 7 ori.

Să încercăm să ne îmbunătățim. Vom scăpa de multiplicări materiale.







Aici funcționează BLUEx256, GREENx256, etc. returnați componenta corespunzătoare deplasată spre stânga cu 8 biți, adică înmulțit cu 256.

Această funcție este demnă de remarcat prin faptul că are o corecție de compensare de 255 de deplasări cu 8 biți la dreapta. Observat? Dacă nu, fiți răbdători, mai jos voi descrie acest punct în mai multe detalii.

În ceea ce privește viteza, această implementare este inferioară programului AlphaBlend de aproximativ 3 ori. Deja mai bine, dar încă departe de ideal.

Rezultat neașteptat

Cum pot îmbunătăți funcția anterioară? Se pare că am făcut tot ce am putut. Cu toate acestea, am reușit să îmbunătățesc această funcție într-un mod care a fost o surpriză pentru mine. Am încercat doar pentru a mă asigura că nu se întâmplă nimic. Cu toate acestea, sa dovedit.
Ce se întâmplă dacă fac operațiunea de a înmulți un byte cu un octet într-o masă. Nu se dovedește prea mult - doar 65536 octeți. Penny.

Am creat un astfel de semn:

Ei bine, atunci. Nu mai e nimic de optimizat. Nimic nu vine în minte. Dar AlphaBlend este încă mai rapid, la fiecare două. Cum au reușit acest lucru? Se pare că e timpul să te retragi?

La înlocuirea diviziei cu 255 schimburi

Există multe modalități de a împărți rapid cu 255. Am întâlnit acest lucru:

Nu e rău. Este mai rapid decât diviziunea cinstită de 255. Dar este încă prea greoaie. M-am gândit de mult timp cât de repede să împărțim cu 255 și să nu pierdem nici în calitate sau viteză. Cum să compensați degradarea culorii când folosiți o schimbare?

Să presupunem că avem o componentă de culoare egală cu 0xff (255) și avem o altă componentă, de asemenea egală cu 0xff (255). Înmulțind-le, obținem:

0xff * 0xff = 0xfe01. Miscând 8 biți în dreapta, obținem 0xfe - luminozitatea componentei este redusă. E rău.
Și dacă am crește una din componente cu 1 înainte de a se multiplica?
0xff * 0x100 = 0xff00. Hmm, se pare că este. Să verificăm cazul în care unul dintre componente este 0:
0xff * 1 = 0x00ff. treci la dreapta cu 8 biți, obținem 0. Voila! Pentru alte valori ale componentelor, rezultatul va fi, de asemenea, corect.
Acum este ușor de găsit spațiul de compensare în a doua funcție: uint not_a = 256 - ALPHA (src);
Nu 255 - A, dar 256 - A, adică +1 la element înainte de înmulțire. Pentru metoda de multiplicare tabelă nu este necesară compensarea; în tabel toate valorile și astfel sunt calculate după cum este necesar.

Artilerie grea - instrucțiuni SSSE3

Este timpul să vă gândiți la optimizarea folosind simd. Ei spun că compilatorul Intel știe cum să facă acest lucru fără participarea unei persoane. Poate. Dar mă îndoiesc că Intel se confruntă cu AlphaBlend. Ei bine, maximul - egal cu acesta. Dar trebuie să o fac mai repede. Deschideți directorul și continuați.

Prima întrebare care ar trebui să fie pusă este în ce instrucțiuni să se optimizeze? Am suspiciunea că AlphaBlend optimizat pentru MMX, altfel nu pot explica superioritatea fata de implementarea x86 pură. MMX este bun, dar este ultimul secol. Acum este greu să găsești un computer unde nu ar exista suport pentru SSE4. Iar pentru SSE, în general, pot fi optimizate, nu deranjează chiar și pentru a verifica disponibilitatea sprijinului pentru aceste situații - probabilitatea ca programul va rula pe ceva mai jos Pentium 3 este aproape de zero. Desigur, vorbesc despre aplicații desktop. Exotica este dincolo de scopul acestui articol.

Am ales SSSE3. Acest set de instrucțiuni este destul de comun, pentru a deveni confuz de optimizare pentru el, având în vedere instrucțiunile foarte convenabile în el.

Dar ghidul de cel mai util, care va sta la baza tuturor optimizărilor - l pshufb (intrinsik _mm_shuffle_epi8). De dragul ei a fost ales SSSE3. Care este puterea sa? Faptul că această afirmație vă permite să împrăștie bytes de original registru de 16 biți, în orice ordine sau chiar arunca aceste octeți sunt inutile. Ie Pot, cu ajutorul acestei instrucțiuni, să pregătesc tot ce este necesar pentru calculele necesare într-o mișcare. Un alt ghid important - pmulhuw (intrinsik _mm_mulhi_epu16) - este de 8 înmulțiri și 8 schimburi la dreapta pe 16 biți. Ca și în cazul operației de amestecare alfa. Ie Cu această comandă singură, de fapt, am calculat 2 pixeli deodată.

Ei bine, hai să mergem:

Fișa de cod ASM

După cum puteți vedea, implementarea simd combină 4 pixeli originali cu 4 pixeli de destinație simultan. Ei bine, atunci este simd. În cadrul acestui articol voi lăsa o descriere a soluției la problemă, atunci când doriți să amestecați nu un multiplu de 4 pixeli. Personal, folosesc apeluri de implementare "one-pixel" c ++ pentru acest lucru.

Codul din articol este doar pentru cunoașterea însăși a principiului optimizării ssse3. Nu sunt aici pentru a da valoarea constantelor folosite. Dacă doriți să utilizați programul optimizat AlphaBlend în proiectul dvs., va trebui să obțineți codul de lucru direct din codul sursă al Isotoxin (aceasta este ceea ce se numește dezvoltarea mea).

Depozitul de izotoxină pe gitub.
Direct fișierul în care este localizată funcția dorită aici.

Îmi cer scuze că nu am pregătit exemple de lucru și că nu am pus totul într-o bibliotecă separată. Dacă într-adevăr nevoie de această funcție, și aveți probleme la tine pentru a ieși din sursele mele, scrie-mi un mesaj privat și am să vă spun în detaliu cum să o facă.







Articole similare

Trimiteți-le prietenilor: