| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311103121031310314103151031610317103181031910320103211032210323103241032510326103271032810329103301033110332103331033410335103361033710338103391034010341103421034310344103451034610347103481034910350103511035210353103541035510356103571035810359103601036110362103631036410365103661036710368103691037010371103721037310374103751037610377103781037910380103811038210383103841038510386103871038810389103901039110392103931039410395103961039710398103991040010401104021040310404104051040610407104081040910410104111041210413104141041510416104171041810419104201042110422104231042410425104261042710428104291043010431104321043310434104351043610437104381043910440104411044210443104441044510446104471044810449104501045110452104531045410455104561045710458104591046010461104621046310464104651046610467104681046910470104711047210473104741047510476104771047810479104801048110482104831048410485104861048710488104891049010491104921049310494104951049610497104981049910500105011050210503105041050510506105071050810509105101051110512105131051410515105161051710518105191052010521105221052310524105251052610527105281052910530105311053210533105341053510536105371053810539105401054110542105431054410545105461054710548105491055010551105521055310554105551055610557105581055910560105611056210563105641056510566105671056810569105701057110572105731057410575105761057710578105791058010581105821058310584105851058610587105881058910590105911059210593105941059510596105971059810599106001060110602106031060410605106061060710608106091061010611106121061310614106151061610617106181061910620106211062210623106241062510626106271062810629106301063110632106331063410635106361063710638106391064010641106421064310644106451064610647106481064910650106511065210653106541065510656106571065810659106601066110662106631066410665106661066710668106691067010671106721067310674106751067610677106781067910680106811068210683106841068510686106871068810689106901069110692106931069410695106961069710698106991070010701107021070310704107051070610707107081070910710107111071210713107141071510716107171071810719107201072110722107231072410725107261072710728107291073010731107321073310734107351073610737107381073910740107411074210743107441074510746107471074810749107501075110752107531075410755107561075710758107591076010761107621076310764107651076610767107681076910770107711077210773107741077510776107771077810779107801078110782107831078410785107861078710788107891079010791107921079310794107951079610797107981079910800108011080210803108041080510806108071080810809108101081110812108131081410815108161081710818108191082010821108221082310824108251082610827108281082910830108311083210833108341083510836108371083810839108401084110842108431084410845108461084710848108491085010851108521085310854108551085610857108581085910860108611086210863108641086510866108671086810869108701087110872108731087410875108761087710878108791088010881108821088310884108851088610887108881088910890108911089210893108941089510896108971089810899109001090110902109031090410905109061090710908109091091010911109121091310914109151091610917109181091910920109211092210923109241092510926109271092810929109301093110932109331093410935109361093710938109391094010941109421094310944109451094610947109481094910950109511095210953109541095510956109571095810959109601096110962109631096410965109661096710968109691097010971109721097310974109751097610977109781097910980109811098210983109841098510986109871098810989109901099110992109931099410995109961099710998109991100011001110021100311004110051100611007110081100911010110111101211013110141101511016110171101811019110201102111022110231102411025110261102711028110291103011031110321103311034110351103611037110381103911040110411104211043110441104511046110471104811049110501105111052110531105411055110561105711058110591106011061110621106311064110651106611067110681106911070110711107211073110741107511076110771107811079110801108111082110831108411085110861108711088110891109011091110921109311094110951109611097110981109911100111011110211103111041110511106111071110811109111101111111112111131111411115111161111711118111191112011121111221112311124111251112611127111281112911130111311113211133111341113511136111371113811139111401114111142111431114411145111461114711148111491115011151111521115311154111551115611157111581115911160111611116211163111641116511166111671116811169111701117111172111731117411175111761117711178111791118011181111821118311184111851118611187111881118911190111911119211193111941119511196111971119811199112001120111202112031120411205112061120711208112091121011211112121121311214112151121611217112181121911220112211122211223112241122511226112271122811229112301123111232112331123411235112361123711238112391124011241112421124311244112451124611247112481124911250112511125211253112541125511256112571125811259112601126111262112631126411265112661126711268112691127011271112721127311274112751127611277112781127911280112811128211283112841128511286112871128811289112901129111292112931129411295112961129711298112991130011301113021130311304113051130611307113081130911310113111131211313113141131511316113171131811319113201132111322113231132411325113261132711328113291133011331113321133311334113351133611337113381133911340113411134211343113441134511346113471134811349113501135111352113531135411355113561135711358113591136011361113621136311364113651136611367113681136911370113711137211373113741137511376113771137811379113801138111382113831138411385113861138711388113891139011391113921139311394113951139611397113981139911400114011140211403114041140511406114071140811409114101141111412114131141411415114161141711418114191142011421114221142311424114251142611427114281142911430114311143211433114341143511436114371143811439114401144111442114431144411445114461144711448114491145011451114521145311454114551145611457114581145911460114611146211463114641146511466114671146811469114701147111472114731147411475114761147711478114791148011481114821148311484114851148611487114881148911490114911149211493114941149511496114971149811499115001150111502115031150411505115061150711508115091151011511115121151311514115151151611517115181151911520115211152211523115241152511526115271152811529115301153111532115331153411535115361153711538115391154011541115421154311544115451154611547115481154911550115511155211553115541155511556115571155811559115601156111562115631156411565115661156711568115691157011571115721157311574115751157611577115781157911580115811158211583115841158511586115871158811589115901159111592115931159411595115961159711598115991160011601116021160311604116051160611607116081160911610116111161211613116141161511616116171161811619116201162111622116231162411625116261162711628116291163011631116321163311634116351163611637116381163911640116411164211643116441164511646116471164811649116501165111652116531165411655116561165711658116591166011661116621166311664116651166611667116681166911670116711167211673116741167511676116771167811679116801168111682116831168411685116861168711688116891169011691116921169311694116951169611697116981169911700117011170211703117041170511706117071170811709117101171111712117131171411715117161171711718117191172011721117221172311724117251172611727117281172911730117311173211733117341173511736117371173811739117401174111742117431174411745117461174711748117491175011751117521175311754117551175611757117581175911760117611176211763117641176511766117671176811769117701177111772117731177411775117761177711778117791178011781117821178311784117851178611787117881178911790117911179211793117941179511796117971179811799118001180111802118031180411805118061180711808118091181011811118121181311814118151181611817118181181911820118211182211823118241182511826118271182811829118301183111832118331183411835118361183711838118391184011841118421184311844118451184611847118481184911850118511185211853118541185511856118571185811859118601186111862118631186411865118661186711868118691187011871118721187311874118751187611877118781187911880118811188211883118841188511886118871188811889118901189111892118931189411895118961189711898118991190011901119021190311904119051190611907119081190911910119111191211913119141191511916119171191811919119201192111922119231192411925119261192711928119291193011931119321193311934119351193611937119381193911940119411194211943119441194511946119471194811949119501195111952119531195411955119561195711958119591196011961119621196311964119651196611967119681196911970119711197211973119741197511976119771197811979119801198111982119831198411985119861198711988119891199011991119921199311994119951199611997119981199912000120011200212003120041200512006120071200812009120101201112012120131201412015120161201712018120191202012021120221202312024120251202612027120281202912030120311203212033120341203512036120371203812039120401204112042120431204412045120461204712048120491205012051120521205312054120551205612057120581205912060120611206212063120641206512066120671206812069120701207112072120731207412075120761207712078120791208012081120821208312084120851208612087120881208912090120911209212093120941209512096120971209812099121001210112102121031210412105121061210712108121091211012111121121211312114121151211612117121181211912120121211212212123121241212512126121271212812129121301213112132121331213412135121361213712138121391214012141121421214312144121451214612147121481214912150121511215212153121541215512156121571215812159121601216112162121631216412165121661216712168121691217012171121721217312174121751217612177121781217912180121811218212183121841218512186121871218812189121901219112192121931219412195121961219712198121991220012201122021220312204122051220612207122081220912210122111221212213122141221512216122171221812219122201222112222122231222412225122261222712228122291223012231122321223312234122351223612237122381223912240122411224212243122441224512246122471224812249122501225112252122531225412255122561225712258122591226012261122621226312264122651226612267122681226912270122711227212273122741227512276122771227812279122801228112282122831228412285122861228712288122891229012291122921229312294122951229612297122981229912300123011230212303123041230512306123071230812309123101231112312123131231412315123161231712318123191232012321123221232312324123251232612327123281232912330123311233212333123341233512336123371233812339123401234112342123431234412345123461234712348123491235012351123521235312354123551235612357123581235912360123611236212363123641236512366123671236812369123701237112372123731237412375123761237712378123791238012381123821238312384123851238612387123881238912390123911239212393123941239512396123971239812399124001240112402124031240412405124061240712408124091241012411124121241312414124151241612417124181241912420124211242212423124241242512426124271242812429124301243112432124331243412435124361243712438124391244012441124421244312444124451244612447124481244912450124511245212453124541245512456124571245812459124601246112462124631246412465124661246712468124691247012471124721247312474124751247612477124781247912480124811248212483124841248512486124871248812489124901249112492124931249412495124961249712498124991250012501125021250312504125051250612507125081250912510125111251212513125141251512516125171251812519125201252112522125231252412525125261252712528125291253012531125321253312534125351253612537125381253912540125411254212543125441254512546125471254812549125501255112552125531255412555125561255712558125591256012561125621256312564125651256612567125681256912570125711257212573125741257512576125771257812579125801258112582125831258412585125861258712588125891259012591125921259312594125951259612597125981259912600126011260212603126041260512606126071260812609126101261112612126131261412615126161261712618126191262012621126221262312624126251262612627126281262912630126311263212633126341263512636126371263812639126401264112642126431264412645126461264712648126491265012651126521265312654126551265612657126581265912660126611266212663126641266512666126671266812669126701267112672126731267412675126761267712678126791268012681126821268312684126851268612687126881268912690126911269212693126941269512696126971269812699127001270112702127031270412705127061270712708127091271012711127121271312714127151271612717127181271912720127211272212723127241272512726127271272812729127301273112732127331273412735127361273712738127391274012741127421274312744127451274612747127481274912750127511275212753127541275512756127571275812759127601276112762127631276412765127661276712768127691277012771127721277312774127751277612777127781277912780127811278212783127841278512786127871278812789127901279112792127931279412795127961279712798127991280012801128021280312804128051280612807128081280912810128111281212813128141281512816128171281812819128201282112822128231282412825128261282712828128291283012831128321283312834128351283612837128381283912840128411284212843128441284512846128471284812849128501285112852128531285412855128561285712858128591286012861128621286312864128651286612867128681286912870128711287212873128741287512876128771287812879128801288112882128831288412885128861288712888128891289012891128921289312894128951289612897128981289912900129011290212903129041290512906129071290812909129101291112912129131291412915129161291712918129191292012921129221292312924129251292612927129281292912930129311293212933129341293512936129371293812939129401294112942129431294412945129461294712948129491295012951129521295312954129551295612957129581295912960129611296212963129641296512966129671296812969129701297112972129731297412975129761297712978129791298012981129821298312984129851298612987129881298912990129911299212993129941299512996129971299812999130001300113002130031300413005130061300713008130091301013011130121301313014130151301613017130181301913020130211302213023130241302513026130271302813029130301303113032130331303413035130361303713038130391304013041130421304313044130451304613047130481304913050130511305213053130541305513056130571305813059130601306113062130631306413065130661306713068130691307013071130721307313074130751307613077130781307913080130811308213083130841308513086130871308813089130901309113092130931309413095130961309713098130991310013101131021310313104131051310613107131081310913110131111311213113131141311513116131171311813119131201312113122131231312413125131261312713128131291313013131131321313313134131351313613137131381313913140131411314213143131441314513146131471314813149131501315113152131531315413155131561315713158131591316013161131621316313164131651316613167131681316913170131711317213173131741317513176131771317813179131801318113182131831318413185131861318713188131891319013191131921319313194131951319613197131981319913200132011320213203132041320513206132071320813209132101321113212132131321413215132161321713218132191322013221132221322313224132251322613227132281322913230132311323213233132341323513236132371323813239132401324113242132431324413245132461324713248132491325013251132521325313254132551325613257132581325913260132611326213263132641326513266132671326813269132701327113272132731327413275132761327713278132791328013281132821328313284132851328613287132881328913290132911329213293132941329513296132971329813299133001330113302133031330413305133061330713308133091331013311133121331313314133151331613317133181331913320133211332213323133241332513326133271332813329133301333113332133331333413335133361333713338133391334013341133421334313344133451334613347133481334913350133511335213353133541335513356133571335813359133601336113362133631336413365133661336713368133691337013371133721337313374133751337613377133781337913380133811338213383133841338513386133871338813389133901339113392133931339413395133961339713398133991340013401134021340313404134051340613407134081340913410134111341213413134141341513416134171341813419134201342113422134231342413425134261342713428134291343013431134321343313434134351343613437134381343913440134411344213443134441344513446134471344813449134501345113452134531345413455134561345713458134591346013461134621346313464134651346613467134681346913470134711347213473134741347513476134771347813479134801348113482134831348413485134861348713488134891349013491134921349313494134951349613497134981349913500135011350213503135041350513506135071350813509135101351113512135131351413515135161351713518135191352013521135221352313524135251352613527135281352913530135311353213533135341353513536135371353813539135401354113542135431354413545135461354713548135491355013551135521355313554135551355613557135581355913560135611356213563135641356513566135671356813569135701357113572135731357413575135761357713578135791358013581135821358313584135851358613587135881358913590135911359213593135941359513596135971359813599136001360113602136031360413605136061360713608136091361013611136121361313614136151361613617136181361913620136211362213623136241362513626136271362813629136301363113632136331363413635136361363713638136391364013641136421364313644136451364613647136481364913650136511365213653136541365513656136571365813659136601366113662136631366413665136661366713668136691367013671136721367313674136751367613677136781367913680136811368213683136841368513686136871368813689136901369113692136931369413695136961369713698136991370013701137021370313704137051370613707137081370913710137111371213713137141371513716137171371813719137201372113722137231372413725137261372713728137291373013731137321373313734137351373613737137381373913740137411374213743137441374513746137471374813749137501375113752137531375413755137561375713758137591376013761137621376313764137651376613767137681376913770137711377213773137741377513776137771377813779137801378113782137831378413785137861378713788137891379013791137921379313794137951379613797137981379913800138011380213803138041380513806138071380813809138101381113812138131381413815138161381713818138191382013821138221382313824138251382613827138281382913830138311383213833138341383513836138371383813839138401384113842138431384413845138461384713848138491385013851138521385313854138551385613857138581385913860138611386213863138641386513866138671386813869138701387113872138731387413875138761387713878138791388013881138821388313884138851388613887138881388913890138911389213893138941389513896138971389813899139001390113902139031390413905139061390713908139091391013911139121391313914139151391613917139181391913920139211392213923139241392513926139271392813929139301393113932139331393413935139361393713938139391394013941139421394313944139451394613947139481394913950139511395213953139541395513956139571395813959139601396113962139631396413965139661396713968139691397013971139721397313974139751397613977139781397913980139811398213983139841398513986139871398813989139901399113992139931399413995139961399713998139991400014001140021400314004140051400614007140081400914010140111401214013140141401514016140171401814019140201402114022140231402414025140261402714028140291403014031140321403314034140351403614037140381403914040140411404214043140441404514046140471404814049140501405114052140531405414055140561405714058140591406014061140621406314064140651406614067140681406914070140711407214073140741407514076140771407814079140801408114082140831408414085140861408714088140891409014091140921409314094140951409614097140981409914100141011410214103141041410514106141071410814109141101411114112141131411414115141161411714118141191412014121141221412314124141251412614127141281412914130141311413214133141341413514136141371413814139141401414114142141431414414145141461414714148141491415014151141521415314154141551415614157141581415914160141611416214163141641416514166141671416814169141701417114172141731417414175141761417714178141791418014181141821418314184141851418614187141881418914190141911419214193141941419514196141971419814199142001420114202142031420414205142061420714208142091421014211142121421314214142151421614217142181421914220142211422214223142241422514226142271422814229142301423114232142331423414235142361423714238142391424014241142421424314244142451424614247142481424914250142511425214253142541425514256142571425814259142601426114262142631426414265142661426714268142691427014271142721427314274142751427614277142781427914280142811428214283142841428514286142871428814289142901429114292142931429414295142961429714298142991430014301143021430314304143051430614307143081430914310143111431214313143141431514316143171431814319143201432114322143231432414325143261432714328143291433014331143321433314334143351433614337143381433914340143411434214343143441434514346143471434814349143501435114352143531435414355143561435714358143591436014361143621436314364143651436614367143681436914370143711437214373143741437514376143771437814379143801438114382143831438414385143861438714388143891439014391143921439314394143951439614397143981439914400144011440214403144041440514406144071440814409144101441114412144131441414415144161441714418144191442014421144221442314424144251442614427144281442914430144311443214433144341443514436144371443814439144401444114442144431444414445144461444714448144491445014451144521445314454144551445614457144581445914460144611446214463144641446514466144671446814469144701447114472144731447414475144761447714478144791448014481144821448314484144851448614487144881448914490144911449214493144941449514496144971449814499145001450114502145031450414505145061450714508145091451014511145121451314514145151451614517145181451914520145211452214523145241452514526145271452814529145301453114532145331453414535145361453714538145391454014541145421454314544145451454614547145481454914550145511455214553145541455514556145571455814559145601456114562145631456414565145661456714568145691457014571145721457314574145751457614577145781457914580145811458214583145841458514586145871458814589145901459114592145931459414595145961459714598145991460014601146021460314604146051460614607146081460914610146111461214613146141461514616146171461814619146201462114622146231462414625146261462714628146291463014631146321463314634146351463614637146381463914640146411464214643146441464514646146471464814649146501465114652146531465414655146561465714658146591466014661146621466314664146651466614667146681466914670146711467214673146741467514676146771467814679146801468114682146831468414685146861468714688146891469014691146921469314694146951469614697146981469914700147011470214703147041470514706147071470814709147101471114712147131471414715147161471714718147191472014721147221472314724147251472614727147281472914730147311473214733147341473514736147371473814739147401474114742147431474414745147461474714748147491475014751147521475314754147551475614757147581475914760147611476214763147641476514766147671476814769147701477114772147731477414775147761477714778147791478014781147821478314784147851478614787147881478914790147911479214793147941479514796147971479814799148001480114802148031480414805148061480714808148091481014811148121481314814148151481614817148181481914820148211482214823148241482514826148271482814829148301483114832148331483414835148361483714838148391484014841148421484314844148451484614847148481484914850148511485214853148541485514856148571485814859148601486114862148631486414865148661486714868148691487014871148721487314874148751487614877148781487914880148811488214883148841488514886148871488814889148901489114892148931489414895148961489714898148991490014901149021490314904149051490614907149081490914910149111491214913149141491514916149171491814919149201492114922149231492414925149261492714928149291493014931149321493314934149351493614937149381493914940149411494214943149441494514946149471494814949149501495114952149531495414955149561495714958149591496014961149621496314964149651496614967149681496914970149711497214973149741497514976149771497814979149801498114982149831498414985149861498714988149891499014991149921499314994149951499614997149981499915000150011500215003150041500515006150071500815009150101501115012150131501415015150161501715018150191502015021150221502315024150251502615027150281502915030150311503215033150341503515036150371503815039150401504115042150431504415045150461504715048150491505015051150521505315054150551505615057150581505915060150611506215063150641506515066150671506815069150701507115072150731507415075150761507715078150791508015081150821508315084150851508615087150881508915090150911509215093150941509515096150971509815099151001510115102151031510415105151061510715108151091511015111151121511315114151151511615117151181511915120151211512215123151241512515126151271512815129151301513115132151331513415135151361513715138151391514015141151421514315144151451514615147151481514915150151511515215153151541515515156151571515815159151601516115162151631516415165151661516715168151691517015171151721517315174151751517615177151781517915180151811518215183151841518515186151871518815189151901519115192151931519415195151961519715198151991520015201152021520315204152051520615207152081520915210152111521215213152141521515216152171521815219152201522115222152231522415225152261522715228152291523015231152321523315234152351523615237152381523915240152411524215243152441524515246152471524815249152501525115252152531525415255152561525715258152591526015261152621526315264152651526615267152681526915270152711527215273152741527515276152771527815279152801528115282152831528415285152861528715288152891529015291152921529315294152951529615297152981529915300153011530215303153041530515306153071530815309153101531115312153131531415315153161531715318153191532015321153221532315324153251532615327153281532915330153311533215333153341533515336153371533815339153401534115342153431534415345153461534715348153491535015351153521535315354153551535615357153581535915360153611536215363153641536515366153671536815369153701537115372153731537415375153761537715378153791538015381153821538315384153851538615387153881538915390153911539215393153941539515396153971539815399154001540115402154031540415405154061540715408154091541015411154121541315414154151541615417154181541915420154211542215423154241542515426154271542815429154301543115432154331543415435154361543715438154391544015441154421544315444154451544615447154481544915450154511545215453154541545515456154571545815459154601546115462154631546415465154661546715468154691547015471154721547315474154751547615477154781547915480154811548215483154841548515486154871548815489154901549115492154931549415495154961549715498154991550015501155021550315504155051550615507155081550915510155111551215513155141551515516155171551815519155201552115522155231552415525155261552715528155291553015531155321553315534155351553615537155381553915540155411554215543155441554515546155471554815549155501555115552155531555415555155561555715558155591556015561155621556315564155651556615567155681556915570155711557215573155741557515576155771557815579155801558115582155831558415585155861558715588155891559015591155921559315594155951559615597155981559915600156011560215603156041560515606156071560815609156101561115612156131561415615156161561715618156191562015621156221562315624156251562615627156281562915630156311563215633156341563515636156371563815639156401564115642156431564415645156461564715648156491565015651156521565315654156551565615657156581565915660156611566215663156641566515666156671566815669156701567115672156731567415675156761567715678156791568015681156821568315684156851568615687156881568915690156911569215693156941569515696156971569815699157001570115702157031570415705157061570715708157091571015711157121571315714157151571615717157181571915720157211572215723157241572515726157271572815729157301573115732157331573415735157361573715738157391574015741157421574315744157451574615747157481574915750157511575215753157541575515756157571575815759157601576115762157631576415765157661576715768157691577015771157721577315774157751577615777157781577915780157811578215783157841578515786157871578815789157901579115792157931579415795157961579715798157991580015801158021580315804158051580615807158081580915810158111581215813158141581515816158171581815819158201582115822158231582415825158261582715828158291583015831158321583315834158351583615837158381583915840158411584215843158441584515846158471584815849158501585115852158531585415855158561585715858158591586015861158621586315864158651586615867158681586915870158711587215873158741587515876158771587815879158801588115882158831588415885158861588715888158891589015891158921589315894158951589615897158981589915900159011590215903159041590515906159071590815909159101591115912159131591415915159161591715918159191592015921159221592315924159251592615927159281592915930159311593215933159341593515936159371593815939159401594115942159431594415945159461594715948159491595015951159521595315954159551595615957159581595915960159611596215963159641596515966159671596815969159701597115972159731597415975159761597715978159791598015981159821598315984159851598615987159881598915990159911599215993159941599515996159971599815999160001600116002160031600416005160061600716008160091601016011160121601316014160151601616017160181601916020160211602216023160241602516026160271602816029160301603116032160331603416035160361603716038160391604016041160421604316044160451604616047160481604916050160511605216053160541605516056160571605816059160601606116062160631606416065160661606716068160691607016071160721607316074160751607616077160781607916080160811608216083160841608516086160871608816089160901609116092160931609416095160961609716098160991610016101161021610316104161051610616107161081610916110161111611216113161141611516116161171611816119161201612116122161231612416125161261612716128161291613016131161321613316134161351613616137161381613916140161411614216143161441614516146161471614816149161501615116152161531615416155161561615716158161591616016161161621616316164161651616616167161681616916170161711617216173161741617516176161771617816179161801618116182161831618416185161861618716188161891619016191161921619316194161951619616197161981619916200162011620216203162041620516206162071620816209162101621116212162131621416215162161621716218162191622016221162221622316224162251622616227162281622916230162311623216233162341623516236162371623816239162401624116242162431624416245162461624716248162491625016251162521625316254162551625616257162581625916260162611626216263162641626516266162671626816269162701627116272162731627416275162761627716278162791628016281162821628316284162851628616287162881628916290162911629216293162941629516296162971629816299163001630116302163031630416305163061630716308163091631016311163121631316314163151631616317163181631916320163211632216323163241632516326163271632816329163301633116332163331633416335163361633716338163391634016341163421634316344163451634616347163481634916350163511635216353163541635516356163571635816359163601636116362163631636416365163661636716368163691637016371163721637316374163751637616377163781637916380163811638216383163841638516386163871638816389163901639116392163931639416395163961639716398163991640016401164021640316404164051640616407164081640916410164111641216413164141641516416164171641816419164201642116422164231642416425164261642716428164291643016431164321643316434164351643616437164381643916440164411644216443164441644516446164471644816449164501645116452164531645416455164561645716458164591646016461164621646316464164651646616467164681646916470164711647216473164741647516476164771647816479164801648116482164831648416485164861648716488164891649016491164921649316494164951649616497164981649916500165011650216503165041650516506165071650816509165101651116512165131651416515165161651716518165191652016521165221652316524165251652616527165281652916530165311653216533165341653516536165371653816539165401654116542165431654416545165461654716548165491655016551165521655316554165551655616557165581655916560165611656216563165641656516566165671656816569165701657116572165731657416575165761657716578165791658016581165821658316584165851658616587165881658916590165911659216593165941659516596165971659816599166001660116602166031660416605166061660716608166091661016611166121661316614166151661616617166181661916620166211662216623166241662516626166271662816629166301663116632166331663416635166361663716638166391664016641166421664316644166451664616647166481664916650166511665216653166541665516656166571665816659166601666116662166631666416665166661666716668166691667016671166721667316674166751667616677166781667916680166811668216683166841668516686166871668816689166901669116692166931669416695166961669716698166991670016701167021670316704167051670616707167081670916710167111671216713167141671516716167171671816719167201672116722167231672416725167261672716728167291673016731167321673316734167351673616737167381673916740167411674216743167441674516746167471674816749167501675116752167531675416755167561675716758167591676016761167621676316764167651676616767167681676916770167711677216773167741677516776167771677816779167801678116782167831678416785167861678716788167891679016791167921679316794167951679616797167981679916800168011680216803168041680516806168071680816809168101681116812168131681416815168161681716818168191682016821168221682316824168251682616827168281682916830168311683216833168341683516836168371683816839168401684116842168431684416845168461684716848168491685016851168521685316854168551685616857168581685916860168611686216863168641686516866168671686816869168701687116872168731687416875168761687716878168791688016881168821688316884168851688616887168881688916890168911689216893168941689516896168971689816899169001690116902169031690416905169061690716908169091691016911169121691316914169151691616917169181691916920169211692216923169241692516926169271692816929169301693116932169331693416935169361693716938169391694016941169421694316944169451694616947169481694916950169511695216953169541695516956169571695816959169601696116962169631696416965169661696716968169691697016971169721697316974169751697616977169781697916980169811698216983169841698516986169871698816989169901699116992169931699416995169961699716998169991700017001170021700317004170051700617007170081700917010170111701217013170141701517016170171701817019170201702117022170231702417025170261702717028170291703017031170321703317034170351703617037170381703917040170411704217043170441704517046170471704817049170501705117052170531705417055170561705717058170591706017061170621706317064170651706617067170681706917070170711707217073170741707517076170771707817079170801708117082170831708417085170861708717088170891709017091170921709317094170951709617097170981709917100171011710217103171041710517106171071710817109171101711117112171131711417115171161711717118171191712017121171221712317124171251712617127171281712917130171311713217133171341713517136171371713817139171401714117142171431714417145171461714717148171491715017151171521715317154171551715617157171581715917160171611716217163171641716517166171671716817169171701717117172171731717417175171761717717178171791718017181171821718317184171851718617187171881718917190171911719217193171941719517196171971719817199172001720117202172031720417205172061720717208172091721017211172121721317214172151721617217172181721917220172211722217223172241722517226172271722817229172301723117232172331723417235172361723717238172391724017241172421724317244172451724617247172481724917250172511725217253172541725517256172571725817259172601726117262172631726417265172661726717268172691727017271172721727317274172751727617277172781727917280172811728217283172841728517286172871728817289172901729117292172931729417295172961729717298172991730017301173021730317304173051730617307173081730917310173111731217313173141731517316173171731817319173201732117322173231732417325173261732717328173291733017331173321733317334173351733617337173381733917340173411734217343173441734517346173471734817349173501735117352173531735417355173561735717358173591736017361173621736317364173651736617367173681736917370173711737217373173741737517376173771737817379173801738117382173831738417385173861738717388173891739017391173921739317394173951739617397173981739917400174011740217403174041740517406174071740817409174101741117412174131741417415174161741717418174191742017421174221742317424174251742617427174281742917430174311743217433174341743517436174371743817439174401744117442174431744417445174461744717448174491745017451174521745317454174551745617457174581745917460174611746217463174641746517466174671746817469174701747117472174731747417475174761747717478174791748017481174821748317484174851748617487174881748917490174911749217493174941749517496174971749817499175001750117502175031750417505175061750717508175091751017511175121751317514175151751617517175181751917520175211752217523175241752517526175271752817529175301753117532175331753417535175361753717538175391754017541175421754317544175451754617547175481754917550175511755217553175541755517556175571755817559175601756117562175631756417565175661756717568175691757017571175721757317574175751757617577175781757917580175811758217583175841758517586175871758817589175901759117592175931759417595175961759717598175991760017601176021760317604176051760617607176081760917610176111761217613176141761517616176171761817619176201762117622176231762417625176261762717628176291763017631176321763317634176351763617637176381763917640176411764217643176441764517646176471764817649176501765117652176531765417655176561765717658176591766017661176621766317664176651766617667176681766917670176711767217673176741767517676176771767817679176801768117682176831768417685176861768717688176891769017691176921769317694176951769617697176981769917700177011770217703177041770517706177071770817709177101771117712177131771417715177161771717718177191772017721177221772317724177251772617727177281772917730177311773217733177341773517736177371773817739177401774117742177431774417745177461774717748177491775017751177521775317754177551775617757177581775917760177611776217763177641776517766177671776817769177701777117772177731777417775177761777717778177791778017781177821778317784177851778617787177881778917790177911779217793177941779517796177971779817799178001780117802178031780417805178061780717808178091781017811178121781317814178151781617817178181781917820178211782217823178241782517826178271782817829178301783117832178331783417835178361783717838178391784017841178421784317844178451784617847178481784917850178511785217853178541785517856178571785817859178601786117862178631786417865178661786717868178691787017871178721787317874178751787617877178781787917880178811788217883178841788517886178871788817889178901789117892178931789417895178961789717898178991790017901179021790317904179051790617907179081790917910179111791217913179141791517916179171791817919179201792117922179231792417925179261792717928179291793017931179321793317934179351793617937179381793917940179411794217943179441794517946179471794817949179501795117952179531795417955179561795717958179591796017961179621796317964179651796617967179681796917970179711797217973179741797517976179771797817979179801798117982179831798417985179861798717988179891799017991179921799317994179951799617997179981799918000180011800218003180041800518006180071800818009180101801118012180131801418015180161801718018180191802018021180221802318024180251802618027180281802918030180311803218033180341803518036180371803818039180401804118042180431804418045180461804718048180491805018051180521805318054180551805618057180581805918060180611806218063180641806518066180671806818069180701807118072180731807418075180761807718078180791808018081180821808318084180851808618087180881808918090180911809218093180941809518096180971809818099181001810118102181031810418105181061810718108181091811018111181121811318114181151811618117181181811918120181211812218123181241812518126181271812818129181301813118132181331813418135181361813718138181391814018141181421814318144181451814618147181481814918150181511815218153181541815518156181571815818159181601816118162181631816418165181661816718168181691817018171181721817318174181751817618177181781817918180181811818218183181841818518186181871818818189181901819118192181931819418195181961819718198181991820018201182021820318204182051820618207182081820918210182111821218213182141821518216182171821818219182201822118222182231822418225182261822718228182291823018231182321823318234182351823618237182381823918240182411824218243182441824518246182471824818249182501825118252182531825418255182561825718258182591826018261182621826318264182651826618267182681826918270182711827218273182741827518276182771827818279182801828118282182831828418285182861828718288182891829018291182921829318294182951829618297182981829918300183011830218303183041830518306183071830818309183101831118312183131831418315183161831718318183191832018321183221832318324183251832618327183281832918330183311833218333183341833518336183371833818339183401834118342183431834418345183461834718348183491835018351183521835318354183551835618357183581835918360183611836218363183641836518366183671836818369183701837118372183731837418375183761837718378183791838018381183821838318384183851838618387183881838918390183911839218393183941839518396183971839818399184001840118402184031840418405184061840718408184091841018411184121841318414184151841618417184181841918420184211842218423184241842518426184271842818429184301843118432184331843418435184361843718438184391844018441184421844318444184451844618447184481844918450184511845218453184541845518456184571845818459184601846118462184631846418465184661846718468184691847018471184721847318474184751847618477184781847918480184811848218483184841848518486184871848818489184901849118492184931849418495184961849718498184991850018501185021850318504185051850618507185081850918510185111851218513185141851518516185171851818519185201852118522185231852418525185261852718528185291853018531185321853318534185351853618537185381853918540185411854218543185441854518546185471854818549185501855118552185531855418555185561855718558185591856018561185621856318564185651856618567185681856918570185711857218573185741857518576185771857818579185801858118582185831858418585185861858718588185891859018591185921859318594185951859618597185981859918600186011860218603186041860518606186071860818609186101861118612186131861418615186161861718618186191862018621186221862318624186251862618627186281862918630186311863218633186341863518636186371863818639186401864118642186431864418645186461864718648186491865018651186521865318654186551865618657186581865918660186611866218663186641866518666186671866818669186701867118672186731867418675186761867718678186791868018681186821868318684186851868618687186881868918690186911869218693186941869518696186971869818699187001870118702187031870418705187061870718708187091871018711187121871318714187151871618717187181871918720187211872218723187241872518726187271872818729187301873118732187331873418735187361873718738187391874018741187421874318744187451874618747187481874918750187511875218753187541875518756187571875818759187601876118762187631876418765187661876718768187691877018771187721877318774187751877618777187781877918780187811878218783187841878518786187871878818789187901879118792187931879418795187961879718798187991880018801188021880318804188051880618807188081880918810188111881218813188141881518816188171881818819188201882118822188231882418825188261882718828188291883018831188321883318834188351883618837188381883918840188411884218843188441884518846188471884818849188501885118852188531885418855188561885718858188591886018861188621886318864188651886618867188681886918870188711887218873188741887518876188771887818879188801888118882188831888418885188861888718888188891889018891188921889318894188951889618897188981889918900189011890218903189041890518906189071890818909189101891118912189131891418915189161891718918189191892018921189221892318924189251892618927189281892918930189311893218933189341893518936189371893818939189401894118942189431894418945189461894718948189491895018951189521895318954189551895618957189581895918960189611896218963189641896518966189671896818969189701897118972189731897418975189761897718978189791898018981189821898318984189851898618987189881898918990189911899218993189941899518996189971899818999190001900119002190031900419005190061900719008190091901019011190121901319014190151901619017190181901919020190211902219023190241902519026190271902819029190301903119032190331903419035190361903719038190391904019041190421904319044190451904619047190481904919050190511905219053190541905519056190571905819059190601906119062190631906419065190661906719068190691907019071190721907319074190751907619077190781907919080190811908219083190841908519086190871908819089190901909119092190931909419095190961909719098190991910019101191021910319104191051910619107191081910919110191111911219113191141911519116191171911819119191201912119122191231912419125191261912719128191291913019131191321913319134191351913619137191381913919140191411914219143191441914519146191471914819149191501915119152191531915419155191561915719158191591916019161191621916319164191651916619167191681916919170191711917219173191741917519176191771917819179191801918119182191831918419185191861918719188191891919019191191921919319194191951919619197191981919919200192011920219203192041920519206192071920819209192101921119212192131921419215192161921719218192191922019221192221922319224192251922619227192281922919230192311923219233192341923519236192371923819239192401924119242192431924419245192461924719248192491925019251192521925319254192551925619257192581925919260192611926219263192641926519266192671926819269192701927119272192731927419275192761927719278192791928019281192821928319284192851928619287192881928919290192911929219293192941929519296192971929819299193001930119302193031930419305193061930719308193091931019311193121931319314193151931619317193181931919320193211932219323193241932519326193271932819329193301933119332193331933419335193361933719338193391934019341193421934319344193451934619347193481934919350193511935219353193541935519356193571935819359193601936119362193631936419365193661936719368193691937019371193721937319374193751937619377193781937919380193811938219383193841938519386193871938819389193901939119392193931939419395193961939719398193991940019401194021940319404194051940619407194081940919410194111941219413194141941519416194171941819419194201942119422194231942419425194261942719428194291943019431194321943319434194351943619437194381943919440194411944219443194441944519446194471944819449194501945119452194531945419455194561945719458194591946019461194621946319464194651946619467194681946919470194711947219473194741947519476194771947819479194801948119482194831948419485194861948719488194891949019491194921949319494194951949619497194981949919500195011950219503195041950519506195071950819509195101951119512195131951419515195161951719518195191952019521195221952319524195251952619527195281952919530195311953219533195341953519536195371953819539195401954119542195431954419545195461954719548195491955019551195521955319554195551955619557195581955919560195611956219563195641956519566195671956819569195701957119572195731957419575195761957719578195791958019581195821958319584195851958619587195881958919590195911959219593195941959519596195971959819599196001960119602196031960419605196061960719608196091961019611196121961319614196151961619617196181961919620196211962219623196241962519626196271962819629196301963119632196331963419635196361963719638196391964019641196421964319644196451964619647196481964919650196511965219653196541965519656196571965819659196601966119662196631966419665196661966719668196691967019671196721967319674196751967619677196781967919680196811968219683196841968519686196871968819689196901969119692196931969419695196961969719698196991970019701197021970319704197051970619707197081970919710197111971219713197141971519716197171971819719197201972119722197231972419725197261972719728197291973019731197321973319734197351973619737197381973919740197411974219743197441974519746197471974819749197501975119752197531975419755197561975719758197591976019761197621976319764197651976619767197681976919770197711977219773197741977519776197771977819779197801978119782197831978419785197861978719788197891979019791197921979319794197951979619797197981979919800198011980219803198041980519806198071980819809198101981119812198131981419815198161981719818198191982019821198221982319824198251982619827198281982919830198311983219833198341983519836198371983819839198401984119842198431984419845198461984719848198491985019851198521985319854198551985619857198581985919860198611986219863198641986519866198671986819869198701987119872198731987419875198761987719878198791988019881198821988319884198851988619887198881988919890198911989219893198941989519896198971989819899199001990119902199031990419905199061990719908199091991019911199121991319914199151991619917199181991919920199211992219923199241992519926199271992819929199301993119932199331993419935199361993719938199391994019941199421994319944199451994619947199481994919950199511995219953199541995519956199571995819959199601996119962199631996419965199661996719968199691997019971199721997319974199751997619977199781997919980199811998219983199841998519986199871998819989199901999119992199931999419995199961999719998199992000020001200022000320004200052000620007200082000920010200112001220013200142001520016200172001820019200202002120022200232002420025200262002720028200292003020031200322003320034200352003620037200382003920040200412004220043200442004520046200472004820049200502005120052200532005420055200562005720058200592006020061200622006320064200652006620067200682006920070200712007220073200742007520076200772007820079200802008120082200832008420085200862008720088200892009020091200922009320094200952009620097200982009920100201012010220103201042010520106201072010820109201102011120112201132011420115201162011720118201192012020121201222012320124201252012620127201282012920130201312013220133201342013520136201372013820139201402014120142201432014420145201462014720148201492015020151201522015320154201552015620157201582015920160201612016220163201642016520166201672016820169201702017120172201732017420175201762017720178201792018020181201822018320184201852018620187201882018920190201912019220193201942019520196201972019820199202002020120202202032020420205202062020720208202092021020211202122021320214202152021620217202182021920220202212022220223202242022520226202272022820229202302023120232202332023420235202362023720238202392024020241202422024320244202452024620247202482024920250202512025220253202542025520256202572025820259202602026120262202632026420265202662026720268202692027020271202722027320274202752027620277202782027920280202812028220283202842028520286202872028820289202902029120292 |
- /*
- * Copyright 2015-2021 Arm Limited
- * SPDX-License-Identifier: Apache-2.0 OR MIT
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- /*
- * At your option, you may choose to accept this material under either:
- * 1. The Apache License, Version 2.0, found at <http://www.apache.org/licenses/LICENSE-2.0>, or
- * 2. The MIT License, found at <http://opensource.org/licenses/MIT>.
- */
- #include "spirv_glsl.hpp"
- #include "GLSL.std.450.h"
- #include "spirv_common.hpp"
- #include <algorithm>
- #include <assert.h>
- #include <cmath>
- #include <limits>
- #include <locale.h>
- #include <utility>
- #include <array>
- #ifndef _WIN32
- #include <langinfo.h>
- #endif
- #include <locale.h>
- using namespace SPIRV_CROSS_SPV_HEADER_NAMESPACE;
- using namespace SPIRV_CROSS_NAMESPACE;
- using namespace std;
- namespace SPIRV_CROSS_NAMESPACE
- {
- enum ExtraSubExpressionType
- {
- // Create masks above any legal ID range to allow multiple address spaces into the extra_sub_expressions map.
- EXTRA_SUB_EXPRESSION_TYPE_STREAM_OFFSET = 0x10000000,
- EXTRA_SUB_EXPRESSION_TYPE_AUX = 0x20000000
- };
- struct GlslConstantNameMapping
- {
- uint32_t value;
- const char *alias;
- };
- #define DEF_GLSL_MAPPING(x) { x, "gl_" #x }
- #define DEF_GLSL_MAPPING_EXT(x) { x##KHR, "gl_" #x }
- static const GlslConstantNameMapping CoopVecComponentTypeNames[] = {
- DEF_GLSL_MAPPING(ComponentTypeFloat16NV),
- DEF_GLSL_MAPPING(ComponentTypeFloat32NV),
- DEF_GLSL_MAPPING(ComponentTypeFloat64NV),
- DEF_GLSL_MAPPING(ComponentTypeSignedInt8NV),
- DEF_GLSL_MAPPING(ComponentTypeSignedInt16NV),
- DEF_GLSL_MAPPING(ComponentTypeSignedInt32NV),
- DEF_GLSL_MAPPING(ComponentTypeSignedInt64NV),
- DEF_GLSL_MAPPING(ComponentTypeUnsignedInt8NV),
- DEF_GLSL_MAPPING(ComponentTypeUnsignedInt16NV),
- DEF_GLSL_MAPPING(ComponentTypeUnsignedInt32NV),
- DEF_GLSL_MAPPING(ComponentTypeUnsignedInt64NV),
- DEF_GLSL_MAPPING(ComponentTypeSignedInt8PackedNV),
- DEF_GLSL_MAPPING(ComponentTypeUnsignedInt8PackedNV),
- DEF_GLSL_MAPPING(ComponentTypeFloatE4M3NV),
- DEF_GLSL_MAPPING(ComponentTypeFloatE5M2NV),
- };
- static const GlslConstantNameMapping CoopVecMatrixLayoutNames[] = {
- DEF_GLSL_MAPPING(CooperativeVectorMatrixLayoutRowMajorNV),
- DEF_GLSL_MAPPING(CooperativeVectorMatrixLayoutColumnMajorNV),
- DEF_GLSL_MAPPING(CooperativeVectorMatrixLayoutInferencingOptimalNV),
- DEF_GLSL_MAPPING(CooperativeVectorMatrixLayoutTrainingOptimalNV),
- };
- static const GlslConstantNameMapping CoopMatMatrixLayoutNames[] = {
- DEF_GLSL_MAPPING_EXT(CooperativeMatrixLayoutRowMajor),
- DEF_GLSL_MAPPING_EXT(CooperativeMatrixLayoutColumnMajor),
- };
- #undef DEF_GLSL_MAPPING
- #undef DEF_GLSL_MAPPING_EXT
- static bool is_unsigned_opcode(Op op)
- {
- // Don't have to be exhaustive, only relevant for legacy target checking ...
- switch (op)
- {
- case OpShiftRightLogical:
- case OpUGreaterThan:
- case OpUGreaterThanEqual:
- case OpULessThan:
- case OpULessThanEqual:
- case OpUConvert:
- case OpUDiv:
- case OpUMod:
- case OpUMulExtended:
- case OpConvertUToF:
- case OpConvertFToU:
- return true;
- default:
- return false;
- }
- }
- static bool is_unsigned_glsl_opcode(GLSLstd450 op)
- {
- // Don't have to be exhaustive, only relevant for legacy target checking ...
- switch (op)
- {
- case GLSLstd450UClamp:
- case GLSLstd450UMin:
- case GLSLstd450UMax:
- case GLSLstd450FindUMsb:
- return true;
- default:
- return false;
- }
- }
- static bool packing_is_vec4_padded(BufferPackingStandard packing)
- {
- switch (packing)
- {
- case BufferPackingHLSLCbuffer:
- case BufferPackingHLSLCbufferPackOffset:
- case BufferPackingStd140:
- case BufferPackingStd140EnhancedLayout:
- return true;
- default:
- return false;
- }
- }
- static bool packing_is_hlsl(BufferPackingStandard packing)
- {
- switch (packing)
- {
- case BufferPackingHLSLCbuffer:
- case BufferPackingHLSLCbufferPackOffset:
- return true;
- default:
- return false;
- }
- }
- static bool packing_has_flexible_offset(BufferPackingStandard packing)
- {
- switch (packing)
- {
- case BufferPackingStd140:
- case BufferPackingStd430:
- case BufferPackingScalar:
- case BufferPackingHLSLCbuffer:
- return false;
- default:
- return true;
- }
- }
- static bool packing_is_scalar(BufferPackingStandard packing)
- {
- switch (packing)
- {
- case BufferPackingScalar:
- case BufferPackingScalarEnhancedLayout:
- return true;
- default:
- return false;
- }
- }
- static BufferPackingStandard packing_to_substruct_packing(BufferPackingStandard packing)
- {
- switch (packing)
- {
- case BufferPackingStd140EnhancedLayout:
- return BufferPackingStd140;
- case BufferPackingStd430EnhancedLayout:
- return BufferPackingStd430;
- case BufferPackingHLSLCbufferPackOffset:
- return BufferPackingHLSLCbuffer;
- case BufferPackingScalarEnhancedLayout:
- return BufferPackingScalar;
- default:
- return packing;
- }
- }
- }
- void CompilerGLSL::init()
- {
- if (ir.source.known)
- {
- options.es = ir.source.es;
- options.version = ir.source.version;
- }
- // Query the locale to see what the decimal point is.
- // We'll rely on fixing it up ourselves in the rare case we have a comma-as-decimal locale
- // rather than setting locales ourselves. Settings locales in a safe and isolated way is rather
- // tricky.
- #ifdef _WIN32
- // On Windows, localeconv uses thread-local storage, so it should be fine.
- const struct lconv *conv = localeconv();
- if (conv && conv->decimal_point)
- current_locale_radix_character = *conv->decimal_point;
- #elif defined(__ANDROID__) && __ANDROID_API__ < 26
- // nl_langinfo is not supported on this platform, fall back to the worse alternative.
- const struct lconv *conv = localeconv();
- if (conv && conv->decimal_point)
- current_locale_radix_character = *conv->decimal_point;
- #else
- // localeconv, the portable function is not MT safe ...
- const char *decimal_point = nl_langinfo(RADIXCHAR);
- if (decimal_point && *decimal_point != '\0')
- current_locale_radix_character = *decimal_point;
- #endif
- }
- static const char *to_pls_layout(PlsFormat format)
- {
- switch (format)
- {
- case PlsR11FG11FB10F:
- return "layout(r11f_g11f_b10f) ";
- case PlsR32F:
- return "layout(r32f) ";
- case PlsRG16F:
- return "layout(rg16f) ";
- case PlsRGB10A2:
- return "layout(rgb10_a2) ";
- case PlsRGBA8:
- return "layout(rgba8) ";
- case PlsRG16:
- return "layout(rg16) ";
- case PlsRGBA8I:
- return "layout(rgba8i)";
- case PlsRG16I:
- return "layout(rg16i) ";
- case PlsRGB10A2UI:
- return "layout(rgb10_a2ui) ";
- case PlsRGBA8UI:
- return "layout(rgba8ui) ";
- case PlsRG16UI:
- return "layout(rg16ui) ";
- case PlsR32UI:
- return "layout(r32ui) ";
- default:
- return "";
- }
- }
- static std::pair<Op, SPIRType::BaseType> pls_format_to_basetype(PlsFormat format)
- {
- switch (format)
- {
- default:
- case PlsR11FG11FB10F:
- case PlsR32F:
- case PlsRG16F:
- case PlsRGB10A2:
- case PlsRGBA8:
- case PlsRG16:
- return std::make_pair(OpTypeFloat, SPIRType::Float);
- case PlsRGBA8I:
- case PlsRG16I:
- return std::make_pair(OpTypeInt, SPIRType::Int);
- case PlsRGB10A2UI:
- case PlsRGBA8UI:
- case PlsRG16UI:
- case PlsR32UI:
- return std::make_pair(OpTypeInt, SPIRType::UInt);
- }
- }
- static uint32_t pls_format_to_components(PlsFormat format)
- {
- switch (format)
- {
- default:
- case PlsR32F:
- case PlsR32UI:
- return 1;
- case PlsRG16F:
- case PlsRG16:
- case PlsRG16UI:
- case PlsRG16I:
- return 2;
- case PlsR11FG11FB10F:
- return 3;
- case PlsRGB10A2:
- case PlsRGBA8:
- case PlsRGBA8I:
- case PlsRGB10A2UI:
- case PlsRGBA8UI:
- return 4;
- }
- }
- const char *CompilerGLSL::vector_swizzle(int vecsize, int index)
- {
- static const char *const swizzle[4][4] = {
- { ".x", ".y", ".z", ".w" },
- { ".xy", ".yz", ".zw", nullptr },
- { ".xyz", ".yzw", nullptr, nullptr },
- #if defined(__GNUC__) && (__GNUC__ == 9)
- // This works around a GCC 9 bug, see details in https://gcc.gnu.org/bugzilla/show_bug.cgi?id=90947.
- // This array ends up being compiled as all nullptrs, tripping the assertions below.
- { "", nullptr, nullptr, "$" },
- #else
- { "", nullptr, nullptr, nullptr },
- #endif
- };
- assert(vecsize >= 1 && vecsize <= 4);
- assert(index >= 0 && index < 4);
- assert(swizzle[vecsize - 1][index]);
- return swizzle[vecsize - 1][index];
- }
- void CompilerGLSL::reset(uint32_t iteration_count)
- {
- // Sanity check the iteration count to be robust against a certain class of bugs where
- // we keep forcing recompilations without making clear forward progress.
- // In buggy situations we will loop forever, or loop for an unbounded number of iterations.
- // Certain types of recompilations are considered to make forward progress,
- // but in almost all situations, we'll never see more than 3 iterations.
- // It is highly context-sensitive when we need to force recompilation,
- // and it is not practical with the current architecture
- // to resolve everything up front.
- if (iteration_count >= options.force_recompile_max_debug_iterations && !is_force_recompile_forward_progress)
- SPIRV_CROSS_THROW("Maximum compilation loops detected and no forward progress was made. Must be a SPIRV-Cross bug!");
- // We do some speculative optimizations which should pretty much always work out,
- // but just in case the SPIR-V is rather weird, recompile until it's happy.
- // This typically only means one extra pass.
- clear_force_recompile();
- // Clear invalid expression tracking.
- invalid_expressions.clear();
- composite_insert_overwritten.clear();
- current_function = nullptr;
- // Clear temporary usage tracking.
- expression_usage_counts.clear();
- forwarded_temporaries.clear();
- suppressed_usage_tracking.clear();
- // Ensure that we declare phi-variable copies even if the original declaration isn't deferred
- flushed_phi_variables.clear();
- current_emitting_switch_stack.clear();
- reset_name_caches();
- ir.for_each_typed_id<SPIRFunction>([&](uint32_t, SPIRFunction &func) {
- func.active = false;
- func.flush_undeclared = true;
- });
- ir.for_each_typed_id<SPIRVariable>([&](uint32_t, SPIRVariable &var) { var.dependees.clear(); });
- ir.reset_all_of_type<SPIRExpression>();
- ir.reset_all_of_type<SPIRAccessChain>();
- statement_count = 0;
- indent = 0;
- current_loop_level = 0;
- }
- void CompilerGLSL::remap_pls_variables()
- {
- for (auto &input : pls_inputs)
- {
- auto &var = get<SPIRVariable>(input.id);
- bool input_is_target = false;
- if (var.storage == StorageClassUniformConstant)
- {
- auto &type = get<SPIRType>(var.basetype);
- input_is_target = type.image.dim == DimSubpassData;
- }
- if (var.storage != StorageClassInput && !input_is_target)
- SPIRV_CROSS_THROW("Can only use in and target variables for PLS inputs.");
- var.remapped_variable = true;
- }
- for (auto &output : pls_outputs)
- {
- auto &var = get<SPIRVariable>(output.id);
- if (var.storage != StorageClassOutput)
- SPIRV_CROSS_THROW("Can only use out variables for PLS outputs.");
- var.remapped_variable = true;
- }
- }
- void CompilerGLSL::remap_ext_framebuffer_fetch(uint32_t input_attachment_index, uint32_t color_location, bool coherent)
- {
- subpass_to_framebuffer_fetch_attachment.push_back({ input_attachment_index, color_location });
- inout_color_attachments.push_back({ color_location, coherent });
- }
- bool CompilerGLSL::location_is_framebuffer_fetch(uint32_t location) const
- {
- return std::find_if(begin(inout_color_attachments), end(inout_color_attachments),
- [&](const std::pair<uint32_t, bool> &elem) {
- return elem.first == location;
- }) != end(inout_color_attachments);
- }
- bool CompilerGLSL::location_is_non_coherent_framebuffer_fetch(uint32_t location) const
- {
- return std::find_if(begin(inout_color_attachments), end(inout_color_attachments),
- [&](const std::pair<uint32_t, bool> &elem) {
- return elem.first == location && !elem.second;
- }) != end(inout_color_attachments);
- }
- void CompilerGLSL::find_static_extensions()
- {
- ir.for_each_typed_id<SPIRType>([&](uint32_t, const SPIRType &type) {
- if (type.basetype == SPIRType::Double)
- {
- if (options.es)
- SPIRV_CROSS_THROW("FP64 not supported in ES profile.");
- if (!options.es && options.version < 400)
- require_extension_internal("GL_ARB_gpu_shader_fp64");
- }
- else if (type.basetype == SPIRType::Int64 || type.basetype == SPIRType::UInt64)
- {
- if (options.es && options.version < 310) // GL_NV_gpu_shader5 fallback requires 310.
- SPIRV_CROSS_THROW("64-bit integers not supported in ES profile before version 310.");
- require_extension_internal("GL_ARB_gpu_shader_int64");
- }
- else if (type.basetype == SPIRType::Half)
- {
- require_extension_internal("GL_EXT_shader_explicit_arithmetic_types_float16");
- if (options.vulkan_semantics)
- require_extension_internal("GL_EXT_shader_16bit_storage");
- }
- else if (type.basetype == SPIRType::SByte || type.basetype == SPIRType::UByte)
- {
- require_extension_internal("GL_EXT_shader_explicit_arithmetic_types_int8");
- if (options.vulkan_semantics)
- require_extension_internal("GL_EXT_shader_8bit_storage");
- }
- else if (type.basetype == SPIRType::Short || type.basetype == SPIRType::UShort)
- {
- require_extension_internal("GL_EXT_shader_explicit_arithmetic_types_int16");
- if (options.vulkan_semantics)
- require_extension_internal("GL_EXT_shader_16bit_storage");
- }
- });
- auto &execution = get_entry_point();
- switch (execution.model)
- {
- case ExecutionModelGLCompute:
- if (!options.es && options.version < 430)
- require_extension_internal("GL_ARB_compute_shader");
- if (options.es && options.version < 310)
- SPIRV_CROSS_THROW("At least ESSL 3.10 required for compute shaders.");
- break;
- case ExecutionModelGeometry:
- if (options.es && options.version < 320)
- require_extension_internal("GL_EXT_geometry_shader");
- if (!options.es && options.version < 150)
- require_extension_internal("GL_ARB_geometry_shader4");
- if (execution.flags.get(ExecutionModeInvocations) && execution.invocations != 1)
- {
- // Instanced GS is part of 400 core or this extension.
- if (!options.es && options.version < 400)
- require_extension_internal("GL_ARB_gpu_shader5");
- }
- break;
- case ExecutionModelTessellationEvaluation:
- case ExecutionModelTessellationControl:
- if (options.es && options.version < 320)
- require_extension_internal("GL_EXT_tessellation_shader");
- if (!options.es && options.version < 400)
- require_extension_internal("GL_ARB_tessellation_shader");
- break;
- case ExecutionModelRayGenerationKHR:
- case ExecutionModelIntersectionKHR:
- case ExecutionModelAnyHitKHR:
- case ExecutionModelClosestHitKHR:
- case ExecutionModelMissKHR:
- case ExecutionModelCallableKHR:
- // NV enums are aliases.
- if (options.es || options.version < 460)
- SPIRV_CROSS_THROW("Ray tracing shaders require non-es profile with version 460 or above.");
- if (!options.vulkan_semantics)
- SPIRV_CROSS_THROW("Ray tracing requires Vulkan semantics.");
- // Need to figure out if we should target KHR or NV extension based on capabilities.
- for (auto &cap : ir.declared_capabilities)
- {
- if (cap == CapabilityRayTracingKHR || cap == CapabilityRayQueryKHR ||
- cap == CapabilityRayTraversalPrimitiveCullingKHR)
- {
- ray_tracing_is_khr = true;
- break;
- }
- }
- if (ray_tracing_is_khr)
- {
- // In KHR ray tracing we pass payloads by pointer instead of location,
- // so make sure we assign locations properly.
- ray_tracing_khr_fixup_locations();
- require_extension_internal("GL_EXT_ray_tracing");
- }
- else
- require_extension_internal("GL_NV_ray_tracing");
- break;
- case ExecutionModelMeshEXT:
- case ExecutionModelTaskEXT:
- if (options.es || options.version < 450)
- SPIRV_CROSS_THROW("Mesh shaders require GLSL 450 or above.");
- if (!options.vulkan_semantics)
- SPIRV_CROSS_THROW("Mesh shaders require Vulkan semantics.");
- require_extension_internal("GL_EXT_mesh_shader");
- break;
- default:
- break;
- }
- if (!pls_inputs.empty() || !pls_outputs.empty())
- {
- if (execution.model != ExecutionModelFragment)
- SPIRV_CROSS_THROW("Can only use GL_EXT_shader_pixel_local_storage in fragment shaders.");
- require_extension_internal("GL_EXT_shader_pixel_local_storage");
- }
- if (!inout_color_attachments.empty())
- {
- if (execution.model != ExecutionModelFragment)
- SPIRV_CROSS_THROW("Can only use GL_EXT_shader_framebuffer_fetch in fragment shaders.");
- if (options.vulkan_semantics)
- SPIRV_CROSS_THROW("Cannot use EXT_shader_framebuffer_fetch in Vulkan GLSL.");
- bool has_coherent = false;
- bool has_incoherent = false;
- for (auto &att : inout_color_attachments)
- {
- if (att.second)
- has_coherent = true;
- else
- has_incoherent = true;
- }
- if (has_coherent)
- require_extension_internal("GL_EXT_shader_framebuffer_fetch");
- if (has_incoherent)
- require_extension_internal("GL_EXT_shader_framebuffer_fetch_non_coherent");
- }
- if (options.separate_shader_objects && !options.es && options.version < 410)
- require_extension_internal("GL_ARB_separate_shader_objects");
- if (ir.addressing_model == AddressingModelPhysicalStorageBuffer64)
- {
- if (!options.vulkan_semantics)
- SPIRV_CROSS_THROW("GL_EXT_buffer_reference is only supported in Vulkan GLSL.");
- if (options.es && options.version < 320)
- SPIRV_CROSS_THROW("GL_EXT_buffer_reference requires ESSL 320.");
- else if (!options.es && options.version < 450)
- SPIRV_CROSS_THROW("GL_EXT_buffer_reference requires GLSL 450.");
- require_extension_internal("GL_EXT_buffer_reference2");
- }
- else if (ir.addressing_model != AddressingModelLogical)
- {
- SPIRV_CROSS_THROW("Only Logical and PhysicalStorageBuffer64 addressing models are supported.");
- }
- // Check for nonuniform qualifier and passthrough.
- // Instead of looping over all decorations to find this, just look at capabilities.
- for (auto &cap : ir.declared_capabilities)
- {
- switch (cap)
- {
- case CapabilityShaderNonUniformEXT:
- if (!options.vulkan_semantics)
- require_extension_internal("GL_NV_gpu_shader5");
- else
- require_extension_internal("GL_EXT_nonuniform_qualifier");
- break;
- case CapabilityRuntimeDescriptorArrayEXT:
- if (!options.vulkan_semantics)
- SPIRV_CROSS_THROW("GL_EXT_nonuniform_qualifier is only supported in Vulkan GLSL.");
- require_extension_internal("GL_EXT_nonuniform_qualifier");
- break;
- case CapabilityGeometryShaderPassthroughNV:
- if (execution.model == ExecutionModelGeometry)
- {
- require_extension_internal("GL_NV_geometry_shader_passthrough");
- execution.geometry_passthrough = true;
- }
- break;
- case CapabilityVariablePointers:
- case CapabilityVariablePointersStorageBuffer:
- SPIRV_CROSS_THROW("VariablePointers capability is not supported in GLSL.");
- case CapabilityMultiView:
- if (options.vulkan_semantics)
- require_extension_internal("GL_EXT_multiview");
- else
- {
- require_extension_internal("GL_OVR_multiview2");
- if (options.ovr_multiview_view_count == 0)
- SPIRV_CROSS_THROW("ovr_multiview_view_count must be non-zero when using GL_OVR_multiview2.");
- if (get_execution_model() != ExecutionModelVertex)
- SPIRV_CROSS_THROW("OVR_multiview2 can only be used with Vertex shaders.");
- }
- break;
- case CapabilityRayQueryKHR:
- if (options.es || options.version < 460 || !options.vulkan_semantics)
- SPIRV_CROSS_THROW("RayQuery requires Vulkan GLSL 460.");
- require_extension_internal("GL_EXT_ray_query");
- ray_tracing_is_khr = true;
- break;
- case CapabilityRayQueryPositionFetchKHR:
- if (options.es || options.version < 460 || !options.vulkan_semantics)
- SPIRV_CROSS_THROW("RayQuery Position Fetch requires Vulkan GLSL 460.");
- require_extension_internal("GL_EXT_ray_tracing_position_fetch");
- ray_tracing_is_khr = true;
- break;
- case CapabilityRayTracingPositionFetchKHR:
- if (options.es || options.version < 460 || !options.vulkan_semantics)
- SPIRV_CROSS_THROW("Ray Tracing Position Fetch requires Vulkan GLSL 460.");
- require_extension_internal("GL_EXT_ray_tracing_position_fetch");
- ray_tracing_is_khr = true;
- break;
- case CapabilityRayTraversalPrimitiveCullingKHR:
- if (options.es || options.version < 460 || !options.vulkan_semantics)
- SPIRV_CROSS_THROW("RayQuery requires Vulkan GLSL 460.");
- require_extension_internal("GL_EXT_ray_flags_primitive_culling");
- ray_tracing_is_khr = true;
- break;
- case CapabilityRayTracingClusterAccelerationStructureNV:
- if (options.es || options.version < 460 || !options.vulkan_semantics)
- SPIRV_CROSS_THROW("Cluster AS requires Vulkan GLSL 460.");
- require_extension_internal("GL_NV_cluster_acceleration_structure");
- ray_tracing_is_khr = true;
- break;
- case CapabilityTensorsARM:
- if (options.es || options.version < 460 || !options.vulkan_semantics)
- SPIRV_CROSS_THROW("Tensor requires Vulkan GLSL 460.");
- require_extension_internal("GL_ARM_tensors");
- break;
- default:
- break;
- }
- }
- if (options.ovr_multiview_view_count)
- {
- if (options.vulkan_semantics)
- SPIRV_CROSS_THROW("OVR_multiview2 cannot be used with Vulkan semantics.");
- if (get_execution_model() != ExecutionModelVertex)
- SPIRV_CROSS_THROW("OVR_multiview2 can only be used with Vertex shaders.");
- require_extension_internal("GL_OVR_multiview2");
- }
- if (execution.flags.get(ExecutionModeQuadDerivativesKHR) ||
- (execution.flags.get(ExecutionModeRequireFullQuadsKHR) && get_execution_model() == ExecutionModelFragment))
- {
- require_extension_internal("GL_EXT_shader_quad_control");
- }
- // KHR one is likely to get promoted at some point, so if we don't see an explicit SPIR-V extension, assume KHR.
- for (auto &ext : ir.declared_extensions)
- if (ext == "SPV_NV_fragment_shader_barycentric")
- barycentric_is_nv = true;
- }
- void CompilerGLSL::require_polyfill(Polyfill polyfill, bool relaxed)
- {
- uint32_t &polyfills = (relaxed && (options.es || options.vulkan_semantics)) ?
- required_polyfills_relaxed : required_polyfills;
- if ((polyfills & polyfill) == 0)
- {
- polyfills |= polyfill;
- force_recompile();
- }
- }
- void CompilerGLSL::ray_tracing_khr_fixup_locations()
- {
- uint32_t location = 0;
- ir.for_each_typed_id<SPIRVariable>([&](uint32_t, SPIRVariable &var) {
- // Incoming payload storage can also be used for tracing.
- if (var.storage != StorageClassRayPayloadKHR && var.storage != StorageClassCallableDataKHR &&
- var.storage != StorageClassIncomingRayPayloadKHR && var.storage != StorageClassIncomingCallableDataKHR)
- return;
- if (is_hidden_variable(var))
- return;
- set_decoration(var.self, DecorationLocation, location++);
- });
- }
- string CompilerGLSL::compile()
- {
- ir.fixup_reserved_names();
- if (!options.vulkan_semantics)
- {
- // only NV_gpu_shader5 supports divergent indexing on OpenGL, and it does so without extra qualifiers
- backend.nonuniform_qualifier = "";
- backend.needs_row_major_load_workaround = options.enable_row_major_load_workaround;
- }
- backend.allow_precision_qualifiers = options.vulkan_semantics || options.es;
- backend.force_gl_in_out_block = true;
- backend.supports_extensions = true;
- backend.use_array_constructor = true;
- backend.workgroup_size_is_hidden = true;
- backend.requires_relaxed_precision_analysis = options.es || options.vulkan_semantics;
- backend.support_precise_qualifier =
- (!options.es && options.version >= 400) || (options.es && options.version >= 320);
- backend.constant_null_initializer = "{ }";
- backend.requires_matching_array_initializer = true;
- if (is_legacy_es())
- backend.support_case_fallthrough = false;
- // Scan the SPIR-V to find trivial uses of extensions.
- fixup_anonymous_struct_names();
- fixup_type_alias();
- reorder_type_alias();
- build_function_control_flow_graphs_and_analyze();
- find_static_extensions();
- fixup_image_load_store_access();
- update_active_builtins();
- analyze_image_and_sampler_usage();
- analyze_interlocked_resource_usage();
- if (!inout_color_attachments.empty())
- emit_inout_fragment_outputs_copy_to_subpass_inputs();
- // Shaders might cast unrelated data to pointers of non-block types.
- // Find all such instances and make sure we can cast the pointers to a synthesized block type.
- if (ir.addressing_model == AddressingModelPhysicalStorageBuffer64)
- analyze_non_block_pointer_types();
- uint32_t pass_count = 0;
- do
- {
- reset(pass_count);
- buffer.reset();
- emit_header();
- emit_resources();
- emit_extension_workarounds(get_execution_model());
- if (required_polyfills != 0)
- emit_polyfills(required_polyfills, false);
- if ((options.es || options.vulkan_semantics) && required_polyfills_relaxed != 0)
- emit_polyfills(required_polyfills_relaxed, true);
- emit_function(get<SPIRFunction>(ir.default_entry_point), Bitset());
- pass_count++;
- } while (is_forcing_recompilation());
- // Implement the interlocked wrapper function at the end.
- // The body was implemented in lieu of main().
- if (interlocked_is_complex)
- {
- statement("void main()");
- begin_scope();
- statement("// Interlocks were used in a way not compatible with GLSL, this is very slow.");
- statement("SPIRV_Cross_beginInvocationInterlock();");
- statement("spvMainInterlockedBody();");
- statement("SPIRV_Cross_endInvocationInterlock();");
- end_scope();
- }
- // Entry point in GLSL is always main().
- get_entry_point().name = "main";
- return buffer.str();
- }
- std::string CompilerGLSL::get_partial_source()
- {
- return buffer.str();
- }
- void CompilerGLSL::build_workgroup_size(SmallVector<string> &arguments, const SpecializationConstant &wg_x,
- const SpecializationConstant &wg_y, const SpecializationConstant &wg_z)
- {
- auto &execution = get_entry_point();
- bool builtin_workgroup = execution.workgroup_size.constant != 0;
- bool use_local_size_id = !builtin_workgroup && execution.flags.get(ExecutionModeLocalSizeId);
- if (wg_x.id)
- {
- if (options.vulkan_semantics)
- arguments.push_back(join("local_size_x_id = ", wg_x.constant_id));
- else
- arguments.push_back(join("local_size_x = ", get<SPIRConstant>(wg_x.id).specialization_constant_macro_name));
- }
- else if (use_local_size_id && execution.workgroup_size.id_x)
- arguments.push_back(join("local_size_x = ", get<SPIRConstant>(execution.workgroup_size.id_x).scalar()));
- else
- arguments.push_back(join("local_size_x = ", execution.workgroup_size.x));
- if (wg_y.id)
- {
- if (options.vulkan_semantics)
- arguments.push_back(join("local_size_y_id = ", wg_y.constant_id));
- else
- arguments.push_back(join("local_size_y = ", get<SPIRConstant>(wg_y.id).specialization_constant_macro_name));
- }
- else if (use_local_size_id && execution.workgroup_size.id_y)
- arguments.push_back(join("local_size_y = ", get<SPIRConstant>(execution.workgroup_size.id_y).scalar()));
- else
- arguments.push_back(join("local_size_y = ", execution.workgroup_size.y));
- if (wg_z.id)
- {
- if (options.vulkan_semantics)
- arguments.push_back(join("local_size_z_id = ", wg_z.constant_id));
- else
- arguments.push_back(join("local_size_z = ", get<SPIRConstant>(wg_z.id).specialization_constant_macro_name));
- }
- else if (use_local_size_id && execution.workgroup_size.id_z)
- arguments.push_back(join("local_size_z = ", get<SPIRConstant>(execution.workgroup_size.id_z).scalar()));
- else
- arguments.push_back(join("local_size_z = ", execution.workgroup_size.z));
- }
- void CompilerGLSL::request_subgroup_feature(ShaderSubgroupSupportHelper::Feature feature)
- {
- if (options.vulkan_semantics)
- {
- auto khr_extension = ShaderSubgroupSupportHelper::get_KHR_extension_for_feature(feature);
- require_extension_internal(ShaderSubgroupSupportHelper::get_extension_name(khr_extension));
- }
- else
- {
- if (!shader_subgroup_supporter.is_feature_requested(feature))
- force_recompile();
- shader_subgroup_supporter.request_feature(feature);
- }
- }
- void CompilerGLSL::emit_header()
- {
- auto &execution = get_entry_point();
- statement("#version ", options.version, options.es && options.version > 100 ? " es" : "");
- if (!options.es && options.version < 420)
- {
- // Needed for binding = # on UBOs, etc.
- if (options.enable_420pack_extension)
- {
- statement("#ifdef GL_ARB_shading_language_420pack");
- statement("#extension GL_ARB_shading_language_420pack : require");
- statement("#endif");
- }
- // Needed for: layout(early_fragment_tests) in;
- if (execution.flags.get(ExecutionModeEarlyFragmentTests))
- require_extension_internal("GL_ARB_shader_image_load_store");
- }
- // Needed for: layout(post_depth_coverage) in;
- if (execution.flags.get(ExecutionModePostDepthCoverage))
- require_extension_internal("GL_ARB_post_depth_coverage");
- // Needed for: layout({pixel,sample}_interlock_[un]ordered) in;
- bool interlock_used = execution.flags.get(ExecutionModePixelInterlockOrderedEXT) ||
- execution.flags.get(ExecutionModePixelInterlockUnorderedEXT) ||
- execution.flags.get(ExecutionModeSampleInterlockOrderedEXT) ||
- execution.flags.get(ExecutionModeSampleInterlockUnorderedEXT);
- if (interlock_used)
- {
- if (options.es)
- {
- if (options.version < 310)
- SPIRV_CROSS_THROW("At least ESSL 3.10 required for fragment shader interlock.");
- require_extension_internal("GL_NV_fragment_shader_interlock");
- }
- else
- {
- if (options.version < 420)
- require_extension_internal("GL_ARB_shader_image_load_store");
- require_extension_internal("GL_ARB_fragment_shader_interlock");
- }
- }
- for (auto &ext : forced_extensions)
- {
- if (ext == "GL_ARB_gpu_shader_int64")
- {
- statement("#if defined(GL_ARB_gpu_shader_int64)");
- statement("#extension GL_ARB_gpu_shader_int64 : require");
- if (!options.vulkan_semantics || options.es)
- {
- statement("#elif defined(GL_NV_gpu_shader5)");
- statement("#extension GL_NV_gpu_shader5 : require");
- }
- statement("#else");
- statement("#error No extension available for 64-bit integers.");
- statement("#endif");
- }
- else if (ext == "GL_EXT_shader_explicit_arithmetic_types_float16")
- {
- // Special case, this extension has a potential fallback to another vendor extension in normal GLSL.
- // GL_AMD_gpu_shader_half_float is a superset, so try that first.
- statement("#if defined(GL_AMD_gpu_shader_half_float)");
- statement("#extension GL_AMD_gpu_shader_half_float : require");
- if (!options.vulkan_semantics)
- {
- statement("#elif defined(GL_NV_gpu_shader5)");
- statement("#extension GL_NV_gpu_shader5 : require");
- }
- else
- {
- statement("#elif defined(GL_EXT_shader_explicit_arithmetic_types_float16)");
- statement("#extension GL_EXT_shader_explicit_arithmetic_types_float16 : require");
- }
- statement("#else");
- statement("#error No extension available for FP16.");
- statement("#endif");
- }
- else if (ext == "GL_EXT_shader_explicit_arithmetic_types_int8")
- {
- if (options.vulkan_semantics)
- statement("#extension GL_EXT_shader_explicit_arithmetic_types_int8 : require");
- else
- {
- statement("#if defined(GL_EXT_shader_explicit_arithmetic_types_int8)");
- statement("#extension GL_EXT_shader_explicit_arithmetic_types_int8 : require");
- statement("#elif defined(GL_NV_gpu_shader5)");
- statement("#extension GL_NV_gpu_shader5 : require");
- statement("#else");
- statement("#error No extension available for Int8.");
- statement("#endif");
- }
- }
- else if (ext == "GL_EXT_shader_explicit_arithmetic_types_int16")
- {
- if (options.vulkan_semantics)
- statement("#extension GL_EXT_shader_explicit_arithmetic_types_int16 : require");
- else
- {
- statement("#if defined(GL_EXT_shader_explicit_arithmetic_types_int16)");
- statement("#extension GL_EXT_shader_explicit_arithmetic_types_int16 : require");
- statement("#elif defined(GL_AMD_gpu_shader_int16)");
- statement("#extension GL_AMD_gpu_shader_int16 : require");
- statement("#elif defined(GL_NV_gpu_shader5)");
- statement("#extension GL_NV_gpu_shader5 : require");
- statement("#else");
- statement("#error No extension available for Int16.");
- statement("#endif");
- }
- }
- else if (ext == "GL_ARB_post_depth_coverage")
- {
- if (options.es)
- statement("#extension GL_EXT_post_depth_coverage : require");
- else
- {
- statement("#if defined(GL_ARB_post_depth_coverge)");
- statement("#extension GL_ARB_post_depth_coverage : require");
- statement("#else");
- statement("#extension GL_EXT_post_depth_coverage : require");
- statement("#endif");
- }
- }
- else if (!options.vulkan_semantics && ext == "GL_ARB_shader_draw_parameters")
- {
- // Soft-enable this extension on plain GLSL.
- statement("#ifdef ", ext);
- statement("#extension ", ext, " : enable");
- statement("#endif");
- }
- else if (ext == "GL_EXT_control_flow_attributes")
- {
- // These are just hints so we can conditionally enable and fallback in the shader.
- statement("#if defined(GL_EXT_control_flow_attributes)");
- statement("#extension GL_EXT_control_flow_attributes : require");
- statement("#define SPIRV_CROSS_FLATTEN [[flatten]]");
- statement("#define SPIRV_CROSS_BRANCH [[dont_flatten]]");
- statement("#define SPIRV_CROSS_UNROLL [[unroll]]");
- statement("#define SPIRV_CROSS_LOOP [[dont_unroll]]");
- statement("#else");
- statement("#define SPIRV_CROSS_FLATTEN");
- statement("#define SPIRV_CROSS_BRANCH");
- statement("#define SPIRV_CROSS_UNROLL");
- statement("#define SPIRV_CROSS_LOOP");
- statement("#endif");
- }
- else if (ext == "GL_NV_fragment_shader_interlock")
- {
- statement("#extension GL_NV_fragment_shader_interlock : require");
- statement("#define SPIRV_Cross_beginInvocationInterlock() beginInvocationInterlockNV()");
- statement("#define SPIRV_Cross_endInvocationInterlock() endInvocationInterlockNV()");
- }
- else if (ext == "GL_ARB_fragment_shader_interlock")
- {
- statement("#ifdef GL_ARB_fragment_shader_interlock");
- statement("#extension GL_ARB_fragment_shader_interlock : enable");
- statement("#define SPIRV_Cross_beginInvocationInterlock() beginInvocationInterlockARB()");
- statement("#define SPIRV_Cross_endInvocationInterlock() endInvocationInterlockARB()");
- statement("#elif defined(GL_INTEL_fragment_shader_ordering)");
- statement("#extension GL_INTEL_fragment_shader_ordering : enable");
- statement("#define SPIRV_Cross_beginInvocationInterlock() beginFragmentShaderOrderingINTEL()");
- statement("#define SPIRV_Cross_endInvocationInterlock()");
- statement("#endif");
- }
- else
- statement("#extension ", ext, " : require");
- }
- if (!options.vulkan_semantics)
- {
- using Supp = ShaderSubgroupSupportHelper;
- auto result = shader_subgroup_supporter.resolve();
- for (uint32_t feature_index = 0; feature_index < Supp::FeatureCount; feature_index++)
- {
- auto feature = static_cast<Supp::Feature>(feature_index);
- if (!shader_subgroup_supporter.is_feature_requested(feature))
- continue;
- auto exts = Supp::get_candidates_for_feature(feature, result);
- if (exts.empty())
- continue;
- statement("");
- for (auto &ext : exts)
- {
- const char *name = Supp::get_extension_name(ext);
- const char *extra_predicate = Supp::get_extra_required_extension_predicate(ext);
- auto extra_names = Supp::get_extra_required_extension_names(ext);
- statement(&ext != &exts.front() ? "#elif" : "#if", " defined(", name, ")",
- (*extra_predicate != '\0' ? " && " : ""), extra_predicate);
- for (const auto &e : extra_names)
- statement("#extension ", e, " : enable");
- statement("#extension ", name, " : require");
- }
- if (!Supp::can_feature_be_implemented_without_extensions(feature))
- {
- statement("#else");
- statement("#error No extensions available to emulate requested subgroup feature.");
- }
- statement("#endif");
- }
- }
- for (auto &header : header_lines)
- statement(header);
- SmallVector<string> inputs;
- SmallVector<string> outputs;
- switch (execution.model)
- {
- case ExecutionModelVertex:
- if (options.ovr_multiview_view_count)
- inputs.push_back(join("num_views = ", options.ovr_multiview_view_count));
- break;
- case ExecutionModelGeometry:
- if ((execution.flags.get(ExecutionModeInvocations)) && execution.invocations != 1)
- inputs.push_back(join("invocations = ", execution.invocations));
- if (execution.flags.get(ExecutionModeInputPoints))
- inputs.push_back("points");
- if (execution.flags.get(ExecutionModeInputLines))
- inputs.push_back("lines");
- if (execution.flags.get(ExecutionModeInputLinesAdjacency))
- inputs.push_back("lines_adjacency");
- if (execution.flags.get(ExecutionModeTriangles))
- inputs.push_back("triangles");
- if (execution.flags.get(ExecutionModeInputTrianglesAdjacency))
- inputs.push_back("triangles_adjacency");
- if (!execution.geometry_passthrough)
- {
- // For passthrough, these are implies and cannot be declared in shader.
- outputs.push_back(join("max_vertices = ", execution.output_vertices));
- if (execution.flags.get(ExecutionModeOutputTriangleStrip))
- outputs.push_back("triangle_strip");
- if (execution.flags.get(ExecutionModeOutputPoints))
- outputs.push_back("points");
- if (execution.flags.get(ExecutionModeOutputLineStrip))
- outputs.push_back("line_strip");
- }
- break;
- case ExecutionModelTessellationControl:
- if (execution.flags.get(ExecutionModeOutputVertices))
- outputs.push_back(join("vertices = ", execution.output_vertices));
- break;
- case ExecutionModelTessellationEvaluation:
- if (execution.flags.get(ExecutionModeQuads))
- inputs.push_back("quads");
- if (execution.flags.get(ExecutionModeTriangles))
- inputs.push_back("triangles");
- if (execution.flags.get(ExecutionModeIsolines))
- inputs.push_back("isolines");
- if (execution.flags.get(ExecutionModePointMode))
- inputs.push_back("point_mode");
- if (!execution.flags.get(ExecutionModeIsolines))
- {
- if (execution.flags.get(ExecutionModeVertexOrderCw))
- inputs.push_back("cw");
- if (execution.flags.get(ExecutionModeVertexOrderCcw))
- inputs.push_back("ccw");
- }
- if (execution.flags.get(ExecutionModeSpacingFractionalEven))
- inputs.push_back("fractional_even_spacing");
- if (execution.flags.get(ExecutionModeSpacingFractionalOdd))
- inputs.push_back("fractional_odd_spacing");
- if (execution.flags.get(ExecutionModeSpacingEqual))
- inputs.push_back("equal_spacing");
- break;
- case ExecutionModelGLCompute:
- case ExecutionModelTaskEXT:
- case ExecutionModelMeshEXT:
- {
- if (execution.workgroup_size.constant != 0 || execution.flags.get(ExecutionModeLocalSizeId))
- {
- SpecializationConstant wg_x, wg_y, wg_z;
- get_work_group_size_specialization_constants(wg_x, wg_y, wg_z);
- // If there are any spec constants on legacy GLSL, defer declaration, we need to set up macro
- // declarations before we can emit the work group size.
- if (options.vulkan_semantics ||
- ((wg_x.id == ConstantID(0)) && (wg_y.id == ConstantID(0)) && (wg_z.id == ConstantID(0))))
- build_workgroup_size(inputs, wg_x, wg_y, wg_z);
- }
- else
- {
- inputs.push_back(join("local_size_x = ", execution.workgroup_size.x));
- inputs.push_back(join("local_size_y = ", execution.workgroup_size.y));
- inputs.push_back(join("local_size_z = ", execution.workgroup_size.z));
- }
- if (execution.model == ExecutionModelMeshEXT)
- {
- outputs.push_back(join("max_vertices = ", execution.output_vertices));
- outputs.push_back(join("max_primitives = ", execution.output_primitives));
- if (execution.flags.get(ExecutionModeOutputTrianglesEXT))
- outputs.push_back("triangles");
- else if (execution.flags.get(ExecutionModeOutputLinesEXT))
- outputs.push_back("lines");
- else if (execution.flags.get(ExecutionModeOutputPoints))
- outputs.push_back("points");
- }
- break;
- }
- case ExecutionModelFragment:
- if (options.es)
- {
- switch (options.fragment.default_float_precision)
- {
- case Options::Lowp:
- statement("precision lowp float;");
- break;
- case Options::Mediump:
- statement("precision mediump float;");
- break;
- case Options::Highp:
- statement("precision highp float;");
- break;
- default:
- break;
- }
- switch (options.fragment.default_int_precision)
- {
- case Options::Lowp:
- statement("precision lowp int;");
- break;
- case Options::Mediump:
- statement("precision mediump int;");
- break;
- case Options::Highp:
- statement("precision highp int;");
- break;
- default:
- break;
- }
- }
- if (execution.flags.get(ExecutionModeEarlyFragmentTests))
- inputs.push_back("early_fragment_tests");
- if (execution.flags.get(ExecutionModePostDepthCoverage))
- inputs.push_back("post_depth_coverage");
- if (interlock_used)
- statement("#if defined(GL_ARB_fragment_shader_interlock)");
- if (execution.flags.get(ExecutionModePixelInterlockOrderedEXT))
- statement("layout(pixel_interlock_ordered) in;");
- else if (execution.flags.get(ExecutionModePixelInterlockUnorderedEXT))
- statement("layout(pixel_interlock_unordered) in;");
- else if (execution.flags.get(ExecutionModeSampleInterlockOrderedEXT))
- statement("layout(sample_interlock_ordered) in;");
- else if (execution.flags.get(ExecutionModeSampleInterlockUnorderedEXT))
- statement("layout(sample_interlock_unordered) in;");
- if (interlock_used)
- {
- statement("#elif !defined(GL_INTEL_fragment_shader_ordering)");
- statement("#error Fragment Shader Interlock/Ordering extension missing!");
- statement("#endif");
- }
- if (!options.es && execution.flags.get(ExecutionModeDepthGreater))
- statement("layout(depth_greater) out float gl_FragDepth;");
- else if (!options.es && execution.flags.get(ExecutionModeDepthLess))
- statement("layout(depth_less) out float gl_FragDepth;");
- if (execution.flags.get(ExecutionModeRequireFullQuadsKHR))
- statement("layout(full_quads) in;");
- break;
- default:
- break;
- }
- for (auto &cap : ir.declared_capabilities)
- if (cap == CapabilityRayTraversalPrimitiveCullingKHR)
- statement("layout(primitive_culling);");
- if (execution.flags.get(ExecutionModeQuadDerivativesKHR))
- statement("layout(quad_derivatives) in;");
- if (!inputs.empty())
- statement("layout(", merge(inputs), ") in;");
- if (!outputs.empty())
- statement("layout(", merge(outputs), ") out;");
- statement("");
- }
- bool CompilerGLSL::type_is_empty(const SPIRType &type)
- {
- return type.basetype == SPIRType::Struct && type.member_types.empty();
- }
- void CompilerGLSL::emit_struct(SPIRType &type)
- {
- // Struct types can be stamped out multiple times
- // with just different offsets, matrix layouts, etc ...
- // Type-punning with these types is legal, which complicates things
- // when we are storing struct and array types in an SSBO for example.
- // If the type master is packed however, we can no longer assume that the struct declaration will be redundant.
- if (type.type_alias != TypeID(0) &&
- !has_extended_decoration(type.type_alias, SPIRVCrossDecorationBufferBlockRepacked))
- return;
- add_resource_name(type.self);
- auto name = type_to_glsl(type);
- statement(!backend.explicit_struct_type ? "struct " : "", name);
- begin_scope();
- type.member_name_cache.clear();
- uint32_t i = 0;
- bool emitted = false;
- for (auto &member : type.member_types)
- {
- add_member_name(type, i);
- emit_struct_member(type, member, i);
- i++;
- emitted = true;
- }
- // Don't declare empty structs in GLSL, this is not allowed.
- if (type_is_empty(type) && !backend.supports_empty_struct)
- {
- statement("int empty_struct_member;");
- emitted = true;
- }
- if (has_extended_decoration(type.self, SPIRVCrossDecorationPaddingTarget))
- emit_struct_padding_target(type);
- end_scope_decl();
- if (emitted)
- statement("");
- }
- string CompilerGLSL::to_interpolation_qualifiers(const Bitset &flags)
- {
- string res;
- //if (flags & (1ull << DecorationSmooth))
- // res += "smooth ";
- if (flags.get(DecorationFlat))
- res += "flat ";
- if (flags.get(DecorationNoPerspective))
- {
- if (options.es)
- {
- if (options.version < 300)
- SPIRV_CROSS_THROW("noperspective requires ESSL 300.");
- require_extension_internal("GL_NV_shader_noperspective_interpolation");
- }
- else if (is_legacy_desktop())
- require_extension_internal("GL_EXT_gpu_shader4");
- res += "noperspective ";
- }
- if (flags.get(DecorationCentroid))
- res += "centroid ";
- if (flags.get(DecorationPatch))
- res += "patch ";
- if (flags.get(DecorationSample))
- {
- if (options.es)
- {
- if (options.version < 300)
- SPIRV_CROSS_THROW("sample requires ESSL 300.");
- else if (options.version < 320)
- require_extension_internal("GL_OES_shader_multisample_interpolation");
- }
- res += "sample ";
- }
- if (flags.get(DecorationInvariant) && (options.es || options.version >= 120))
- res += "invariant ";
- if (flags.get(DecorationPerPrimitiveEXT))
- {
- res += "perprimitiveEXT ";
- require_extension_internal("GL_EXT_mesh_shader");
- }
- if (flags.get(DecorationExplicitInterpAMD))
- {
- require_extension_internal("GL_AMD_shader_explicit_vertex_parameter");
- res += "__explicitInterpAMD ";
- }
- if (flags.get(DecorationPerVertexKHR))
- {
- if (options.es && options.version < 320)
- SPIRV_CROSS_THROW("pervertexEXT requires ESSL 320.");
- else if (!options.es && options.version < 450)
- SPIRV_CROSS_THROW("pervertexEXT requires GLSL 450.");
- if (barycentric_is_nv)
- {
- require_extension_internal("GL_NV_fragment_shader_barycentric");
- res += "pervertexNV ";
- }
- else
- {
- require_extension_internal("GL_EXT_fragment_shader_barycentric");
- res += "pervertexEXT ";
- }
- }
- return res;
- }
- string CompilerGLSL::layout_for_member(const SPIRType &type, uint32_t index)
- {
- if (is_legacy())
- return "";
- bool is_block = has_decoration(type.self, DecorationBlock) || has_decoration(type.self, DecorationBufferBlock);
- if (!is_block)
- return "";
- auto &memb = ir.meta[type.self].members;
- if (index >= memb.size())
- return "";
- auto &dec = memb[index];
- SmallVector<string> attr;
- if (has_member_decoration(type.self, index, DecorationPassthroughNV))
- attr.push_back("passthrough");
- // We can only apply layouts on members in block interfaces.
- // This is a bit problematic because in SPIR-V decorations are applied on the struct types directly.
- // This is not supported on GLSL, so we have to make the assumption that if a struct within our buffer block struct
- // has a decoration, it was originally caused by a top-level layout() qualifier in GLSL.
- //
- // We would like to go from (SPIR-V style):
- //
- // struct Foo { layout(row_major) mat4 matrix; };
- // buffer UBO { Foo foo; };
- //
- // to
- //
- // struct Foo { mat4 matrix; }; // GLSL doesn't support any layout shenanigans in raw struct declarations.
- // buffer UBO { layout(row_major) Foo foo; }; // Apply the layout on top-level.
- auto flags = combined_decoration_for_member(type, index);
- if (flags.get(DecorationRowMajor))
- attr.push_back("row_major");
- // We don't emit any global layouts, so column_major is default.
- //if (flags & (1ull << DecorationColMajor))
- // attr.push_back("column_major");
- if (dec.decoration_flags.get(DecorationLocation) && can_use_io_location(type.storage, true))
- attr.push_back(join("location = ", dec.location));
- // Can only declare component if we can declare location.
- if (dec.decoration_flags.get(DecorationComponent) && can_use_io_location(type.storage, true))
- {
- if (!options.es)
- {
- if (options.version < 440 && options.version >= 140)
- require_extension_internal("GL_ARB_enhanced_layouts");
- else if (options.version < 140)
- SPIRV_CROSS_THROW("Component decoration is not supported in targets below GLSL 1.40.");
- attr.push_back(join("component = ", dec.component));
- }
- else
- SPIRV_CROSS_THROW("Component decoration is not supported in ES targets.");
- }
- // SPIRVCrossDecorationPacked is set by layout_for_variable earlier to mark that we need to emit offset qualifiers.
- // This is only done selectively in GLSL as needed.
- if (has_extended_decoration(type.self, SPIRVCrossDecorationExplicitOffset) &&
- dec.decoration_flags.get(DecorationOffset))
- attr.push_back(join("offset = ", dec.offset));
- else if (type.storage == StorageClassOutput && dec.decoration_flags.get(DecorationOffset))
- attr.push_back(join("xfb_offset = ", dec.offset));
- if (attr.empty())
- return "";
- string res = "layout(";
- res += merge(attr);
- res += ") ";
- return res;
- }
- const char *CompilerGLSL::format_to_glsl(ImageFormat format)
- {
- if (options.es && is_desktop_only_format(format))
- SPIRV_CROSS_THROW("Attempting to use image format not supported in ES profile.");
- switch (format)
- {
- case ImageFormatRgba32f:
- return "rgba32f";
- case ImageFormatRgba16f:
- return "rgba16f";
- case ImageFormatR32f:
- return "r32f";
- case ImageFormatRgba8:
- return "rgba8";
- case ImageFormatRgba8Snorm:
- return "rgba8_snorm";
- case ImageFormatRg32f:
- return "rg32f";
- case ImageFormatRg16f:
- return "rg16f";
- case ImageFormatRgba32i:
- return "rgba32i";
- case ImageFormatRgba16i:
- return "rgba16i";
- case ImageFormatR32i:
- return "r32i";
- case ImageFormatRgba8i:
- return "rgba8i";
- case ImageFormatRg32i:
- return "rg32i";
- case ImageFormatRg16i:
- return "rg16i";
- case ImageFormatRgba32ui:
- return "rgba32ui";
- case ImageFormatRgba16ui:
- return "rgba16ui";
- case ImageFormatR32ui:
- return "r32ui";
- case ImageFormatRgba8ui:
- return "rgba8ui";
- case ImageFormatRg32ui:
- return "rg32ui";
- case ImageFormatRg16ui:
- return "rg16ui";
- case ImageFormatR11fG11fB10f:
- return "r11f_g11f_b10f";
- case ImageFormatR16f:
- return "r16f";
- case ImageFormatRgb10A2:
- return "rgb10_a2";
- case ImageFormatR8:
- return "r8";
- case ImageFormatRg8:
- return "rg8";
- case ImageFormatR16:
- return "r16";
- case ImageFormatRg16:
- return "rg16";
- case ImageFormatRgba16:
- return "rgba16";
- case ImageFormatR16Snorm:
- return "r16_snorm";
- case ImageFormatRg16Snorm:
- return "rg16_snorm";
- case ImageFormatRgba16Snorm:
- return "rgba16_snorm";
- case ImageFormatR8Snorm:
- return "r8_snorm";
- case ImageFormatRg8Snorm:
- return "rg8_snorm";
- case ImageFormatR8ui:
- return "r8ui";
- case ImageFormatRg8ui:
- return "rg8ui";
- case ImageFormatR16ui:
- return "r16ui";
- case ImageFormatRgb10a2ui:
- return "rgb10_a2ui";
- case ImageFormatR8i:
- return "r8i";
- case ImageFormatRg8i:
- return "rg8i";
- case ImageFormatR16i:
- return "r16i";
- case ImageFormatR64i:
- return "r64i";
- case ImageFormatR64ui:
- return "r64ui";
- default:
- case ImageFormatUnknown:
- return nullptr;
- }
- }
- uint32_t CompilerGLSL::type_to_packed_base_size(const SPIRType &type, BufferPackingStandard)
- {
- switch (type.basetype)
- {
- case SPIRType::Double:
- case SPIRType::Int64:
- case SPIRType::UInt64:
- return 8;
- case SPIRType::Float:
- case SPIRType::Int:
- case SPIRType::UInt:
- return 4;
- case SPIRType::Half:
- case SPIRType::Short:
- case SPIRType::UShort:
- case SPIRType::BFloat16:
- return 2;
- case SPIRType::SByte:
- case SPIRType::UByte:
- case SPIRType::FloatE4M3:
- case SPIRType::FloatE5M2:
- return 1;
- default:
- SPIRV_CROSS_THROW("Unrecognized type in type_to_packed_base_size.");
- }
- }
- uint32_t CompilerGLSL::type_to_packed_alignment(const SPIRType &type, const Bitset &flags,
- BufferPackingStandard packing)
- {
- // If using PhysicalStorageBuffer storage class, this is a pointer,
- // and is 64-bit.
- if (is_physical_pointer(type))
- {
- if (!type.pointer)
- SPIRV_CROSS_THROW("Types in PhysicalStorageBuffer must be pointers.");
- if (ir.addressing_model == AddressingModelPhysicalStorageBuffer64)
- {
- if (packing_is_vec4_padded(packing) && type_is_array_of_pointers(type))
- return 16;
- else
- return 8;
- }
- else
- SPIRV_CROSS_THROW("AddressingModelPhysicalStorageBuffer64 must be used for PhysicalStorageBuffer.");
- }
- else if (is_array(type))
- {
- uint32_t minimum_alignment = 1;
- if (packing_is_vec4_padded(packing))
- minimum_alignment = 16;
- auto *tmp = &get<SPIRType>(type.parent_type);
- while (!tmp->array.empty())
- tmp = &get<SPIRType>(tmp->parent_type);
- // Get the alignment of the base type, then maybe round up.
- return max(minimum_alignment, type_to_packed_alignment(*tmp, flags, packing));
- }
- if (type.basetype == SPIRType::Struct)
- {
- // Rule 9. Structs alignments are maximum alignment of its members.
- uint32_t alignment = 1;
- for (uint32_t i = 0; i < type.member_types.size(); i++)
- {
- auto member_flags = ir.meta[type.self].members[i].decoration_flags;
- alignment =
- max(alignment, type_to_packed_alignment(get<SPIRType>(type.member_types[i]), member_flags, packing));
- }
- // In std140, struct alignment is rounded up to 16.
- if (packing_is_vec4_padded(packing))
- alignment = max<uint32_t>(alignment, 16u);
- return alignment;
- }
- else
- {
- const uint32_t base_alignment = type_to_packed_base_size(type, packing);
- // Alignment requirement for scalar block layout is always the alignment for the most basic component.
- if (packing_is_scalar(packing))
- return base_alignment;
- // Vectors are *not* aligned in HLSL, but there's an extra rule where vectors cannot straddle
- // a vec4, this is handled outside since that part knows our current offset.
- if (type.columns == 1 && packing_is_hlsl(packing))
- return base_alignment;
- // From 7.6.2.2 in GL 4.5 core spec.
- // Rule 1
- if (type.vecsize == 1 && type.columns == 1)
- return base_alignment;
- // Rule 2
- if ((type.vecsize == 2 || type.vecsize == 4) && type.columns == 1)
- return type.vecsize * base_alignment;
- // Rule 3
- if (type.vecsize == 3 && type.columns == 1)
- return 4 * base_alignment;
- // Rule 4 implied. Alignment does not change in std430.
- // Rule 5. Column-major matrices are stored as arrays of
- // vectors.
- if (flags.get(DecorationColMajor) && type.columns > 1)
- {
- if (packing_is_vec4_padded(packing))
- return 4 * base_alignment;
- else if (type.vecsize == 3)
- return 4 * base_alignment;
- else
- return type.vecsize * base_alignment;
- }
- // Rule 6 implied.
- // Rule 7.
- if (flags.get(DecorationRowMajor) && type.vecsize > 1)
- {
- if (packing_is_vec4_padded(packing))
- return 4 * base_alignment;
- else if (type.columns == 3)
- return 4 * base_alignment;
- else
- return type.columns * base_alignment;
- }
- // Rule 8 implied.
- }
- SPIRV_CROSS_THROW("Did not find suitable rule for type. Bogus decorations?");
- }
- uint32_t CompilerGLSL::type_to_packed_array_stride(const SPIRType &type, const Bitset &flags,
- BufferPackingStandard packing)
- {
- // Array stride is equal to aligned size of the underlying type.
- uint32_t parent = type.parent_type;
- assert(parent);
- auto &tmp = get<SPIRType>(parent);
- uint32_t size = type_to_packed_size(tmp, flags, packing);
- uint32_t alignment = type_to_packed_alignment(type, flags, packing);
- return (size + alignment - 1) & ~(alignment - 1);
- }
- uint32_t CompilerGLSL::type_to_packed_size(const SPIRType &type, const Bitset &flags, BufferPackingStandard packing)
- {
- // If using PhysicalStorageBuffer storage class, this is a pointer,
- // and is 64-bit.
- if (is_physical_pointer(type))
- {
- if (!type.pointer)
- SPIRV_CROSS_THROW("Types in PhysicalStorageBuffer must be pointers.");
- if (ir.addressing_model == AddressingModelPhysicalStorageBuffer64)
- return 8;
- else
- SPIRV_CROSS_THROW("AddressingModelPhysicalStorageBuffer64 must be used for PhysicalStorageBuffer.");
- }
- else if (is_array(type))
- {
- uint32_t packed_size = to_array_size_literal(type) * type_to_packed_array_stride(type, flags, packing);
- // For arrays of vectors and matrices in HLSL, the last element has a size which depends on its vector size,
- // so that it is possible to pack other vectors into the last element.
- if (packing_is_hlsl(packing) && type.basetype != SPIRType::Struct)
- packed_size -= (4 - type.vecsize) * (type.width / 8);
- return packed_size;
- }
- uint32_t size = 0;
- if (type.basetype == SPIRType::Struct)
- {
- uint32_t pad_alignment = 1;
- for (uint32_t i = 0; i < type.member_types.size(); i++)
- {
- auto member_flags = ir.meta[type.self].members[i].decoration_flags;
- auto &member_type = get<SPIRType>(type.member_types[i]);
- uint32_t packed_alignment = type_to_packed_alignment(member_type, member_flags, packing);
- uint32_t alignment = max(packed_alignment, pad_alignment);
- // The next member following a struct member is aligned to the base alignment of the struct that came before.
- // GL 4.5 spec, 7.6.2.2.
- if (member_type.basetype == SPIRType::Struct)
- pad_alignment = packed_alignment;
- else
- pad_alignment = 1;
- size = (size + alignment - 1) & ~(alignment - 1);
- size += type_to_packed_size(member_type, member_flags, packing);
- }
- }
- else
- {
- const uint32_t base_alignment = type_to_packed_base_size(type, packing);
- if (packing_is_scalar(packing))
- {
- size = type.vecsize * type.columns * base_alignment;
- }
- else
- {
- if (type.columns == 1)
- size = type.vecsize * base_alignment;
- if (flags.get(DecorationColMajor) && type.columns > 1)
- {
- if (packing_is_vec4_padded(packing))
- size = type.columns * 4 * base_alignment;
- else if (type.vecsize == 3)
- size = type.columns * 4 * base_alignment;
- else
- size = type.columns * type.vecsize * base_alignment;
- }
- if (flags.get(DecorationRowMajor) && type.vecsize > 1)
- {
- if (packing_is_vec4_padded(packing))
- size = type.vecsize * 4 * base_alignment;
- else if (type.columns == 3)
- size = type.vecsize * 4 * base_alignment;
- else
- size = type.vecsize * type.columns * base_alignment;
- }
- // For matrices in HLSL, the last element has a size which depends on its vector size,
- // so that it is possible to pack other vectors into the last element.
- if (packing_is_hlsl(packing) && type.columns > 1)
- size -= (4 - type.vecsize) * (type.width / 8);
- }
- }
- return size;
- }
- bool CompilerGLSL::buffer_is_packing_standard(const SPIRType &type, BufferPackingStandard packing,
- uint32_t *failed_validation_index, uint32_t start_offset,
- uint32_t end_offset)
- {
- // This is very tricky and error prone, but try to be exhaustive and correct here.
- // SPIR-V doesn't directly say if we're using std430 or std140.
- // SPIR-V communicates this using Offset and ArrayStride decorations (which is what really matters),
- // so we have to try to infer whether or not the original GLSL source was std140 or std430 based on this information.
- // We do not have to consider shared or packed since these layouts are not allowed in Vulkan SPIR-V (they are useless anyways, and custom offsets would do the same thing).
- //
- // It is almost certain that we're using std430, but it gets tricky with arrays in particular.
- // We will assume std430, but infer std140 if we can prove the struct is not compliant with std430.
- //
- // The only two differences between std140 and std430 are related to padding alignment/array stride
- // in arrays and structs. In std140 they take minimum vec4 alignment.
- // std430 only removes the vec4 requirement.
- uint32_t offset = 0;
- uint32_t pad_alignment = 1;
- bool is_top_level_block =
- has_decoration(type.self, DecorationBlock) || has_decoration(type.self, DecorationBufferBlock);
- for (uint32_t i = 0; i < type.member_types.size(); i++)
- {
- auto &memb_type = get<SPIRType>(type.member_types[i]);
- auto *type_meta = ir.find_meta(type.self);
- auto member_flags = type_meta ? type_meta->members[i].decoration_flags : Bitset{};
- // Verify alignment rules.
- uint32_t packed_alignment = type_to_packed_alignment(memb_type, member_flags, packing);
- // This is a rather dirty workaround to deal with some cases of OpSpecConstantOp used as array size, e.g:
- // layout(constant_id = 0) const int s = 10;
- // const int S = s + 5; // SpecConstantOp
- // buffer Foo { int data[S]; }; // <-- Very hard for us to deduce a fixed value here,
- // we would need full implementation of compile-time constant folding. :(
- // If we are the last member of a struct, there might be cases where the actual size of that member is irrelevant
- // for our analysis (e.g. unsized arrays).
- // This lets us simply ignore that there are spec constant op sized arrays in our buffers.
- // Querying size of this member will fail, so just don't call it unless we have to.
- //
- // This is likely "best effort" we can support without going into unacceptably complicated workarounds.
- bool member_can_be_unsized =
- is_top_level_block && size_t(i + 1) == type.member_types.size() && !memb_type.array.empty();
- uint32_t packed_size = 0;
- if (!member_can_be_unsized || packing_is_hlsl(packing))
- packed_size = type_to_packed_size(memb_type, member_flags, packing);
- // We only need to care about this if we have non-array types which can straddle the vec4 boundary.
- uint32_t actual_offset = type_struct_member_offset(type, i);
- if (packing_is_hlsl(packing))
- {
- // If a member straddles across a vec4 boundary, alignment is actually vec4.
- uint32_t target_offset;
- // If we intend to use explicit packing, we must check for improper straddle with that offset.
- // In implicit packing, we must check with implicit offset, since the explicit offset
- // might have already accounted for the straddle, and we'd miss the alignment promotion to vec4.
- // This is important when packing sub-structs that don't support packoffset().
- if (packing_has_flexible_offset(packing))
- target_offset = actual_offset;
- else
- target_offset = offset;
- uint32_t begin_word = target_offset / 16;
- uint32_t end_word = (target_offset + packed_size - 1) / 16;
- if (begin_word != end_word)
- packed_alignment = max<uint32_t>(packed_alignment, 16u);
- }
- // Field is not in the specified range anymore and we can ignore any further fields.
- if (actual_offset >= end_offset)
- break;
- uint32_t alignment = max(packed_alignment, pad_alignment);
- offset = (offset + alignment - 1) & ~(alignment - 1);
- // The next member following a struct member is aligned to the base alignment of the struct that came before.
- // GL 4.5 spec, 7.6.2.2.
- if (memb_type.basetype == SPIRType::Struct && !memb_type.pointer)
- pad_alignment = packed_alignment;
- else
- pad_alignment = 1;
- // Only care about packing if we are in the given range
- if (actual_offset >= start_offset)
- {
- // We only care about offsets in std140, std430, etc ...
- // For EnhancedLayout variants, we have the flexibility to choose our own offsets.
- if (!packing_has_flexible_offset(packing))
- {
- if (actual_offset != offset) // This cannot be the packing we're looking for.
- {
- if (failed_validation_index)
- *failed_validation_index = i;
- return false;
- }
- }
- else if ((actual_offset & (alignment - 1)) != 0)
- {
- // We still need to verify that alignment rules are observed, even if we have explicit offset.
- if (failed_validation_index)
- *failed_validation_index = i;
- return false;
- }
- // Verify array stride rules.
- if (is_array(memb_type) &&
- type_to_packed_array_stride(memb_type, member_flags, packing) !=
- type_struct_member_array_stride(type, i))
- {
- if (failed_validation_index)
- *failed_validation_index = i;
- return false;
- }
- // Verify that sub-structs also follow packing rules.
- // We cannot use enhanced layouts on substructs, so they better be up to spec.
- auto substruct_packing = packing_to_substruct_packing(packing);
- if (!memb_type.pointer && !memb_type.member_types.empty() &&
- !buffer_is_packing_standard(memb_type, substruct_packing))
- {
- if (failed_validation_index)
- *failed_validation_index = i;
- return false;
- }
- }
- // Bump size.
- offset = actual_offset + packed_size;
- }
- return true;
- }
- bool CompilerGLSL::can_use_io_location(StorageClass storage, bool block)
- {
- // Location specifiers are must have in SPIR-V, but they aren't really supported in earlier versions of GLSL.
- // Be very explicit here about how to solve the issue.
- if ((get_execution_model() != ExecutionModelVertex && storage == StorageClassInput) ||
- (get_execution_model() != ExecutionModelFragment && storage == StorageClassOutput))
- {
- uint32_t minimum_desktop_version = block ? 440 : 410;
- // ARB_enhanced_layouts vs ARB_separate_shader_objects ...
- if (!options.es && options.version < minimum_desktop_version && !options.separate_shader_objects)
- return false;
- else if (options.es && options.version < 310)
- return false;
- }
- if ((get_execution_model() == ExecutionModelVertex && storage == StorageClassInput) ||
- (get_execution_model() == ExecutionModelFragment && storage == StorageClassOutput))
- {
- if (options.es && options.version < 300)
- return false;
- else if (!options.es && options.version < 330)
- return false;
- }
- if (storage == StorageClassUniform || storage == StorageClassUniformConstant || storage == StorageClassPushConstant)
- {
- if (options.es && options.version < 310)
- return false;
- else if (!options.es && options.version < 430)
- return false;
- }
- return true;
- }
- string CompilerGLSL::layout_for_variable(const SPIRVariable &var)
- {
- // FIXME: Come up with a better solution for when to disable layouts.
- // Having layouts depend on extensions as well as which types
- // of layouts are used. For now, the simple solution is to just disable
- // layouts for legacy versions.
- if (is_legacy())
- return "";
- if (subpass_input_is_framebuffer_fetch(var.self))
- return "";
- SmallVector<string> attr;
- auto &type = get<SPIRType>(var.basetype);
- auto &flags = get_decoration_bitset(var.self);
- auto &typeflags = get_decoration_bitset(type.self);
- if (flags.get(DecorationPassthroughNV))
- attr.push_back("passthrough");
- if (options.vulkan_semantics && var.storage == StorageClassPushConstant)
- attr.push_back("push_constant");
- else if (var.storage == StorageClassShaderRecordBufferKHR)
- attr.push_back(ray_tracing_is_khr ? "shaderRecordEXT" : "shaderRecordNV");
- if (flags.get(DecorationRowMajor))
- attr.push_back("row_major");
- if (flags.get(DecorationColMajor))
- attr.push_back("column_major");
- if (options.vulkan_semantics)
- {
- if (flags.get(DecorationInputAttachmentIndex))
- attr.push_back(join("input_attachment_index = ", get_decoration(var.self, DecorationInputAttachmentIndex)));
- }
- bool is_block = has_decoration(type.self, DecorationBlock);
- if (flags.get(DecorationLocation) && can_use_io_location(var.storage, is_block))
- {
- Bitset combined_decoration;
- for (uint32_t i = 0; i < ir.meta[type.self].members.size(); i++)
- combined_decoration.merge_or(combined_decoration_for_member(type, i));
- // If our members have location decorations, we don't need to
- // emit location decorations at the top as well (looks weird).
- if (!combined_decoration.get(DecorationLocation))
- attr.push_back(join("location = ", get_decoration(var.self, DecorationLocation)));
- }
- if (get_execution_model() == ExecutionModelFragment && var.storage == StorageClassOutput &&
- location_is_non_coherent_framebuffer_fetch(get_decoration(var.self, DecorationLocation)))
- {
- attr.push_back("noncoherent");
- }
- // Transform feedback
- bool uses_enhanced_layouts = false;
- if (is_block && var.storage == StorageClassOutput)
- {
- // For blocks, there is a restriction where xfb_stride/xfb_buffer must only be declared on the block itself,
- // since all members must match the same xfb_buffer. The only thing we will declare for members of the block
- // is the xfb_offset.
- uint32_t member_count = uint32_t(type.member_types.size());
- bool have_xfb_buffer_stride = false;
- bool have_any_xfb_offset = false;
- bool have_geom_stream = false;
- uint32_t xfb_stride = 0, xfb_buffer = 0, geom_stream = 0;
- if (flags.get(DecorationXfbBuffer) && flags.get(DecorationXfbStride))
- {
- have_xfb_buffer_stride = true;
- xfb_buffer = get_decoration(var.self, DecorationXfbBuffer);
- xfb_stride = get_decoration(var.self, DecorationXfbStride);
- }
- if (flags.get(DecorationStream))
- {
- have_geom_stream = true;
- geom_stream = get_decoration(var.self, DecorationStream);
- }
- // Verify that none of the members violate our assumption.
- for (uint32_t i = 0; i < member_count; i++)
- {
- if (has_member_decoration(type.self, i, DecorationStream))
- {
- uint32_t member_geom_stream = get_member_decoration(type.self, i, DecorationStream);
- if (have_geom_stream && member_geom_stream != geom_stream)
- SPIRV_CROSS_THROW("IO block member Stream mismatch.");
- have_geom_stream = true;
- geom_stream = member_geom_stream;
- }
- // Only members with an Offset decoration participate in XFB.
- if (!has_member_decoration(type.self, i, DecorationOffset))
- continue;
- have_any_xfb_offset = true;
- if (has_member_decoration(type.self, i, DecorationXfbBuffer))
- {
- uint32_t buffer_index = get_member_decoration(type.self, i, DecorationXfbBuffer);
- if (have_xfb_buffer_stride && buffer_index != xfb_buffer)
- SPIRV_CROSS_THROW("IO block member XfbBuffer mismatch.");
- have_xfb_buffer_stride = true;
- xfb_buffer = buffer_index;
- }
- if (has_member_decoration(type.self, i, DecorationXfbStride))
- {
- uint32_t stride = get_member_decoration(type.self, i, DecorationXfbStride);
- if (have_xfb_buffer_stride && stride != xfb_stride)
- SPIRV_CROSS_THROW("IO block member XfbStride mismatch.");
- have_xfb_buffer_stride = true;
- xfb_stride = stride;
- }
- }
- if (have_xfb_buffer_stride && have_any_xfb_offset)
- {
- attr.push_back(join("xfb_buffer = ", xfb_buffer));
- attr.push_back(join("xfb_stride = ", xfb_stride));
- uses_enhanced_layouts = true;
- }
- if (have_geom_stream)
- {
- if (get_execution_model() != ExecutionModelGeometry)
- SPIRV_CROSS_THROW("Geometry streams can only be used in geometry shaders.");
- if (options.es)
- SPIRV_CROSS_THROW("Multiple geometry streams not supported in ESSL.");
- if (options.version < 400)
- require_extension_internal("GL_ARB_transform_feedback3");
- attr.push_back(join("stream = ", get_decoration(var.self, DecorationStream)));
- }
- }
- else if (var.storage == StorageClassOutput)
- {
- if (flags.get(DecorationXfbBuffer) && flags.get(DecorationXfbStride) && flags.get(DecorationOffset))
- {
- // XFB for standalone variables, we can emit all decorations.
- attr.push_back(join("xfb_buffer = ", get_decoration(var.self, DecorationXfbBuffer)));
- attr.push_back(join("xfb_stride = ", get_decoration(var.self, DecorationXfbStride)));
- attr.push_back(join("xfb_offset = ", get_decoration(var.self, DecorationOffset)));
- uses_enhanced_layouts = true;
- }
- if (flags.get(DecorationStream))
- {
- if (get_execution_model() != ExecutionModelGeometry)
- SPIRV_CROSS_THROW("Geometry streams can only be used in geometry shaders.");
- if (options.es)
- SPIRV_CROSS_THROW("Multiple geometry streams not supported in ESSL.");
- if (options.version < 400)
- require_extension_internal("GL_ARB_transform_feedback3");
- attr.push_back(join("stream = ", get_decoration(var.self, DecorationStream)));
- }
- }
- // Can only declare Component if we can declare location.
- if (flags.get(DecorationComponent) && can_use_io_location(var.storage, is_block))
- {
- uses_enhanced_layouts = true;
- attr.push_back(join("component = ", get_decoration(var.self, DecorationComponent)));
- }
- if (uses_enhanced_layouts)
- {
- if (!options.es)
- {
- if (options.version < 440 && options.version >= 140)
- require_extension_internal("GL_ARB_enhanced_layouts");
- else if (options.version < 140)
- SPIRV_CROSS_THROW("GL_ARB_enhanced_layouts is not supported in targets below GLSL 1.40.");
- if (!options.es && options.version < 440)
- require_extension_internal("GL_ARB_enhanced_layouts");
- }
- else if (options.es)
- SPIRV_CROSS_THROW("GL_ARB_enhanced_layouts is not supported in ESSL.");
- }
- if (flags.get(DecorationIndex))
- attr.push_back(join("index = ", get_decoration(var.self, DecorationIndex)));
- // Do not emit set = decoration in regular GLSL output, but
- // we need to preserve it in Vulkan GLSL mode.
- if (var.storage != StorageClassPushConstant && var.storage != StorageClassShaderRecordBufferKHR)
- {
- if (flags.get(DecorationDescriptorSet) && options.vulkan_semantics)
- attr.push_back(join("set = ", get_decoration(var.self, DecorationDescriptorSet)));
- }
- bool push_constant_block = options.vulkan_semantics && var.storage == StorageClassPushConstant;
- bool ssbo_block = var.storage == StorageClassStorageBuffer || var.storage == StorageClassShaderRecordBufferKHR ||
- (var.storage == StorageClassUniform && typeflags.get(DecorationBufferBlock));
- bool emulated_ubo = var.storage == StorageClassPushConstant && options.emit_push_constant_as_uniform_buffer;
- bool ubo_block = var.storage == StorageClassUniform && typeflags.get(DecorationBlock);
- // GL 3.0/GLSL 1.30 is not considered legacy, but it doesn't have UBOs ...
- bool can_use_buffer_blocks = (options.es && options.version >= 300) || (!options.es && options.version >= 140);
- // pretend no UBOs when options say so
- if (ubo_block && options.emit_uniform_buffer_as_plain_uniforms)
- can_use_buffer_blocks = false;
- bool can_use_binding;
- if (options.es)
- can_use_binding = options.version >= 310;
- else
- can_use_binding = options.enable_420pack_extension || (options.version >= 420);
- // Make sure we don't emit binding layout for a classic uniform on GLSL 1.30.
- if (!can_use_buffer_blocks && var.storage == StorageClassUniform)
- can_use_binding = false;
- if (var.storage == StorageClassShaderRecordBufferKHR)
- can_use_binding = false;
- if (can_use_binding && flags.get(DecorationBinding))
- attr.push_back(join("binding = ", get_decoration(var.self, DecorationBinding)));
- if (var.storage != StorageClassOutput && flags.get(DecorationOffset))
- attr.push_back(join("offset = ", get_decoration(var.self, DecorationOffset)));
- // Instead of adding explicit offsets for every element here, just assume we're using std140 or std430.
- // If SPIR-V does not comply with either layout, we cannot really work around it.
- if (can_use_buffer_blocks && (ubo_block || emulated_ubo))
- {
- attr.push_back(buffer_to_packing_standard(type, false, true));
- }
- else if (can_use_buffer_blocks && (push_constant_block || ssbo_block))
- {
- attr.push_back(buffer_to_packing_standard(type, true, true));
- }
- // For images, the type itself adds a layout qualifer.
- // Only emit the format for storage images.
- if (type.basetype == SPIRType::Image && type.image.sampled == 2)
- {
- const char *fmt = format_to_glsl(type.image.format);
- if (fmt)
- attr.push_back(fmt);
- }
- if (attr.empty())
- return "";
- string res = "layout(";
- res += merge(attr);
- res += ") ";
- return res;
- }
- string CompilerGLSL::buffer_to_packing_standard(const SPIRType &type,
- bool support_std430_without_scalar_layout,
- bool support_enhanced_layouts)
- {
- if (support_std430_without_scalar_layout && buffer_is_packing_standard(type, BufferPackingStd430))
- return "std430";
- else if (buffer_is_packing_standard(type, BufferPackingStd140))
- return "std140";
- else if (options.vulkan_semantics && buffer_is_packing_standard(type, BufferPackingScalar))
- {
- require_extension_internal("GL_EXT_scalar_block_layout");
- return "scalar";
- }
- else if (support_std430_without_scalar_layout &&
- support_enhanced_layouts &&
- buffer_is_packing_standard(type, BufferPackingStd430EnhancedLayout))
- {
- if (options.es && !options.vulkan_semantics)
- SPIRV_CROSS_THROW("Push constant block cannot be expressed as neither std430 nor std140. ES-targets do "
- "not support GL_ARB_enhanced_layouts.");
- if (!options.es && !options.vulkan_semantics && options.version < 440)
- require_extension_internal("GL_ARB_enhanced_layouts");
- set_extended_decoration(type.self, SPIRVCrossDecorationExplicitOffset);
- return "std430";
- }
- else if (support_enhanced_layouts &&
- buffer_is_packing_standard(type, BufferPackingStd140EnhancedLayout))
- {
- // Fallback time. We might be able to use the ARB_enhanced_layouts to deal with this difference,
- // however, we can only use layout(offset) on the block itself, not any substructs, so the substructs better be the appropriate layout.
- // Enhanced layouts seem to always work in Vulkan GLSL, so no need for extensions there.
- if (options.es && !options.vulkan_semantics)
- SPIRV_CROSS_THROW("Push constant block cannot be expressed as neither std430 nor std140. ES-targets do "
- "not support GL_ARB_enhanced_layouts.");
- if (!options.es && !options.vulkan_semantics && options.version < 440)
- require_extension_internal("GL_ARB_enhanced_layouts");
- set_extended_decoration(type.self, SPIRVCrossDecorationExplicitOffset);
- return "std140";
- }
- else if (options.vulkan_semantics &&
- support_enhanced_layouts &&
- buffer_is_packing_standard(type, BufferPackingScalarEnhancedLayout))
- {
- set_extended_decoration(type.self, SPIRVCrossDecorationExplicitOffset);
- require_extension_internal("GL_EXT_scalar_block_layout");
- return "scalar";
- }
- else if (!support_std430_without_scalar_layout && options.vulkan_semantics &&
- buffer_is_packing_standard(type, BufferPackingStd430))
- {
- // UBOs can support std430 with GL_EXT_scalar_block_layout.
- require_extension_internal("GL_EXT_scalar_block_layout");
- return "std430";
- }
- else if (!support_std430_without_scalar_layout && options.vulkan_semantics &&
- support_enhanced_layouts &&
- buffer_is_packing_standard(type, BufferPackingStd430EnhancedLayout))
- {
- // UBOs can support std430 with GL_EXT_scalar_block_layout.
- set_extended_decoration(type.self, SPIRVCrossDecorationExplicitOffset);
- require_extension_internal("GL_EXT_scalar_block_layout");
- return "std430";
- }
- else
- {
- SPIRV_CROSS_THROW("Buffer block cannot be expressed as any of std430, std140, scalar, even with enhanced "
- "layouts. You can try flattening this block to support a more flexible layout.");
- }
- }
- void CompilerGLSL::emit_push_constant_block(const SPIRVariable &var)
- {
- if (flattened_buffer_blocks.count(var.self))
- emit_buffer_block_flattened(var);
- else if (options.vulkan_semantics)
- emit_push_constant_block_vulkan(var);
- else if (options.emit_push_constant_as_uniform_buffer)
- emit_buffer_block_native(var);
- else
- emit_push_constant_block_glsl(var);
- }
- void CompilerGLSL::emit_push_constant_block_vulkan(const SPIRVariable &var)
- {
- emit_buffer_block(var);
- }
- void CompilerGLSL::emit_push_constant_block_glsl(const SPIRVariable &var)
- {
- // OpenGL has no concept of push constant blocks, implement it as a uniform struct.
- auto &type = get<SPIRType>(var.basetype);
- unset_decoration(var.self, DecorationBinding);
- unset_decoration(var.self, DecorationDescriptorSet);
- #if 0
- if (flags & ((1ull << DecorationBinding) | (1ull << DecorationDescriptorSet)))
- SPIRV_CROSS_THROW("Push constant blocks cannot be compiled to GLSL with Binding or Set syntax. "
- "Remap to location with reflection API first or disable these decorations.");
- #endif
- // We're emitting the push constant block as a regular struct, so disable the block qualifier temporarily.
- // Otherwise, we will end up emitting layout() qualifiers on naked structs which is not allowed.
- bool block_flag = has_decoration(type.self, DecorationBlock);
- unset_decoration(type.self, DecorationBlock);
- emit_struct(type);
- if (block_flag)
- set_decoration(type.self, DecorationBlock);
- emit_uniform(var);
- statement("");
- }
- void CompilerGLSL::emit_buffer_block(const SPIRVariable &var)
- {
- auto &type = get<SPIRType>(var.basetype);
- bool ubo_block = var.storage == StorageClassUniform && has_decoration(type.self, DecorationBlock);
- if (flattened_buffer_blocks.count(var.self))
- emit_buffer_block_flattened(var);
- else if (is_legacy() || (!options.es && options.version == 130) ||
- (ubo_block && options.emit_uniform_buffer_as_plain_uniforms))
- emit_buffer_block_legacy(var);
- else
- emit_buffer_block_native(var);
- }
- void CompilerGLSL::emit_buffer_block_legacy(const SPIRVariable &var)
- {
- auto &type = get<SPIRType>(var.basetype);
- bool ssbo = var.storage == StorageClassStorageBuffer ||
- ir.meta[type.self].decoration.decoration_flags.get(DecorationBufferBlock);
- if (ssbo)
- SPIRV_CROSS_THROW("SSBOs not supported in legacy targets.");
- // We're emitting the push constant block as a regular struct, so disable the block qualifier temporarily.
- // Otherwise, we will end up emitting layout() qualifiers on naked structs which is not allowed.
- auto &block_flags = ir.meta[type.self].decoration.decoration_flags;
- bool block_flag = block_flags.get(DecorationBlock);
- block_flags.clear(DecorationBlock);
- emit_struct(type);
- if (block_flag)
- block_flags.set(DecorationBlock);
- emit_uniform(var);
- statement("");
- }
- void CompilerGLSL::emit_buffer_reference_block(uint32_t type_id, bool forward_declaration)
- {
- auto &type = get<SPIRType>(type_id);
- string buffer_name;
- if (forward_declaration && is_physical_pointer_to_buffer_block(type))
- {
- // Block names should never alias, but from HLSL input they kind of can because block types are reused for UAVs ...
- // Allow aliased name since we might be declaring the block twice. Once with buffer reference (forward declared) and one proper declaration.
- // The names must match up.
- buffer_name = to_name(type.self, false);
- // Shaders never use the block by interface name, so we don't
- // have to track this other than updating name caches.
- // If we have a collision for any reason, just fallback immediately.
- if (ir.meta[type.self].decoration.alias.empty() ||
- block_ssbo_names.find(buffer_name) != end(block_ssbo_names) ||
- resource_names.find(buffer_name) != end(resource_names))
- {
- buffer_name = join("_", type.self);
- }
- // Make sure we get something unique for both global name scope and block name scope.
- // See GLSL 4.5 spec: section 4.3.9 for details.
- add_variable(block_ssbo_names, resource_names, buffer_name);
- // If for some reason buffer_name is an illegal name, make a final fallback to a workaround name.
- // This cannot conflict with anything else, so we're safe now.
- // We cannot reuse this fallback name in neither global scope (blocked by block_names) nor block name scope.
- if (buffer_name.empty())
- buffer_name = join("_", type.self);
- block_names.insert(buffer_name);
- block_ssbo_names.insert(buffer_name);
- // Ensure we emit the correct name when emitting non-forward pointer type.
- ir.meta[type.self].decoration.alias = buffer_name;
- }
- else
- {
- buffer_name = type_to_glsl(type);
- }
- if (!forward_declaration)
- {
- auto itr = physical_storage_type_to_alignment.find(type_id);
- uint32_t alignment = 0;
- if (itr != physical_storage_type_to_alignment.end())
- alignment = itr->second.alignment;
- if (is_physical_pointer_to_buffer_block(type))
- {
- SmallVector<std::string> attributes;
- attributes.push_back("buffer_reference");
- if (alignment)
- attributes.push_back(join("buffer_reference_align = ", alignment));
- attributes.push_back(buffer_to_packing_standard(type, true, true));
- auto flags = ir.get_buffer_block_type_flags(type);
- string decorations;
- if (flags.get(DecorationRestrict))
- decorations += " restrict";
- if (flags.get(DecorationCoherent))
- decorations += " coherent";
- if (flags.get(DecorationNonReadable))
- decorations += " writeonly";
- if (flags.get(DecorationNonWritable))
- decorations += " readonly";
- statement("layout(", merge(attributes), ")", decorations, " buffer ", buffer_name);
- }
- else
- {
- string packing_standard;
- if (type.basetype == SPIRType::Struct)
- {
- // The non-block type is embedded in a block, so we cannot use enhanced layouts :(
- packing_standard = buffer_to_packing_standard(type, true, false) + ", ";
- }
- else if (is_array(get_pointee_type(type)))
- {
- SPIRType wrap_type{OpTypeStruct};
- wrap_type.self = ir.increase_bound_by(1);
- wrap_type.member_types.push_back(get_pointee_type_id(type_id));
- ir.set_member_decoration(wrap_type.self, 0, DecorationOffset, 0);
- packing_standard = buffer_to_packing_standard(wrap_type, true, false) + ", ";
- }
- if (alignment)
- statement("layout(", packing_standard, "buffer_reference, buffer_reference_align = ", alignment, ") buffer ", buffer_name);
- else
- statement("layout(", packing_standard, "buffer_reference) buffer ", buffer_name);
- }
- begin_scope();
- if (is_physical_pointer_to_buffer_block(type))
- {
- type.member_name_cache.clear();
- uint32_t i = 0;
- for (auto &member : type.member_types)
- {
- add_member_name(type, i);
- emit_struct_member(type, member, i);
- i++;
- }
- }
- else
- {
- auto &pointee_type = get_pointee_type(type);
- statement(type_to_glsl(pointee_type), " value", type_to_array_glsl(pointee_type, 0), ";");
- }
- end_scope_decl();
- statement("");
- }
- else
- {
- statement("layout(buffer_reference) buffer ", buffer_name, ";");
- }
- }
- void CompilerGLSL::emit_buffer_block_native(const SPIRVariable &var)
- {
- auto &type = get<SPIRType>(var.basetype);
- Bitset flags = ir.get_buffer_block_flags(var);
- bool ssbo = var.storage == StorageClassStorageBuffer || var.storage == StorageClassShaderRecordBufferKHR ||
- ir.meta[type.self].decoration.decoration_flags.get(DecorationBufferBlock);
- bool is_restrict = ssbo && flags.get(DecorationRestrict);
- bool is_writeonly = ssbo && flags.get(DecorationNonReadable);
- bool is_readonly = ssbo && flags.get(DecorationNonWritable);
- bool is_coherent = ssbo && flags.get(DecorationCoherent);
- // Block names should never alias, but from HLSL input they kind of can because block types are reused for UAVs ...
- auto buffer_name = to_name(type.self, false);
- auto &block_namespace = ssbo ? block_ssbo_names : block_ubo_names;
- // Shaders never use the block by interface name, so we don't
- // have to track this other than updating name caches.
- // If we have a collision for any reason, just fallback immediately.
- if (ir.meta[type.self].decoration.alias.empty() || block_namespace.find(buffer_name) != end(block_namespace) ||
- resource_names.find(buffer_name) != end(resource_names))
- {
- buffer_name = get_block_fallback_name(var.self);
- }
- // Make sure we get something unique for both global name scope and block name scope.
- // See GLSL 4.5 spec: section 4.3.9 for details.
- add_variable(block_namespace, resource_names, buffer_name);
- // If for some reason buffer_name is an illegal name, make a final fallback to a workaround name.
- // This cannot conflict with anything else, so we're safe now.
- // We cannot reuse this fallback name in neither global scope (blocked by block_names) nor block name scope.
- if (buffer_name.empty())
- buffer_name = join("_", get<SPIRType>(var.basetype).self, "_", var.self);
- block_names.insert(buffer_name);
- block_namespace.insert(buffer_name);
- // Save for post-reflection later.
- declared_block_names[var.self] = buffer_name;
- statement(layout_for_variable(var), is_coherent ? "coherent " : "", is_restrict ? "restrict " : "",
- is_writeonly ? "writeonly " : "", is_readonly ? "readonly " : "", ssbo ? "buffer " : "uniform ",
- buffer_name);
- begin_scope();
- type.member_name_cache.clear();
- uint32_t i = 0;
- for (auto &member : type.member_types)
- {
- add_member_name(type, i);
- emit_struct_member(type, member, i);
- i++;
- }
- // Don't declare empty blocks in GLSL, this is not allowed.
- if (type_is_empty(type) && !backend.supports_empty_struct)
- statement("int empty_struct_member;");
- // var.self can be used as a backup name for the block name,
- // so we need to make sure we don't disturb the name here on a recompile.
- // It will need to be reset if we have to recompile.
- preserve_alias_on_reset(var.self);
- add_resource_name(var.self);
- end_scope_decl(to_name(var.self) + type_to_array_glsl(type, var.self));
- statement("");
- }
- void CompilerGLSL::emit_buffer_block_flattened(const SPIRVariable &var)
- {
- auto &type = get<SPIRType>(var.basetype);
- // Block names should never alias.
- auto buffer_name = to_name(type.self, false);
- size_t buffer_size = (get_declared_struct_size(type) + 15) / 16;
- SPIRType::BaseType basic_type;
- if (get_common_basic_type(type, basic_type))
- {
- SPIRType tmp { OpTypeVector };
- tmp.basetype = basic_type;
- tmp.vecsize = 4;
- if (basic_type != SPIRType::Float && basic_type != SPIRType::Int && basic_type != SPIRType::UInt)
- SPIRV_CROSS_THROW("Basic types in a flattened UBO must be float, int or uint.");
- auto flags = ir.get_buffer_block_flags(var);
- statement("uniform ", flags_to_qualifiers_glsl(tmp, 0, flags), type_to_glsl(tmp), " ", buffer_name, "[",
- buffer_size, "];");
- }
- else
- SPIRV_CROSS_THROW("All basic types in a flattened block must be the same.");
- }
- const char *CompilerGLSL::to_storage_qualifiers_glsl(const SPIRVariable &var)
- {
- auto &execution = get_entry_point();
- if (subpass_input_is_framebuffer_fetch(var.self))
- return "";
- if (var.storage == StorageClassInput || var.storage == StorageClassOutput)
- {
- if (is_legacy() && execution.model == ExecutionModelVertex)
- return var.storage == StorageClassInput ? "attribute " : "varying ";
- else if (is_legacy() && execution.model == ExecutionModelFragment)
- return "varying "; // Fragment outputs are renamed so they never hit this case.
- else if (execution.model == ExecutionModelFragment && var.storage == StorageClassOutput)
- {
- uint32_t loc = get_decoration(var.self, DecorationLocation);
- bool is_inout = location_is_framebuffer_fetch(loc);
- if (is_inout)
- return "inout ";
- else
- return "out ";
- }
- else
- return var.storage == StorageClassInput ? "in " : "out ";
- }
- else if (var.storage == StorageClassUniformConstant || var.storage == StorageClassUniform ||
- var.storage == StorageClassPushConstant || var.storage == StorageClassAtomicCounter)
- {
- return "uniform ";
- }
- else if (var.storage == StorageClassRayPayloadKHR)
- {
- return ray_tracing_is_khr ? "rayPayloadEXT " : "rayPayloadNV ";
- }
- else if (var.storage == StorageClassIncomingRayPayloadKHR)
- {
- return ray_tracing_is_khr ? "rayPayloadInEXT " : "rayPayloadInNV ";
- }
- else if (var.storage == StorageClassHitAttributeKHR)
- {
- return ray_tracing_is_khr ? "hitAttributeEXT " : "hitAttributeNV ";
- }
- else if (var.storage == StorageClassCallableDataKHR)
- {
- return ray_tracing_is_khr ? "callableDataEXT " : "callableDataNV ";
- }
- else if (var.storage == StorageClassIncomingCallableDataKHR)
- {
- return ray_tracing_is_khr ? "callableDataInEXT " : "callableDataInNV ";
- }
- return "";
- }
- void CompilerGLSL::emit_flattened_io_block_member(const std::string &basename, const SPIRType &type, const char *qual,
- const SmallVector<uint32_t> &indices)
- {
- uint32_t member_type_id = type.self;
- const SPIRType *member_type = &type;
- const SPIRType *parent_type = nullptr;
- auto flattened_name = basename;
- for (auto &index : indices)
- {
- flattened_name += "_";
- flattened_name += to_member_name(*member_type, index);
- parent_type = member_type;
- member_type_id = member_type->member_types[index];
- member_type = &get<SPIRType>(member_type_id);
- }
- assert(member_type->basetype != SPIRType::Struct);
- // We're overriding struct member names, so ensure we do so on the primary type.
- if (parent_type->type_alias)
- parent_type = &get<SPIRType>(parent_type->type_alias);
- // Sanitize underscores because joining the two identifiers might create more than 1 underscore in a row,
- // which is not allowed.
- ParsedIR::sanitize_underscores(flattened_name);
- uint32_t last_index = indices.back();
- // Pass in the varying qualifier here so it will appear in the correct declaration order.
- // Replace member name while emitting it so it encodes both struct name and member name.
- auto backup_name = get_member_name(parent_type->self, last_index);
- auto member_name = to_member_name(*parent_type, last_index);
- set_member_name(parent_type->self, last_index, flattened_name);
- emit_struct_member(*parent_type, member_type_id, last_index, qual);
- // Restore member name.
- set_member_name(parent_type->self, last_index, member_name);
- }
- void CompilerGLSL::emit_flattened_io_block_struct(const std::string &basename, const SPIRType &type, const char *qual,
- const SmallVector<uint32_t> &indices)
- {
- auto sub_indices = indices;
- sub_indices.push_back(0);
- const SPIRType *member_type = &type;
- for (auto &index : indices)
- member_type = &get<SPIRType>(member_type->member_types[index]);
- assert(member_type->basetype == SPIRType::Struct);
- if (!member_type->array.empty())
- SPIRV_CROSS_THROW("Cannot flatten array of structs in I/O blocks.");
- for (uint32_t i = 0; i < uint32_t(member_type->member_types.size()); i++)
- {
- sub_indices.back() = i;
- if (get<SPIRType>(member_type->member_types[i]).basetype == SPIRType::Struct)
- emit_flattened_io_block_struct(basename, type, qual, sub_indices);
- else
- emit_flattened_io_block_member(basename, type, qual, sub_indices);
- }
- }
- void CompilerGLSL::emit_flattened_io_block(const SPIRVariable &var, const char *qual)
- {
- auto &var_type = get<SPIRType>(var.basetype);
- if (!var_type.array.empty())
- SPIRV_CROSS_THROW("Array of varying structs cannot be flattened to legacy-compatible varyings.");
- // Emit flattened types based on the type alias. Normally, we are never supposed to emit
- // struct declarations for aliased types.
- auto &type = var_type.type_alias ? get<SPIRType>(var_type.type_alias) : var_type;
- auto old_flags = ir.meta[type.self].decoration.decoration_flags;
- // Emit the members as if they are part of a block to get all qualifiers.
- ir.meta[type.self].decoration.decoration_flags.set(DecorationBlock);
- type.member_name_cache.clear();
- SmallVector<uint32_t> member_indices;
- member_indices.push_back(0);
- auto basename = to_name(var.self);
- uint32_t i = 0;
- for (auto &member : type.member_types)
- {
- add_member_name(type, i);
- auto &membertype = get<SPIRType>(member);
- member_indices.back() = i;
- if (membertype.basetype == SPIRType::Struct)
- emit_flattened_io_block_struct(basename, type, qual, member_indices);
- else
- emit_flattened_io_block_member(basename, type, qual, member_indices);
- i++;
- }
- ir.meta[type.self].decoration.decoration_flags = old_flags;
- // Treat this variable as fully flattened from now on.
- flattened_structs[var.self] = true;
- }
- void CompilerGLSL::emit_interface_block(const SPIRVariable &var)
- {
- auto &type = get<SPIRType>(var.basetype);
- if (var.storage == StorageClassInput && type.basetype == SPIRType::Double &&
- !options.es && options.version < 410)
- {
- require_extension_internal("GL_ARB_vertex_attrib_64bit");
- }
- // Either make it plain in/out or in/out blocks depending on what shader is doing ...
- bool block = ir.meta[type.self].decoration.decoration_flags.get(DecorationBlock);
- const char *qual = to_storage_qualifiers_glsl(var);
- if (block)
- {
- // ESSL earlier than 310 and GLSL earlier than 150 did not support
- // I/O variables which are struct types.
- // To support this, flatten the struct into separate varyings instead.
- if (options.force_flattened_io_blocks || (options.es && options.version < 310) ||
- (!options.es && options.version < 150))
- {
- // I/O blocks on ES require version 310 with Android Extension Pack extensions, or core version 320.
- // On desktop, I/O blocks were introduced with geometry shaders in GL 3.2 (GLSL 150).
- emit_flattened_io_block(var, qual);
- }
- else
- {
- if (options.es && options.version < 320)
- {
- // Geometry and tessellation extensions imply this extension.
- if (!has_extension("GL_EXT_geometry_shader") && !has_extension("GL_EXT_tessellation_shader"))
- require_extension_internal("GL_EXT_shader_io_blocks");
- }
- // Workaround to make sure we can emit "patch in/out" correctly.
- fixup_io_block_patch_primitive_qualifiers(var);
- // Block names should never alias.
- auto block_name = to_name(type.self, false);
- // The namespace for I/O blocks is separate from other variables in GLSL.
- auto &block_namespace = type.storage == StorageClassInput ? block_input_names : block_output_names;
- // Shaders never use the block by interface name, so we don't
- // have to track this other than updating name caches.
- if (block_name.empty() || block_namespace.find(block_name) != end(block_namespace))
- block_name = get_fallback_name(type.self);
- else
- block_namespace.insert(block_name);
- // If for some reason buffer_name is an illegal name, make a final fallback to a workaround name.
- // This cannot conflict with anything else, so we're safe now.
- if (block_name.empty())
- block_name = join("_", get<SPIRType>(var.basetype).self, "_", var.self);
- // Instance names cannot alias block names.
- resource_names.insert(block_name);
- const char *block_qualifier;
- if (has_decoration(var.self, DecorationPatch))
- block_qualifier = "patch ";
- else if (has_decoration(var.self, DecorationPerPrimitiveEXT))
- block_qualifier = "perprimitiveEXT ";
- else if (has_decoration(var.self, DecorationPerVertexKHR))
- block_qualifier = "pervertexEXT ";
- else
- block_qualifier = "";
- statement(layout_for_variable(var), block_qualifier, qual, block_name);
- begin_scope();
- type.member_name_cache.clear();
- uint32_t i = 0;
- for (auto &member : type.member_types)
- {
- add_member_name(type, i);
- emit_struct_member(type, member, i);
- i++;
- }
- add_resource_name(var.self);
- end_scope_decl(join(to_name(var.self), type_to_array_glsl(type, var.self)));
- statement("");
- }
- }
- else
- {
- // ESSL earlier than 310 and GLSL earlier than 150 did not support
- // I/O variables which are struct types.
- // To support this, flatten the struct into separate varyings instead.
- if (type.basetype == SPIRType::Struct &&
- (options.force_flattened_io_blocks || (options.es && options.version < 310) ||
- (!options.es && options.version < 150)))
- {
- emit_flattened_io_block(var, qual);
- }
- else
- {
- add_resource_name(var.self);
- // Legacy GLSL did not support int attributes, we automatically
- // declare them as float and cast them on load/store
- SPIRType newtype = type;
- if (is_legacy() && var.storage == StorageClassInput && type.basetype == SPIRType::Int)
- newtype.basetype = SPIRType::Float;
- // Tessellation control and evaluation shaders must have either
- // gl_MaxPatchVertices or unsized arrays for input arrays.
- // Opt for unsized as it's the more "correct" variant to use.
- if (type.storage == StorageClassInput && !type.array.empty() &&
- !has_decoration(var.self, DecorationPatch) &&
- (get_entry_point().model == ExecutionModelTessellationControl ||
- get_entry_point().model == ExecutionModelTessellationEvaluation))
- {
- newtype.array.back() = 0;
- newtype.array_size_literal.back() = true;
- }
- statement(layout_for_variable(var), to_qualifiers_glsl(var.self),
- variable_decl(newtype, to_name(var.self), var.self), ";");
- }
- }
- }
- void CompilerGLSL::emit_uniform(const SPIRVariable &var)
- {
- auto &type = get<SPIRType>(var.basetype);
- if (type.basetype == SPIRType::Image && type.image.sampled == 2 && type.image.dim != DimSubpassData)
- {
- if (!options.es && options.version < 420)
- require_extension_internal("GL_ARB_shader_image_load_store");
- else if (options.es && options.version < 310)
- SPIRV_CROSS_THROW("At least ESSL 3.10 required for shader image load store.");
- }
- add_resource_name(var.self);
- statement(layout_for_variable(var), variable_decl(var), ";");
- }
- string CompilerGLSL::constant_value_macro_name(uint32_t id) const
- {
- return join("SPIRV_CROSS_CONSTANT_ID_", id);
- }
- void CompilerGLSL::emit_specialization_constant_op(const SPIRConstantOp &constant)
- {
- auto &type = get<SPIRType>(constant.basetype);
- // This will break. It is bogus and should not be legal.
- if (type_is_top_level_block(type))
- return;
- add_resource_name(constant.self);
- auto name = to_name(constant.self);
- statement("const ", variable_decl(type, name), " = ", constant_op_expression(constant), ";");
- }
- int CompilerGLSL::get_constant_mapping_to_workgroup_component(const SPIRConstant &c) const
- {
- auto &entry_point = get_entry_point();
- int index = -1;
- // Need to redirect specialization constants which are used as WorkGroupSize to the builtin,
- // since the spec constant declarations are never explicitly declared.
- if (entry_point.workgroup_size.constant == 0 && entry_point.flags.get(ExecutionModeLocalSizeId))
- {
- if (c.self == entry_point.workgroup_size.id_x)
- index = 0;
- else if (c.self == entry_point.workgroup_size.id_y)
- index = 1;
- else if (c.self == entry_point.workgroup_size.id_z)
- index = 2;
- }
- return index;
- }
- void CompilerGLSL::emit_constant(const SPIRConstant &constant)
- {
- auto &type = get<SPIRType>(constant.constant_type);
- // This will break. It is bogus and should not be legal.
- if (type_is_top_level_block(type))
- return;
- SpecializationConstant wg_x, wg_y, wg_z;
- ID workgroup_size_id = get_work_group_size_specialization_constants(wg_x, wg_y, wg_z);
- // This specialization constant is implicitly declared by emitting layout() in;
- if (constant.self == workgroup_size_id)
- return;
- // These specialization constants are implicitly declared by emitting layout() in;
- // In legacy GLSL, we will still need to emit macros for these, so a layout() in; declaration
- // later can use macro overrides for work group size.
- bool is_workgroup_size_constant = ConstantID(constant.self) == wg_x.id || ConstantID(constant.self) == wg_y.id ||
- ConstantID(constant.self) == wg_z.id;
- if (options.vulkan_semantics && is_workgroup_size_constant)
- {
- // Vulkan GLSL does not need to declare workgroup spec constants explicitly, it is handled in layout().
- return;
- }
- else if (!options.vulkan_semantics && is_workgroup_size_constant &&
- !has_decoration(constant.self, DecorationSpecId))
- {
- // Only bother declaring a workgroup size if it is actually a specialization constant, because we need macros.
- return;
- }
- add_resource_name(constant.self);
- auto name = to_name(constant.self);
- // Only scalars have constant IDs.
- if (has_decoration(constant.self, DecorationSpecId))
- {
- if (options.vulkan_semantics)
- {
- statement("layout(constant_id = ", get_decoration(constant.self, DecorationSpecId), ") const ",
- variable_decl(type, name), " = ", constant_expression(constant), ";");
- }
- else
- {
- const string ¯o_name = constant.specialization_constant_macro_name;
- statement("#ifndef ", macro_name);
- statement("#define ", macro_name, " ", constant_expression(constant));
- statement("#endif");
- // For workgroup size constants, only emit the macros.
- if (!is_workgroup_size_constant)
- statement("const ", variable_decl(type, name), " = ", macro_name, ";");
- }
- }
- else
- {
- statement("const ", variable_decl(type, name), " = ", constant_expression(constant), ";");
- }
- }
- void CompilerGLSL::emit_entry_point_declarations()
- {
- }
- void CompilerGLSL::replace_illegal_names(const unordered_set<string> &keywords)
- {
- ir.for_each_typed_id<SPIRVariable>([&](uint32_t, const SPIRVariable &var) {
- if (is_hidden_variable(var))
- return;
- auto *meta = ir.find_meta(var.self);
- if (!meta)
- return;
- auto &m = meta->decoration;
- if (keywords.find(m.alias) != end(keywords))
- m.alias = join("_", m.alias);
- });
- ir.for_each_typed_id<SPIRFunction>([&](uint32_t, const SPIRFunction &func) {
- auto *meta = ir.find_meta(func.self);
- if (!meta)
- return;
- auto &m = meta->decoration;
- if (keywords.find(m.alias) != end(keywords))
- m.alias = join("_", m.alias);
- });
- ir.for_each_typed_id<SPIRType>([&](uint32_t, const SPIRType &type) {
- auto *meta = ir.find_meta(type.self);
- if (!meta)
- return;
- auto &m = meta->decoration;
- if (keywords.find(m.alias) != end(keywords))
- m.alias = join("_", m.alias);
- for (auto &memb : meta->members)
- if (keywords.find(memb.alias) != end(keywords))
- memb.alias = join("_", memb.alias);
- });
- }
- void CompilerGLSL::replace_illegal_names()
- {
- // clang-format off
- static const unordered_set<string> keywords = {
- "abs", "acos", "acosh", "all", "any", "asin", "asinh", "atan", "atanh",
- "atomicAdd", "atomicCompSwap", "atomicCounter", "atomicCounterDecrement", "atomicCounterIncrement",
- "atomicExchange", "atomicMax", "atomicMin", "atomicOr", "atomicXor",
- "bitCount", "bitfieldExtract", "bitfieldInsert", "bitfieldReverse",
- "ceil", "cos", "cosh", "cross", "degrees",
- "dFdx", "dFdxCoarse", "dFdxFine",
- "dFdy", "dFdyCoarse", "dFdyFine",
- "distance", "dot", "EmitStreamVertex", "EmitVertex", "EndPrimitive", "EndStreamPrimitive", "equal", "exp", "exp2",
- "faceforward", "findLSB", "findMSB", "float16BitsToInt16", "float16BitsToUint16", "floatBitsToInt", "floatBitsToUint", "floor", "fma", "fract",
- "frexp", "fwidth", "fwidthCoarse", "fwidthFine",
- "greaterThan", "greaterThanEqual", "groupMemoryBarrier",
- "imageAtomicAdd", "imageAtomicAnd", "imageAtomicCompSwap", "imageAtomicExchange", "imageAtomicMax", "imageAtomicMin", "imageAtomicOr", "imageAtomicXor",
- "imageLoad", "imageSamples", "imageSize", "imageStore", "imulExtended", "int16BitsToFloat16", "intBitsToFloat", "interpolateAtOffset", "interpolateAtCentroid", "interpolateAtSample",
- "inverse", "inversesqrt", "isinf", "isnan", "ldexp", "length", "lessThan", "lessThanEqual", "log", "log2",
- "matrixCompMult", "max", "memoryBarrier", "memoryBarrierAtomicCounter", "memoryBarrierBuffer", "memoryBarrierImage", "memoryBarrierShared",
- "min", "mix", "mod", "modf", "noise", "noise1", "noise2", "noise3", "noise4", "normalize", "not", "notEqual",
- "outerProduct", "packDouble2x32", "packHalf2x16", "packInt2x16", "packInt4x16", "packSnorm2x16", "packSnorm4x8",
- "packUint2x16", "packUint4x16", "packUnorm2x16", "packUnorm4x8", "pow",
- "radians", "reflect", "refract", "round", "roundEven", "sign", "sin", "sinh", "smoothstep", "sqrt", "step",
- "tan", "tanh", "texelFetch", "texelFetchOffset", "texture", "textureGather", "textureGatherOffset", "textureGatherOffsets",
- "textureGrad", "textureGradOffset", "textureLod", "textureLodOffset", "textureOffset", "textureProj", "textureProjGrad",
- "textureProjGradOffset", "textureProjLod", "textureProjLodOffset", "textureProjOffset", "textureQueryLevels", "textureQueryLod", "textureSamples", "textureSize",
- "transpose", "trunc", "uaddCarry", "uint16BitsToFloat16", "uintBitsToFloat", "umulExtended", "unpackDouble2x32", "unpackHalf2x16", "unpackInt2x16", "unpackInt4x16",
- "unpackSnorm2x16", "unpackSnorm4x8", "unpackUint2x16", "unpackUint4x16", "unpackUnorm2x16", "unpackUnorm4x8", "usubBorrow",
- "active", "asm", "atomic_uint", "attribute", "bool", "break", "buffer",
- "bvec2", "bvec3", "bvec4", "case", "cast", "centroid", "class", "coherent", "common", "const", "continue", "default", "discard",
- "dmat2", "dmat2x2", "dmat2x3", "dmat2x4", "dmat3", "dmat3x2", "dmat3x3", "dmat3x4", "dmat4", "dmat4x2", "dmat4x3", "dmat4x4",
- "do", "double", "dvec2", "dvec3", "dvec4", "else", "enum", "extern", "external", "false", "filter", "fixed", "flat", "float",
- "for", "fvec2", "fvec3", "fvec4", "goto", "half", "highp", "hvec2", "hvec3", "hvec4", "if", "iimage1D", "iimage1DArray",
- "iimage2D", "iimage2DArray", "iimage2DMS", "iimage2DMSArray", "iimage2DRect", "iimage3D", "iimageBuffer", "iimageCube",
- "iimageCubeArray", "image1D", "image1DArray", "image2D", "image2DArray", "image2DMS", "image2DMSArray", "image2DRect",
- "image3D", "imageBuffer", "imageCube", "imageCubeArray", "in", "inline", "inout", "input", "int", "interface", "invariant",
- "isampler1D", "isampler1DArray", "isampler2D", "isampler2DArray", "isampler2DMS", "isampler2DMSArray", "isampler2DRect",
- "isampler3D", "isamplerBuffer", "isamplerCube", "isamplerCubeArray", "ivec2", "ivec3", "ivec4", "layout", "long", "lowp",
- "mat2", "mat2x2", "mat2x3", "mat2x4", "mat3", "mat3x2", "mat3x3", "mat3x4", "mat4", "mat4x2", "mat4x3", "mat4x4", "mediump",
- "namespace", "noinline", "noperspective", "out", "output", "packed", "partition", "patch", "precise", "precision", "public", "readonly",
- "resource", "restrict", "return", "sample", "sampler1D", "sampler1DArray", "sampler1DArrayShadow",
- "sampler1DShadow", "sampler2D", "sampler2DArray", "sampler2DArrayShadow", "sampler2DMS", "sampler2DMSArray",
- "sampler2DRect", "sampler2DRectShadow", "sampler2DShadow", "sampler3D", "sampler3DRect", "samplerBuffer",
- "samplerCube", "samplerCubeArray", "samplerCubeArrayShadow", "samplerCubeShadow", "shared", "short", "sizeof", "smooth", "static",
- "struct", "subroutine", "superp", "switch", "template", "this", "true", "typedef", "uimage1D", "uimage1DArray", "uimage2D",
- "uimage2DArray", "uimage2DMS", "uimage2DMSArray", "uimage2DRect", "uimage3D", "uimageBuffer", "uimageCube",
- "uimageCubeArray", "uint", "uniform", "union", "unsigned", "usampler1D", "usampler1DArray", "usampler2D", "usampler2DArray",
- "usampler2DMS", "usampler2DMSArray", "usampler2DRect", "usampler3D", "usamplerBuffer", "usamplerCube",
- "usamplerCubeArray", "using", "uvec2", "uvec3", "uvec4", "varying", "vec2", "vec3", "vec4", "void", "volatile",
- "while", "writeonly",
- };
- // clang-format on
- replace_illegal_names(keywords);
- }
- void CompilerGLSL::replace_fragment_output(SPIRVariable &var)
- {
- auto &m = ir.meta[var.self].decoration;
- uint32_t location = 0;
- if (m.decoration_flags.get(DecorationLocation))
- location = m.location;
- // If our variable is arrayed, we must not emit the array part of this as the SPIR-V will
- // do the access chain part of this for us.
- auto &type = get<SPIRType>(var.basetype);
- if (type.array.empty())
- {
- // Redirect the write to a specific render target in legacy GLSL.
- m.alias = join("gl_FragData[", location, "]");
- if (is_legacy_es() && location != 0)
- require_extension_internal("GL_EXT_draw_buffers");
- }
- else if (type.array.size() == 1)
- {
- // If location is non-zero, we probably have to add an offset.
- // This gets really tricky since we'd have to inject an offset in the access chain.
- // FIXME: This seems like an extremely odd-ball case, so it's probably fine to leave it like this for now.
- m.alias = "gl_FragData";
- if (location != 0)
- SPIRV_CROSS_THROW("Arrayed output variable used, but location is not 0. "
- "This is unimplemented in SPIRV-Cross.");
- if (is_legacy_es())
- require_extension_internal("GL_EXT_draw_buffers");
- }
- else
- SPIRV_CROSS_THROW("Array-of-array output variable used. This cannot be implemented in legacy GLSL.");
- var.compat_builtin = true; // We don't want to declare this variable, but use the name as-is.
- }
- void CompilerGLSL::replace_fragment_outputs()
- {
- ir.for_each_typed_id<SPIRVariable>([&](uint32_t, SPIRVariable &var) {
- auto &type = this->get<SPIRType>(var.basetype);
- if (!is_builtin_variable(var) && !var.remapped_variable && type.pointer && var.storage == StorageClassOutput)
- replace_fragment_output(var);
- });
- }
- string CompilerGLSL::remap_swizzle(const SPIRType &out_type, uint32_t input_components, const string &expr)
- {
- if (out_type.vecsize == input_components)
- return expr;
- else if (input_components == 1 && !backend.can_swizzle_scalar)
- return join(type_to_glsl(out_type), "(", expr, ")");
- else
- {
- // FIXME: This will not work with packed expressions.
- auto e = enclose_expression(expr) + ".";
- // Just clamp the swizzle index if we have more outputs than inputs.
- for (uint32_t c = 0; c < out_type.vecsize; c++)
- e += index_to_swizzle(min(c, input_components - 1));
- if (backend.swizzle_is_function && out_type.vecsize > 1)
- e += "()";
- remove_duplicate_swizzle(e);
- return e;
- }
- }
- void CompilerGLSL::emit_pls()
- {
- auto &execution = get_entry_point();
- if (execution.model != ExecutionModelFragment)
- SPIRV_CROSS_THROW("Pixel local storage only supported in fragment shaders.");
- if (!options.es)
- SPIRV_CROSS_THROW("Pixel local storage only supported in OpenGL ES.");
- if (options.version < 300)
- SPIRV_CROSS_THROW("Pixel local storage only supported in ESSL 3.0 and above.");
- if (!pls_inputs.empty())
- {
- statement("__pixel_local_inEXT _PLSIn");
- begin_scope();
- for (auto &input : pls_inputs)
- statement(pls_decl(input), ";");
- end_scope_decl();
- statement("");
- }
- if (!pls_outputs.empty())
- {
- statement("__pixel_local_outEXT _PLSOut");
- begin_scope();
- for (auto &output : pls_outputs)
- statement(pls_decl(output), ";");
- end_scope_decl();
- statement("");
- }
- }
- void CompilerGLSL::fixup_image_load_store_access()
- {
- if (!options.enable_storage_image_qualifier_deduction)
- return;
- ir.for_each_typed_id<SPIRVariable>([&](uint32_t var, const SPIRVariable &) {
- auto &vartype = expression_type(var);
- if (vartype.basetype == SPIRType::Image && vartype.image.sampled == 2)
- {
- // Very old glslangValidator and HLSL compilers do not emit required qualifiers here.
- // Solve this by making the image access as restricted as possible and loosen up if we need to.
- // If any no-read/no-write flags are actually set, assume that the compiler knows what it's doing.
- if (!has_decoration(var, DecorationNonWritable) && !has_decoration(var, DecorationNonReadable))
- {
- set_decoration(var, DecorationNonWritable);
- set_decoration(var, DecorationNonReadable);
- }
- }
- });
- }
- static bool is_block_builtin(BuiltIn builtin)
- {
- return builtin == BuiltInPosition || builtin == BuiltInPointSize || builtin == BuiltInClipDistance ||
- builtin == BuiltInCullDistance;
- }
- bool CompilerGLSL::should_force_emit_builtin_block(StorageClass storage)
- {
- // If the builtin block uses XFB, we need to force explicit redeclaration of the builtin block.
- if (storage != StorageClassOutput)
- return false;
- bool should_force = false;
- ir.for_each_typed_id<SPIRVariable>([&](uint32_t, SPIRVariable &var) {
- if (should_force)
- return;
- auto &type = this->get<SPIRType>(var.basetype);
- bool block = has_decoration(type.self, DecorationBlock);
- if (var.storage == storage && block && is_builtin_variable(var))
- {
- uint32_t member_count = uint32_t(type.member_types.size());
- for (uint32_t i = 0; i < member_count; i++)
- {
- if (has_member_decoration(type.self, i, DecorationBuiltIn) &&
- is_block_builtin(BuiltIn(get_member_decoration(type.self, i, DecorationBuiltIn))) &&
- has_member_decoration(type.self, i, DecorationOffset))
- {
- should_force = true;
- }
- }
- }
- else if (var.storage == storage && !block && is_builtin_variable(var))
- {
- if (is_block_builtin(BuiltIn(get_decoration(type.self, DecorationBuiltIn))) &&
- has_decoration(var.self, DecorationOffset))
- {
- should_force = true;
- }
- }
- });
- // If we're declaring clip/cull planes with control points we need to force block declaration.
- if ((get_execution_model() == ExecutionModelTessellationControl ||
- get_execution_model() == ExecutionModelMeshEXT) &&
- (clip_distance_count || cull_distance_count))
- {
- should_force = true;
- }
- // Either glslang bug or oversight, but global invariant position does not work in mesh shaders.
- if (get_execution_model() == ExecutionModelMeshEXT && position_invariant)
- should_force = true;
- return should_force;
- }
- void CompilerGLSL::fixup_implicit_builtin_block_names(ExecutionModel model)
- {
- ir.for_each_typed_id<SPIRVariable>([&](uint32_t, SPIRVariable &var) {
- auto &type = this->get<SPIRType>(var.basetype);
- bool block = has_decoration(type.self, DecorationBlock);
- if ((var.storage == StorageClassOutput || var.storage == StorageClassInput) && block &&
- is_builtin_variable(var))
- {
- if (model != ExecutionModelMeshEXT)
- {
- // Make sure the array has a supported name in the code.
- if (var.storage == StorageClassOutput)
- set_name(var.self, "gl_out");
- else if (var.storage == StorageClassInput)
- set_name(var.self, "gl_in");
- }
- else
- {
- auto flags = get_buffer_block_flags(var.self);
- if (flags.get(DecorationPerPrimitiveEXT))
- {
- set_name(var.self, "gl_MeshPrimitivesEXT");
- set_name(type.self, "gl_MeshPerPrimitiveEXT");
- }
- else
- {
- set_name(var.self, "gl_MeshVerticesEXT");
- set_name(type.self, "gl_MeshPerVertexEXT");
- }
- }
- }
- if (model == ExecutionModelMeshEXT && var.storage == StorageClassOutput && !block)
- {
- auto *m = ir.find_meta(var.self);
- if (m && m->decoration.builtin)
- {
- auto builtin_type = m->decoration.builtin_type;
- if (builtin_type == BuiltInPrimitivePointIndicesEXT)
- set_name(var.self, "gl_PrimitivePointIndicesEXT");
- else if (builtin_type == BuiltInPrimitiveLineIndicesEXT)
- set_name(var.self, "gl_PrimitiveLineIndicesEXT");
- else if (builtin_type == BuiltInPrimitiveTriangleIndicesEXT)
- set_name(var.self, "gl_PrimitiveTriangleIndicesEXT");
- }
- }
- });
- }
- void CompilerGLSL::emit_declared_builtin_block(StorageClass storage, ExecutionModel model)
- {
- Bitset emitted_builtins;
- Bitset global_builtins;
- const SPIRVariable *block_var = nullptr;
- bool emitted_block = false;
- // Need to use declared size in the type.
- // These variables might have been declared, but not statically used, so we haven't deduced their size yet.
- uint32_t cull_distance_size = 0;
- uint32_t clip_distance_size = 0;
- bool have_xfb_buffer_stride = false;
- bool have_geom_stream = false;
- bool have_any_xfb_offset = false;
- uint32_t xfb_stride = 0, xfb_buffer = 0, geom_stream = 0;
- std::unordered_map<uint32_t, uint32_t> builtin_xfb_offsets;
- const auto builtin_is_per_vertex_set = [](BuiltIn builtin) -> bool {
- return builtin == BuiltInPosition || builtin == BuiltInPointSize ||
- builtin == BuiltInClipDistance || builtin == BuiltInCullDistance;
- };
- ir.for_each_typed_id<SPIRVariable>([&](uint32_t, SPIRVariable &var) {
- auto &type = this->get<SPIRType>(var.basetype);
- bool block = has_decoration(type.self, DecorationBlock);
- Bitset builtins;
- if (var.storage == storage && block && is_builtin_variable(var))
- {
- uint32_t index = 0;
- for (auto &m : ir.meta[type.self].members)
- {
- if (m.builtin && builtin_is_per_vertex_set(m.builtin_type))
- {
- builtins.set(m.builtin_type);
- if (m.builtin_type == BuiltInCullDistance)
- cull_distance_size = to_array_size_literal(this->get<SPIRType>(type.member_types[index]));
- else if (m.builtin_type == BuiltInClipDistance)
- clip_distance_size = to_array_size_literal(this->get<SPIRType>(type.member_types[index]));
- if (is_block_builtin(m.builtin_type) && m.decoration_flags.get(DecorationOffset))
- {
- have_any_xfb_offset = true;
- builtin_xfb_offsets[m.builtin_type] = m.offset;
- }
- if (is_block_builtin(m.builtin_type) && m.decoration_flags.get(DecorationStream))
- {
- uint32_t stream = m.stream;
- if (have_geom_stream && geom_stream != stream)
- SPIRV_CROSS_THROW("IO block member Stream mismatch.");
- have_geom_stream = true;
- geom_stream = stream;
- }
- }
- index++;
- }
- if (storage == StorageClassOutput && has_decoration(var.self, DecorationXfbBuffer) &&
- has_decoration(var.self, DecorationXfbStride))
- {
- uint32_t buffer_index = get_decoration(var.self, DecorationXfbBuffer);
- uint32_t stride = get_decoration(var.self, DecorationXfbStride);
- if (have_xfb_buffer_stride && buffer_index != xfb_buffer)
- SPIRV_CROSS_THROW("IO block member XfbBuffer mismatch.");
- if (have_xfb_buffer_stride && stride != xfb_stride)
- SPIRV_CROSS_THROW("IO block member XfbBuffer mismatch.");
- have_xfb_buffer_stride = true;
- xfb_buffer = buffer_index;
- xfb_stride = stride;
- }
- if (storage == StorageClassOutput && has_decoration(var.self, DecorationStream))
- {
- uint32_t stream = get_decoration(var.self, DecorationStream);
- if (have_geom_stream && geom_stream != stream)
- SPIRV_CROSS_THROW("IO block member Stream mismatch.");
- have_geom_stream = true;
- geom_stream = stream;
- }
- }
- else if (var.storage == storage && !block && is_builtin_variable(var))
- {
- // While we're at it, collect all declared global builtins (HLSL mostly ...).
- auto &m = ir.meta[var.self].decoration;
- if (m.builtin && builtin_is_per_vertex_set(m.builtin_type))
- {
- // For mesh/tesc output, Clip/Cull is an array-of-array. Look at innermost array type
- // for correct result.
- global_builtins.set(m.builtin_type);
- if (m.builtin_type == BuiltInCullDistance)
- cull_distance_size = to_array_size_literal(type, 0);
- else if (m.builtin_type == BuiltInClipDistance)
- clip_distance_size = to_array_size_literal(type, 0);
- if (is_block_builtin(m.builtin_type) && m.decoration_flags.get(DecorationXfbStride) &&
- m.decoration_flags.get(DecorationXfbBuffer) && m.decoration_flags.get(DecorationOffset))
- {
- have_any_xfb_offset = true;
- builtin_xfb_offsets[m.builtin_type] = m.offset;
- uint32_t buffer_index = m.xfb_buffer;
- uint32_t stride = m.xfb_stride;
- if (have_xfb_buffer_stride && buffer_index != xfb_buffer)
- SPIRV_CROSS_THROW("IO block member XfbBuffer mismatch.");
- if (have_xfb_buffer_stride && stride != xfb_stride)
- SPIRV_CROSS_THROW("IO block member XfbBuffer mismatch.");
- have_xfb_buffer_stride = true;
- xfb_buffer = buffer_index;
- xfb_stride = stride;
- }
- if (is_block_builtin(m.builtin_type) && m.decoration_flags.get(DecorationStream))
- {
- uint32_t stream = get_decoration(var.self, DecorationStream);
- if (have_geom_stream && geom_stream != stream)
- SPIRV_CROSS_THROW("IO block member Stream mismatch.");
- have_geom_stream = true;
- geom_stream = stream;
- }
- }
- }
- if (builtins.empty())
- return;
- if (emitted_block)
- SPIRV_CROSS_THROW("Cannot use more than one builtin I/O block.");
- emitted_builtins = builtins;
- emitted_block = true;
- block_var = &var;
- });
- global_builtins =
- Bitset(global_builtins.get_lower() & ((1ull << BuiltInPosition) | (1ull << BuiltInPointSize) |
- (1ull << BuiltInClipDistance) | (1ull << BuiltInCullDistance)));
- // Try to collect all other declared builtins.
- if (!emitted_block)
- emitted_builtins = global_builtins;
- // Can't declare an empty interface block.
- if (emitted_builtins.empty())
- return;
- if (storage == StorageClassOutput)
- {
- SmallVector<string> attr;
- if (have_xfb_buffer_stride && have_any_xfb_offset)
- {
- if (!options.es)
- {
- if (options.version < 440 && options.version >= 140)
- require_extension_internal("GL_ARB_enhanced_layouts");
- else if (options.version < 140)
- SPIRV_CROSS_THROW("Component decoration is not supported in targets below GLSL 1.40.");
- if (!options.es && options.version < 440)
- require_extension_internal("GL_ARB_enhanced_layouts");
- }
- else if (options.es)
- SPIRV_CROSS_THROW("Need GL_ARB_enhanced_layouts for xfb_stride or xfb_buffer.");
- attr.push_back(join("xfb_buffer = ", xfb_buffer, ", xfb_stride = ", xfb_stride));
- }
- if (have_geom_stream)
- {
- if (get_execution_model() != ExecutionModelGeometry)
- SPIRV_CROSS_THROW("Geometry streams can only be used in geometry shaders.");
- if (options.es)
- SPIRV_CROSS_THROW("Multiple geometry streams not supported in ESSL.");
- if (options.version < 400)
- require_extension_internal("GL_ARB_transform_feedback3");
- attr.push_back(join("stream = ", geom_stream));
- }
- if (model == ExecutionModelMeshEXT)
- statement("out gl_MeshPerVertexEXT");
- else if (!attr.empty())
- statement("layout(", merge(attr), ") out gl_PerVertex");
- else
- statement("out gl_PerVertex");
- }
- else
- {
- // If we have passthrough, there is no way PerVertex cannot be passthrough.
- if (get_entry_point().geometry_passthrough)
- statement("layout(passthrough) in gl_PerVertex");
- else
- statement("in gl_PerVertex");
- }
- begin_scope();
- if (emitted_builtins.get(BuiltInPosition))
- {
- auto itr = builtin_xfb_offsets.find(BuiltInPosition);
- if (itr != end(builtin_xfb_offsets))
- statement("layout(xfb_offset = ", itr->second, ") vec4 gl_Position;");
- else if (position_invariant)
- statement("invariant vec4 gl_Position;");
- else
- statement("vec4 gl_Position;");
- }
- if (emitted_builtins.get(BuiltInPointSize))
- {
- auto itr = builtin_xfb_offsets.find(BuiltInPointSize);
- if (itr != end(builtin_xfb_offsets))
- statement("layout(xfb_offset = ", itr->second, ") float gl_PointSize;");
- else
- statement("float gl_PointSize;");
- }
- if (emitted_builtins.get(BuiltInClipDistance))
- {
- auto itr = builtin_xfb_offsets.find(BuiltInClipDistance);
- if (itr != end(builtin_xfb_offsets))
- statement("layout(xfb_offset = ", itr->second, ") float gl_ClipDistance[", clip_distance_size, "];");
- else
- statement("float gl_ClipDistance[", clip_distance_size, "];");
- }
- if (emitted_builtins.get(BuiltInCullDistance))
- {
- auto itr = builtin_xfb_offsets.find(BuiltInCullDistance);
- if (itr != end(builtin_xfb_offsets))
- statement("layout(xfb_offset = ", itr->second, ") float gl_CullDistance[", cull_distance_size, "];");
- else
- statement("float gl_CullDistance[", cull_distance_size, "];");
- }
- bool builtin_array = model == ExecutionModelTessellationControl ||
- (model == ExecutionModelMeshEXT && storage == StorageClassOutput) ||
- (model == ExecutionModelGeometry && storage == StorageClassInput) ||
- (model == ExecutionModelTessellationEvaluation && storage == StorageClassInput);
- if (builtin_array)
- {
- const char *instance_name;
- if (model == ExecutionModelMeshEXT)
- instance_name = "gl_MeshVerticesEXT"; // Per primitive is never synthesized.
- else
- instance_name = storage == StorageClassInput ? "gl_in" : "gl_out";
- if (model == ExecutionModelTessellationControl && storage == StorageClassOutput)
- end_scope_decl(join(instance_name, "[", get_entry_point().output_vertices, "]"));
- else
- end_scope_decl(join(instance_name, "[]"));
- }
- else
- end_scope_decl();
- statement("");
- }
- bool CompilerGLSL::variable_is_lut(const SPIRVariable &var) const
- {
- bool statically_assigned = var.statically_assigned && var.static_expression != ID(0) && var.remapped_variable;
- if (statically_assigned)
- {
- auto *constant = maybe_get<SPIRConstant>(var.static_expression);
- if (constant && constant->is_used_as_lut)
- return true;
- }
- return false;
- }
- void CompilerGLSL::emit_resources()
- {
- auto &execution = get_entry_point();
- replace_illegal_names();
- // Legacy GL uses gl_FragData[], redeclare all fragment outputs
- // with builtins.
- if (execution.model == ExecutionModelFragment && is_legacy())
- replace_fragment_outputs();
- // Emit PLS blocks if we have such variables.
- if (!pls_inputs.empty() || !pls_outputs.empty())
- emit_pls();
- switch (execution.model)
- {
- case ExecutionModelGeometry:
- case ExecutionModelTessellationControl:
- case ExecutionModelTessellationEvaluation:
- case ExecutionModelMeshEXT:
- fixup_implicit_builtin_block_names(execution.model);
- break;
- default:
- break;
- }
- bool global_invariant_position = position_invariant && (options.es || options.version >= 120);
- // Emit custom gl_PerVertex for SSO compatibility.
- if (options.separate_shader_objects && !options.es && execution.model != ExecutionModelFragment)
- {
- switch (execution.model)
- {
- case ExecutionModelGeometry:
- case ExecutionModelTessellationControl:
- case ExecutionModelTessellationEvaluation:
- emit_declared_builtin_block(StorageClassInput, execution.model);
- emit_declared_builtin_block(StorageClassOutput, execution.model);
- global_invariant_position = false;
- break;
- case ExecutionModelVertex:
- case ExecutionModelMeshEXT:
- emit_declared_builtin_block(StorageClassOutput, execution.model);
- global_invariant_position = false;
- break;
- default:
- break;
- }
- }
- else if (should_force_emit_builtin_block(StorageClassOutput))
- {
- emit_declared_builtin_block(StorageClassOutput, execution.model);
- global_invariant_position = false;
- }
- else if (execution.geometry_passthrough)
- {
- // Need to declare gl_in with Passthrough.
- // If we're doing passthrough, we cannot emit an output block, so the output block test above will never pass.
- emit_declared_builtin_block(StorageClassInput, execution.model);
- }
- else
- {
- // Need to redeclare clip/cull distance with explicit size to use them.
- // SPIR-V mandates these builtins have a size declared.
- const char *storage = execution.model == ExecutionModelFragment ? "in" : "out";
- if (clip_distance_count != 0)
- statement(storage, " float gl_ClipDistance[", clip_distance_count, "];");
- if (cull_distance_count != 0)
- statement(storage, " float gl_CullDistance[", cull_distance_count, "];");
- if (clip_distance_count != 0 || cull_distance_count != 0)
- statement("");
- }
- if (global_invariant_position)
- {
- statement("invariant gl_Position;");
- statement("");
- }
- bool emitted = false;
- if (ir.addressing_model == AddressingModelPhysicalStorageBuffer64)
- {
- // Output buffer reference block forward declarations.
- ir.for_each_typed_id<SPIRType>([&](uint32_t id, SPIRType &type)
- {
- if (is_physical_pointer(type))
- {
- bool emit_type = true;
- if (!is_physical_pointer_to_buffer_block(type))
- {
- // Only forward-declare if we intend to emit it in the non_block_pointer types.
- // Otherwise, these are just "benign" pointer types that exist as a result of access chains.
- emit_type = std::find(physical_storage_non_block_pointer_types.begin(),
- physical_storage_non_block_pointer_types.end(),
- id) != physical_storage_non_block_pointer_types.end();
- }
- if (emit_type)
- {
- emit_buffer_reference_block(id, true);
- emitted = true;
- }
- }
- });
- }
- if (emitted)
- statement("");
- emitted = false;
- // If emitted Vulkan GLSL,
- // emit specialization constants as actual floats,
- // spec op expressions will redirect to the constant name.
- //
- {
- auto loop_lock = ir.create_loop_hard_lock();
- for (auto &id_ : ir.ids_for_constant_undef_or_type)
- {
- auto &id = ir.ids[id_];
- // Skip declaring any bogus constants or undefs which use block types.
- // We don't declare block types directly, so this will never work.
- // Should not be legal SPIR-V, so this is considered a workaround.
- if (id.get_type() == TypeConstant)
- {
- auto &c = id.get<SPIRConstant>();
- bool needs_declaration = c.specialization || c.is_used_as_lut;
- if (needs_declaration)
- {
- if (!options.vulkan_semantics && c.specialization)
- {
- c.specialization_constant_macro_name =
- constant_value_macro_name(get_decoration(c.self, DecorationSpecId));
- }
- emit_constant(c);
- emitted = true;
- }
- }
- else if (id.get_type() == TypeConstantOp)
- {
- emit_specialization_constant_op(id.get<SPIRConstantOp>());
- emitted = true;
- }
- else if (id.get_type() == TypeType)
- {
- auto *type = &id.get<SPIRType>();
- bool is_natural_struct = type->basetype == SPIRType::Struct && type->array.empty() && !type->pointer &&
- (!has_decoration(type->self, DecorationBlock) &&
- !has_decoration(type->self, DecorationBufferBlock));
- // Special case, ray payload and hit attribute blocks are not really blocks, just regular structs.
- if (type->basetype == SPIRType::Struct && type->pointer &&
- has_decoration(type->self, DecorationBlock) &&
- (type->storage == StorageClassRayPayloadKHR || type->storage == StorageClassIncomingRayPayloadKHR ||
- type->storage == StorageClassHitAttributeKHR))
- {
- type = &get<SPIRType>(type->parent_type);
- is_natural_struct = true;
- }
- if (is_natural_struct)
- {
- if (emitted)
- statement("");
- emitted = false;
- emit_struct(*type);
- }
- }
- else if (id.get_type() == TypeUndef)
- {
- auto &undef = id.get<SPIRUndef>();
- auto &type = this->get<SPIRType>(undef.basetype);
- // OpUndef can be void for some reason ...
- if (type.basetype == SPIRType::Void)
- continue;
- // This will break. It is bogus and should not be legal.
- if (type_is_top_level_block(type))
- continue;
- string initializer;
- if (options.force_zero_initialized_variables && type_can_zero_initialize(type))
- initializer = join(" = ", to_zero_initialized_expression(undef.basetype));
- // FIXME: If used in a constant, we must declare it as one.
- statement(variable_decl(type, to_name(undef.self), undef.self), initializer, ";");
- emitted = true;
- }
- }
- }
- if (emitted)
- statement("");
- // If we needed to declare work group size late, check here.
- // If the work group size depends on a specialization constant, we need to declare the layout() block
- // after constants (and their macros) have been declared.
- if (execution.model == ExecutionModelGLCompute && !options.vulkan_semantics &&
- (execution.workgroup_size.constant != 0 || execution.flags.get(ExecutionModeLocalSizeId)))
- {
- SpecializationConstant wg_x, wg_y, wg_z;
- get_work_group_size_specialization_constants(wg_x, wg_y, wg_z);
- if ((wg_x.id != ConstantID(0)) || (wg_y.id != ConstantID(0)) || (wg_z.id != ConstantID(0)))
- {
- SmallVector<string> inputs;
- build_workgroup_size(inputs, wg_x, wg_y, wg_z);
- statement("layout(", merge(inputs), ") in;");
- statement("");
- }
- }
- emitted = false;
- if (ir.addressing_model == AddressingModelPhysicalStorageBuffer64)
- {
- // Output buffer reference blocks.
- // Buffer reference blocks can reference themselves to support things like linked lists.
- for (auto type : physical_storage_non_block_pointer_types)
- emit_buffer_reference_block(type, false);
- ir.for_each_typed_id<SPIRType>([&](uint32_t id, SPIRType &type) {
- if (is_physical_pointer_to_buffer_block(type))
- emit_buffer_reference_block(id, false);
- });
- }
- // Output UBOs and SSBOs
- ir.for_each_typed_id<SPIRVariable>([&](uint32_t, SPIRVariable &var) {
- auto &type = this->get<SPIRType>(var.basetype);
- bool is_block_storage = type.storage == StorageClassStorageBuffer || type.storage == StorageClassUniform ||
- type.storage == StorageClassShaderRecordBufferKHR;
- bool has_block_flags = ir.meta[type.self].decoration.decoration_flags.get(DecorationBlock) ||
- ir.meta[type.self].decoration.decoration_flags.get(DecorationBufferBlock);
- if (var.storage != StorageClassFunction && type.pointer && is_block_storage && !is_hidden_variable(var) &&
- has_block_flags)
- {
- emit_buffer_block(var);
- }
- });
- // Output push constant blocks
- ir.for_each_typed_id<SPIRVariable>([&](uint32_t, SPIRVariable &var) {
- auto &type = this->get<SPIRType>(var.basetype);
- if (var.storage != StorageClassFunction && type.pointer && type.storage == StorageClassPushConstant &&
- !is_hidden_variable(var))
- {
- emit_push_constant_block(var);
- }
- });
- bool skip_separate_image_sampler = !combined_image_samplers.empty() || !options.vulkan_semantics;
- // Output Uniform Constants (values, samplers, images, etc).
- ir.for_each_typed_id<SPIRVariable>([&](uint32_t, SPIRVariable &var) {
- auto &type = this->get<SPIRType>(var.basetype);
- // If we're remapping separate samplers and images, only emit the combined samplers.
- if (skip_separate_image_sampler)
- {
- // Sampler buffers are always used without a sampler, and they will also work in regular GL.
- bool sampler_buffer = type.basetype == SPIRType::Image && type.image.dim == DimBuffer;
- bool separate_image = type.basetype == SPIRType::Image && type.image.sampled == 1;
- bool separate_sampler = type.basetype == SPIRType::Sampler;
- if (!sampler_buffer && (separate_image || separate_sampler))
- return;
- }
- if (var.storage != StorageClassFunction && type.pointer &&
- (type.storage == StorageClassUniformConstant || type.storage == StorageClassAtomicCounter ||
- type.storage == StorageClassRayPayloadKHR || type.storage == StorageClassIncomingRayPayloadKHR ||
- type.storage == StorageClassCallableDataKHR || type.storage == StorageClassIncomingCallableDataKHR ||
- type.storage == StorageClassHitAttributeKHR) &&
- !is_hidden_variable(var))
- {
- emit_uniform(var);
- emitted = true;
- }
- });
- if (emitted)
- statement("");
- emitted = false;
- bool emitted_base_instance = false;
- // Output in/out interfaces.
- ir.for_each_typed_id<SPIRVariable>([&](uint32_t, SPIRVariable &var) {
- auto &type = this->get<SPIRType>(var.basetype);
- bool is_hidden = is_hidden_variable(var);
- // Unused output I/O variables might still be required to implement framebuffer fetch.
- if (var.storage == StorageClassOutput && !is_legacy() &&
- location_is_framebuffer_fetch(get_decoration(var.self, DecorationLocation)) != 0)
- {
- is_hidden = false;
- }
- if (var.storage != StorageClassFunction && type.pointer &&
- (var.storage == StorageClassInput || var.storage == StorageClassOutput) &&
- interface_variable_exists_in_entry_point(var.self) && !is_hidden)
- {
- if (options.es && get_execution_model() == ExecutionModelVertex && var.storage == StorageClassInput &&
- type.array.size() == 1)
- {
- SPIRV_CROSS_THROW("OpenGL ES doesn't support array input variables in vertex shader.");
- }
- emit_interface_block(var);
- emitted = true;
- }
- else if (is_builtin_variable(var))
- {
- auto builtin = BuiltIn(get_decoration(var.self, DecorationBuiltIn));
- // For gl_InstanceIndex emulation on GLES, the API user needs to
- // supply this uniform.
- // The draw parameter extension is soft-enabled on GL with some fallbacks.
- if (!options.vulkan_semantics)
- {
- if (!emitted_base_instance &&
- ((options.vertex.support_nonzero_base_instance && builtin == BuiltInInstanceIndex) ||
- (builtin == BuiltInBaseInstance)))
- {
- statement("#ifdef GL_ARB_shader_draw_parameters");
- statement("#define SPIRV_Cross_BaseInstance gl_BaseInstanceARB");
- statement("#else");
- // A crude, but simple workaround which should be good enough for non-indirect draws.
- statement("uniform int SPIRV_Cross_BaseInstance;");
- statement("#endif");
- emitted = true;
- emitted_base_instance = true;
- }
- else if (builtin == BuiltInBaseVertex)
- {
- statement("#ifdef GL_ARB_shader_draw_parameters");
- statement("#define SPIRV_Cross_BaseVertex gl_BaseVertexARB");
- statement("#else");
- // A crude, but simple workaround which should be good enough for non-indirect draws.
- statement("uniform int SPIRV_Cross_BaseVertex;");
- statement("#endif");
- }
- else if (builtin == BuiltInDrawIndex)
- {
- statement("#ifndef GL_ARB_shader_draw_parameters");
- // Cannot really be worked around.
- statement("#error GL_ARB_shader_draw_parameters is not supported.");
- statement("#endif");
- }
- }
- }
- });
- // Global variables.
- for (auto global : global_variables)
- {
- auto &var = get<SPIRVariable>(global);
- if (is_hidden_variable(var, true))
- continue;
- if (var.storage != StorageClassOutput)
- {
- if (!variable_is_lut(var))
- {
- add_resource_name(var.self);
- string initializer;
- if (options.force_zero_initialized_variables && var.storage == StorageClassPrivate &&
- !var.initializer && !var.static_expression && type_can_zero_initialize(get_variable_data_type(var)))
- {
- initializer = join(" = ", to_zero_initialized_expression(get_variable_data_type_id(var)));
- }
- statement(variable_decl(var), initializer, ";");
- emitted = true;
- }
- }
- else if (var.initializer && maybe_get<SPIRConstant>(var.initializer) != nullptr)
- {
- emit_output_variable_initializer(var);
- }
- }
- if (emitted)
- statement("");
- }
- void CompilerGLSL::emit_output_variable_initializer(const SPIRVariable &var)
- {
- // If a StorageClassOutput variable has an initializer, we need to initialize it in main().
- auto &entry_func = this->get<SPIRFunction>(ir.default_entry_point);
- auto &type = get<SPIRType>(var.basetype);
- bool is_patch = has_decoration(var.self, DecorationPatch);
- bool is_block = has_decoration(type.self, DecorationBlock);
- bool is_control_point = get_execution_model() == ExecutionModelTessellationControl && !is_patch;
- if (is_block)
- {
- uint32_t member_count = uint32_t(type.member_types.size());
- bool type_is_array = type.array.size() == 1;
- uint32_t array_size = 1;
- if (type_is_array)
- array_size = to_array_size_literal(type);
- uint32_t iteration_count = is_control_point ? 1 : array_size;
- // If the initializer is a block, we must initialize each block member one at a time.
- for (uint32_t i = 0; i < member_count; i++)
- {
- // These outputs might not have been properly declared, so don't initialize them in that case.
- if (has_member_decoration(type.self, i, DecorationBuiltIn))
- {
- if (get_member_decoration(type.self, i, DecorationBuiltIn) == BuiltInCullDistance &&
- !cull_distance_count)
- continue;
- if (get_member_decoration(type.self, i, DecorationBuiltIn) == BuiltInClipDistance &&
- !clip_distance_count)
- continue;
- }
- // We need to build a per-member array first, essentially transposing from AoS to SoA.
- // This code path hits when we have an array of blocks.
- string lut_name;
- if (type_is_array)
- {
- lut_name = join("_", var.self, "_", i, "_init");
- uint32_t member_type_id = get<SPIRType>(var.basetype).member_types[i];
- auto &member_type = get<SPIRType>(member_type_id);
- auto array_type = member_type;
- array_type.parent_type = member_type_id;
- array_type.op = OpTypeArray;
- array_type.array.push_back(array_size);
- array_type.array_size_literal.push_back(true);
- SmallVector<string> exprs;
- exprs.reserve(array_size);
- auto &c = get<SPIRConstant>(var.initializer);
- for (uint32_t j = 0; j < array_size; j++)
- exprs.push_back(to_expression(get<SPIRConstant>(c.subconstants[j]).subconstants[i]));
- statement("const ", type_to_glsl(array_type), " ", lut_name, type_to_array_glsl(array_type, 0), " = ",
- type_to_glsl_constructor(array_type), "(", merge(exprs, ", "), ");");
- }
- for (uint32_t j = 0; j < iteration_count; j++)
- {
- entry_func.fixup_hooks_in.push_back([=, &var]() {
- AccessChainMeta meta;
- auto &c = this->get<SPIRConstant>(var.initializer);
- uint32_t invocation_id = 0;
- uint32_t member_index_id = 0;
- if (is_control_point)
- {
- uint32_t ids = ir.increase_bound_by(3);
- auto &uint_type = set<SPIRType>(ids, OpTypeInt);
- uint_type.basetype = SPIRType::UInt;
- uint_type.width = 32;
- set<SPIRExpression>(ids + 1, builtin_to_glsl(BuiltInInvocationId, StorageClassInput), ids, true);
- set<SPIRConstant>(ids + 2, ids, i, false);
- invocation_id = ids + 1;
- member_index_id = ids + 2;
- }
- if (is_patch)
- {
- statement("if (gl_InvocationID == 0)");
- begin_scope();
- }
- if (type_is_array && !is_control_point)
- {
- uint32_t indices[2] = { j, i };
- auto chain = access_chain_internal(var.self, indices, 2, ACCESS_CHAIN_INDEX_IS_LITERAL_BIT, &meta);
- statement(chain, " = ", lut_name, "[", j, "];");
- }
- else if (is_control_point)
- {
- uint32_t indices[2] = { invocation_id, member_index_id };
- auto chain = access_chain_internal(var.self, indices, 2, 0, &meta);
- statement(chain, " = ", lut_name, "[", builtin_to_glsl(BuiltInInvocationId, StorageClassInput), "];");
- }
- else
- {
- auto chain =
- access_chain_internal(var.self, &i, 1, ACCESS_CHAIN_INDEX_IS_LITERAL_BIT, &meta);
- statement(chain, " = ", to_expression(c.subconstants[i]), ";");
- }
- if (is_patch)
- end_scope();
- });
- }
- }
- }
- else if (is_control_point)
- {
- auto lut_name = join("_", var.self, "_init");
- statement("const ", type_to_glsl(type), " ", lut_name, type_to_array_glsl(type, 0),
- " = ", to_expression(var.initializer), ";");
- entry_func.fixup_hooks_in.push_back([&, lut_name]() {
- statement(to_expression(var.self), "[gl_InvocationID] = ", lut_name, "[gl_InvocationID];");
- });
- }
- else if (has_decoration(var.self, DecorationBuiltIn) &&
- BuiltIn(get_decoration(var.self, DecorationBuiltIn)) == BuiltInSampleMask)
- {
- // We cannot copy the array since gl_SampleMask is unsized in GLSL. Unroll time! <_<
- entry_func.fixup_hooks_in.push_back([&] {
- auto &c = this->get<SPIRConstant>(var.initializer);
- uint32_t num_constants = uint32_t(c.subconstants.size());
- for (uint32_t i = 0; i < num_constants; i++)
- {
- // Don't use to_expression on constant since it might be uint, just fish out the raw int.
- statement(to_expression(var.self), "[", i, "] = ",
- convert_to_string(this->get<SPIRConstant>(c.subconstants[i]).scalar_i32()), ";");
- }
- });
- }
- else
- {
- auto lut_name = join("_", var.self, "_init");
- statement("const ", type_to_glsl(type), " ", lut_name,
- type_to_array_glsl(type, var.self), " = ", to_expression(var.initializer), ";");
- entry_func.fixup_hooks_in.push_back([&, lut_name, is_patch]() {
- if (is_patch)
- {
- statement("if (gl_InvocationID == 0)");
- begin_scope();
- }
- statement(to_expression(var.self), " = ", lut_name, ";");
- if (is_patch)
- end_scope();
- });
- }
- }
- void CompilerGLSL::emit_subgroup_arithmetic_workaround(const std::string &func, Op op, GroupOperation group_op)
- {
- std::string result;
- switch (group_op)
- {
- case GroupOperationReduce:
- result = "reduction";
- break;
- case GroupOperationExclusiveScan:
- result = "excl_scan";
- break;
- case GroupOperationInclusiveScan:
- result = "incl_scan";
- break;
- default:
- SPIRV_CROSS_THROW("Unsupported workaround for arithmetic group operation");
- }
- struct TypeInfo
- {
- std::string type;
- std::string identity;
- };
- std::vector<TypeInfo> type_infos;
- switch (op)
- {
- case OpGroupNonUniformIAdd:
- {
- type_infos.emplace_back(TypeInfo{ "uint", "0u" });
- type_infos.emplace_back(TypeInfo{ "uvec2", "uvec2(0u)" });
- type_infos.emplace_back(TypeInfo{ "uvec3", "uvec3(0u)" });
- type_infos.emplace_back(TypeInfo{ "uvec4", "uvec4(0u)" });
- type_infos.emplace_back(TypeInfo{ "int", "0" });
- type_infos.emplace_back(TypeInfo{ "ivec2", "ivec2(0)" });
- type_infos.emplace_back(TypeInfo{ "ivec3", "ivec3(0)" });
- type_infos.emplace_back(TypeInfo{ "ivec4", "ivec4(0)" });
- break;
- }
- case OpGroupNonUniformFAdd:
- {
- type_infos.emplace_back(TypeInfo{ "float", "0.0f" });
- type_infos.emplace_back(TypeInfo{ "vec2", "vec2(0.0f)" });
- type_infos.emplace_back(TypeInfo{ "vec3", "vec3(0.0f)" });
- type_infos.emplace_back(TypeInfo{ "vec4", "vec4(0.0f)" });
- // ARB_gpu_shader_fp64 is required in GL4.0 which in turn is required by NV_thread_shuffle
- type_infos.emplace_back(TypeInfo{ "double", "0.0LF" });
- type_infos.emplace_back(TypeInfo{ "dvec2", "dvec2(0.0LF)" });
- type_infos.emplace_back(TypeInfo{ "dvec3", "dvec3(0.0LF)" });
- type_infos.emplace_back(TypeInfo{ "dvec4", "dvec4(0.0LF)" });
- break;
- }
- case OpGroupNonUniformIMul:
- {
- type_infos.emplace_back(TypeInfo{ "uint", "1u" });
- type_infos.emplace_back(TypeInfo{ "uvec2", "uvec2(1u)" });
- type_infos.emplace_back(TypeInfo{ "uvec3", "uvec3(1u)" });
- type_infos.emplace_back(TypeInfo{ "uvec4", "uvec4(1u)" });
- type_infos.emplace_back(TypeInfo{ "int", "1" });
- type_infos.emplace_back(TypeInfo{ "ivec2", "ivec2(1)" });
- type_infos.emplace_back(TypeInfo{ "ivec3", "ivec3(1)" });
- type_infos.emplace_back(TypeInfo{ "ivec4", "ivec4(1)" });
- break;
- }
- case OpGroupNonUniformFMul:
- {
- type_infos.emplace_back(TypeInfo{ "float", "1.0f" });
- type_infos.emplace_back(TypeInfo{ "vec2", "vec2(1.0f)" });
- type_infos.emplace_back(TypeInfo{ "vec3", "vec3(1.0f)" });
- type_infos.emplace_back(TypeInfo{ "vec4", "vec4(1.0f)" });
- type_infos.emplace_back(TypeInfo{ "double", "0.0LF" });
- type_infos.emplace_back(TypeInfo{ "dvec2", "dvec2(1.0LF)" });
- type_infos.emplace_back(TypeInfo{ "dvec3", "dvec3(1.0LF)" });
- type_infos.emplace_back(TypeInfo{ "dvec4", "dvec4(1.0LF)" });
- break;
- }
- default:
- SPIRV_CROSS_THROW("Unsupported workaround for arithmetic group operation");
- }
- const bool op_is_addition = op == OpGroupNonUniformIAdd || op == OpGroupNonUniformFAdd;
- const bool op_is_multiplication = op == OpGroupNonUniformIMul || op == OpGroupNonUniformFMul;
- std::string op_symbol;
- if (op_is_addition)
- {
- op_symbol = "+=";
- }
- else if (op_is_multiplication)
- {
- op_symbol = "*=";
- }
- for (const TypeInfo &t : type_infos)
- {
- statement(t.type, " ", func, "(", t.type, " v)");
- begin_scope();
- statement(t.type, " ", result, " = ", t.identity, ";");
- statement("uvec4 active_threads = subgroupBallot(true);");
- statement("if (subgroupBallotBitCount(active_threads) == gl_SubgroupSize)");
- begin_scope();
- statement("uint total = gl_SubgroupSize / 2u;");
- statement(result, " = v;");
- statement("for (uint i = 1u; i <= total; i <<= 1u)");
- begin_scope();
- statement("bool valid;");
- if (group_op == GroupOperationReduce)
- {
- statement(t.type, " s = shuffleXorNV(", result, ", i, gl_SubgroupSize, valid);");
- }
- else if (group_op == GroupOperationExclusiveScan || group_op == GroupOperationInclusiveScan)
- {
- statement(t.type, " s = shuffleUpNV(", result, ", i, gl_SubgroupSize, valid);");
- }
- if (op_is_addition || op_is_multiplication)
- {
- statement(result, " ", op_symbol, " valid ? s : ", t.identity, ";");
- }
- end_scope();
- if (group_op == GroupOperationExclusiveScan)
- {
- statement(result, " = shuffleUpNV(", result, ", 1u, gl_SubgroupSize);");
- statement("if (subgroupElect())");
- begin_scope();
- statement(result, " = ", t.identity, ";");
- end_scope();
- }
- end_scope();
- statement("else");
- begin_scope();
- if (group_op == GroupOperationExclusiveScan)
- {
- statement("uint total = subgroupBallotBitCount(gl_SubgroupLtMask);");
- }
- else if (group_op == GroupOperationInclusiveScan)
- {
- statement("uint total = subgroupBallotBitCount(gl_SubgroupLeMask);");
- }
- statement("for (uint i = 0u; i < gl_SubgroupSize; ++i)");
- begin_scope();
- statement("bool valid = subgroupBallotBitExtract(active_threads, i);");
- statement(t.type, " s = shuffleNV(v, i, gl_SubgroupSize);");
- if (group_op == GroupOperationExclusiveScan || group_op == GroupOperationInclusiveScan)
- {
- statement("valid = valid && (i < total);");
- }
- if (op_is_addition || op_is_multiplication)
- {
- statement(result, " ", op_symbol, " valid ? s : ", t.identity, ";");
- }
- end_scope();
- end_scope();
- statement("return ", result, ";");
- end_scope();
- }
- }
- void CompilerGLSL::emit_extension_workarounds(ExecutionModel model)
- {
- static const char *workaround_types[] = { "int", "ivec2", "ivec3", "ivec4", "uint", "uvec2", "uvec3", "uvec4",
- "float", "vec2", "vec3", "vec4", "double", "dvec2", "dvec3", "dvec4" };
- if (!options.vulkan_semantics)
- {
- using Supp = ShaderSubgroupSupportHelper;
- auto result = shader_subgroup_supporter.resolve();
- if (shader_subgroup_supporter.is_feature_requested(Supp::SubgroupMask))
- {
- auto exts = Supp::get_candidates_for_feature(Supp::SubgroupMask, result);
- for (auto &e : exts)
- {
- const char *name = Supp::get_extension_name(e);
- statement(&e == &exts.front() ? "#if" : "#elif", " defined(", name, ")");
- switch (e)
- {
- case Supp::NV_shader_thread_group:
- statement("#define gl_SubgroupEqMask uvec4(gl_ThreadEqMaskNV, 0u, 0u, 0u)");
- statement("#define gl_SubgroupGeMask uvec4(gl_ThreadGeMaskNV, 0u, 0u, 0u)");
- statement("#define gl_SubgroupGtMask uvec4(gl_ThreadGtMaskNV, 0u, 0u, 0u)");
- statement("#define gl_SubgroupLeMask uvec4(gl_ThreadLeMaskNV, 0u, 0u, 0u)");
- statement("#define gl_SubgroupLtMask uvec4(gl_ThreadLtMaskNV, 0u, 0u, 0u)");
- break;
- case Supp::ARB_shader_ballot:
- statement("#define gl_SubgroupEqMask uvec4(unpackUint2x32(gl_SubGroupEqMaskARB), 0u, 0u)");
- statement("#define gl_SubgroupGeMask uvec4(unpackUint2x32(gl_SubGroupGeMaskARB), 0u, 0u)");
- statement("#define gl_SubgroupGtMask uvec4(unpackUint2x32(gl_SubGroupGtMaskARB), 0u, 0u)");
- statement("#define gl_SubgroupLeMask uvec4(unpackUint2x32(gl_SubGroupLeMaskARB), 0u, 0u)");
- statement("#define gl_SubgroupLtMask uvec4(unpackUint2x32(gl_SubGroupLtMaskARB), 0u, 0u)");
- break;
- default:
- break;
- }
- }
- statement("#endif");
- statement("");
- }
- if (shader_subgroup_supporter.is_feature_requested(Supp::SubgroupSize))
- {
- auto exts = Supp::get_candidates_for_feature(Supp::SubgroupSize, result);
- for (auto &e : exts)
- {
- const char *name = Supp::get_extension_name(e);
- statement(&e == &exts.front() ? "#if" : "#elif", " defined(", name, ")");
- switch (e)
- {
- case Supp::NV_shader_thread_group:
- statement("#define gl_SubgroupSize gl_WarpSizeNV");
- break;
- case Supp::ARB_shader_ballot:
- statement("#define gl_SubgroupSize gl_SubGroupSizeARB");
- break;
- case Supp::AMD_gcn_shader:
- statement("#define gl_SubgroupSize uint(gl_SIMDGroupSizeAMD)");
- break;
- default:
- break;
- }
- }
- statement("#endif");
- statement("");
- }
- if (shader_subgroup_supporter.is_feature_requested(Supp::SubgroupInvocationID))
- {
- auto exts = Supp::get_candidates_for_feature(Supp::SubgroupInvocationID, result);
- for (auto &e : exts)
- {
- const char *name = Supp::get_extension_name(e);
- statement(&e == &exts.front() ? "#if" : "#elif", " defined(", name, ")");
- switch (e)
- {
- case Supp::NV_shader_thread_group:
- statement("#define gl_SubgroupInvocationID gl_ThreadInWarpNV");
- break;
- case Supp::ARB_shader_ballot:
- statement("#define gl_SubgroupInvocationID gl_SubGroupInvocationARB");
- break;
- default:
- break;
- }
- }
- statement("#endif");
- statement("");
- }
- if (shader_subgroup_supporter.is_feature_requested(Supp::SubgroupID))
- {
- auto exts = Supp::get_candidates_for_feature(Supp::SubgroupID, result);
- for (auto &e : exts)
- {
- const char *name = Supp::get_extension_name(e);
- statement(&e == &exts.front() ? "#if" : "#elif", " defined(", name, ")");
- switch (e)
- {
- case Supp::NV_shader_thread_group:
- statement("#define gl_SubgroupID gl_WarpIDNV");
- break;
- default:
- break;
- }
- }
- statement("#endif");
- statement("");
- }
- if (shader_subgroup_supporter.is_feature_requested(Supp::NumSubgroups))
- {
- auto exts = Supp::get_candidates_for_feature(Supp::NumSubgroups, result);
- for (auto &e : exts)
- {
- const char *name = Supp::get_extension_name(e);
- statement(&e == &exts.front() ? "#if" : "#elif", " defined(", name, ")");
- switch (e)
- {
- case Supp::NV_shader_thread_group:
- statement("#define gl_NumSubgroups gl_WarpsPerSMNV");
- break;
- default:
- break;
- }
- }
- statement("#endif");
- statement("");
- }
- if (shader_subgroup_supporter.is_feature_requested(Supp::SubgroupBroadcast_First))
- {
- auto exts = Supp::get_candidates_for_feature(Supp::SubgroupBroadcast_First, result);
- for (auto &e : exts)
- {
- const char *name = Supp::get_extension_name(e);
- statement(&e == &exts.front() ? "#if" : "#elif", " defined(", name, ")");
- switch (e)
- {
- case Supp::NV_shader_thread_shuffle:
- for (const char *t : workaround_types)
- {
- statement(t, " subgroupBroadcastFirst(", t,
- " value) { return shuffleNV(value, findLSB(ballotThreadNV(true)), gl_WarpSizeNV); }");
- }
- for (const char *t : workaround_types)
- {
- statement(t, " subgroupBroadcast(", t,
- " value, uint id) { return shuffleNV(value, id, gl_WarpSizeNV); }");
- }
- break;
- case Supp::ARB_shader_ballot:
- for (const char *t : workaround_types)
- {
- statement(t, " subgroupBroadcastFirst(", t,
- " value) { return readFirstInvocationARB(value); }");
- }
- for (const char *t : workaround_types)
- {
- statement(t, " subgroupBroadcast(", t,
- " value, uint id) { return readInvocationARB(value, id); }");
- }
- break;
- default:
- break;
- }
- }
- statement("#endif");
- statement("");
- }
- if (shader_subgroup_supporter.is_feature_requested(Supp::SubgroupBallotFindLSB_MSB))
- {
- auto exts = Supp::get_candidates_for_feature(Supp::SubgroupBallotFindLSB_MSB, result);
- for (auto &e : exts)
- {
- const char *name = Supp::get_extension_name(e);
- statement(&e == &exts.front() ? "#if" : "#elif", " defined(", name, ")");
- switch (e)
- {
- case Supp::NV_shader_thread_group:
- statement("uint subgroupBallotFindLSB(uvec4 value) { return findLSB(value.x); }");
- statement("uint subgroupBallotFindMSB(uvec4 value) { return findMSB(value.x); }");
- break;
- default:
- break;
- }
- }
- statement("#else");
- statement("uint subgroupBallotFindLSB(uvec4 value)");
- begin_scope();
- statement("int firstLive = findLSB(value.x);");
- statement("return uint(firstLive != -1 ? firstLive : (findLSB(value.y) + 32));");
- end_scope();
- statement("uint subgroupBallotFindMSB(uvec4 value)");
- begin_scope();
- statement("int firstLive = findMSB(value.y);");
- statement("return uint(firstLive != -1 ? (firstLive + 32) : findMSB(value.x));");
- end_scope();
- statement("#endif");
- statement("");
- }
- if (shader_subgroup_supporter.is_feature_requested(Supp::SubgroupAll_Any_AllEqualBool))
- {
- auto exts = Supp::get_candidates_for_feature(Supp::SubgroupAll_Any_AllEqualBool, result);
- for (auto &e : exts)
- {
- const char *name = Supp::get_extension_name(e);
- statement(&e == &exts.front() ? "#if" : "#elif", " defined(", name, ")");
- switch (e)
- {
- case Supp::NV_gpu_shader_5:
- statement("bool subgroupAll(bool value) { return allThreadsNV(value); }");
- statement("bool subgroupAny(bool value) { return anyThreadNV(value); }");
- statement("bool subgroupAllEqual(bool value) { return allThreadsEqualNV(value); }");
- break;
- case Supp::ARB_shader_group_vote:
- statement("bool subgroupAll(bool v) { return allInvocationsARB(v); }");
- statement("bool subgroupAny(bool v) { return anyInvocationARB(v); }");
- statement("bool subgroupAllEqual(bool v) { return allInvocationsEqualARB(v); }");
- break;
- case Supp::AMD_gcn_shader:
- statement("bool subgroupAll(bool value) { return ballotAMD(value) == ballotAMD(true); }");
- statement("bool subgroupAny(bool value) { return ballotAMD(value) != 0ull; }");
- statement("bool subgroupAllEqual(bool value) { uint64_t b = ballotAMD(value); return b == 0ull || "
- "b == ballotAMD(true); }");
- break;
- default:
- break;
- }
- }
- statement("#endif");
- statement("");
- }
- if (shader_subgroup_supporter.is_feature_requested(Supp::SubgroupAllEqualT))
- {
- statement("#ifndef GL_KHR_shader_subgroup_vote");
- statement(
- "#define _SPIRV_CROSS_SUBGROUP_ALL_EQUAL_WORKAROUND(type) bool subgroupAllEqual(type value) { return "
- "subgroupAllEqual(subgroupBroadcastFirst(value) == value); }");
- for (const char *t : workaround_types)
- statement("_SPIRV_CROSS_SUBGROUP_ALL_EQUAL_WORKAROUND(", t, ")");
- statement("#undef _SPIRV_CROSS_SUBGROUP_ALL_EQUAL_WORKAROUND");
- statement("#endif");
- statement("");
- }
- if (shader_subgroup_supporter.is_feature_requested(Supp::SubgroupBallot))
- {
- auto exts = Supp::get_candidates_for_feature(Supp::SubgroupBallot, result);
- for (auto &e : exts)
- {
- const char *name = Supp::get_extension_name(e);
- statement(&e == &exts.front() ? "#if" : "#elif", " defined(", name, ")");
- switch (e)
- {
- case Supp::NV_shader_thread_group:
- statement("uvec4 subgroupBallot(bool v) { return uvec4(ballotThreadNV(v), 0u, 0u, 0u); }");
- break;
- case Supp::ARB_shader_ballot:
- statement("uvec4 subgroupBallot(bool v) { return uvec4(unpackUint2x32(ballotARB(v)), 0u, 0u); }");
- break;
- default:
- break;
- }
- }
- statement("#endif");
- statement("");
- }
- if (shader_subgroup_supporter.is_feature_requested(Supp::SubgroupElect))
- {
- statement("#ifndef GL_KHR_shader_subgroup_basic");
- statement("bool subgroupElect()");
- begin_scope();
- statement("uvec4 activeMask = subgroupBallot(true);");
- statement("uint firstLive = subgroupBallotFindLSB(activeMask);");
- statement("return gl_SubgroupInvocationID == firstLive;");
- end_scope();
- statement("#endif");
- statement("");
- }
- if (shader_subgroup_supporter.is_feature_requested(Supp::SubgroupBarrier))
- {
- // Extensions we're using in place of GL_KHR_shader_subgroup_basic state
- // that subgroup execute in lockstep so this barrier is implicit.
- // However the GL 4.6 spec also states that `barrier` implies a shared memory barrier,
- // and a specific test of optimizing scans by leveraging lock-step invocation execution,
- // has shown that a `memoryBarrierShared` is needed in place of a `subgroupBarrier`.
- // https://github.com/buildaworldnet/IrrlichtBAW/commit/d8536857991b89a30a6b65d29441e51b64c2c7ad#diff-9f898d27be1ea6fc79b03d9b361e299334c1a347b6e4dc344ee66110c6aa596aR19
- statement("#ifndef GL_KHR_shader_subgroup_basic");
- statement("void subgroupBarrier() { memoryBarrierShared(); }");
- statement("#endif");
- statement("");
- }
- if (shader_subgroup_supporter.is_feature_requested(Supp::SubgroupMemBarrier))
- {
- if (model == ExecutionModelGLCompute)
- {
- statement("#ifndef GL_KHR_shader_subgroup_basic");
- statement("void subgroupMemoryBarrier() { groupMemoryBarrier(); }");
- statement("void subgroupMemoryBarrierBuffer() { groupMemoryBarrier(); }");
- statement("void subgroupMemoryBarrierShared() { memoryBarrierShared(); }");
- statement("void subgroupMemoryBarrierImage() { groupMemoryBarrier(); }");
- statement("#endif");
- }
- else
- {
- statement("#ifndef GL_KHR_shader_subgroup_basic");
- statement("void subgroupMemoryBarrier() { memoryBarrier(); }");
- statement("void subgroupMemoryBarrierBuffer() { memoryBarrierBuffer(); }");
- statement("void subgroupMemoryBarrierImage() { memoryBarrierImage(); }");
- statement("#endif");
- }
- statement("");
- }
- if (shader_subgroup_supporter.is_feature_requested(Supp::SubgroupInverseBallot_InclBitCount_ExclBitCout))
- {
- statement("#ifndef GL_KHR_shader_subgroup_ballot");
- statement("bool subgroupInverseBallot(uvec4 value)");
- begin_scope();
- statement("return any(notEqual(value.xy & gl_SubgroupEqMask.xy, uvec2(0u)));");
- end_scope();
- statement("uint subgroupBallotInclusiveBitCount(uvec4 value)");
- begin_scope();
- statement("uvec2 v = value.xy & gl_SubgroupLeMask.xy;");
- statement("ivec2 c = bitCount(v);");
- statement_no_indent("#ifdef GL_NV_shader_thread_group");
- statement("return uint(c.x);");
- statement_no_indent("#else");
- statement("return uint(c.x + c.y);");
- statement_no_indent("#endif");
- end_scope();
- statement("uint subgroupBallotExclusiveBitCount(uvec4 value)");
- begin_scope();
- statement("uvec2 v = value.xy & gl_SubgroupLtMask.xy;");
- statement("ivec2 c = bitCount(v);");
- statement_no_indent("#ifdef GL_NV_shader_thread_group");
- statement("return uint(c.x);");
- statement_no_indent("#else");
- statement("return uint(c.x + c.y);");
- statement_no_indent("#endif");
- end_scope();
- statement("#endif");
- statement("");
- }
- if (shader_subgroup_supporter.is_feature_requested(Supp::SubgroupBallotBitCount))
- {
- statement("#ifndef GL_KHR_shader_subgroup_ballot");
- statement("uint subgroupBallotBitCount(uvec4 value)");
- begin_scope();
- statement("ivec2 c = bitCount(value.xy);");
- statement_no_indent("#ifdef GL_NV_shader_thread_group");
- statement("return uint(c.x);");
- statement_no_indent("#else");
- statement("return uint(c.x + c.y);");
- statement_no_indent("#endif");
- end_scope();
- statement("#endif");
- statement("");
- }
- if (shader_subgroup_supporter.is_feature_requested(Supp::SubgroupBallotBitExtract))
- {
- statement("#ifndef GL_KHR_shader_subgroup_ballot");
- statement("bool subgroupBallotBitExtract(uvec4 value, uint index)");
- begin_scope();
- statement_no_indent("#ifdef GL_NV_shader_thread_group");
- statement("uint shifted = value.x >> index;");
- statement_no_indent("#else");
- statement("uint shifted = value[index >> 5u] >> (index & 0x1fu);");
- statement_no_indent("#endif");
- statement("return (shifted & 1u) != 0u;");
- end_scope();
- statement("#endif");
- statement("");
- }
- auto arithmetic_feature_helper =
- [&](Supp::Feature feat, std::string func_name, Op op, GroupOperation group_op)
- {
- if (shader_subgroup_supporter.is_feature_requested(feat))
- {
- auto exts = Supp::get_candidates_for_feature(feat, result);
- for (auto &e : exts)
- {
- const char *name = Supp::get_extension_name(e);
- statement(&e == &exts.front() ? "#if" : "#elif", " defined(", name, ")");
- switch (e)
- {
- case Supp::NV_shader_thread_shuffle:
- emit_subgroup_arithmetic_workaround(func_name, op, group_op);
- break;
- default:
- break;
- }
- }
- statement("#endif");
- statement("");
- }
- };
- arithmetic_feature_helper(Supp::SubgroupArithmeticIAddReduce, "subgroupAdd", OpGroupNonUniformIAdd,
- GroupOperationReduce);
- arithmetic_feature_helper(Supp::SubgroupArithmeticIAddExclusiveScan, "subgroupExclusiveAdd",
- OpGroupNonUniformIAdd, GroupOperationExclusiveScan);
- arithmetic_feature_helper(Supp::SubgroupArithmeticIAddInclusiveScan, "subgroupInclusiveAdd",
- OpGroupNonUniformIAdd, GroupOperationInclusiveScan);
- arithmetic_feature_helper(Supp::SubgroupArithmeticFAddReduce, "subgroupAdd", OpGroupNonUniformFAdd,
- GroupOperationReduce);
- arithmetic_feature_helper(Supp::SubgroupArithmeticFAddExclusiveScan, "subgroupExclusiveAdd",
- OpGroupNonUniformFAdd, GroupOperationExclusiveScan);
- arithmetic_feature_helper(Supp::SubgroupArithmeticFAddInclusiveScan, "subgroupInclusiveAdd",
- OpGroupNonUniformFAdd, GroupOperationInclusiveScan);
- arithmetic_feature_helper(Supp::SubgroupArithmeticIMulReduce, "subgroupMul", OpGroupNonUniformIMul,
- GroupOperationReduce);
- arithmetic_feature_helper(Supp::SubgroupArithmeticIMulExclusiveScan, "subgroupExclusiveMul",
- OpGroupNonUniformIMul, GroupOperationExclusiveScan);
- arithmetic_feature_helper(Supp::SubgroupArithmeticIMulInclusiveScan, "subgroupInclusiveMul",
- OpGroupNonUniformIMul, GroupOperationInclusiveScan);
- arithmetic_feature_helper(Supp::SubgroupArithmeticFMulReduce, "subgroupMul", OpGroupNonUniformFMul,
- GroupOperationReduce);
- arithmetic_feature_helper(Supp::SubgroupArithmeticFMulExclusiveScan, "subgroupExclusiveMul",
- OpGroupNonUniformFMul, GroupOperationExclusiveScan);
- arithmetic_feature_helper(Supp::SubgroupArithmeticFMulInclusiveScan, "subgroupInclusiveMul",
- OpGroupNonUniformFMul, GroupOperationInclusiveScan);
- }
- if (!workaround_ubo_load_overload_types.empty())
- {
- for (auto &type_id : workaround_ubo_load_overload_types)
- {
- auto &type = get<SPIRType>(type_id);
- if (options.es && is_matrix(type))
- {
- // Need both variants.
- // GLSL cannot overload on precision, so need to dispatch appropriately.
- statement("highp ", type_to_glsl(type), " spvWorkaroundRowMajor(highp ", type_to_glsl(type), " wrap) { return wrap; }");
- statement("mediump ", type_to_glsl(type), " spvWorkaroundRowMajorMP(mediump ", type_to_glsl(type), " wrap) { return wrap; }");
- }
- else
- {
- statement(type_to_glsl(type), " spvWorkaroundRowMajor(", type_to_glsl(type), " wrap) { return wrap; }");
- }
- }
- statement("");
- }
- }
- void CompilerGLSL::emit_polyfills(uint32_t polyfills, bool relaxed)
- {
- const char *qual = "";
- const char *suffix = (options.es && relaxed) ? "MP" : "";
- if (options.es)
- qual = relaxed ? "mediump " : "highp ";
- if (polyfills & PolyfillTranspose2x2)
- {
- statement(qual, "mat2 spvTranspose", suffix, "(", qual, "mat2 m)");
- begin_scope();
- statement("return mat2(m[0][0], m[1][0], m[0][1], m[1][1]);");
- end_scope();
- statement("");
- }
- if (polyfills & PolyfillTranspose3x3)
- {
- statement(qual, "mat3 spvTranspose", suffix, "(", qual, "mat3 m)");
- begin_scope();
- statement("return mat3(m[0][0], m[1][0], m[2][0], m[0][1], m[1][1], m[2][1], m[0][2], m[1][2], m[2][2]);");
- end_scope();
- statement("");
- }
- if (polyfills & PolyfillTranspose4x4)
- {
- statement(qual, "mat4 spvTranspose", suffix, "(", qual, "mat4 m)");
- begin_scope();
- statement("return mat4(m[0][0], m[1][0], m[2][0], m[3][0], m[0][1], m[1][1], m[2][1], m[3][1], m[0][2], "
- "m[1][2], m[2][2], m[3][2], m[0][3], m[1][3], m[2][3], m[3][3]);");
- end_scope();
- statement("");
- }
- if (polyfills & PolyfillDeterminant2x2)
- {
- statement(qual, "float spvDeterminant", suffix, "(", qual, "mat2 m)");
- begin_scope();
- statement("return m[0][0] * m[1][1] - m[0][1] * m[1][0];");
- end_scope();
- statement("");
- }
- if (polyfills & PolyfillDeterminant3x3)
- {
- statement(qual, "float spvDeterminant", suffix, "(", qual, "mat3 m)");
- begin_scope();
- statement("return dot(m[0], vec3(m[1][1] * m[2][2] - m[1][2] * m[2][1], "
- "m[1][2] * m[2][0] - m[1][0] * m[2][2], "
- "m[1][0] * m[2][1] - m[1][1] * m[2][0]));");
- end_scope();
- statement("");
- }
- if (polyfills & PolyfillDeterminant4x4)
- {
- statement(qual, "float spvDeterminant", suffix, "(", qual, "mat4 m)");
- begin_scope();
- statement("return dot(m[0], vec4("
- "m[2][1] * m[3][2] * m[1][3] - m[3][1] * m[2][2] * m[1][3] + m[3][1] * m[1][2] * m[2][3] - m[1][1] * m[3][2] * m[2][3] - m[2][1] * m[1][2] * m[3][3] + m[1][1] * m[2][2] * m[3][3], "
- "m[3][0] * m[2][2] * m[1][3] - m[2][0] * m[3][2] * m[1][3] - m[3][0] * m[1][2] * m[2][3] + m[1][0] * m[3][2] * m[2][3] + m[2][0] * m[1][2] * m[3][3] - m[1][0] * m[2][2] * m[3][3], "
- "m[2][0] * m[3][1] * m[1][3] - m[3][0] * m[2][1] * m[1][3] + m[3][0] * m[1][1] * m[2][3] - m[1][0] * m[3][1] * m[2][3] - m[2][0] * m[1][1] * m[3][3] + m[1][0] * m[2][1] * m[3][3], "
- "m[3][0] * m[2][1] * m[1][2] - m[2][0] * m[3][1] * m[1][2] - m[3][0] * m[1][1] * m[2][2] + m[1][0] * m[3][1] * m[2][2] + m[2][0] * m[1][1] * m[3][2] - m[1][0] * m[2][1] * m[3][2]));");
- end_scope();
- statement("");
- }
- if (polyfills & PolyfillMatrixInverse2x2)
- {
- statement(qual, "mat2 spvInverse", suffix, "(", qual, "mat2 m)");
- begin_scope();
- statement("return mat2(m[1][1], -m[0][1], -m[1][0], m[0][0]) "
- "* (1.0 / (m[0][0] * m[1][1] - m[1][0] * m[0][1]));");
- end_scope();
- statement("");
- }
- if (polyfills & PolyfillMatrixInverse3x3)
- {
- statement(qual, "mat3 spvInverse", suffix, "(", qual, "mat3 m)");
- begin_scope();
- statement(qual, "vec3 t = vec3(m[1][1] * m[2][2] - m[1][2] * m[2][1], m[1][2] * m[2][0] - m[1][0] * m[2][2], m[1][0] * m[2][1] - m[1][1] * m[2][0]);");
- statement("return mat3(t[0], "
- "m[0][2] * m[2][1] - m[0][1] * m[2][2], "
- "m[0][1] * m[1][2] - m[0][2] * m[1][1], "
- "t[1], "
- "m[0][0] * m[2][2] - m[0][2] * m[2][0], "
- "m[0][2] * m[1][0] - m[0][0] * m[1][2], "
- "t[2], "
- "m[0][1] * m[2][0] - m[0][0] * m[2][1], "
- "m[0][0] * m[1][1] - m[0][1] * m[1][0]) "
- "* (1.0 / dot(m[0], t));");
- end_scope();
- statement("");
- }
- if (polyfills & PolyfillMatrixInverse4x4)
- {
- statement(qual, "mat4 spvInverse", suffix, "(", qual, "mat4 m)");
- begin_scope();
- statement(qual, "vec4 t = vec4("
- "m[2][1] * m[3][2] * m[1][3] - m[3][1] * m[2][2] * m[1][3] + m[3][1] * m[1][2] * m[2][3] - m[1][1] * m[3][2] * m[2][3] - m[2][1] * m[1][2] * m[3][3] + m[1][1] * m[2][2] * m[3][3], "
- "m[3][0] * m[2][2] * m[1][3] - m[2][0] * m[3][2] * m[1][3] - m[3][0] * m[1][2] * m[2][3] + m[1][0] * m[3][2] * m[2][3] + m[2][0] * m[1][2] * m[3][3] - m[1][0] * m[2][2] * m[3][3], "
- "m[2][0] * m[3][1] * m[1][3] - m[3][0] * m[2][1] * m[1][3] + m[3][0] * m[1][1] * m[2][3] - m[1][0] * m[3][1] * m[2][3] - m[2][0] * m[1][1] * m[3][3] + m[1][0] * m[2][1] * m[3][3], "
- "m[3][0] * m[2][1] * m[1][2] - m[2][0] * m[3][1] * m[1][2] - m[3][0] * m[1][1] * m[2][2] + m[1][0] * m[3][1] * m[2][2] + m[2][0] * m[1][1] * m[3][2] - m[1][0] * m[2][1] * m[3][2]);");
- statement("return mat4("
- "t[0], "
- "m[3][1] * m[2][2] * m[0][3] - m[2][1] * m[3][2] * m[0][3] - m[3][1] * m[0][2] * m[2][3] + m[0][1] * m[3][2] * m[2][3] + m[2][1] * m[0][2] * m[3][3] - m[0][1] * m[2][2] * m[3][3], "
- "m[1][1] * m[3][2] * m[0][3] - m[3][1] * m[1][2] * m[0][3] + m[3][1] * m[0][2] * m[1][3] - m[0][1] * m[3][2] * m[1][3] - m[1][1] * m[0][2] * m[3][3] + m[0][1] * m[1][2] * m[3][3], "
- "m[2][1] * m[1][2] * m[0][3] - m[1][1] * m[2][2] * m[0][3] - m[2][1] * m[0][2] * m[1][3] + m[0][1] * m[2][2] * m[1][3] + m[1][1] * m[0][2] * m[2][3] - m[0][1] * m[1][2] * m[2][3], "
- "t[1], "
- "m[2][0] * m[3][2] * m[0][3] - m[3][0] * m[2][2] * m[0][3] + m[3][0] * m[0][2] * m[2][3] - m[0][0] * m[3][2] * m[2][3] - m[2][0] * m[0][2] * m[3][3] + m[0][0] * m[2][2] * m[3][3], "
- "m[3][0] * m[1][2] * m[0][3] - m[1][0] * m[3][2] * m[0][3] - m[3][0] * m[0][2] * m[1][3] + m[0][0] * m[3][2] * m[1][3] + m[1][0] * m[0][2] * m[3][3] - m[0][0] * m[1][2] * m[3][3], "
- "m[1][0] * m[2][2] * m[0][3] - m[2][0] * m[1][2] * m[0][3] + m[2][0] * m[0][2] * m[1][3] - m[0][0] * m[2][2] * m[1][3] - m[1][0] * m[0][2] * m[2][3] + m[0][0] * m[1][2] * m[2][3], "
- "t[2], "
- "m[3][0] * m[2][1] * m[0][3] - m[2][0] * m[3][1] * m[0][3] - m[3][0] * m[0][1] * m[2][3] + m[0][0] * m[3][1] * m[2][3] + m[2][0] * m[0][1] * m[3][3] - m[0][0] * m[2][1] * m[3][3], "
- "m[1][0] * m[3][1] * m[0][3] - m[3][0] * m[1][1] * m[0][3] + m[3][0] * m[0][1] * m[1][3] - m[0][0] * m[3][1] * m[1][3] - m[1][0] * m[0][1] * m[3][3] + m[0][0] * m[1][1] * m[3][3], "
- "m[2][0] * m[1][1] * m[0][3] - m[1][0] * m[2][1] * m[0][3] - m[2][0] * m[0][1] * m[1][3] + m[0][0] * m[2][1] * m[1][3] + m[1][0] * m[0][1] * m[2][3] - m[0][0] * m[1][1] * m[2][3], "
- "t[3], "
- "m[2][0] * m[3][1] * m[0][2] - m[3][0] * m[2][1] * m[0][2] + m[3][0] * m[0][1] * m[2][2] - m[0][0] * m[3][1] * m[2][2] - m[2][0] * m[0][1] * m[3][2] + m[0][0] * m[2][1] * m[3][2], "
- "m[3][0] * m[1][1] * m[0][2] - m[1][0] * m[3][1] * m[0][2] - m[3][0] * m[0][1] * m[1][2] + m[0][0] * m[3][1] * m[1][2] + m[1][0] * m[0][1] * m[3][2] - m[0][0] * m[1][1] * m[3][2], "
- "m[1][0] * m[2][1] * m[0][2] - m[2][0] * m[1][1] * m[0][2] + m[2][0] * m[0][1] * m[1][2] - m[0][0] * m[2][1] * m[1][2] - m[1][0] * m[0][1] * m[2][2] + m[0][0] * m[1][1] * m[2][2]) "
- "* (1.0 / dot(m[0], t));");
- end_scope();
- statement("");
- }
- if (!relaxed)
- {
- static const Polyfill polys[3][3] = {
- { PolyfillNMin16, PolyfillNMin32, PolyfillNMin64 },
- { PolyfillNMax16, PolyfillNMax32, PolyfillNMax64 },
- { PolyfillNClamp16, PolyfillNClamp32, PolyfillNClamp64 },
- };
- static const GLSLstd450 glsl_ops[] = { GLSLstd450NMin, GLSLstd450NMax, GLSLstd450NClamp };
- static const char *spv_ops[] = { "spvNMin", "spvNMax", "spvNClamp" };
- bool has_poly = false;
- for (uint32_t i = 0; i < 3; i++)
- {
- for (uint32_t j = 0; j < 3; j++)
- {
- if ((polyfills & polys[i][j]) == 0)
- continue;
- const char *types[3][4] = {
- { "float16_t", "f16vec2", "f16vec3", "f16vec4" },
- { "float", "vec2", "vec3", "vec4" },
- { "double", "dvec2", "dvec3", "dvec4" },
- };
- for (uint32_t k = 0; k < 4; k++)
- {
- auto *type = types[j][k];
- if (i < 2)
- {
- statement("spirv_instruction(set = \"GLSL.std.450\", id = ", glsl_ops[i], ") ",
- type, " ", spv_ops[i], "(", type, ", ", type, ");");
- }
- else
- {
- statement("spirv_instruction(set = \"GLSL.std.450\", id = ", glsl_ops[i], ") ",
- type, " ", spv_ops[i], "(", type, ", ", type, ", ", type, ");");
- }
- has_poly = true;
- }
- }
- }
- if (has_poly)
- statement("");
- }
- else
- {
- // Mediump intrinsics don't work correctly, so wrap the intrinsic in an outer shell that ensures mediump
- // propagation.
- static const Polyfill polys[3][3] = {
- { PolyfillNMin16, PolyfillNMin32, PolyfillNMin64 },
- { PolyfillNMax16, PolyfillNMax32, PolyfillNMax64 },
- { PolyfillNClamp16, PolyfillNClamp32, PolyfillNClamp64 },
- };
- static const char *spv_ops[] = { "spvNMin", "spvNMax", "spvNClamp" };
- for (uint32_t i = 0; i < 3; i++)
- {
- for (uint32_t j = 0; j < 3; j++)
- {
- if ((polyfills & polys[i][j]) == 0)
- continue;
- const char *types[3][4] = {
- { "float16_t", "f16vec2", "f16vec3", "f16vec4" },
- { "float", "vec2", "vec3", "vec4" },
- { "double", "dvec2", "dvec3", "dvec4" },
- };
- for (uint32_t k = 0; k < 4; k++)
- {
- auto *type = types[j][k];
- if (i < 2)
- {
- statement("mediump ", type, " ", spv_ops[i], "Relaxed(",
- "mediump ", type, " a, mediump ", type, " b)");
- begin_scope();
- statement("mediump ", type, " res = ", spv_ops[i], "(a, b);");
- statement("return res;");
- end_scope();
- statement("");
- }
- else
- {
- statement("mediump ", type, " ", spv_ops[i], "Relaxed(",
- "mediump ", type, " a, mediump ", type, " b, mediump ", type, " c)");
- begin_scope();
- statement("mediump ", type, " res = ", spv_ops[i], "(a, b, c);");
- statement("return res;");
- end_scope();
- statement("");
- }
- }
- }
- }
- }
- }
- // Returns a string representation of the ID, usable as a function arg.
- // Default is to simply return the expression representation fo the arg ID.
- // Subclasses may override to modify the return value.
- string CompilerGLSL::to_func_call_arg(const SPIRFunction::Parameter &arg, uint32_t id)
- {
- // BDA expects pointers through function interface.
- if (!arg.alias_global_variable && is_physical_or_buffer_pointer(expression_type(id)))
- return to_pointer_expression(id);
- // Make sure that we use the name of the original variable, and not the parameter alias.
- uint32_t name_id = id;
- auto *var = maybe_get<SPIRVariable>(id);
- if (var && var->basevariable)
- name_id = var->basevariable;
- return to_unpacked_expression(name_id);
- }
- void CompilerGLSL::force_temporary_and_recompile(uint32_t id)
- {
- auto res = forced_temporaries.insert(id);
- // Forcing new temporaries guarantees forward progress.
- if (res.second)
- force_recompile_guarantee_forward_progress();
- else
- force_recompile();
- }
- uint32_t CompilerGLSL::consume_temporary_in_precision_context(uint32_t type_id, uint32_t id, Options::Precision precision)
- {
- // Constants do not have innate precision.
- auto handle_type = ir.ids[id].get_type();
- if (handle_type == TypeConstant || handle_type == TypeConstantOp || handle_type == TypeUndef)
- return id;
- // Ignore anything that isn't 32-bit values.
- auto &type = get<SPIRType>(type_id);
- if (type.pointer)
- return id;
- if (type.basetype != SPIRType::Float && type.basetype != SPIRType::UInt && type.basetype != SPIRType::Int)
- return id;
- if (precision == Options::DontCare)
- {
- // If precision is consumed as don't care (operations only consisting of constants),
- // we need to bind the expression to a temporary,
- // otherwise we have no way of controlling the precision later.
- auto itr = forced_temporaries.insert(id);
- if (itr.second)
- force_recompile_guarantee_forward_progress();
- return id;
- }
- auto current_precision = has_decoration(id, DecorationRelaxedPrecision) ? Options::Mediump : Options::Highp;
- if (current_precision == precision)
- return id;
- auto itr = temporary_to_mirror_precision_alias.find(id);
- if (itr == temporary_to_mirror_precision_alias.end())
- {
- uint32_t alias_id = ir.increase_bound_by(1);
- auto &m = ir.meta[alias_id];
- if (auto *input_m = ir.find_meta(id))
- m = *input_m;
- const char *prefix;
- if (precision == Options::Mediump)
- {
- set_decoration(alias_id, DecorationRelaxedPrecision);
- prefix = "mp_copy_";
- }
- else
- {
- unset_decoration(alias_id, DecorationRelaxedPrecision);
- prefix = "hp_copy_";
- }
- auto alias_name = join(prefix, to_name(id));
- ParsedIR::sanitize_underscores(alias_name);
- set_name(alias_id, alias_name);
- emit_op(type_id, alias_id, to_expression(id), true);
- temporary_to_mirror_precision_alias[id] = alias_id;
- forced_temporaries.insert(id);
- forced_temporaries.insert(alias_id);
- force_recompile_guarantee_forward_progress();
- id = alias_id;
- }
- else
- {
- id = itr->second;
- }
- return id;
- }
- void CompilerGLSL::handle_invalid_expression(uint32_t id)
- {
- // We tried to read an invalidated expression.
- // This means we need another pass at compilation, but next time,
- // force temporary variables so that they cannot be invalidated.
- force_temporary_and_recompile(id);
- // If the invalid expression happened as a result of a CompositeInsert
- // overwrite, we must block this from happening next iteration.
- if (composite_insert_overwritten.count(id))
- block_composite_insert_overwrite.insert(id);
- }
- // Converts the format of the current expression from packed to unpacked,
- // by wrapping the expression in a constructor of the appropriate type.
- // GLSL does not support packed formats, so simply return the expression.
- // Subclasses that do will override.
- string CompilerGLSL::unpack_expression_type(string expr_str, const SPIRType &, uint32_t, bool, bool)
- {
- return expr_str;
- }
- // Sometimes we proactively enclosed an expression where it turns out we might have not needed it after all.
- void CompilerGLSL::strip_enclosed_expression(string &expr)
- {
- if (expr.size() < 2 || expr.front() != '(' || expr.back() != ')')
- return;
- // Have to make sure that our first and last parens actually enclose everything inside it.
- uint32_t paren_count = 0;
- for (auto &c : expr)
- {
- if (c == '(')
- paren_count++;
- else if (c == ')')
- {
- paren_count--;
- // If we hit 0 and this is not the final char, our first and final parens actually don't
- // enclose the expression, and we cannot strip, e.g.: (a + b) * (c + d).
- if (paren_count == 0 && &c != &expr.back())
- return;
- }
- }
- expr.erase(expr.size() - 1, 1);
- expr.erase(begin(expr));
- }
- bool CompilerGLSL::needs_enclose_expression(const std::string &expr)
- {
- bool need_parens = false;
- // If the expression starts with a unary we need to enclose to deal with cases where we have back-to-back
- // unary expressions.
- if (!expr.empty())
- {
- auto c = expr.front();
- if (c == '-' || c == '+' || c == '!' || c == '~' || c == '&' || c == '*')
- need_parens = true;
- }
- if (!need_parens)
- {
- uint32_t paren_count = 0;
- for (auto c : expr)
- {
- if (c == '(' || c == '[')
- paren_count++;
- else if (c == ')' || c == ']')
- {
- assert(paren_count);
- paren_count--;
- }
- else if (c == ' ' && paren_count == 0)
- {
- need_parens = true;
- break;
- }
- }
- assert(paren_count == 0);
- }
- return need_parens;
- }
- string CompilerGLSL::enclose_expression(const string &expr)
- {
- // If this expression contains any spaces which are not enclosed by parentheses,
- // we need to enclose it so we can treat the whole string as an expression.
- // This happens when two expressions have been part of a binary op earlier.
- if (needs_enclose_expression(expr))
- return join('(', expr, ')');
- else
- return expr;
- }
- string CompilerGLSL::dereference_expression(const SPIRType &expr_type, const std::string &expr)
- {
- // If this expression starts with an address-of operator ('&'), then
- // just return the part after the operator.
- // TODO: Strip parens if unnecessary?
- if (expr.front() == '&')
- return expr.substr(1);
- else if (backend.native_pointers)
- return join('*', expr);
- else if (is_physical_pointer(expr_type) && !is_physical_pointer_to_buffer_block(expr_type))
- return join(enclose_expression(expr), ".value");
- else
- return expr;
- }
- string CompilerGLSL::address_of_expression(const std::string &expr)
- {
- if (expr.size() > 3 && expr[0] == '(' && expr[1] == '*' && expr.back() == ')')
- {
- // If we have an expression which looks like (*foo), taking the address of it is the same as stripping
- // the first two and last characters. We might have to enclose the expression.
- // This doesn't work for cases like (*foo + 10),
- // but this is an r-value expression which we cannot take the address of anyways.
- return enclose_expression(expr.substr(2, expr.size() - 3));
- }
- else if (expr.front() == '*')
- {
- // If this expression starts with a dereference operator ('*'), then
- // just return the part after the operator.
- return expr.substr(1);
- }
- else
- return join('&', enclose_expression(expr));
- }
- // Just like to_expression except that we enclose the expression inside parentheses if needed.
- string CompilerGLSL::to_enclosed_expression(uint32_t id, bool register_expression_read)
- {
- return enclose_expression(to_expression(id, register_expression_read));
- }
- // Used explicitly when we want to read a row-major expression, but without any transpose shenanigans.
- // need_transpose must be forced to false.
- string CompilerGLSL::to_unpacked_row_major_matrix_expression(uint32_t id)
- {
- return unpack_expression_type(to_expression(id), expression_type(id),
- get_extended_decoration(id, SPIRVCrossDecorationPhysicalTypeID),
- has_extended_decoration(id, SPIRVCrossDecorationPhysicalTypePacked), true);
- }
- string CompilerGLSL::to_unpacked_expression(uint32_t id, bool register_expression_read)
- {
- // If we need to transpose, it will also take care of unpacking rules.
- auto *e = maybe_get<SPIRExpression>(id);
- bool need_transpose = e && e->need_transpose;
- bool is_remapped = has_extended_decoration(id, SPIRVCrossDecorationPhysicalTypeID);
- bool is_packed = has_extended_decoration(id, SPIRVCrossDecorationPhysicalTypePacked);
- if (!need_transpose && (is_remapped || is_packed))
- {
- return unpack_expression_type(to_expression(id, register_expression_read),
- get_pointee_type(expression_type_id(id)),
- get_extended_decoration(id, SPIRVCrossDecorationPhysicalTypeID),
- has_extended_decoration(id, SPIRVCrossDecorationPhysicalTypePacked), false);
- }
- else
- return to_expression(id, register_expression_read);
- }
- string CompilerGLSL::to_enclosed_unpacked_expression(uint32_t id, bool register_expression_read)
- {
- return enclose_expression(to_unpacked_expression(id, register_expression_read));
- }
- string CompilerGLSL::to_dereferenced_expression(uint32_t id, bool register_expression_read)
- {
- auto &type = expression_type(id);
- if (is_pointer(type) && should_dereference(id))
- return dereference_expression(type, to_enclosed_expression(id, register_expression_read));
- else
- return to_expression(id, register_expression_read);
- }
- string CompilerGLSL::to_pointer_expression(uint32_t id, bool register_expression_read)
- {
- auto &type = expression_type(id);
- if (is_pointer(type) && expression_is_lvalue(id) && !should_dereference(id))
- return address_of_expression(to_enclosed_expression(id, register_expression_read));
- else
- return to_unpacked_expression(id, register_expression_read);
- }
- string CompilerGLSL::to_enclosed_pointer_expression(uint32_t id, bool register_expression_read)
- {
- auto &type = expression_type(id);
- if (is_pointer(type) && expression_is_lvalue(id) && !should_dereference(id))
- return address_of_expression(to_enclosed_expression(id, register_expression_read));
- else
- return to_enclosed_unpacked_expression(id, register_expression_read);
- }
- string CompilerGLSL::to_extract_component_expression(uint32_t id, uint32_t index)
- {
- auto expr = to_enclosed_expression(id);
- if (has_extended_decoration(id, SPIRVCrossDecorationPhysicalTypePacked))
- return join(expr, "[", index, "]");
- else
- return join(expr, ".", index_to_swizzle(index));
- }
- string CompilerGLSL::to_extract_constant_composite_expression(uint32_t result_type, const SPIRConstant &c,
- const uint32_t *chain, uint32_t length)
- {
- // It is kinda silly if application actually enter this path since they know the constant up front.
- // It is useful here to extract the plain constant directly.
- SPIRConstant tmp;
- tmp.constant_type = result_type;
- auto &composite_type = get<SPIRType>(c.constant_type);
- assert(composite_type.basetype != SPIRType::Struct && composite_type.array.empty());
- assert(!c.specialization);
- if (is_matrix(composite_type))
- {
- if (length == 2)
- {
- tmp.m.c[0].vecsize = 1;
- tmp.m.columns = 1;
- tmp.m.c[0].r[0] = c.m.c[chain[0]].r[chain[1]];
- }
- else
- {
- assert(length == 1);
- tmp.m.c[0].vecsize = composite_type.vecsize;
- tmp.m.columns = 1;
- tmp.m.c[0] = c.m.c[chain[0]];
- }
- }
- else
- {
- assert(length == 1);
- tmp.m.c[0].vecsize = 1;
- tmp.m.columns = 1;
- tmp.m.c[0].r[0] = c.m.c[0].r[chain[0]];
- }
- return constant_expression(tmp);
- }
- string CompilerGLSL::to_rerolled_array_expression(const SPIRType &parent_type,
- const string &base_expr, const SPIRType &type)
- {
- bool remapped_boolean = parent_type.basetype == SPIRType::Struct &&
- type.basetype == SPIRType::Boolean &&
- backend.boolean_in_struct_remapped_type != SPIRType::Boolean;
- SPIRType tmp_type { OpNop };
- if (remapped_boolean)
- {
- tmp_type = get<SPIRType>(type.parent_type);
- tmp_type.basetype = backend.boolean_in_struct_remapped_type;
- }
- else if (type.basetype == SPIRType::Boolean && backend.boolean_in_struct_remapped_type != SPIRType::Boolean)
- {
- // It's possible that we have an r-value expression that was OpLoaded from a struct.
- // We have to reroll this and explicitly cast the input to bool, because the r-value is short.
- tmp_type = get<SPIRType>(type.parent_type);
- remapped_boolean = true;
- }
- uint32_t size = to_array_size_literal(type);
- auto &parent = get<SPIRType>(type.parent_type);
- string expr = "{ ";
- for (uint32_t i = 0; i < size; i++)
- {
- auto subexpr = join(base_expr, "[", convert_to_string(i), "]");
- if (!is_array(parent))
- {
- if (remapped_boolean)
- subexpr = join(type_to_glsl(tmp_type), "(", subexpr, ")");
- expr += subexpr;
- }
- else
- expr += to_rerolled_array_expression(parent_type, subexpr, parent);
- if (i + 1 < size)
- expr += ", ";
- }
- expr += " }";
- return expr;
- }
- string CompilerGLSL::to_composite_constructor_expression(const SPIRType &parent_type, uint32_t id, bool block_like_type)
- {
- auto &type = expression_type(id);
- bool reroll_array = false;
- bool remapped_boolean = parent_type.basetype == SPIRType::Struct &&
- type.basetype == SPIRType::Boolean &&
- backend.boolean_in_struct_remapped_type != SPIRType::Boolean;
- if (is_array(type))
- {
- reroll_array = !backend.array_is_value_type ||
- (block_like_type && !backend.array_is_value_type_in_buffer_blocks);
- if (remapped_boolean)
- {
- // Forced to reroll if we have to change bool[] to short[].
- reroll_array = true;
- }
- }
- if (reroll_array)
- {
- // For this case, we need to "re-roll" an array initializer from a temporary.
- // We cannot simply pass the array directly, since it decays to a pointer and it cannot
- // participate in a struct initializer. E.g.
- // float arr[2] = { 1.0, 2.0 };
- // Foo foo = { arr }; must be transformed to
- // Foo foo = { { arr[0], arr[1] } };
- // The array sizes cannot be deduced from specialization constants since we cannot use any loops.
- // We're only triggering one read of the array expression, but this is fine since arrays have to be declared
- // as temporaries anyways.
- return to_rerolled_array_expression(parent_type, to_enclosed_expression(id), type);
- }
- else
- {
- auto expr = to_unpacked_expression(id);
- if (remapped_boolean)
- {
- auto tmp_type = type;
- tmp_type.basetype = backend.boolean_in_struct_remapped_type;
- expr = join(type_to_glsl(tmp_type), "(", expr, ")");
- }
- return expr;
- }
- }
- string CompilerGLSL::to_non_uniform_aware_expression(uint32_t id)
- {
- string expr = to_expression(id);
- if (has_decoration(id, DecorationNonUniform))
- convert_non_uniform_expression(expr, id);
- return expr;
- }
- string CompilerGLSL::to_atomic_ptr_expression(uint32_t id)
- {
- string expr = to_non_uniform_aware_expression(id);
- // If we have naked pointer to POD, we need to dereference to get the proper ".value" resolve.
- if (should_dereference(id))
- expr = dereference_expression(expression_type(id), expr);
- return expr;
- }
- string CompilerGLSL::to_expression(uint32_t id, bool register_expression_read)
- {
- auto itr = invalid_expressions.find(id);
- if (itr != end(invalid_expressions))
- handle_invalid_expression(id);
- if (ir.ids[id].get_type() == TypeExpression)
- {
- // We might have a more complex chain of dependencies.
- // A possible scenario is that we
- //
- // %1 = OpLoad
- // %2 = OpDoSomething %1 %1. here %2 will have a dependency on %1.
- // %3 = OpDoSomethingAgain %2 %2. Here %3 will lose the link to %1 since we don't propagate the dependencies like that.
- // OpStore %1 %foo // Here we can invalidate %1, and hence all expressions which depend on %1. Only %2 will know since it's part of invalid_expressions.
- // %4 = OpDoSomethingAnotherTime %3 %3 // If we forward all expressions we will see %1 expression after store, not before.
- //
- // However, we can propagate up a list of depended expressions when we used %2, so we can check if %2 is invalid when reading %3 after the store,
- // and see that we should not forward reads of the original variable.
- auto &expr = get<SPIRExpression>(id);
- for (uint32_t dep : expr.expression_dependencies)
- if (invalid_expressions.find(dep) != end(invalid_expressions))
- handle_invalid_expression(dep);
- }
- if (register_expression_read)
- track_expression_read(id);
- switch (ir.ids[id].get_type())
- {
- case TypeExpression:
- {
- auto &e = get<SPIRExpression>(id);
- if (e.base_expression)
- return to_enclosed_expression(e.base_expression) + e.expression;
- else if (e.need_transpose)
- {
- // This should not be reached for access chains, since we always deal explicitly with transpose state
- // when consuming an access chain expression.
- uint32_t physical_type_id = get_extended_decoration(id, SPIRVCrossDecorationPhysicalTypeID);
- bool is_packed = has_extended_decoration(id, SPIRVCrossDecorationPhysicalTypePacked);
- bool relaxed = has_decoration(id, DecorationRelaxedPrecision);
- return convert_row_major_matrix(e.expression, get<SPIRType>(e.expression_type), physical_type_id,
- is_packed, relaxed);
- }
- else if (flattened_structs.count(id))
- {
- return load_flattened_struct(e.expression, get<SPIRType>(e.expression_type));
- }
- else
- {
- if (is_forcing_recompilation())
- {
- // During first compilation phase, certain expression patterns can trigger exponential growth of memory.
- // Avoid this by returning dummy expressions during this phase.
- // Do not use empty expressions here, because those are sentinels for other cases.
- return "_";
- }
- else
- return e.expression;
- }
- }
- case TypeConstant:
- {
- auto &c = get<SPIRConstant>(id);
- auto &type = get<SPIRType>(c.constant_type);
- // WorkGroupSize may be a constant.
- if (has_decoration(c.self, DecorationBuiltIn))
- return builtin_to_glsl(BuiltIn(get_decoration(c.self, DecorationBuiltIn)), StorageClassGeneric);
- else if (c.specialization)
- {
- if (backend.workgroup_size_is_hidden)
- {
- int wg_index = get_constant_mapping_to_workgroup_component(c);
- if (wg_index >= 0)
- {
- auto wg_size = join(builtin_to_glsl(BuiltInWorkgroupSize, StorageClassInput), vector_swizzle(1, wg_index));
- if (type.basetype != SPIRType::UInt)
- wg_size = bitcast_expression(type, SPIRType::UInt, wg_size);
- return wg_size;
- }
- }
- if (expression_is_forwarded(id))
- return constant_expression(c);
- return to_name(id);
- }
- else if (c.is_used_as_lut)
- return to_name(id);
- else if (type.basetype == SPIRType::Struct && !backend.can_declare_struct_inline)
- return to_name(id);
- else if (!type.array.empty() && !backend.can_declare_arrays_inline)
- return to_name(id);
- else
- return constant_expression(c);
- }
- case TypeConstantOp:
- return to_name(id);
- case TypeVariable:
- {
- auto &var = get<SPIRVariable>(id);
- // If we try to use a loop variable before the loop header, we have to redirect it to the static expression,
- // the variable has not been declared yet.
- if (var.statically_assigned || (var.loop_variable && !var.loop_variable_enable))
- {
- // We might try to load from a loop variable before it has been initialized.
- // Prefer static expression and fallback to initializer.
- if (var.static_expression)
- return to_expression(var.static_expression);
- else if (var.initializer)
- return to_expression(var.initializer);
- else
- {
- // We cannot declare the variable yet, so have to fake it.
- uint32_t undef_id = ir.increase_bound_by(1);
- return emit_uninitialized_temporary_expression(get_variable_data_type_id(var), undef_id).expression;
- }
- }
- else if (var.deferred_declaration)
- {
- var.deferred_declaration = false;
- return variable_decl(var);
- }
- else if (flattened_structs.count(id))
- {
- return load_flattened_struct(to_name(id), get<SPIRType>(var.basetype));
- }
- else
- {
- auto &dec = ir.meta[var.self].decoration;
- if (dec.builtin)
- return builtin_to_glsl(dec.builtin_type, var.storage);
- else
- return to_name(id);
- }
- }
- case TypeCombinedImageSampler:
- // This type should never be taken the expression of directly.
- // The intention is that texture sampling functions will extract the image and samplers
- // separately and take their expressions as needed.
- // GLSL does not use this type because OpSampledImage immediately creates a combined image sampler
- // expression ala sampler2D(texture, sampler).
- SPIRV_CROSS_THROW("Combined image samplers have no default expression representation.");
- case TypeAccessChain:
- // We cannot express this type. They only have meaning in other OpAccessChains, OpStore or OpLoad.
- SPIRV_CROSS_THROW("Access chains have no default expression representation.");
- default:
- return to_name(id);
- }
- }
- SmallVector<ConstantID> CompilerGLSL::get_composite_constant_ids(ConstantID const_id)
- {
- if (auto *constant = maybe_get<SPIRConstant>(const_id))
- {
- const auto &type = get<SPIRType>(constant->constant_type);
- if (is_array(type) || type.basetype == SPIRType::Struct)
- return constant->subconstants;
- if (is_matrix(type))
- return SmallVector<ConstantID>(constant->m.id);
- if (is_vector(type))
- return SmallVector<ConstantID>(constant->m.c[0].id);
- SPIRV_CROSS_THROW("Unexpected scalar constant!");
- }
- if (!const_composite_insert_ids.count(const_id))
- SPIRV_CROSS_THROW("Unimplemented for this OpSpecConstantOp!");
- return const_composite_insert_ids[const_id];
- }
- void CompilerGLSL::fill_composite_constant(SPIRConstant &constant, TypeID type_id,
- const SmallVector<ConstantID> &initializers)
- {
- auto &type = get<SPIRType>(type_id);
- constant.specialization = true;
- if (is_array(type) || type.basetype == SPIRType::Struct)
- {
- constant.subconstants = initializers;
- }
- else if (is_matrix(type))
- {
- constant.m.columns = type.columns;
- for (uint32_t i = 0; i < type.columns; ++i)
- {
- constant.m.id[i] = initializers[i];
- constant.m.c[i].vecsize = type.vecsize;
- }
- }
- else if (is_vector(type))
- {
- constant.m.c[0].vecsize = type.vecsize;
- for (uint32_t i = 0; i < type.vecsize; ++i)
- constant.m.c[0].id[i] = initializers[i];
- }
- else
- SPIRV_CROSS_THROW("Unexpected scalar in SpecConstantOp CompositeInsert!");
- }
- void CompilerGLSL::set_composite_constant(ConstantID const_id, TypeID type_id,
- const SmallVector<ConstantID> &initializers)
- {
- if (maybe_get<SPIRConstantOp>(const_id))
- {
- const_composite_insert_ids[const_id] = initializers;
- return;
- }
- auto &constant = set<SPIRConstant>(const_id, type_id);
- fill_composite_constant(constant, type_id, initializers);
- forwarded_temporaries.insert(const_id);
- }
- TypeID CompilerGLSL::get_composite_member_type(TypeID type_id, uint32_t member_idx)
- {
- auto &type = get<SPIRType>(type_id);
- if (is_array(type))
- return type.parent_type;
- if (type.basetype == SPIRType::Struct)
- return type.member_types[member_idx];
- if (is_matrix(type))
- return type.parent_type;
- if (is_vector(type))
- return type.parent_type;
- SPIRV_CROSS_THROW("Shouldn't reach lower than vector handling OpSpecConstantOp CompositeInsert!");
- }
- string CompilerGLSL::constant_op_expression(const SPIRConstantOp &cop)
- {
- auto &type = get<SPIRType>(cop.basetype);
- bool binary = false;
- bool unary = false;
- string op;
- if (is_legacy() && is_unsigned_opcode(cop.opcode))
- SPIRV_CROSS_THROW("Unsigned integers are not supported on legacy targets.");
- // TODO: Find a clean way to reuse emit_instruction.
- switch (cop.opcode)
- {
- case OpSConvert:
- case OpUConvert:
- case OpFConvert:
- op = type_to_glsl_constructor(type);
- break;
- #define GLSL_BOP(opname, x) \
- case Op##opname: \
- binary = true; \
- op = x; \
- break
- #define GLSL_UOP(opname, x) \
- case Op##opname: \
- unary = true; \
- op = x; \
- break
- GLSL_UOP(SNegate, "-");
- GLSL_UOP(Not, "~");
- GLSL_BOP(IAdd, "+");
- GLSL_BOP(ISub, "-");
- GLSL_BOP(IMul, "*");
- GLSL_BOP(SDiv, "/");
- GLSL_BOP(UDiv, "/");
- GLSL_BOP(UMod, "%");
- GLSL_BOP(SMod, "%");
- GLSL_BOP(ShiftRightLogical, ">>");
- GLSL_BOP(ShiftRightArithmetic, ">>");
- GLSL_BOP(ShiftLeftLogical, "<<");
- GLSL_BOP(BitwiseOr, "|");
- GLSL_BOP(BitwiseXor, "^");
- GLSL_BOP(BitwiseAnd, "&");
- GLSL_BOP(LogicalOr, "||");
- GLSL_BOP(LogicalAnd, "&&");
- GLSL_UOP(LogicalNot, "!");
- GLSL_BOP(LogicalEqual, "==");
- GLSL_BOP(LogicalNotEqual, "!=");
- GLSL_BOP(IEqual, "==");
- GLSL_BOP(INotEqual, "!=");
- GLSL_BOP(ULessThan, "<");
- GLSL_BOP(SLessThan, "<");
- GLSL_BOP(ULessThanEqual, "<=");
- GLSL_BOP(SLessThanEqual, "<=");
- GLSL_BOP(UGreaterThan, ">");
- GLSL_BOP(SGreaterThan, ">");
- GLSL_BOP(UGreaterThanEqual, ">=");
- GLSL_BOP(SGreaterThanEqual, ">=");
- case OpSRem:
- {
- uint32_t op0 = cop.arguments[0];
- uint32_t op1 = cop.arguments[1];
- return join(to_enclosed_expression(op0), " - ", to_enclosed_expression(op1), " * ", "(",
- to_enclosed_expression(op0), " / ", to_enclosed_expression(op1), ")");
- }
- case OpSelect:
- {
- if (cop.arguments.size() < 3)
- SPIRV_CROSS_THROW("Not enough arguments to OpSpecConstantOp.");
- // This one is pretty annoying. It's triggered from
- // uint(bool), int(bool) from spec constants.
- // In order to preserve its compile-time constness in Vulkan GLSL,
- // we need to reduce the OpSelect expression back to this simplified model.
- // If we cannot, fail.
- if (to_trivial_mix_op(type, op, cop.arguments[2], cop.arguments[1], cop.arguments[0]))
- {
- // Implement as a simple cast down below.
- }
- else
- {
- // Implement a ternary and pray the compiler understands it :)
- return to_ternary_expression(type, cop.arguments[0], cop.arguments[1], cop.arguments[2]);
- }
- break;
- }
- case OpVectorShuffle:
- {
- string expr = type_to_glsl_constructor(type);
- expr += "(";
- uint32_t left_components = expression_type(cop.arguments[0]).vecsize;
- string left_arg = to_enclosed_expression(cop.arguments[0]);
- string right_arg = to_enclosed_expression(cop.arguments[1]);
- for (uint32_t i = 2; i < uint32_t(cop.arguments.size()); i++)
- {
- uint32_t index = cop.arguments[i];
- if (index == 0xFFFFFFFF)
- {
- SPIRConstant c;
- c.constant_type = type.parent_type;
- assert(type.parent_type != ID(0));
- expr += constant_expression(c);
- }
- else if (index >= left_components)
- {
- expr += right_arg + "." + "xyzw"[index - left_components];
- }
- else
- {
- expr += left_arg + "." + "xyzw"[index];
- }
- if (i + 1 < uint32_t(cop.arguments.size()))
- expr += ", ";
- }
- expr += ")";
- return expr;
- }
- case OpCompositeExtract:
- {
- // Trivial vector extracts (of WorkGroupSize typically),
- // punch through to the input spec constant if the composite is used as array size.
- const auto *c = maybe_get<SPIRConstant>(cop.arguments[0]);
- string expr;
- if (c && cop.arguments.size() == 2 && c->is_used_as_array_length &&
- !backend.supports_spec_constant_array_size &&
- is_vector(get<SPIRType>(c->constant_type)))
- {
- expr = to_expression(c->specialization_constant_id(0, cop.arguments[1]));
- }
- else
- {
- expr = access_chain_internal(cop.arguments[0], &cop.arguments[1], uint32_t(cop.arguments.size() - 1),
- ACCESS_CHAIN_INDEX_IS_LITERAL_BIT, nullptr);
- }
- return expr;
- }
- case OpCompositeInsert:
- {
- SmallVector<ConstantID> new_init = get_composite_constant_ids(cop.arguments[1]);
- uint32_t idx;
- uint32_t target_id = cop.self;
- uint32_t target_type_id = cop.basetype;
- // We have to drill down to the part we want to modify, and create new
- // constants for each containing part.
- for (idx = 2; idx < cop.arguments.size() - 1; ++idx)
- {
- uint32_t new_const = ir.increase_bound_by(1);
- uint32_t old_const = new_init[cop.arguments[idx]];
- new_init[cop.arguments[idx]] = new_const;
- set_composite_constant(target_id, target_type_id, new_init);
- new_init = get_composite_constant_ids(old_const);
- target_id = new_const;
- target_type_id = get_composite_member_type(target_type_id, cop.arguments[idx]);
- }
- // Now replace the initializer with the one from this instruction.
- new_init[cop.arguments[idx]] = cop.arguments[0];
- set_composite_constant(target_id, target_type_id, new_init);
- SPIRConstant tmp_const(cop.basetype);
- fill_composite_constant(tmp_const, cop.basetype, const_composite_insert_ids[cop.self]);
- return constant_expression(tmp_const);
- }
- default:
- // Some opcodes are unimplemented here, these are currently not possible to test from glslang.
- SPIRV_CROSS_THROW("Unimplemented spec constant op.");
- }
- uint32_t bit_width = 0;
- if (unary || binary || cop.opcode == OpSConvert || cop.opcode == OpUConvert)
- bit_width = expression_type(cop.arguments[0]).width;
- SPIRType::BaseType input_type;
- bool skip_cast_if_equal_type = opcode_is_sign_invariant(cop.opcode);
- switch (cop.opcode)
- {
- case OpIEqual:
- case OpINotEqual:
- input_type = to_signed_basetype(bit_width);
- break;
- case OpSLessThan:
- case OpSLessThanEqual:
- case OpSGreaterThan:
- case OpSGreaterThanEqual:
- case OpSMod:
- case OpSDiv:
- case OpShiftRightArithmetic:
- case OpSConvert:
- case OpSNegate:
- input_type = to_signed_basetype(bit_width);
- break;
- case OpULessThan:
- case OpULessThanEqual:
- case OpUGreaterThan:
- case OpUGreaterThanEqual:
- case OpUMod:
- case OpUDiv:
- case OpShiftRightLogical:
- case OpUConvert:
- input_type = to_unsigned_basetype(bit_width);
- break;
- default:
- input_type = type.basetype;
- break;
- }
- #undef GLSL_BOP
- #undef GLSL_UOP
- if (binary)
- {
- if (cop.arguments.size() < 2)
- SPIRV_CROSS_THROW("Not enough arguments to OpSpecConstantOp.");
- string cast_op0;
- string cast_op1;
- auto expected_type = binary_op_bitcast_helper(cast_op0, cast_op1, input_type, cop.arguments[0],
- cop.arguments[1], skip_cast_if_equal_type);
- if (type.basetype != input_type && type.basetype != SPIRType::Boolean)
- {
- expected_type.basetype = input_type;
- auto expr = bitcast_glsl_op(type, expected_type);
- expr += '(';
- expr += join(cast_op0, " ", op, " ", cast_op1);
- expr += ')';
- return expr;
- }
- else
- return join("(", cast_op0, " ", op, " ", cast_op1, ")");
- }
- else if (unary)
- {
- if (cop.arguments.size() < 1)
- SPIRV_CROSS_THROW("Not enough arguments to OpSpecConstantOp.");
- // Auto-bitcast to result type as needed.
- // Works around various casting scenarios in glslang as there is no OpBitcast for specialization constants.
- return join("(", op, bitcast_glsl(type, cop.arguments[0]), ")");
- }
- else if (cop.opcode == OpSConvert || cop.opcode == OpUConvert)
- {
- if (cop.arguments.size() < 1)
- SPIRV_CROSS_THROW("Not enough arguments to OpSpecConstantOp.");
- auto &arg_type = expression_type(cop.arguments[0]);
- if (arg_type.width < type.width && input_type != arg_type.basetype)
- {
- auto expected = arg_type;
- expected.basetype = input_type;
- return join(op, "(", bitcast_glsl(expected, cop.arguments[0]), ")");
- }
- else
- return join(op, "(", to_expression(cop.arguments[0]), ")");
- }
- else
- {
- if (cop.arguments.size() < 1)
- SPIRV_CROSS_THROW("Not enough arguments to OpSpecConstantOp.");
- return join(op, "(", to_expression(cop.arguments[0]), ")");
- }
- }
- string CompilerGLSL::constant_expression(const SPIRConstant &c,
- bool inside_block_like_struct_scope,
- bool inside_struct_scope)
- {
- auto &type = get<SPIRType>(c.constant_type);
- if (is_pointer(type))
- {
- return backend.null_pointer_literal;
- }
- else if (c.is_null_array_specialized_length && backend.requires_matching_array_initializer)
- {
- require_extension_internal("GL_EXT_null_initializer");
- return backend.constant_null_initializer;
- }
- else if (c.replicated && type.op != OpTypeArray)
- {
- if (type.op == OpTypeMatrix)
- {
- uint32_t num_elements = type.columns;
- // GLSL does not allow the replication constructor for matrices
- // mat4(vec4(0.0)) needs to be manually expanded to mat4(vec4(0.0), vec4(0.0), vec4(0.0), vec4(0.0));
- std::string res;
- res += type_to_glsl(type);
- res += "(";
- for (uint32_t i = 0; i < num_elements; i++)
- {
- res += to_expression(c.subconstants[0]);
- if (i < num_elements - 1)
- res += ", ";
- }
- res += ")";
- return res;
- }
- else
- {
- return join(type_to_glsl(type), "(", to_expression(c.subconstants[0]), ")");
- }
- }
- else if (!c.subconstants.empty())
- {
- // Handles Arrays and structures.
- string res;
- // Only consider the decay if we are inside a struct scope where we are emitting a member with Offset decoration.
- // Outside a block-like struct declaration, we can always bind to a constant array with templated type.
- // Should look at ArrayStride here as well, but it's possible to declare a constant struct
- // with Offset = 0, using no ArrayStride on the enclosed array type.
- // A particular CTS test hits this scenario.
- bool array_type_decays = inside_block_like_struct_scope &&
- is_array(type) &&
- !backend.array_is_value_type_in_buffer_blocks;
- // Allow Metal to use the array<T> template to make arrays a value type
- bool needs_trailing_tracket = false;
- if (backend.use_initializer_list && backend.use_typed_initializer_list && type.basetype == SPIRType::Struct &&
- !is_array(type))
- {
- res = type_to_glsl_constructor(type) + "{ ";
- }
- else if (backend.use_initializer_list && backend.use_typed_initializer_list && backend.array_is_value_type &&
- is_array(type) && !array_type_decays)
- {
- const auto *p_type = &type;
- SPIRType tmp_type { OpNop };
- if (inside_struct_scope &&
- backend.boolean_in_struct_remapped_type != SPIRType::Boolean &&
- type.basetype == SPIRType::Boolean)
- {
- tmp_type = type;
- tmp_type.basetype = backend.boolean_in_struct_remapped_type;
- p_type = &tmp_type;
- }
- res = type_to_glsl_constructor(*p_type) + "({ ";
- needs_trailing_tracket = true;
- }
- else if (backend.use_initializer_list)
- {
- res = "{ ";
- }
- else
- {
- res = type_to_glsl_constructor(type) + "(";
- }
- uint32_t subconstant_index = 0;
- size_t num_elements = c.subconstants.size();
- if (c.replicated)
- {
- if (type.array.size() != 1)
- SPIRV_CROSS_THROW("Multidimensional arrays not yet supported as replicated constans");
- num_elements = type.array[0];
- }
- for (size_t i = 0; i < num_elements; i++)
- {
- auto &elem = c.subconstants[c.replicated ? 0 : i];
- if (auto *op = maybe_get<SPIRConstantOp>(elem))
- {
- res += constant_op_expression(*op);
- }
- else if (maybe_get<SPIRUndef>(elem) != nullptr)
- {
- res += to_name(elem);
- }
- else
- {
- auto &subc = get<SPIRConstant>(elem);
- if (subc.specialization && !expression_is_forwarded(elem))
- res += to_name(elem);
- else
- {
- if (!is_array(type) && type.basetype == SPIRType::Struct)
- {
- // When we get down to emitting struct members, override the block-like information.
- // For constants, we can freely mix and match block-like state.
- inside_block_like_struct_scope =
- has_member_decoration(type.self, subconstant_index, DecorationOffset);
- }
- if (type.basetype == SPIRType::Struct)
- inside_struct_scope = true;
- res += constant_expression(subc, inside_block_like_struct_scope, inside_struct_scope);
- }
- }
- if (i != num_elements - 1)
- res += ", ";
- subconstant_index++;
- }
- res += backend.use_initializer_list ? " }" : ")";
- if (needs_trailing_tracket)
- res += ")";
- return res;
- }
- else if (type.basetype == SPIRType::Struct && type.member_types.size() == 0)
- {
- // Metal tessellation likes empty structs which are then constant expressions.
- if (backend.supports_empty_struct)
- return "{ }";
- else if (backend.use_typed_initializer_list)
- return join(type_to_glsl(type), "{ 0 }");
- else if (backend.use_initializer_list)
- return "{ 0 }";
- else
- return join(type_to_glsl(type), "(0)");
- }
- else if (c.columns() == 1 && type.op != OpTypeCooperativeMatrixKHR)
- {
- auto res = constant_expression_vector(c, 0);
- if (inside_struct_scope &&
- backend.boolean_in_struct_remapped_type != SPIRType::Boolean &&
- type.basetype == SPIRType::Boolean)
- {
- SPIRType tmp_type = type;
- tmp_type.basetype = backend.boolean_in_struct_remapped_type;
- res = join(type_to_glsl(tmp_type), "(", res, ")");
- }
- return res;
- }
- else
- {
- string res = type_to_glsl(type) + "(";
- for (uint32_t col = 0; col < c.columns(); col++)
- {
- if (c.specialization_constant_id(col) != 0)
- res += to_name(c.specialization_constant_id(col));
- else
- res += constant_expression_vector(c, col);
- if (col + 1 < c.columns())
- res += ", ";
- }
- res += ")";
- if (inside_struct_scope &&
- backend.boolean_in_struct_remapped_type != SPIRType::Boolean &&
- type.basetype == SPIRType::Boolean)
- {
- SPIRType tmp_type = type;
- tmp_type.basetype = backend.boolean_in_struct_remapped_type;
- res = join(type_to_glsl(tmp_type), "(", res, ")");
- }
- return res;
- }
- }
- #ifdef _MSC_VER
- // snprintf does not exist or is buggy on older MSVC versions, some of them
- // being used by MinGW. Use sprintf instead and disable corresponding warning.
- #pragma warning(push)
- #pragma warning(disable : 4996)
- #endif
- string CompilerGLSL::convert_floate4m3_to_string(const SPIRConstant &c, uint32_t col, uint32_t row)
- {
- string res;
- float float_value = c.scalar_floate4m3(col, row);
- // There is no infinity in e4m3.
- if (std::isnan(float_value))
- {
- SPIRType type { OpTypeFloat };
- type.basetype = SPIRType::Half;
- type.vecsize = 1;
- type.columns = 1;
- res = join(type_to_glsl(type), "(0.0 / 0.0)");
- }
- else
- {
- SPIRType type { OpTypeFloat };
- type.basetype = SPIRType::FloatE4M3;
- type.vecsize = 1;
- type.columns = 1;
- res = join(type_to_glsl(type), "(", format_float(float_value), ")");
- }
- return res;
- }
- string CompilerGLSL::convert_half_to_string(const SPIRConstant &c, uint32_t col, uint32_t row)
- {
- string res;
- bool is_bfloat8 = get<SPIRType>(c.constant_type).basetype == SPIRType::FloatE5M2;
- float float_value = is_bfloat8 ? c.scalar_bf8(col, row) : c.scalar_f16(col, row);
- // There is no literal "hf" in GL_NV_gpu_shader5, so to avoid lots
- // of complicated workarounds, just value-cast to the half type always.
- if (std::isnan(float_value) || std::isinf(float_value))
- {
- SPIRType type { OpTypeFloat };
- type.basetype = is_bfloat8 ? SPIRType::FloatE5M2 : SPIRType::Half;
- type.vecsize = 1;
- type.columns = 1;
- if (float_value == numeric_limits<float>::infinity())
- res = join(type_to_glsl(type), "(1.0 / 0.0)");
- else if (float_value == -numeric_limits<float>::infinity())
- res = join(type_to_glsl(type), "(-1.0 / 0.0)");
- else if (std::isnan(float_value))
- res = join(type_to_glsl(type), "(0.0 / 0.0)");
- else
- SPIRV_CROSS_THROW("Cannot represent non-finite floating point constant.");
- }
- else
- {
- SPIRType type { OpTypeFloat };
- type.basetype = is_bfloat8 ? SPIRType::FloatE5M2 : SPIRType::Half;
- type.vecsize = 1;
- type.columns = 1;
- res = join(type_to_glsl(type), "(", format_float(float_value), ")");
- }
- return res;
- }
- string CompilerGLSL::convert_float_to_string(const SPIRConstant &c, uint32_t col, uint32_t row)
- {
- string res;
- bool is_bfloat16 = get<SPIRType>(c.constant_type).basetype == SPIRType::BFloat16;
- float float_value = is_bfloat16 ? c.scalar_bf16(col, row) : c.scalar_f32(col, row);
- if (std::isnan(float_value) || std::isinf(float_value))
- {
- // Use special representation.
- if (!is_legacy())
- {
- SPIRType out_type { OpTypeFloat };
- SPIRType in_type { OpTypeInt };
- out_type.basetype = SPIRType::Float;
- in_type.basetype = SPIRType::UInt;
- out_type.vecsize = 1;
- in_type.vecsize = 1;
- out_type.width = 32;
- in_type.width = 32;
- char print_buffer[32];
- #ifdef _WIN32
- sprintf(print_buffer, "0x%xu", c.scalar(col, row));
- #else
- snprintf(print_buffer, sizeof(print_buffer), "0x%xu", c.scalar(col, row));
- #endif
- const char *comment = "inf";
- if (float_value == -numeric_limits<float>::infinity())
- comment = "-inf";
- else if (std::isnan(float_value))
- comment = "nan";
- res = join(bitcast_glsl_op(out_type, in_type), "(", print_buffer, " /* ", comment, " */)");
- }
- else
- {
- if (float_value == numeric_limits<float>::infinity())
- {
- if (backend.float_literal_suffix)
- res = "(1.0f / 0.0f)";
- else
- res = "(1.0 / 0.0)";
- }
- else if (float_value == -numeric_limits<float>::infinity())
- {
- if (backend.float_literal_suffix)
- res = "(-1.0f / 0.0f)";
- else
- res = "(-1.0 / 0.0)";
- }
- else if (std::isnan(float_value))
- {
- if (backend.float_literal_suffix)
- res = "(0.0f / 0.0f)";
- else
- res = "(0.0 / 0.0)";
- }
- else
- SPIRV_CROSS_THROW("Cannot represent non-finite floating point constant.");
- }
- }
- else
- {
- res = format_float(float_value);
- if (backend.float_literal_suffix)
- res += "f";
- }
- if (is_bfloat16)
- res = join("bfloat16_t(", res, ")");
- return res;
- }
- std::string CompilerGLSL::convert_double_to_string(const SPIRConstant &c, uint32_t col, uint32_t row)
- {
- string res;
- double double_value = c.scalar_f64(col, row);
- if (std::isnan(double_value) || std::isinf(double_value))
- {
- // Use special representation.
- if (!is_legacy())
- {
- SPIRType out_type { OpTypeFloat };
- SPIRType in_type { OpTypeInt };
- out_type.basetype = SPIRType::Double;
- in_type.basetype = SPIRType::UInt64;
- out_type.vecsize = 1;
- in_type.vecsize = 1;
- out_type.width = 64;
- in_type.width = 64;
- uint64_t u64_value = c.scalar_u64(col, row);
- if (options.es && options.version < 310) // GL_NV_gpu_shader5 fallback requires 310.
- SPIRV_CROSS_THROW("64-bit integers not supported in ES profile before version 310.");
- require_extension_internal("GL_ARB_gpu_shader_int64");
- char print_buffer[64];
- #ifdef _WIN32
- sprintf(print_buffer, "0x%llx%s", static_cast<unsigned long long>(u64_value),
- backend.long_long_literal_suffix ? "ull" : "ul");
- #else
- snprintf(print_buffer, sizeof(print_buffer), "0x%llx%s", static_cast<unsigned long long>(u64_value),
- backend.long_long_literal_suffix ? "ull" : "ul");
- #endif
- const char *comment = "inf";
- if (double_value == -numeric_limits<double>::infinity())
- comment = "-inf";
- else if (std::isnan(double_value))
- comment = "nan";
- res = join(bitcast_glsl_op(out_type, in_type), "(", print_buffer, " /* ", comment, " */)");
- }
- else
- {
- if (options.es)
- SPIRV_CROSS_THROW("FP64 not supported in ES profile.");
- if (options.version < 400)
- require_extension_internal("GL_ARB_gpu_shader_fp64");
- if (double_value == numeric_limits<double>::infinity())
- {
- if (backend.double_literal_suffix)
- res = "(1.0lf / 0.0lf)";
- else
- res = "(1.0 / 0.0)";
- }
- else if (double_value == -numeric_limits<double>::infinity())
- {
- if (backend.double_literal_suffix)
- res = "(-1.0lf / 0.0lf)";
- else
- res = "(-1.0 / 0.0)";
- }
- else if (std::isnan(double_value))
- {
- if (backend.double_literal_suffix)
- res = "(0.0lf / 0.0lf)";
- else
- res = "(0.0 / 0.0)";
- }
- else
- SPIRV_CROSS_THROW("Cannot represent non-finite floating point constant.");
- }
- }
- else
- {
- res = format_double(double_value);
- if (backend.double_literal_suffix)
- res += "lf";
- }
- return res;
- }
- #ifdef _MSC_VER
- #pragma warning(pop)
- #endif
- string CompilerGLSL::constant_expression_vector(const SPIRConstant &c, uint32_t vector)
- {
- auto type = get<SPIRType>(c.constant_type);
- type.columns = 1;
- auto scalar_type = type;
- scalar_type.vecsize = 1;
- string res;
- bool splat = backend.use_constructor_splatting && c.vector_size() > 1;
- bool swizzle_splat = backend.can_swizzle_scalar && c.vector_size() > 1;
- if (!type_is_floating_point(type))
- {
- // Cannot swizzle literal integers as a special case.
- swizzle_splat = false;
- }
- if (splat || swizzle_splat)
- {
- // Cannot use constant splatting if we have specialization constants somewhere in the vector.
- for (uint32_t i = 0; i < c.vector_size(); i++)
- {
- if (c.specialization_constant_id(vector, i) != 0)
- {
- splat = false;
- swizzle_splat = false;
- break;
- }
- }
- }
- if (splat || swizzle_splat)
- {
- if (type.width == 64)
- {
- uint64_t ident = c.scalar_u64(vector, 0);
- for (uint32_t i = 1; i < c.vector_size(); i++)
- {
- if (ident != c.scalar_u64(vector, i))
- {
- splat = false;
- swizzle_splat = false;
- break;
- }
- }
- }
- else
- {
- uint32_t ident = c.scalar(vector, 0);
- for (uint32_t i = 1; i < c.vector_size(); i++)
- {
- if (ident != c.scalar(vector, i))
- {
- splat = false;
- swizzle_splat = false;
- }
- }
- }
- }
- if (c.vector_size() > 1 && !swizzle_splat)
- res += type_to_glsl(type) + "(";
- switch (type.basetype)
- {
- case SPIRType::FloatE4M3:
- if (splat || swizzle_splat)
- {
- res += convert_floate4m3_to_string(c, vector, 0);
- if (swizzle_splat)
- res = remap_swizzle(get<SPIRType>(c.constant_type), 1, res);
- }
- else
- {
- for (uint32_t i = 0; i < c.vector_size(); i++)
- {
- if (c.vector_size() > 1 && c.specialization_constant_id(vector, i) != 0)
- res += to_expression(c.specialization_constant_id(vector, i));
- else
- res += convert_floate4m3_to_string(c, vector, i);
- if (i + 1 < c.vector_size())
- res += ", ";
- }
- }
- break;
- case SPIRType::FloatE5M2:
- case SPIRType::Half:
- if (splat || swizzle_splat)
- {
- res += convert_half_to_string(c, vector, 0);
- if (swizzle_splat)
- res = remap_swizzle(get<SPIRType>(c.constant_type), 1, res);
- }
- else
- {
- for (uint32_t i = 0; i < c.vector_size(); i++)
- {
- if (c.vector_size() > 1 && c.specialization_constant_id(vector, i) != 0)
- res += to_expression(c.specialization_constant_id(vector, i));
- else
- res += convert_half_to_string(c, vector, i);
- if (i + 1 < c.vector_size())
- res += ", ";
- }
- }
- break;
- case SPIRType::BFloat16:
- case SPIRType::Float:
- if (splat || swizzle_splat)
- {
- res += convert_float_to_string(c, vector, 0);
- if (swizzle_splat)
- res = remap_swizzle(get<SPIRType>(c.constant_type), 1, res);
- }
- else
- {
- for (uint32_t i = 0; i < c.vector_size(); i++)
- {
- if (c.vector_size() > 1 && c.specialization_constant_id(vector, i) != 0)
- res += to_expression(c.specialization_constant_id(vector, i));
- else
- res += convert_float_to_string(c, vector, i);
- if (i + 1 < c.vector_size())
- res += ", ";
- }
- }
- break;
- case SPIRType::Double:
- if (splat || swizzle_splat)
- {
- res += convert_double_to_string(c, vector, 0);
- if (swizzle_splat)
- res = remap_swizzle(get<SPIRType>(c.constant_type), 1, res);
- }
- else
- {
- for (uint32_t i = 0; i < c.vector_size(); i++)
- {
- if (c.vector_size() > 1 && c.specialization_constant_id(vector, i) != 0)
- res += to_expression(c.specialization_constant_id(vector, i));
- else
- res += convert_double_to_string(c, vector, i);
- if (i + 1 < c.vector_size())
- res += ", ";
- }
- }
- break;
- case SPIRType::Int64:
- {
- auto tmp = type;
- tmp.vecsize = 1;
- tmp.columns = 1;
- auto int64_type = type_to_glsl(tmp);
- if (splat)
- {
- res += convert_to_string(c.scalar_i64(vector, 0), int64_type, backend.long_long_literal_suffix);
- }
- else
- {
- for (uint32_t i = 0; i < c.vector_size(); i++)
- {
- if (c.vector_size() > 1 && c.specialization_constant_id(vector, i) != 0)
- res += to_expression(c.specialization_constant_id(vector, i));
- else
- res += convert_to_string(c.scalar_i64(vector, i), int64_type, backend.long_long_literal_suffix);
- if (i + 1 < c.vector_size())
- res += ", ";
- }
- }
- break;
- }
- case SPIRType::UInt64:
- if (splat)
- {
- res += convert_to_string(c.scalar_u64(vector, 0));
- if (backend.long_long_literal_suffix)
- res += "ull";
- else
- res += "ul";
- }
- else
- {
- for (uint32_t i = 0; i < c.vector_size(); i++)
- {
- if (c.vector_size() > 1 && c.specialization_constant_id(vector, i) != 0)
- res += to_expression(c.specialization_constant_id(vector, i));
- else
- {
- res += convert_to_string(c.scalar_u64(vector, i));
- if (backend.long_long_literal_suffix)
- res += "ull";
- else
- res += "ul";
- }
- if (i + 1 < c.vector_size())
- res += ", ";
- }
- }
- break;
- case SPIRType::UInt:
- if (splat)
- {
- res += convert_to_string(c.scalar(vector, 0));
- if (is_legacy() && !has_extension("GL_EXT_gpu_shader4"))
- {
- // Fake unsigned constant literals with signed ones if possible.
- // Things like array sizes, etc, tend to be unsigned even though they could just as easily be signed.
- if (c.scalar_i32(vector, 0) < 0)
- SPIRV_CROSS_THROW("Tried to convert uint literal into int, but this made the literal negative.");
- }
- else if (backend.uint32_t_literal_suffix)
- res += "u";
- }
- else
- {
- for (uint32_t i = 0; i < c.vector_size(); i++)
- {
- if (c.vector_size() > 1 && c.specialization_constant_id(vector, i) != 0)
- res += to_expression(c.specialization_constant_id(vector, i));
- else
- {
- res += convert_to_string(c.scalar(vector, i));
- if (is_legacy() && !has_extension("GL_EXT_gpu_shader4"))
- {
- // Fake unsigned constant literals with signed ones if possible.
- // Things like array sizes, etc, tend to be unsigned even though they could just as easily be signed.
- if (c.scalar_i32(vector, i) < 0)
- SPIRV_CROSS_THROW("Tried to convert uint literal into int, but this made "
- "the literal negative.");
- }
- else if (backend.uint32_t_literal_suffix)
- res += "u";
- }
- if (i + 1 < c.vector_size())
- res += ", ";
- }
- }
- break;
- case SPIRType::Int:
- if (splat)
- res += convert_to_string(c.scalar_i32(vector, 0));
- else
- {
- for (uint32_t i = 0; i < c.vector_size(); i++)
- {
- if (c.vector_size() > 1 && c.specialization_constant_id(vector, i) != 0)
- res += to_expression(c.specialization_constant_id(vector, i));
- else
- res += convert_to_string(c.scalar_i32(vector, i));
- if (i + 1 < c.vector_size())
- res += ", ";
- }
- }
- break;
- case SPIRType::UShort:
- if (splat)
- {
- res += convert_to_string(c.scalar(vector, 0));
- }
- else
- {
- for (uint32_t i = 0; i < c.vector_size(); i++)
- {
- if (c.vector_size() > 1 && c.specialization_constant_id(vector, i) != 0)
- res += to_expression(c.specialization_constant_id(vector, i));
- else
- {
- if (*backend.uint16_t_literal_suffix)
- {
- res += convert_to_string(c.scalar_u16(vector, i));
- res += backend.uint16_t_literal_suffix;
- }
- else
- {
- // If backend doesn't have a literal suffix, we need to value cast.
- res += type_to_glsl(scalar_type);
- res += "(";
- res += convert_to_string(c.scalar_u16(vector, i));
- res += ")";
- }
- }
- if (i + 1 < c.vector_size())
- res += ", ";
- }
- }
- break;
- case SPIRType::Short:
- if (splat)
- {
- res += convert_to_string(c.scalar_i16(vector, 0));
- }
- else
- {
- for (uint32_t i = 0; i < c.vector_size(); i++)
- {
- if (c.vector_size() > 1 && c.specialization_constant_id(vector, i) != 0)
- res += to_expression(c.specialization_constant_id(vector, i));
- else
- {
- if (*backend.int16_t_literal_suffix)
- {
- res += convert_to_string(c.scalar_i16(vector, i));
- res += backend.int16_t_literal_suffix;
- }
- else
- {
- // If backend doesn't have a literal suffix, we need to value cast.
- res += type_to_glsl(scalar_type);
- res += "(";
- res += convert_to_string(c.scalar_i16(vector, i));
- res += ")";
- }
- }
- if (i + 1 < c.vector_size())
- res += ", ";
- }
- }
- break;
- case SPIRType::UByte:
- if (splat)
- {
- res += convert_to_string(c.scalar_u8(vector, 0));
- }
- else
- {
- for (uint32_t i = 0; i < c.vector_size(); i++)
- {
- if (c.vector_size() > 1 && c.specialization_constant_id(vector, i) != 0)
- res += to_expression(c.specialization_constant_id(vector, i));
- else
- {
- res += type_to_glsl(scalar_type);
- res += "(";
- res += convert_to_string(c.scalar_u8(vector, i));
- res += ")";
- }
- if (i + 1 < c.vector_size())
- res += ", ";
- }
- }
- break;
- case SPIRType::SByte:
- if (splat)
- {
- res += convert_to_string(c.scalar_i8(vector, 0));
- }
- else
- {
- for (uint32_t i = 0; i < c.vector_size(); i++)
- {
- if (c.vector_size() > 1 && c.specialization_constant_id(vector, i) != 0)
- res += to_expression(c.specialization_constant_id(vector, i));
- else
- {
- res += type_to_glsl(scalar_type);
- res += "(";
- res += convert_to_string(c.scalar_i8(vector, i));
- res += ")";
- }
- if (i + 1 < c.vector_size())
- res += ", ";
- }
- }
- break;
- case SPIRType::Boolean:
- if (splat)
- res += c.scalar(vector, 0) ? "true" : "false";
- else
- {
- for (uint32_t i = 0; i < c.vector_size(); i++)
- {
- if (c.vector_size() > 1 && c.specialization_constant_id(vector, i) != 0)
- res += to_expression(c.specialization_constant_id(vector, i));
- else
- res += c.scalar(vector, i) ? "true" : "false";
- if (i + 1 < c.vector_size())
- res += ", ";
- }
- }
- break;
- default:
- SPIRV_CROSS_THROW("Invalid constant expression basetype.");
- }
- if (c.vector_size() > 1 && !swizzle_splat)
- res += ")";
- return res;
- }
- SPIRExpression &CompilerGLSL::emit_uninitialized_temporary_expression(uint32_t type, uint32_t id)
- {
- forced_temporaries.insert(id);
- emit_uninitialized_temporary(type, id);
- return set<SPIRExpression>(id, to_name(id), type, true);
- }
- void CompilerGLSL::emit_uninitialized_temporary(uint32_t result_type, uint32_t result_id)
- {
- // If we're declaring temporaries inside continue blocks,
- // we must declare the temporary in the loop header so that the continue block can avoid declaring new variables.
- if (!block_temporary_hoisting && current_continue_block && !hoisted_temporaries.count(result_id))
- {
- auto &header = get<SPIRBlock>(current_continue_block->loop_dominator);
- if (find_if(begin(header.declare_temporary), end(header.declare_temporary),
- [result_type, result_id](const pair<uint32_t, uint32_t> &tmp) {
- return tmp.first == result_type && tmp.second == result_id;
- }) == end(header.declare_temporary))
- {
- header.declare_temporary.emplace_back(result_type, result_id);
- hoisted_temporaries.insert(result_id);
- force_recompile();
- }
- }
- else if (hoisted_temporaries.count(result_id) == 0)
- {
- auto &type = get<SPIRType>(result_type);
- auto &flags = get_decoration_bitset(result_id);
- // The result_id has not been made into an expression yet, so use flags interface.
- add_local_variable_name(result_id);
- string initializer;
- if (options.force_zero_initialized_variables && type_can_zero_initialize(type))
- initializer = join(" = ", to_zero_initialized_expression(result_type));
- statement(flags_to_qualifiers_glsl(type, result_id, flags), variable_decl(type, to_name(result_id)), initializer, ";");
- }
- }
- bool CompilerGLSL::can_declare_inline_temporary(uint32_t id) const
- {
- if (!block_temporary_hoisting && current_continue_block && !hoisted_temporaries.count(id))
- return false;
- if (hoisted_temporaries.count(id))
- return false;
- return true;
- }
- string CompilerGLSL::declare_temporary(uint32_t result_type, uint32_t result_id)
- {
- auto &type = get<SPIRType>(result_type);
- // If we're declaring temporaries inside continue blocks,
- // we must declare the temporary in the loop header so that the continue block can avoid declaring new variables.
- if (!block_temporary_hoisting && current_continue_block && !hoisted_temporaries.count(result_id))
- {
- auto &header = get<SPIRBlock>(current_continue_block->loop_dominator);
- if (find_if(begin(header.declare_temporary), end(header.declare_temporary),
- [result_type, result_id](const pair<uint32_t, uint32_t> &tmp) {
- return tmp.first == result_type && tmp.second == result_id;
- }) == end(header.declare_temporary))
- {
- header.declare_temporary.emplace_back(result_type, result_id);
- hoisted_temporaries.insert(result_id);
- force_recompile_guarantee_forward_progress();
- }
- return join(to_name(result_id), " = ");
- }
- else if (hoisted_temporaries.count(result_id))
- {
- // The temporary has already been declared earlier, so just "declare" the temporary by writing to it.
- return join(to_name(result_id), " = ");
- }
- else
- {
- // The result_id has not been made into an expression yet, so use flags interface.
- add_local_variable_name(result_id);
- auto &flags = get_decoration_bitset(result_id);
- return join(flags_to_qualifiers_glsl(type, result_id, flags), variable_decl(type, to_name(result_id)), " = ");
- }
- }
- bool CompilerGLSL::expression_is_forwarded(uint32_t id) const
- {
- return forwarded_temporaries.count(id) != 0;
- }
- bool CompilerGLSL::expression_suppresses_usage_tracking(uint32_t id) const
- {
- return suppressed_usage_tracking.count(id) != 0;
- }
- bool CompilerGLSL::expression_read_implies_multiple_reads(uint32_t id) const
- {
- auto *expr = maybe_get<SPIRExpression>(id);
- if (!expr)
- return false;
- // If we're emitting code at a deeper loop level than when we emitted the expression,
- // we're probably reading the same expression over and over.
- return current_loop_level > expr->emitted_loop_level;
- }
- SPIRExpression &CompilerGLSL::emit_op(uint32_t result_type, uint32_t result_id, const string &rhs, bool forwarding,
- bool suppress_usage_tracking)
- {
- if (forwarding && (forced_temporaries.find(result_id) == end(forced_temporaries)))
- {
- // Just forward it without temporary.
- // If the forward is trivial, we do not force flushing to temporary for this expression.
- forwarded_temporaries.insert(result_id);
- if (suppress_usage_tracking)
- suppressed_usage_tracking.insert(result_id);
- return set<SPIRExpression>(result_id, rhs, result_type, true);
- }
- else
- {
- // If expression isn't immutable, bind it to a temporary and make the new temporary immutable (they always are).
- statement(declare_temporary(result_type, result_id), rhs, ";");
- return set<SPIRExpression>(result_id, to_name(result_id), result_type, true);
- }
- }
- void CompilerGLSL::emit_transposed_op(uint32_t result_type, uint32_t result_id, const string &rhs, bool forwarding)
- {
- if (forwarding && (forced_temporaries.find(result_id) == end(forced_temporaries)))
- {
- // Just forward it without temporary.
- // If the forward is trivial, we do not force flushing to temporary for this expression.
- forwarded_temporaries.insert(result_id);
- auto &e = set<SPIRExpression>(result_id, rhs, result_type, true);
- e.need_transpose = true;
- }
- else if (can_declare_inline_temporary(result_id))
- {
- // If expression isn't immutable, bind it to a temporary and make the new temporary immutable (they always are).
- // Since the expression is transposed, we have to ensure the temporary is the transposed type.
- auto &transposed_type_id = extra_sub_expressions[result_id];
- if (!transposed_type_id)
- {
- auto dummy_type = get<SPIRType>(result_type);
- std::swap(dummy_type.columns, dummy_type.vecsize);
- transposed_type_id = ir.increase_bound_by(1);
- set<SPIRType>(transposed_type_id, dummy_type);
- }
- statement(declare_temporary(transposed_type_id, result_id), rhs, ";");
- auto &e = set<SPIRExpression>(result_id, to_name(result_id), result_type, true);
- e.need_transpose = true;
- }
- else
- {
- // If we cannot declare the temporary because it's already been hoisted, we don't have the
- // chance to override the temporary type ourselves. Just transpose() the expression.
- emit_op(result_type, result_id, join("transpose(", rhs, ")"), forwarding);
- }
- }
- void CompilerGLSL::emit_unary_op(uint32_t result_type, uint32_t result_id, uint32_t op0, const char *op)
- {
- bool forward = should_forward(op0);
- emit_op(result_type, result_id, join(op, to_enclosed_unpacked_expression(op0)), forward);
- inherit_expression_dependencies(result_id, op0);
- }
- void CompilerGLSL::emit_unary_op_cast(uint32_t result_type, uint32_t result_id, uint32_t op0, const char *op)
- {
- auto &type = get<SPIRType>(result_type);
- bool forward = should_forward(op0);
- emit_op(result_type, result_id, join(type_to_glsl(type), "(", op, to_enclosed_unpacked_expression(op0), ")"), forward);
- inherit_expression_dependencies(result_id, op0);
- }
- void CompilerGLSL::emit_mesh_tasks(SPIRBlock &block)
- {
- statement("EmitMeshTasksEXT(",
- to_unpacked_expression(block.mesh.groups[0]), ", ",
- to_unpacked_expression(block.mesh.groups[1]), ", ",
- to_unpacked_expression(block.mesh.groups[2]), ");");
- }
- void CompilerGLSL::emit_binary_op(uint32_t result_type, uint32_t result_id, uint32_t op0, uint32_t op1, const char *op)
- {
- // Various FP arithmetic opcodes such as add, sub, mul will hit this.
- bool force_temporary_precise = backend.support_precise_qualifier &&
- has_legacy_nocontract(result_type, result_id) &&
- type_is_floating_point(get<SPIRType>(result_type));
- bool forward = should_forward(op0) && should_forward(op1) && !force_temporary_precise;
- emit_op(result_type, result_id,
- join(to_enclosed_unpacked_expression(op0), " ", op, " ", to_enclosed_unpacked_expression(op1)), forward);
- inherit_expression_dependencies(result_id, op0);
- inherit_expression_dependencies(result_id, op1);
- }
- void CompilerGLSL::emit_unrolled_unary_op(uint32_t result_type, uint32_t result_id, uint32_t operand, const char *op)
- {
- auto &type = get<SPIRType>(result_type);
- auto expr = type_to_glsl_constructor(type);
- expr += '(';
- for (uint32_t i = 0; i < type.vecsize; i++)
- {
- // Make sure to call to_expression multiple times to ensure
- // that these expressions are properly flushed to temporaries if needed.
- expr += op;
- expr += to_extract_component_expression(operand, i);
- if (i + 1 < type.vecsize)
- expr += ", ";
- }
- expr += ')';
- emit_op(result_type, result_id, expr, should_forward(operand));
- inherit_expression_dependencies(result_id, operand);
- }
- void CompilerGLSL::emit_unrolled_binary_op(uint32_t result_type, uint32_t result_id, uint32_t op0, uint32_t op1,
- const char *op, bool negate, SPIRType::BaseType expected_type)
- {
- auto &type0 = expression_type(op0);
- auto &type1 = expression_type(op1);
- SPIRType target_type0 = type0;
- SPIRType target_type1 = type1;
- target_type0.basetype = expected_type;
- target_type1.basetype = expected_type;
- target_type0.vecsize = 1;
- target_type1.vecsize = 1;
- auto &type = get<SPIRType>(result_type);
- auto expr = type_to_glsl_constructor(type);
- expr += '(';
- for (uint32_t i = 0; i < type.vecsize; i++)
- {
- // Make sure to call to_expression multiple times to ensure
- // that these expressions are properly flushed to temporaries if needed.
- if (negate)
- expr += "!(";
- if (expected_type != SPIRType::Unknown && type0.basetype != expected_type)
- expr += bitcast_expression(target_type0, type0.basetype, to_extract_component_expression(op0, i));
- else
- expr += to_extract_component_expression(op0, i);
- expr += ' ';
- expr += op;
- expr += ' ';
- if (expected_type != SPIRType::Unknown && type1.basetype != expected_type)
- expr += bitcast_expression(target_type1, type1.basetype, to_extract_component_expression(op1, i));
- else
- expr += to_extract_component_expression(op1, i);
- if (negate)
- expr += ")";
- if (i + 1 < type.vecsize)
- expr += ", ";
- }
- expr += ')';
- emit_op(result_type, result_id, expr, should_forward(op0) && should_forward(op1));
- inherit_expression_dependencies(result_id, op0);
- inherit_expression_dependencies(result_id, op1);
- }
- SPIRType CompilerGLSL::binary_op_bitcast_helper(string &cast_op0, string &cast_op1, SPIRType::BaseType &input_type,
- uint32_t op0, uint32_t op1, bool skip_cast_if_equal_type)
- {
- auto &type0 = expression_type(op0);
- auto &type1 = expression_type(op1);
- // We have to bitcast if our inputs are of different type, or if our types are not equal to expected inputs.
- // For some functions like OpIEqual and INotEqual, we don't care if inputs are of different types than expected
- // since equality test is exactly the same.
- bool cast = (type0.basetype != type1.basetype) || (!skip_cast_if_equal_type && type0.basetype != input_type);
- // Create a fake type so we can bitcast to it.
- // We only deal with regular arithmetic types here like int, uints and so on.
- SPIRType expected_type{type0.op};
- expected_type.basetype = input_type;
- expected_type.vecsize = type0.vecsize;
- expected_type.columns = type0.columns;
- expected_type.width = type0.width;
- if (cast)
- {
- cast_op0 = bitcast_glsl(expected_type, op0);
- cast_op1 = bitcast_glsl(expected_type, op1);
- }
- else
- {
- // If we don't cast, our actual input type is that of the first (or second) argument.
- cast_op0 = to_enclosed_unpacked_expression(op0);
- cast_op1 = to_enclosed_unpacked_expression(op1);
- input_type = type0.basetype;
- }
- return expected_type;
- }
- bool CompilerGLSL::emit_complex_bitcast(uint32_t result_type, uint32_t id, uint32_t op0)
- {
- // Some bitcasts may require complex casting sequences, and are implemented here.
- // Otherwise a simply unary function will do with bitcast_glsl_op.
- auto &output_type = get<SPIRType>(result_type);
- auto &input_type = expression_type(op0);
- string expr;
- if (output_type.basetype == SPIRType::Half && input_type.basetype == SPIRType::Float && input_type.vecsize == 1)
- expr = join("unpackFloat2x16(floatBitsToUint(", to_unpacked_expression(op0), "))");
- else if (output_type.basetype == SPIRType::Float && input_type.basetype == SPIRType::Half &&
- input_type.vecsize == 2)
- expr = join("uintBitsToFloat(packFloat2x16(", to_unpacked_expression(op0), "))");
- else
- return false;
- emit_op(result_type, id, expr, should_forward(op0));
- return true;
- }
- void CompilerGLSL::emit_binary_op_cast(uint32_t result_type, uint32_t result_id, uint32_t op0, uint32_t op1,
- const char *op, SPIRType::BaseType input_type,
- bool skip_cast_if_equal_type,
- bool implicit_integer_promotion)
- {
- string cast_op0, cast_op1;
- auto expected_type = binary_op_bitcast_helper(cast_op0, cast_op1, input_type, op0, op1, skip_cast_if_equal_type);
- auto &out_type = get<SPIRType>(result_type);
- // We might have casted away from the result type, so bitcast again.
- // For example, arithmetic right shift with uint inputs.
- // Special case boolean outputs since relational opcodes output booleans instead of int/uint.
- auto bitop = join(cast_op0, " ", op, " ", cast_op1);
- string expr;
- if (implicit_integer_promotion)
- {
- // Simple value cast.
- expr = join(type_to_glsl(out_type), '(', bitop, ')');
- }
- else if (out_type.basetype != input_type && out_type.basetype != SPIRType::Boolean)
- {
- expected_type.basetype = input_type;
- expr = join(bitcast_glsl_op(out_type, expected_type), '(', bitop, ')');
- }
- else
- {
- expr = std::move(bitop);
- }
- emit_op(result_type, result_id, expr, should_forward(op0) && should_forward(op1));
- inherit_expression_dependencies(result_id, op0);
- inherit_expression_dependencies(result_id, op1);
- }
- void CompilerGLSL::emit_unary_func_op(uint32_t result_type, uint32_t result_id, uint32_t op0, const char *op)
- {
- bool forward = should_forward(op0);
- emit_op(result_type, result_id, join(op, "(", to_unpacked_expression(op0), ")"), forward);
- inherit_expression_dependencies(result_id, op0);
- }
- void CompilerGLSL::emit_binary_func_op(uint32_t result_type, uint32_t result_id, uint32_t op0, uint32_t op1,
- const char *op)
- {
- // Opaque types (e.g. OpTypeSampledImage) must always be forwarded in GLSL
- const auto &type = get_type(result_type);
- bool must_forward = type_is_opaque_value(type);
- bool forward = must_forward || (should_forward(op0) && should_forward(op1));
- emit_op(result_type, result_id, join(op, "(", to_unpacked_expression(op0), ", ", to_unpacked_expression(op1), ")"),
- forward);
- inherit_expression_dependencies(result_id, op0);
- inherit_expression_dependencies(result_id, op1);
- }
- void CompilerGLSL::emit_atomic_func_op(uint32_t result_type, uint32_t result_id, uint32_t op0, uint32_t op1,
- const char *op)
- {
- auto &type = get<SPIRType>(result_type);
- if (type_is_floating_point(type))
- {
- if (!options.vulkan_semantics)
- SPIRV_CROSS_THROW("Floating point atomics requires Vulkan semantics.");
- if (options.es)
- SPIRV_CROSS_THROW("Floating point atomics requires desktop GLSL.");
- require_extension_internal("GL_EXT_shader_atomic_float");
- }
- if (type.basetype == SPIRType::UInt64 || type.basetype == SPIRType::Int64)
- require_extension_internal("GL_EXT_shader_atomic_int64");
- forced_temporaries.insert(result_id);
- emit_op(result_type, result_id,
- join(op, "(", to_atomic_ptr_expression(op0), ", ",
- to_unpacked_expression(op1), ")"), false);
- flush_all_atomic_capable_variables();
- }
- void CompilerGLSL::emit_atomic_func_op(uint32_t result_type, uint32_t result_id,
- uint32_t op0, uint32_t op1, uint32_t op2,
- const char *op)
- {
- forced_temporaries.insert(result_id);
- emit_op(result_type, result_id,
- join(op, "(", to_non_uniform_aware_expression(op0), ", ",
- to_unpacked_expression(op1), ", ", to_unpacked_expression(op2), ")"), false);
- flush_all_atomic_capable_variables();
- }
- void CompilerGLSL::emit_unary_func_op_cast(uint32_t result_type, uint32_t result_id, uint32_t op0, const char *op,
- SPIRType::BaseType input_type, SPIRType::BaseType expected_result_type)
- {
- auto &out_type = get<SPIRType>(result_type);
- auto &expr_type = expression_type(op0);
- auto expected_type = out_type;
- // Bit-widths might be different in unary cases because we use it for SConvert/UConvert and friends.
- expected_type.basetype = input_type;
- expected_type.width = expr_type.width;
- string cast_op;
- if (expr_type.basetype != input_type)
- {
- if (expr_type.basetype == SPIRType::Boolean)
- cast_op = join(type_to_glsl(expected_type), "(", to_unpacked_expression(op0), ")");
- else
- cast_op = bitcast_glsl(expected_type, op0);
- }
- else
- cast_op = to_unpacked_expression(op0);
- string expr;
- if (out_type.basetype != expected_result_type)
- {
- expected_type.basetype = expected_result_type;
- expected_type.width = out_type.width;
- if (out_type.basetype == SPIRType::Boolean)
- expr = type_to_glsl(out_type);
- else
- expr = bitcast_glsl_op(out_type, expected_type);
- expr += '(';
- expr += join(op, "(", cast_op, ")");
- expr += ')';
- }
- else
- {
- expr += join(op, "(", cast_op, ")");
- }
- emit_op(result_type, result_id, expr, should_forward(op0));
- inherit_expression_dependencies(result_id, op0);
- }
- // Very special case. Handling bitfieldExtract requires us to deal with different bitcasts of different signs
- // and different vector sizes all at once. Need a special purpose method here.
- void CompilerGLSL::emit_trinary_func_op_bitextract(uint32_t result_type, uint32_t result_id, uint32_t op0, uint32_t op1,
- uint32_t op2, const char *op,
- SPIRType::BaseType expected_result_type,
- SPIRType::BaseType input_type0, SPIRType::BaseType input_type1,
- SPIRType::BaseType input_type2)
- {
- auto &out_type = get<SPIRType>(result_type);
- auto expected_type = out_type;
- expected_type.basetype = input_type0;
- string cast_op0 =
- expression_type(op0).basetype != input_type0 ? bitcast_glsl(expected_type, op0) : to_unpacked_expression(op0);
- auto op1_expr = to_unpacked_expression(op1);
- auto op2_expr = to_unpacked_expression(op2);
- // Use value casts here instead. Input must be exactly int or uint, but SPIR-V might be 16-bit.
- expected_type.basetype = input_type1;
- expected_type.vecsize = 1;
- string cast_op1 = expression_type(op1).basetype != input_type1 ?
- join(type_to_glsl_constructor(expected_type), "(", op1_expr, ")") :
- op1_expr;
- expected_type.basetype = input_type2;
- expected_type.vecsize = 1;
- string cast_op2 = expression_type(op2).basetype != input_type2 ?
- join(type_to_glsl_constructor(expected_type), "(", op2_expr, ")") :
- op2_expr;
- string expr;
- if (out_type.basetype != expected_result_type)
- {
- expected_type.vecsize = out_type.vecsize;
- expected_type.basetype = expected_result_type;
- expr = bitcast_glsl_op(out_type, expected_type);
- expr += '(';
- expr += join(op, "(", cast_op0, ", ", cast_op1, ", ", cast_op2, ")");
- expr += ')';
- }
- else
- {
- expr += join(op, "(", cast_op0, ", ", cast_op1, ", ", cast_op2, ")");
- }
- emit_op(result_type, result_id, expr, should_forward(op0) && should_forward(op1) && should_forward(op2));
- inherit_expression_dependencies(result_id, op0);
- inherit_expression_dependencies(result_id, op1);
- inherit_expression_dependencies(result_id, op2);
- }
- void CompilerGLSL::emit_trinary_func_op_cast(uint32_t result_type, uint32_t result_id, uint32_t op0, uint32_t op1,
- uint32_t op2, const char *op, SPIRType::BaseType input_type)
- {
- auto &out_type = get<SPIRType>(result_type);
- auto expected_type = out_type;
- expected_type.basetype = input_type;
- string cast_op0 =
- expression_type(op0).basetype != input_type ? bitcast_glsl(expected_type, op0) : to_unpacked_expression(op0);
- string cast_op1 =
- expression_type(op1).basetype != input_type ? bitcast_glsl(expected_type, op1) : to_unpacked_expression(op1);
- string cast_op2 =
- expression_type(op2).basetype != input_type ? bitcast_glsl(expected_type, op2) : to_unpacked_expression(op2);
- string expr;
- if (out_type.basetype != input_type)
- {
- expr = bitcast_glsl_op(out_type, expected_type);
- expr += '(';
- expr += join(op, "(", cast_op0, ", ", cast_op1, ", ", cast_op2, ")");
- expr += ')';
- }
- else
- {
- expr += join(op, "(", cast_op0, ", ", cast_op1, ", ", cast_op2, ")");
- }
- emit_op(result_type, result_id, expr, should_forward(op0) && should_forward(op1) && should_forward(op2));
- inherit_expression_dependencies(result_id, op0);
- inherit_expression_dependencies(result_id, op1);
- inherit_expression_dependencies(result_id, op2);
- }
- void CompilerGLSL::emit_binary_func_op_cast_clustered(uint32_t result_type, uint32_t result_id, uint32_t op0,
- uint32_t op1, const char *op, SPIRType::BaseType input_type)
- {
- // Special purpose method for implementing clustered subgroup opcodes.
- // Main difference is that op1 does not participate in any casting, it needs to be a literal.
- auto &out_type = get<SPIRType>(result_type);
- auto expected_type = out_type;
- expected_type.basetype = input_type;
- string cast_op0 =
- expression_type(op0).basetype != input_type ? bitcast_glsl(expected_type, op0) : to_unpacked_expression(op0);
- string expr;
- if (out_type.basetype != input_type)
- {
- expr = bitcast_glsl_op(out_type, expected_type);
- expr += '(';
- expr += join(op, "(", cast_op0, ", ", to_expression(op1), ")");
- expr += ')';
- }
- else
- {
- expr += join(op, "(", cast_op0, ", ", to_expression(op1), ")");
- }
- emit_op(result_type, result_id, expr, should_forward(op0));
- inherit_expression_dependencies(result_id, op0);
- }
- void CompilerGLSL::emit_binary_func_op_cast(uint32_t result_type, uint32_t result_id, uint32_t op0, uint32_t op1,
- const char *op, SPIRType::BaseType input_type, bool skip_cast_if_equal_type)
- {
- string cast_op0, cast_op1;
- auto expected_type = binary_op_bitcast_helper(cast_op0, cast_op1, input_type, op0, op1, skip_cast_if_equal_type);
- auto &out_type = get<SPIRType>(result_type);
- // Special case boolean outputs since relational opcodes output booleans instead of int/uint.
- string expr;
- if (out_type.basetype != input_type && out_type.basetype != SPIRType::Boolean)
- {
- expected_type.basetype = input_type;
- expr = bitcast_glsl_op(out_type, expected_type);
- expr += '(';
- expr += join(op, "(", cast_op0, ", ", cast_op1, ")");
- expr += ')';
- }
- else
- {
- expr += join(op, "(", cast_op0, ", ", cast_op1, ")");
- }
- emit_op(result_type, result_id, expr, should_forward(op0) && should_forward(op1));
- inherit_expression_dependencies(result_id, op0);
- inherit_expression_dependencies(result_id, op1);
- }
- void CompilerGLSL::emit_trinary_func_op(uint32_t result_type, uint32_t result_id, uint32_t op0, uint32_t op1,
- uint32_t op2, const char *op)
- {
- bool forward = should_forward(op0) && should_forward(op1) && should_forward(op2);
- emit_op(result_type, result_id,
- join(op, "(", to_unpacked_expression(op0), ", ", to_unpacked_expression(op1), ", ",
- to_unpacked_expression(op2), ")"),
- forward);
- inherit_expression_dependencies(result_id, op0);
- inherit_expression_dependencies(result_id, op1);
- inherit_expression_dependencies(result_id, op2);
- }
- void CompilerGLSL::emit_quaternary_func_op(uint32_t result_type, uint32_t result_id, uint32_t op0, uint32_t op1,
- uint32_t op2, uint32_t op3, const char *op)
- {
- bool forward = should_forward(op0) && should_forward(op1) && should_forward(op2) && should_forward(op3);
- emit_op(result_type, result_id,
- join(op, "(", to_unpacked_expression(op0), ", ", to_unpacked_expression(op1), ", ",
- to_unpacked_expression(op2), ", ", to_unpacked_expression(op3), ")"),
- forward);
- inherit_expression_dependencies(result_id, op0);
- inherit_expression_dependencies(result_id, op1);
- inherit_expression_dependencies(result_id, op2);
- inherit_expression_dependencies(result_id, op3);
- }
- void CompilerGLSL::emit_bitfield_insert_op(uint32_t result_type, uint32_t result_id, uint32_t op0, uint32_t op1,
- uint32_t op2, uint32_t op3, const char *op,
- SPIRType::BaseType offset_count_type)
- {
- // Only need to cast offset/count arguments. Types of base/insert must be same as result type,
- // and bitfieldInsert is sign invariant.
- bool forward = should_forward(op0) && should_forward(op1) && should_forward(op2) && should_forward(op3);
- auto op0_expr = to_unpacked_expression(op0);
- auto op1_expr = to_unpacked_expression(op1);
- auto op2_expr = to_unpacked_expression(op2);
- auto op3_expr = to_unpacked_expression(op3);
- assert(offset_count_type == SPIRType::UInt || offset_count_type == SPIRType::Int);
- SPIRType target_type { OpTypeInt };
- target_type.width = 32;
- target_type.vecsize = 1;
- target_type.basetype = offset_count_type;
- if (expression_type(op2).basetype != offset_count_type)
- {
- // Value-cast here. Input might be 16-bit. GLSL requires int.
- op2_expr = join(type_to_glsl_constructor(target_type), "(", op2_expr, ")");
- }
- if (expression_type(op3).basetype != offset_count_type)
- {
- // Value-cast here. Input might be 16-bit. GLSL requires int.
- op3_expr = join(type_to_glsl_constructor(target_type), "(", op3_expr, ")");
- }
- emit_op(result_type, result_id, join(op, "(", op0_expr, ", ", op1_expr, ", ", op2_expr, ", ", op3_expr, ")"),
- forward);
- inherit_expression_dependencies(result_id, op0);
- inherit_expression_dependencies(result_id, op1);
- inherit_expression_dependencies(result_id, op2);
- inherit_expression_dependencies(result_id, op3);
- }
- string CompilerGLSL::legacy_tex_op(const std::string &op, const SPIRType &imgtype, uint32_t tex)
- {
- const char *type;
- switch (imgtype.image.dim)
- {
- case Dim1D:
- // Force 2D path for ES.
- if (options.es)
- type = (imgtype.image.arrayed && !options.es) ? "2DArray" : "2D";
- else
- type = (imgtype.image.arrayed && !options.es) ? "1DArray" : "1D";
- break;
- case Dim2D:
- type = (imgtype.image.arrayed && !options.es) ? "2DArray" : "2D";
- break;
- case Dim3D:
- type = "3D";
- break;
- case DimCube:
- type = "Cube";
- break;
- case DimRect:
- type = "2DRect";
- break;
- case DimBuffer:
- type = "Buffer";
- break;
- case DimSubpassData:
- type = "2D";
- break;
- default:
- type = "";
- break;
- }
- // In legacy GLSL, an extension is required for textureLod in the fragment
- // shader or textureGrad anywhere.
- bool legacy_lod_ext = false;
- auto &execution = get_entry_point();
- if (op == "textureGrad" || op == "textureProjGrad" ||
- ((op == "textureLod" || op == "textureProjLod") && execution.model != ExecutionModelVertex))
- {
- if (is_legacy_es())
- {
- legacy_lod_ext = true;
- require_extension_internal("GL_EXT_shader_texture_lod");
- }
- else if (is_legacy_desktop())
- require_extension_internal("GL_ARB_shader_texture_lod");
- }
- if (op == "textureLodOffset" || op == "textureProjLodOffset")
- {
- if (is_legacy_es())
- SPIRV_CROSS_THROW(join(op, " not allowed in legacy ES"));
- require_extension_internal("GL_EXT_gpu_shader4");
- }
- // GLES has very limited support for shadow samplers.
- // Basically shadow2D and shadow2DProj work through EXT_shadow_samplers,
- // everything else can just throw
- bool is_comparison = is_depth_image(imgtype, tex);
- if (is_comparison && is_legacy_es())
- {
- if (op == "texture" || op == "textureProj")
- require_extension_internal("GL_EXT_shadow_samplers");
- else
- SPIRV_CROSS_THROW(join(op, " not allowed on depth samplers in legacy ES"));
- if (imgtype.image.dim == DimCube)
- return "shadowCubeNV";
- }
- if (op == "textureSize")
- {
- if (is_legacy_es())
- SPIRV_CROSS_THROW("textureSize not supported in legacy ES");
- if (is_comparison)
- SPIRV_CROSS_THROW("textureSize not supported on shadow sampler in legacy GLSL");
- require_extension_internal("GL_EXT_gpu_shader4");
- }
- if (op == "texelFetch" && is_legacy_es())
- SPIRV_CROSS_THROW("texelFetch not supported in legacy ES");
- bool is_es_and_depth = is_legacy_es() && is_comparison;
- std::string type_prefix = is_comparison ? "shadow" : "texture";
- if (op == "texture")
- return is_es_and_depth ? join(type_prefix, type, "EXT") : join(type_prefix, type);
- else if (op == "textureLod")
- return join(type_prefix, type, legacy_lod_ext ? "LodEXT" : "Lod");
- else if (op == "textureProj")
- return join(type_prefix, type, is_es_and_depth ? "ProjEXT" : "Proj");
- else if (op == "textureGrad")
- return join(type_prefix, type, is_legacy_es() ? "GradEXT" : is_legacy_desktop() ? "GradARB" : "Grad");
- else if (op == "textureProjLod")
- return join(type_prefix, type, legacy_lod_ext ? "ProjLodEXT" : "ProjLod");
- else if (op == "textureLodOffset")
- return join(type_prefix, type, "LodOffset");
- else if (op == "textureProjGrad")
- return join(type_prefix, type,
- is_legacy_es() ? "ProjGradEXT" : is_legacy_desktop() ? "ProjGradARB" : "ProjGrad");
- else if (op == "textureProjLodOffset")
- return join(type_prefix, type, "ProjLodOffset");
- else if (op == "textureSize")
- return join("textureSize", type);
- else if (op == "texelFetch")
- return join("texelFetch", type);
- else
- {
- SPIRV_CROSS_THROW(join("Unsupported legacy texture op: ", op));
- }
- }
- bool CompilerGLSL::to_trivial_mix_op(const SPIRType &type, string &op, uint32_t left, uint32_t right, uint32_t lerp)
- {
- auto *cleft = maybe_get<SPIRConstant>(left);
- auto *cright = maybe_get<SPIRConstant>(right);
- auto &lerptype = expression_type(lerp);
- // If our targets aren't constants, we cannot use construction.
- if (!cleft || !cright)
- return false;
- // If our targets are spec constants, we cannot use construction.
- if (cleft->specialization || cright->specialization)
- return false;
- auto &value_type = get<SPIRType>(cleft->constant_type);
- if (lerptype.basetype != SPIRType::Boolean)
- return false;
- if (value_type.basetype == SPIRType::Struct || is_array(value_type))
- return false;
- if (!backend.use_constructor_splatting && value_type.vecsize != lerptype.vecsize)
- return false;
- // Only valid way in SPIR-V 1.4 to use matrices in select is a scalar select.
- // matrix(scalar) constructor fills in diagnonals, so gets messy very quickly.
- // Just avoid this case.
- if (value_type.columns > 1)
- return false;
- // If our bool selects between 0 and 1, we can cast from bool instead, making our trivial constructor.
- bool ret = true;
- for (uint32_t row = 0; ret && row < value_type.vecsize; row++)
- {
- switch (type.basetype)
- {
- case SPIRType::Short:
- case SPIRType::UShort:
- ret = cleft->scalar_u16(0, row) == 0 && cright->scalar_u16(0, row) == 1;
- break;
- case SPIRType::Int:
- case SPIRType::UInt:
- ret = cleft->scalar(0, row) == 0 && cright->scalar(0, row) == 1;
- break;
- case SPIRType::Half:
- ret = cleft->scalar_f16(0, row) == 0.0f && cright->scalar_f16(0, row) == 1.0f;
- break;
- case SPIRType::Float:
- ret = cleft->scalar_f32(0, row) == 0.0f && cright->scalar_f32(0, row) == 1.0f;
- break;
- case SPIRType::Double:
- ret = cleft->scalar_f64(0, row) == 0.0 && cright->scalar_f64(0, row) == 1.0;
- break;
- case SPIRType::Int64:
- case SPIRType::UInt64:
- ret = cleft->scalar_u64(0, row) == 0 && cright->scalar_u64(0, row) == 1;
- break;
- default:
- ret = false;
- break;
- }
- }
- if (ret)
- op = type_to_glsl_constructor(type);
- return ret;
- }
- string CompilerGLSL::to_ternary_expression(const SPIRType &restype, uint32_t select, uint32_t true_value,
- uint32_t false_value)
- {
- string expr;
- auto &lerptype = expression_type(select);
- if (lerptype.vecsize == 1)
- expr = join(to_enclosed_expression(select), " ? ", to_enclosed_pointer_expression(true_value), " : ",
- to_enclosed_pointer_expression(false_value));
- else
- {
- auto swiz = [this](uint32_t expression, uint32_t i) { return to_extract_component_expression(expression, i); };
- expr = type_to_glsl_constructor(restype);
- expr += "(";
- for (uint32_t i = 0; i < restype.vecsize; i++)
- {
- expr += swiz(select, i);
- expr += " ? ";
- expr += swiz(true_value, i);
- expr += " : ";
- expr += swiz(false_value, i);
- if (i + 1 < restype.vecsize)
- expr += ", ";
- }
- expr += ")";
- }
- return expr;
- }
- void CompilerGLSL::emit_mix_op(uint32_t result_type, uint32_t id, uint32_t left, uint32_t right, uint32_t lerp)
- {
- auto &lerptype = expression_type(lerp);
- auto &restype = get<SPIRType>(result_type);
- // If this results in a variable pointer, assume it may be written through.
- if (restype.pointer)
- {
- register_write(left);
- register_write(right);
- }
- string mix_op;
- bool has_boolean_mix = *backend.boolean_mix_function &&
- ((options.es && options.version >= 310) || (!options.es && options.version >= 450));
- bool trivial_mix = to_trivial_mix_op(restype, mix_op, left, right, lerp);
- // Cannot use boolean mix when the lerp argument is just one boolean,
- // fall back to regular trinary statements.
- if (lerptype.vecsize == 1)
- has_boolean_mix = false;
- // If we can reduce the mix to a simple cast, do so.
- // This helps for cases like int(bool), uint(bool) which is implemented with
- // OpSelect bool 1 0.
- if (trivial_mix)
- {
- emit_unary_func_op(result_type, id, lerp, mix_op.c_str());
- }
- else if (!has_boolean_mix && lerptype.basetype == SPIRType::Boolean)
- {
- // Boolean mix not supported on desktop without extension.
- // Was added in OpenGL 4.5 with ES 3.1 compat.
- //
- // Could use GL_EXT_shader_integer_mix on desktop at least,
- // but Apple doesn't support it. :(
- // Just implement it as ternary expressions.
- auto expr = to_ternary_expression(get<SPIRType>(result_type), lerp, right, left);
- emit_op(result_type, id, expr, should_forward(left) && should_forward(right) && should_forward(lerp));
- inherit_expression_dependencies(id, left);
- inherit_expression_dependencies(id, right);
- inherit_expression_dependencies(id, lerp);
- }
- else if (lerptype.basetype == SPIRType::Boolean)
- emit_trinary_func_op(result_type, id, left, right, lerp, backend.boolean_mix_function);
- else
- emit_trinary_func_op(result_type, id, left, right, lerp, "mix");
- }
- string CompilerGLSL::to_combined_image_sampler(VariableID image_id, VariableID samp_id)
- {
- // Keep track of the array indices we have used to load the image.
- // We'll need to use the same array index into the combined image sampler array.
- auto image_expr = to_non_uniform_aware_expression(image_id);
- string array_expr;
- auto array_index = image_expr.find_first_of('[');
- if (array_index != string::npos)
- array_expr = image_expr.substr(array_index, string::npos);
- auto &args = current_function->arguments;
- // For GLSL and ESSL targets, we must enumerate all possible combinations for sampler2D(texture2D, sampler) and redirect
- // all possible combinations into new sampler2D uniforms.
- auto *image = maybe_get_backing_variable(image_id);
- auto *samp = maybe_get_backing_variable(samp_id);
- if (image)
- image_id = image->self;
- if (samp)
- samp_id = samp->self;
- auto image_itr = find_if(begin(args), end(args),
- [image_id](const SPIRFunction::Parameter ¶m) { return image_id == param.id; });
- auto sampler_itr = find_if(begin(args), end(args),
- [samp_id](const SPIRFunction::Parameter ¶m) { return samp_id == param.id; });
- if (image_itr != end(args) || sampler_itr != end(args))
- {
- // If any parameter originates from a parameter, we will find it in our argument list.
- bool global_image = image_itr == end(args);
- bool global_sampler = sampler_itr == end(args);
- VariableID iid = global_image ? image_id : VariableID(uint32_t(image_itr - begin(args)));
- VariableID sid = global_sampler ? samp_id : VariableID(uint32_t(sampler_itr - begin(args)));
- auto &combined = current_function->combined_parameters;
- auto itr = find_if(begin(combined), end(combined), [=](const SPIRFunction::CombinedImageSamplerParameter &p) {
- return p.global_image == global_image && p.global_sampler == global_sampler && p.image_id == iid &&
- p.sampler_id == sid;
- });
- if (itr != end(combined))
- return to_expression(itr->id) + array_expr;
- else
- {
- SPIRV_CROSS_THROW("Cannot find mapping for combined sampler parameter, was "
- "build_combined_image_samplers() used "
- "before compile() was called?");
- }
- }
- else
- {
- // For global sampler2D, look directly at the global remapping table.
- auto &mapping = combined_image_samplers;
- auto itr = find_if(begin(mapping), end(mapping), [image_id, samp_id](const CombinedImageSampler &combined) {
- return combined.image_id == image_id && combined.sampler_id == samp_id;
- });
- if (itr != end(combined_image_samplers))
- return to_expression(itr->combined_id) + array_expr;
- else
- {
- SPIRV_CROSS_THROW("Cannot find mapping for combined sampler, was build_combined_image_samplers() used "
- "before compile() was called?");
- }
- }
- }
- bool CompilerGLSL::is_supported_subgroup_op_in_opengl(Op op, const uint32_t *ops)
- {
- switch (op)
- {
- case OpGroupNonUniformElect:
- case OpGroupNonUniformBallot:
- case OpGroupNonUniformBallotFindLSB:
- case OpGroupNonUniformBallotFindMSB:
- case OpGroupNonUniformBroadcast:
- case OpGroupNonUniformBroadcastFirst:
- case OpGroupNonUniformAll:
- case OpGroupNonUniformAny:
- case OpGroupNonUniformAllEqual:
- case OpControlBarrier:
- case OpMemoryBarrier:
- case OpGroupNonUniformBallotBitCount:
- case OpGroupNonUniformBallotBitExtract:
- case OpGroupNonUniformInverseBallot:
- return true;
- case OpGroupNonUniformIAdd:
- case OpGroupNonUniformFAdd:
- case OpGroupNonUniformIMul:
- case OpGroupNonUniformFMul:
- {
- const GroupOperation operation = static_cast<GroupOperation>(ops[3]);
- if (operation == GroupOperationReduce || operation == GroupOperationInclusiveScan ||
- operation == GroupOperationExclusiveScan)
- {
- return true;
- }
- else
- {
- return false;
- }
- }
- default:
- return false;
- }
- }
- void CompilerGLSL::emit_sampled_image_op(uint32_t result_type, uint32_t result_id, uint32_t image_id, uint32_t samp_id)
- {
- if (options.vulkan_semantics && combined_image_samplers.empty())
- {
- emit_binary_func_op(result_type, result_id, image_id, samp_id,
- type_to_glsl(get<SPIRType>(result_type), result_id).c_str());
- }
- else
- {
- // Make sure to suppress usage tracking. It is illegal to create temporaries of opaque types.
- emit_op(result_type, result_id, to_combined_image_sampler(image_id, samp_id), true, true);
- }
- // Make sure to suppress usage tracking and any expression invalidation.
- // It is illegal to create temporaries of opaque types.
- forwarded_temporaries.erase(result_id);
- }
- static inline bool image_opcode_is_sample_no_dref(Op op)
- {
- switch (op)
- {
- case OpImageSampleExplicitLod:
- case OpImageSampleImplicitLod:
- case OpImageSampleProjExplicitLod:
- case OpImageSampleProjImplicitLod:
- case OpImageFetch:
- case OpImageRead:
- case OpImageSparseSampleExplicitLod:
- case OpImageSparseSampleImplicitLod:
- case OpImageSparseSampleProjExplicitLod:
- case OpImageSparseSampleProjImplicitLod:
- case OpImageSparseFetch:
- case OpImageSparseRead:
- return true;
- default:
- return false;
- }
- }
- void CompilerGLSL::emit_sparse_feedback_temporaries(uint32_t result_type_id, uint32_t id, uint32_t &feedback_id,
- uint32_t &texel_id)
- {
- // Need to allocate two temporaries.
- if (options.es)
- SPIRV_CROSS_THROW("Sparse texture feedback is not supported on ESSL.");
- require_extension_internal("GL_ARB_sparse_texture2");
- auto &temps = extra_sub_expressions[id];
- if (temps == 0)
- temps = ir.increase_bound_by(2);
- feedback_id = temps + 0;
- texel_id = temps + 1;
- auto &return_type = get<SPIRType>(result_type_id);
- if (return_type.basetype != SPIRType::Struct || return_type.member_types.size() != 2)
- SPIRV_CROSS_THROW("Invalid return type for sparse feedback.");
- emit_uninitialized_temporary(return_type.member_types[0], feedback_id);
- emit_uninitialized_temporary(return_type.member_types[1], texel_id);
- }
- uint32_t CompilerGLSL::get_sparse_feedback_texel_id(uint32_t id) const
- {
- auto itr = extra_sub_expressions.find(id);
- if (itr == extra_sub_expressions.end())
- return 0;
- else
- return itr->second + 1;
- }
- void CompilerGLSL::emit_texture_op(const Instruction &i, bool sparse)
- {
- auto *ops = stream(i);
- auto op = static_cast<Op>(i.op);
- SmallVector<uint32_t> inherited_expressions;
- uint32_t result_type_id = ops[0];
- uint32_t id = ops[1];
- auto &return_type = get<SPIRType>(result_type_id);
- uint32_t sparse_code_id = 0;
- uint32_t sparse_texel_id = 0;
- if (sparse)
- emit_sparse_feedback_temporaries(result_type_id, id, sparse_code_id, sparse_texel_id);
- bool forward = false;
- string expr = to_texture_op(i, sparse, &forward, inherited_expressions);
- if (sparse)
- {
- statement(to_expression(sparse_code_id), " = ", expr, ";");
- expr = join(type_to_glsl(return_type), "(", to_expression(sparse_code_id), ", ", to_expression(sparse_texel_id),
- ")");
- forward = true;
- inherited_expressions.clear();
- }
- emit_op(result_type_id, id, expr, forward);
- for (auto &inherit : inherited_expressions)
- inherit_expression_dependencies(id, inherit);
- // Do not register sparse ops as control dependent as they are always lowered to a temporary.
- switch (op)
- {
- case OpImageSampleDrefImplicitLod:
- case OpImageSampleImplicitLod:
- case OpImageSampleProjImplicitLod:
- case OpImageSampleProjDrefImplicitLod:
- register_control_dependent_expression(id);
- break;
- default:
- break;
- }
- }
- std::string CompilerGLSL::to_texture_op(const Instruction &i, bool sparse, bool *forward,
- SmallVector<uint32_t> &inherited_expressions)
- {
- auto *ops = stream(i);
- auto op = static_cast<Op>(i.op);
- uint32_t length = i.length;
- uint32_t result_type_id = ops[0];
- VariableID img = ops[2];
- uint32_t coord = ops[3];
- uint32_t dref = 0;
- uint32_t comp = 0;
- bool gather = false;
- bool proj = false;
- bool fetch = false;
- bool nonuniform_expression = false;
- const uint32_t *opt = nullptr;
- auto &result_type = get<SPIRType>(result_type_id);
- inherited_expressions.push_back(coord);
- if (has_decoration(img, DecorationNonUniform) && !maybe_get_backing_variable(img))
- nonuniform_expression = true;
- switch (op)
- {
- case OpImageSampleDrefImplicitLod:
- case OpImageSampleDrefExplicitLod:
- case OpImageSparseSampleDrefImplicitLod:
- case OpImageSparseSampleDrefExplicitLod:
- dref = ops[4];
- opt = &ops[5];
- length -= 5;
- break;
- case OpImageSampleProjDrefImplicitLod:
- case OpImageSampleProjDrefExplicitLod:
- case OpImageSparseSampleProjDrefImplicitLod:
- case OpImageSparseSampleProjDrefExplicitLod:
- dref = ops[4];
- opt = &ops[5];
- length -= 5;
- proj = true;
- break;
- case OpImageDrefGather:
- case OpImageSparseDrefGather:
- dref = ops[4];
- opt = &ops[5];
- length -= 5;
- gather = true;
- if (options.es && options.version < 310)
- SPIRV_CROSS_THROW("textureGather requires ESSL 310.");
- else if (!options.es && options.version < 400)
- SPIRV_CROSS_THROW("textureGather with depth compare requires GLSL 400.");
- break;
- case OpImageGather:
- case OpImageSparseGather:
- comp = ops[4];
- opt = &ops[5];
- length -= 5;
- gather = true;
- if (options.es && options.version < 310)
- SPIRV_CROSS_THROW("textureGather requires ESSL 310.");
- else if (!options.es && options.version < 400)
- {
- if (!expression_is_constant_null(comp))
- SPIRV_CROSS_THROW("textureGather with component requires GLSL 400.");
- require_extension_internal("GL_ARB_texture_gather");
- }
- break;
- case OpImageFetch:
- case OpImageSparseFetch:
- case OpImageRead: // Reads == fetches in Metal (other langs will not get here)
- opt = &ops[4];
- length -= 4;
- fetch = true;
- break;
- case OpImageSampleProjImplicitLod:
- case OpImageSampleProjExplicitLod:
- case OpImageSparseSampleProjImplicitLod:
- case OpImageSparseSampleProjExplicitLod:
- opt = &ops[4];
- length -= 4;
- proj = true;
- break;
- default:
- opt = &ops[4];
- length -= 4;
- break;
- }
- // Bypass pointers because we need the real image struct
- auto &type = expression_type(img);
- auto &imgtype = get<SPIRType>(type.self);
- uint32_t coord_components = 0;
- switch (imgtype.image.dim)
- {
- case Dim1D:
- coord_components = 1;
- break;
- case Dim2D:
- coord_components = 2;
- break;
- case Dim3D:
- coord_components = 3;
- break;
- case DimCube:
- coord_components = 3;
- break;
- case DimBuffer:
- coord_components = 1;
- break;
- default:
- coord_components = 2;
- break;
- }
- if (dref)
- inherited_expressions.push_back(dref);
- if (proj)
- coord_components++;
- if (imgtype.image.arrayed)
- coord_components++;
- uint32_t bias = 0;
- uint32_t lod = 0;
- uint32_t grad_x = 0;
- uint32_t grad_y = 0;
- uint32_t coffset = 0;
- uint32_t offset = 0;
- uint32_t coffsets = 0;
- uint32_t sample = 0;
- uint32_t minlod = 0;
- uint32_t flags = 0;
- if (length)
- {
- flags = *opt++;
- length--;
- }
- auto test = [&](uint32_t &v, uint32_t flag) {
- if (length && (flags & flag))
- {
- v = *opt++;
- inherited_expressions.push_back(v);
- length--;
- }
- };
- test(bias, ImageOperandsBiasMask);
- test(lod, ImageOperandsLodMask);
- test(grad_x, ImageOperandsGradMask);
- test(grad_y, ImageOperandsGradMask);
- test(coffset, ImageOperandsConstOffsetMask);
- test(offset, ImageOperandsOffsetMask);
- test(coffsets, ImageOperandsConstOffsetsMask);
- test(sample, ImageOperandsSampleMask);
- test(minlod, ImageOperandsMinLodMask);
- TextureFunctionBaseArguments base_args = {};
- base_args.img = img;
- base_args.imgtype = &imgtype;
- base_args.is_fetch = fetch != 0;
- base_args.is_gather = gather != 0;
- base_args.is_proj = proj != 0;
- string expr;
- TextureFunctionNameArguments name_args = {};
- name_args.base = base_args;
- name_args.has_array_offsets = coffsets != 0;
- name_args.has_offset = coffset != 0 || offset != 0;
- name_args.has_grad = grad_x != 0 || grad_y != 0;
- name_args.has_dref = dref != 0;
- name_args.is_sparse_feedback = sparse;
- name_args.has_min_lod = minlod != 0;
- name_args.lod = lod;
- expr += to_function_name(name_args);
- expr += "(";
- uint32_t sparse_texel_id = 0;
- if (sparse)
- sparse_texel_id = get_sparse_feedback_texel_id(ops[1]);
- TextureFunctionArguments args = {};
- args.base = base_args;
- args.coord = coord;
- args.coord_components = coord_components;
- args.dref = dref;
- args.grad_x = grad_x;
- args.grad_y = grad_y;
- args.lod = lod;
- args.has_array_offsets = coffsets != 0;
- if (coffsets)
- args.offset = coffsets;
- else if (coffset)
- args.offset = coffset;
- else
- args.offset = offset;
- args.bias = bias;
- args.component = comp;
- args.sample = sample;
- args.sparse_texel = sparse_texel_id;
- args.min_lod = minlod;
- args.nonuniform_expression = nonuniform_expression;
- expr += to_function_args(args, forward);
- expr += ")";
- // texture(samplerXShadow) returns float. shadowX() returns vec4, but only in desktop GLSL. Swizzle here.
- if (is_legacy() && !options.es && is_depth_image(imgtype, img))
- expr += ".r";
- // Sampling from a texture which was deduced to be a depth image, might actually return 1 component here.
- // Remap back to 4 components as sampling opcodes expect.
- if (backend.comparison_image_samples_scalar && image_opcode_is_sample_no_dref(op))
- {
- bool image_is_depth = false;
- const auto *combined = maybe_get<SPIRCombinedImageSampler>(img);
- VariableID image_id = combined ? combined->image : img;
- if (combined && is_depth_image(imgtype, combined->image))
- image_is_depth = true;
- else if (is_depth_image(imgtype, img))
- image_is_depth = true;
- // We must also check the backing variable for the image.
- // We might have loaded an OpImage, and used that handle for two different purposes.
- // Once with comparison, once without.
- auto *image_variable = maybe_get_backing_variable(image_id);
- if (image_variable && is_depth_image(get<SPIRType>(image_variable->basetype), image_variable->self))
- image_is_depth = true;
- if (image_is_depth)
- expr = remap_swizzle(result_type, 1, expr);
- }
- if (!sparse && !backend.support_small_type_sampling_result && result_type.width < 32)
- {
- // Just value cast (narrowing) to expected type since we cannot rely on narrowing to work automatically.
- // Hopefully compiler picks this up and converts the texturing instruction to the appropriate precision.
- expr = join(type_to_glsl_constructor(result_type), "(", expr, ")");
- }
- // Deals with reads from MSL. We might need to downconvert to fewer components.
- if (op == OpImageRead)
- expr = remap_swizzle(result_type, 4, expr);
- return expr;
- }
- bool CompilerGLSL::expression_is_constant_null(uint32_t id) const
- {
- auto *c = maybe_get<SPIRConstant>(id);
- if (!c)
- return false;
- return c->constant_is_null();
- }
- bool CompilerGLSL::expression_is_non_value_type_array(uint32_t ptr)
- {
- auto &type = expression_type(ptr);
- if (!is_array(get_pointee_type(type)))
- return false;
- if (!backend.array_is_value_type)
- return true;
- auto *var = maybe_get_backing_variable(ptr);
- if (!var)
- return false;
- auto &backed_type = get<SPIRType>(var->basetype);
- return !backend.array_is_value_type_in_buffer_blocks && backed_type.basetype == SPIRType::Struct &&
- has_member_decoration(backed_type.self, 0, DecorationOffset);
- }
- // Returns the function name for a texture sampling function for the specified image and sampling characteristics.
- // For some subclasses, the function is a method on the specified image.
- string CompilerGLSL::to_function_name(const TextureFunctionNameArguments &args)
- {
- if (args.has_min_lod)
- {
- if (options.es)
- SPIRV_CROSS_THROW("Sparse residency is not supported in ESSL.");
- require_extension_internal("GL_ARB_sparse_texture_clamp");
- }
- string fname;
- auto &imgtype = *args.base.imgtype;
- VariableID tex = args.base.img;
- // textureLod on sampler2DArrayShadow and samplerCubeShadow does not exist in GLSL for some reason.
- // To emulate this, we will have to use textureGrad with a constant gradient of 0.
- // The workaround will assert that the LOD is in fact constant 0, or we cannot emit correct code.
- // This happens for HLSL SampleCmpLevelZero on Texture2DArray and TextureCube.
- bool workaround_lod_array_shadow_as_grad = false;
- if (((imgtype.image.arrayed && imgtype.image.dim == Dim2D) || imgtype.image.dim == DimCube) &&
- is_depth_image(imgtype, tex) && args.lod && !args.base.is_fetch)
- {
- if (!expression_is_constant_null(args.lod))
- {
- SPIRV_CROSS_THROW("textureLod on sampler2DArrayShadow is not constant 0.0. This cannot be "
- "expressed in GLSL.");
- }
- workaround_lod_array_shadow_as_grad = true;
- }
- if (args.is_sparse_feedback)
- fname += "sparse";
- if (args.base.is_fetch)
- fname += args.is_sparse_feedback ? "TexelFetch" : "texelFetch";
- else
- {
- fname += args.is_sparse_feedback ? "Texture" : "texture";
- if (args.base.is_gather)
- fname += "Gather";
- if (args.has_array_offsets)
- fname += "Offsets";
- if (args.base.is_proj)
- fname += "Proj";
- if (args.has_grad || workaround_lod_array_shadow_as_grad)
- fname += "Grad";
- if (args.lod != 0 && !workaround_lod_array_shadow_as_grad)
- fname += "Lod";
- }
- if (args.has_offset)
- fname += "Offset";
- if (args.has_min_lod)
- fname += "Clamp";
- if (args.is_sparse_feedback || args.has_min_lod)
- fname += "ARB";
- return (is_legacy() && !args.base.is_gather) ? legacy_tex_op(fname, imgtype, tex) : fname;
- }
- std::string CompilerGLSL::convert_separate_image_to_expression(uint32_t id)
- {
- auto *var = maybe_get_backing_variable(id);
- // If we are fetching from a plain OpTypeImage, we must combine with a dummy sampler in GLSL.
- // In Vulkan GLSL, we can make use of the newer GL_EXT_samplerless_texture_functions.
- if (var)
- {
- auto &type = get<SPIRType>(var->basetype);
- if (type.basetype == SPIRType::Image && type.image.sampled == 1 && type.image.dim != DimBuffer)
- {
- if (options.vulkan_semantics)
- {
- if (dummy_sampler_id)
- {
- // Don't need to consider Shadow state since the dummy sampler is always non-shadow.
- auto sampled_type = type;
- sampled_type.basetype = SPIRType::SampledImage;
- return join(type_to_glsl(sampled_type), "(", to_non_uniform_aware_expression(id), ", ",
- to_expression(dummy_sampler_id), ")");
- }
- else
- {
- // Newer glslang supports this extension to deal with texture2D as argument to texture functions.
- require_extension_internal("GL_EXT_samplerless_texture_functions");
- }
- }
- else
- {
- if (!dummy_sampler_id)
- SPIRV_CROSS_THROW("Cannot find dummy sampler ID. Was "
- "build_dummy_sampler_for_combined_images() called?");
- return to_combined_image_sampler(id, dummy_sampler_id);
- }
- }
- }
- return to_non_uniform_aware_expression(id);
- }
- // Returns the function args for a texture sampling function for the specified image and sampling characteristics.
- string CompilerGLSL::to_function_args(const TextureFunctionArguments &args, bool *p_forward)
- {
- VariableID img = args.base.img;
- auto &imgtype = *args.base.imgtype;
- string farg_str;
- if (args.base.is_fetch)
- farg_str = convert_separate_image_to_expression(img);
- else
- farg_str = to_non_uniform_aware_expression(img);
- if (args.nonuniform_expression && farg_str.find_first_of('[') != string::npos)
- {
- // Only emit nonuniformEXT() wrapper if the underlying expression is arrayed in some way.
- farg_str = join(backend.nonuniform_qualifier, "(", farg_str, ")");
- }
- bool swizz_func = backend.swizzle_is_function;
- auto swizzle = [swizz_func](uint32_t comps, uint32_t in_comps) -> const char * {
- if (comps == in_comps)
- return "";
- switch (comps)
- {
- case 1:
- return ".x";
- case 2:
- return swizz_func ? ".xy()" : ".xy";
- case 3:
- return swizz_func ? ".xyz()" : ".xyz";
- default:
- return "";
- }
- };
- bool forward = should_forward(args.coord);
- // The IR can give us more components than we need, so chop them off as needed.
- auto swizzle_expr = swizzle(args.coord_components, expression_type(args.coord).vecsize);
- // Only enclose the UV expression if needed.
- auto coord_expr =
- (*swizzle_expr == '\0') ? to_expression(args.coord) : (to_enclosed_expression(args.coord) + swizzle_expr);
- // texelFetch only takes int, not uint.
- auto &coord_type = expression_type(args.coord);
- if (coord_type.basetype == SPIRType::UInt)
- {
- auto expected_type = coord_type;
- expected_type.vecsize = args.coord_components;
- expected_type.basetype = SPIRType::Int;
- coord_expr = bitcast_expression(expected_type, coord_type.basetype, coord_expr);
- }
- // textureLod on sampler2DArrayShadow and samplerCubeShadow does not exist in GLSL for some reason.
- // To emulate this, we will have to use textureGrad with a constant gradient of 0.
- // The workaround will assert that the LOD is in fact constant 0, or we cannot emit correct code.
- // This happens for HLSL SampleCmpLevelZero on Texture2DArray and TextureCube.
- bool workaround_lod_array_shadow_as_grad =
- ((imgtype.image.arrayed && imgtype.image.dim == Dim2D) || imgtype.image.dim == DimCube) &&
- is_depth_image(imgtype, img) && args.lod != 0 && !args.base.is_fetch;
- if (args.dref)
- {
- forward = forward && should_forward(args.dref);
- // SPIR-V splits dref and coordinate.
- if (args.base.is_gather ||
- args.coord_components == 4) // GLSL also splits the arguments in two. Same for textureGather.
- {
- farg_str += ", ";
- farg_str += to_expression(args.coord);
- farg_str += ", ";
- farg_str += to_expression(args.dref);
- }
- else if (args.base.is_proj)
- {
- // Have to reshuffle so we get vec4(coord, dref, proj), special case.
- // Other shading languages splits up the arguments for coord and compare value like SPIR-V.
- // The coordinate type for textureProj shadow is always vec4 even for sampler1DShadow.
- farg_str += ", vec4(";
- if (imgtype.image.dim == Dim1D)
- {
- // Could reuse coord_expr, but we will mess up the temporary usage checking.
- farg_str += to_enclosed_expression(args.coord) + ".x";
- farg_str += ", ";
- farg_str += "0.0, ";
- farg_str += to_expression(args.dref);
- farg_str += ", ";
- farg_str += to_enclosed_expression(args.coord) + ".y)";
- }
- else if (imgtype.image.dim == Dim2D)
- {
- // Could reuse coord_expr, but we will mess up the temporary usage checking.
- farg_str += to_enclosed_expression(args.coord) + (swizz_func ? ".xy()" : ".xy");
- farg_str += ", ";
- farg_str += to_expression(args.dref);
- farg_str += ", ";
- farg_str += to_enclosed_expression(args.coord) + ".z)";
- }
- else
- SPIRV_CROSS_THROW("Invalid type for textureProj with shadow.");
- }
- else
- {
- // Create a composite which merges coord/dref into a single vector.
- auto type = expression_type(args.coord);
- type.vecsize = args.coord_components + 1;
- if (imgtype.image.dim == Dim1D && options.es)
- type.vecsize++;
- farg_str += ", ";
- farg_str += type_to_glsl_constructor(type);
- farg_str += "(";
- if (imgtype.image.dim == Dim1D && options.es)
- {
- if (imgtype.image.arrayed)
- {
- farg_str += enclose_expression(coord_expr) + ".x";
- farg_str += ", 0.0, ";
- farg_str += enclose_expression(coord_expr) + ".y";
- }
- else
- {
- farg_str += coord_expr;
- farg_str += ", 0.0";
- }
- }
- else
- farg_str += coord_expr;
- farg_str += ", ";
- farg_str += to_expression(args.dref);
- farg_str += ")";
- }
- }
- else
- {
- if (imgtype.image.dim == Dim1D && options.es)
- {
- // Have to fake a second coordinate.
- if (type_is_floating_point(coord_type))
- {
- // Cannot mix proj and array.
- if (imgtype.image.arrayed || args.base.is_proj)
- {
- coord_expr = join("vec3(", enclose_expression(coord_expr), ".x, 0.0, ",
- enclose_expression(coord_expr), ".y)");
- }
- else
- coord_expr = join("vec2(", coord_expr, ", 0.0)");
- }
- else
- {
- if (imgtype.image.arrayed)
- {
- coord_expr = join("ivec3(", enclose_expression(coord_expr),
- ".x, 0, ",
- enclose_expression(coord_expr), ".y)");
- }
- else
- coord_expr = join("ivec2(", coord_expr, ", 0)");
- }
- }
- farg_str += ", ";
- farg_str += coord_expr;
- }
- if (args.grad_x || args.grad_y)
- {
- forward = forward && should_forward(args.grad_x);
- forward = forward && should_forward(args.grad_y);
- farg_str += ", ";
- farg_str += to_expression(args.grad_x);
- farg_str += ", ";
- farg_str += to_expression(args.grad_y);
- }
- if (args.lod)
- {
- if (workaround_lod_array_shadow_as_grad)
- {
- // Implement textureGrad() instead. LOD == 0.0 is implemented as gradient of 0.0.
- // Implementing this as plain texture() is not safe on some implementations.
- if (imgtype.image.dim == Dim2D)
- farg_str += ", vec2(0.0), vec2(0.0)";
- else if (imgtype.image.dim == DimCube)
- farg_str += ", vec3(0.0), vec3(0.0)";
- }
- else
- {
- forward = forward && should_forward(args.lod);
- farg_str += ", ";
- // Lod expression for TexelFetch in GLSL must be int, and only int.
- if (args.base.is_fetch && imgtype.image.dim != DimBuffer && !imgtype.image.ms)
- farg_str += bitcast_expression(SPIRType::Int, args.lod);
- else
- farg_str += to_expression(args.lod);
- }
- }
- else if (args.base.is_fetch && imgtype.image.dim != DimBuffer && !imgtype.image.ms)
- {
- // Lod argument is optional in OpImageFetch, but we require a LOD value, pick 0 as the default.
- farg_str += ", 0";
- }
- if (args.offset)
- {
- forward = forward && should_forward(args.offset);
- farg_str += ", ";
- farg_str += bitcast_expression(SPIRType::Int, args.offset);
- }
- if (args.sample)
- {
- farg_str += ", ";
- farg_str += bitcast_expression(SPIRType::Int, args.sample);
- }
- if (args.min_lod)
- {
- farg_str += ", ";
- farg_str += to_expression(args.min_lod);
- }
- if (args.sparse_texel)
- {
- // Sparse texel output parameter comes after everything else, except it's before the optional, component/bias arguments.
- farg_str += ", ";
- farg_str += to_expression(args.sparse_texel);
- }
- if (args.bias)
- {
- forward = forward && should_forward(args.bias);
- farg_str += ", ";
- farg_str += to_expression(args.bias);
- }
- if (args.component && !expression_is_constant_null(args.component))
- {
- forward = forward && should_forward(args.component);
- farg_str += ", ";
- farg_str += bitcast_expression(SPIRType::Int, args.component);
- }
- *p_forward = forward;
- return farg_str;
- }
- Op CompilerGLSL::get_remapped_spirv_op(Op op) const
- {
- if (options.relax_nan_checks)
- {
- switch (op)
- {
- case OpFUnordLessThan:
- op = OpFOrdLessThan;
- break;
- case OpFUnordLessThanEqual:
- op = OpFOrdLessThanEqual;
- break;
- case OpFUnordGreaterThan:
- op = OpFOrdGreaterThan;
- break;
- case OpFUnordGreaterThanEqual:
- op = OpFOrdGreaterThanEqual;
- break;
- case OpFUnordEqual:
- op = OpFOrdEqual;
- break;
- case OpFOrdNotEqual:
- op = OpFUnordNotEqual;
- break;
- default:
- break;
- }
- }
- return op;
- }
- GLSLstd450 CompilerGLSL::get_remapped_glsl_op(GLSLstd450 std450_op) const
- {
- // Relax to non-NaN aware opcodes.
- if (options.relax_nan_checks)
- {
- switch (std450_op)
- {
- case GLSLstd450NClamp:
- std450_op = GLSLstd450FClamp;
- break;
- case GLSLstd450NMin:
- std450_op = GLSLstd450FMin;
- break;
- case GLSLstd450NMax:
- std450_op = GLSLstd450FMax;
- break;
- default:
- break;
- }
- }
- return std450_op;
- }
- void CompilerGLSL::emit_glsl_op(uint32_t result_type, uint32_t id, uint32_t eop, const uint32_t *args, uint32_t length)
- {
- auto op = static_cast<GLSLstd450>(eop);
- if (is_legacy() && is_unsigned_glsl_opcode(op))
- SPIRV_CROSS_THROW("Unsigned integers are not supported on legacy GLSL targets.");
- // If we need to do implicit bitcasts, make sure we do it with the correct type.
- uint32_t integer_width = get_integer_width_for_glsl_instruction(op, args, length);
- auto int_type = to_signed_basetype(integer_width);
- auto uint_type = to_unsigned_basetype(integer_width);
- op = get_remapped_glsl_op(op);
- switch (op)
- {
- // FP fiddling
- case GLSLstd450Round:
- if (!is_legacy())
- emit_unary_func_op(result_type, id, args[0], "round");
- else
- {
- auto op0 = to_enclosed_expression(args[0]);
- auto &op0_type = expression_type(args[0]);
- auto expr = join("floor(", op0, " + ", type_to_glsl_constructor(op0_type), "(0.5))");
- bool forward = should_forward(args[0]);
- emit_op(result_type, id, expr, forward);
- inherit_expression_dependencies(id, args[0]);
- }
- break;
- case GLSLstd450RoundEven:
- if (!is_legacy())
- emit_unary_func_op(result_type, id, args[0], "roundEven");
- else if (!options.es)
- {
- // This extension provides round() with round-to-even semantics.
- require_extension_internal("GL_EXT_gpu_shader4");
- emit_unary_func_op(result_type, id, args[0], "round");
- }
- else
- SPIRV_CROSS_THROW("roundEven supported only in ESSL 300.");
- break;
- case GLSLstd450Trunc:
- if (!is_legacy())
- emit_unary_func_op(result_type, id, args[0], "trunc");
- else
- {
- // Implement by value-casting to int and back.
- bool forward = should_forward(args[0]);
- auto op0 = to_unpacked_expression(args[0]);
- auto &op0_type = expression_type(args[0]);
- auto via_type = op0_type;
- via_type.basetype = SPIRType::Int;
- auto expr = join(type_to_glsl(op0_type), "(", type_to_glsl(via_type), "(", op0, "))");
- emit_op(result_type, id, expr, forward);
- inherit_expression_dependencies(id, args[0]);
- }
- break;
- case GLSLstd450SAbs:
- emit_unary_func_op_cast(result_type, id, args[0], "abs", int_type, int_type);
- break;
- case GLSLstd450FAbs:
- emit_unary_func_op(result_type, id, args[0], "abs");
- break;
- case GLSLstd450SSign:
- emit_unary_func_op_cast(result_type, id, args[0], "sign", int_type, int_type);
- break;
- case GLSLstd450FSign:
- emit_unary_func_op(result_type, id, args[0], "sign");
- break;
- case GLSLstd450Floor:
- emit_unary_func_op(result_type, id, args[0], "floor");
- break;
- case GLSLstd450Ceil:
- emit_unary_func_op(result_type, id, args[0], "ceil");
- break;
- case GLSLstd450Fract:
- emit_unary_func_op(result_type, id, args[0], "fract");
- break;
- case GLSLstd450Radians:
- emit_unary_func_op(result_type, id, args[0], "radians");
- break;
- case GLSLstd450Degrees:
- emit_unary_func_op(result_type, id, args[0], "degrees");
- break;
- case GLSLstd450Fma:
- if ((!options.es && options.version < 400) || (options.es && options.version < 320))
- {
- auto expr = join(to_enclosed_expression(args[0]), " * ", to_enclosed_expression(args[1]), " + ",
- to_enclosed_expression(args[2]));
- emit_op(result_type, id, expr,
- should_forward(args[0]) && should_forward(args[1]) && should_forward(args[2]));
- for (uint32_t i = 0; i < 3; i++)
- inherit_expression_dependencies(id, args[i]);
- }
- else
- emit_trinary_func_op(result_type, id, args[0], args[1], args[2], "fma");
- break;
- case GLSLstd450Modf:
- register_call_out_argument(args[1]);
- if (!is_legacy())
- {
- forced_temporaries.insert(id);
- emit_binary_func_op(result_type, id, args[0], args[1], "modf");
- }
- else
- {
- //NB. legacy GLSL doesn't have trunc() either, so we do a value cast
- auto &op1_type = expression_type(args[1]);
- auto via_type = op1_type;
- via_type.basetype = SPIRType::Int;
- statement(to_expression(args[1]), " = ",
- type_to_glsl(op1_type), "(", type_to_glsl(via_type),
- "(", to_expression(args[0]), "));");
- emit_binary_op(result_type, id, args[0], args[1], "-");
- }
- break;
- case GLSLstd450ModfStruct:
- {
- auto &type = get<SPIRType>(result_type);
- emit_uninitialized_temporary_expression(result_type, id);
- if (!is_legacy())
- {
- statement(to_expression(id), ".", to_member_name(type, 0), " = ", "modf(", to_expression(args[0]), ", ",
- to_expression(id), ".", to_member_name(type, 1), ");");
- }
- else
- {
- //NB. legacy GLSL doesn't have trunc() either, so we do a value cast
- auto &op0_type = expression_type(args[0]);
- auto via_type = op0_type;
- via_type.basetype = SPIRType::Int;
- statement(to_expression(id), ".", to_member_name(type, 1), " = ", type_to_glsl(op0_type),
- "(", type_to_glsl(via_type), "(", to_expression(args[0]), "));");
- statement(to_expression(id), ".", to_member_name(type, 0), " = ", to_enclosed_expression(args[0]), " - ",
- to_expression(id), ".", to_member_name(type, 1), ";");
- }
- break;
- }
- // Minmax
- case GLSLstd450UMin:
- emit_binary_func_op_cast(result_type, id, args[0], args[1], "min", uint_type, false);
- break;
- case GLSLstd450SMin:
- emit_binary_func_op_cast(result_type, id, args[0], args[1], "min", int_type, false);
- break;
- case GLSLstd450FMin:
- emit_binary_func_op(result_type, id, args[0], args[1], "min");
- break;
- case GLSLstd450FMax:
- emit_binary_func_op(result_type, id, args[0], args[1], "max");
- break;
- case GLSLstd450UMax:
- emit_binary_func_op_cast(result_type, id, args[0], args[1], "max", uint_type, false);
- break;
- case GLSLstd450SMax:
- emit_binary_func_op_cast(result_type, id, args[0], args[1], "max", int_type, false);
- break;
- case GLSLstd450FClamp:
- emit_trinary_func_op(result_type, id, args[0], args[1], args[2], "clamp");
- break;
- case GLSLstd450UClamp:
- emit_trinary_func_op_cast(result_type, id, args[0], args[1], args[2], "clamp", uint_type);
- break;
- case GLSLstd450SClamp:
- emit_trinary_func_op_cast(result_type, id, args[0], args[1], args[2], "clamp", int_type);
- break;
- // Trig
- case GLSLstd450Sin:
- emit_unary_func_op(result_type, id, args[0], "sin");
- break;
- case GLSLstd450Cos:
- emit_unary_func_op(result_type, id, args[0], "cos");
- break;
- case GLSLstd450Tan:
- emit_unary_func_op(result_type, id, args[0], "tan");
- break;
- case GLSLstd450Asin:
- emit_unary_func_op(result_type, id, args[0], "asin");
- break;
- case GLSLstd450Acos:
- emit_unary_func_op(result_type, id, args[0], "acos");
- break;
- case GLSLstd450Atan:
- emit_unary_func_op(result_type, id, args[0], "atan");
- break;
- case GLSLstd450Sinh:
- if (!is_legacy())
- emit_unary_func_op(result_type, id, args[0], "sinh");
- else
- {
- bool forward = should_forward(args[0]);
- auto expr = join("(exp(", to_expression(args[0]), ") - exp(-", to_enclosed_expression(args[0]), ")) * 0.5");
- emit_op(result_type, id, expr, forward);
- inherit_expression_dependencies(id, args[0]);
- }
- break;
- case GLSLstd450Cosh:
- if (!is_legacy())
- emit_unary_func_op(result_type, id, args[0], "cosh");
- else
- {
- bool forward = should_forward(args[0]);
- auto expr = join("(exp(", to_expression(args[0]), ") + exp(-", to_enclosed_expression(args[0]), ")) * 0.5");
- emit_op(result_type, id, expr, forward);
- inherit_expression_dependencies(id, args[0]);
- }
- break;
- case GLSLstd450Tanh:
- if (!is_legacy())
- emit_unary_func_op(result_type, id, args[0], "tanh");
- else
- {
- // Create temporaries to store the result of exp(arg) and exp(-arg).
- uint32_t &ids = extra_sub_expressions[id];
- if (!ids)
- {
- ids = ir.increase_bound_by(2);
- // Inherit precision qualifier (legacy has no NoContraction).
- if (has_decoration(id, DecorationRelaxedPrecision))
- {
- set_decoration(ids, DecorationRelaxedPrecision);
- set_decoration(ids + 1, DecorationRelaxedPrecision);
- }
- }
- uint32_t epos_id = ids;
- uint32_t eneg_id = ids + 1;
- emit_op(result_type, epos_id, join("exp(", to_expression(args[0]), ")"), false);
- emit_op(result_type, eneg_id, join("exp(-", to_enclosed_expression(args[0]), ")"), false);
- inherit_expression_dependencies(epos_id, args[0]);
- inherit_expression_dependencies(eneg_id, args[0]);
- auto expr = join("(", to_enclosed_expression(epos_id), " - ", to_enclosed_expression(eneg_id), ") / "
- "(", to_enclosed_expression(epos_id), " + ", to_enclosed_expression(eneg_id), ")");
- emit_op(result_type, id, expr, true);
- inherit_expression_dependencies(id, epos_id);
- inherit_expression_dependencies(id, eneg_id);
- }
- break;
- case GLSLstd450Asinh:
- if (!is_legacy())
- emit_unary_func_op(result_type, id, args[0], "asinh");
- else
- emit_emulated_ahyper_op(result_type, id, args[0], GLSLstd450Asinh);
- break;
- case GLSLstd450Acosh:
- if (!is_legacy())
- emit_unary_func_op(result_type, id, args[0], "acosh");
- else
- emit_emulated_ahyper_op(result_type, id, args[0], GLSLstd450Acosh);
- break;
- case GLSLstd450Atanh:
- if (!is_legacy())
- emit_unary_func_op(result_type, id, args[0], "atanh");
- else
- emit_emulated_ahyper_op(result_type, id, args[0], GLSLstd450Atanh);
- break;
- case GLSLstd450Atan2:
- emit_binary_func_op(result_type, id, args[0], args[1], "atan");
- break;
- // Exponentials
- case GLSLstd450Pow:
- emit_binary_func_op(result_type, id, args[0], args[1], "pow");
- break;
- case GLSLstd450Exp:
- emit_unary_func_op(result_type, id, args[0], "exp");
- break;
- case GLSLstd450Log:
- emit_unary_func_op(result_type, id, args[0], "log");
- break;
- case GLSLstd450Exp2:
- emit_unary_func_op(result_type, id, args[0], "exp2");
- break;
- case GLSLstd450Log2:
- emit_unary_func_op(result_type, id, args[0], "log2");
- break;
- case GLSLstd450Sqrt:
- emit_unary_func_op(result_type, id, args[0], "sqrt");
- break;
- case GLSLstd450InverseSqrt:
- emit_unary_func_op(result_type, id, args[0], "inversesqrt");
- break;
- // Matrix math
- case GLSLstd450Determinant:
- {
- // No need to transpose - it doesn't affect the determinant
- auto *e = maybe_get<SPIRExpression>(args[0]);
- bool old_transpose = e && e->need_transpose;
- if (old_transpose)
- e->need_transpose = false;
- if (options.version < 150) // also matches ES 100
- {
- auto &type = expression_type(args[0]);
- assert(type.vecsize >= 2 && type.vecsize <= 4);
- assert(type.vecsize == type.columns);
- // ARB_gpu_shader_fp64 needs GLSL 150, other types are not valid
- if (type.basetype != SPIRType::Float)
- SPIRV_CROSS_THROW("Unsupported type for matrix determinant");
- bool relaxed = has_decoration(id, DecorationRelaxedPrecision);
- require_polyfill(static_cast<Polyfill>(PolyfillDeterminant2x2 << (type.vecsize - 2)),
- relaxed);
- emit_unary_func_op(result_type, id, args[0],
- (options.es && relaxed) ? "spvDeterminantMP" : "spvDeterminant");
- }
- else
- emit_unary_func_op(result_type, id, args[0], "determinant");
- if (old_transpose)
- e->need_transpose = true;
- break;
- }
- case GLSLstd450MatrixInverse:
- {
- // The inverse of the transpose is the same as the transpose of
- // the inverse, so we can just flip need_transpose of the result.
- auto *a = maybe_get<SPIRExpression>(args[0]);
- bool old_transpose = a && a->need_transpose;
- if (old_transpose)
- a->need_transpose = false;
- const char *func = "inverse";
- if (options.version < 140) // also matches ES 100
- {
- auto &type = get<SPIRType>(result_type);
- assert(type.vecsize >= 2 && type.vecsize <= 4);
- assert(type.vecsize == type.columns);
- // ARB_gpu_shader_fp64 needs GLSL 150, other types are invalid
- if (type.basetype != SPIRType::Float)
- SPIRV_CROSS_THROW("Unsupported type for matrix inverse");
- bool relaxed = has_decoration(id, DecorationRelaxedPrecision);
- require_polyfill(static_cast<Polyfill>(PolyfillMatrixInverse2x2 << (type.vecsize - 2)),
- relaxed);
- func = (options.es && relaxed) ? "spvInverseMP" : "spvInverse";
- }
- bool forward = should_forward(args[0]);
- auto &e = emit_op(result_type, id, join(func, "(", to_unpacked_expression(args[0]), ")"), forward);
- inherit_expression_dependencies(id, args[0]);
- if (old_transpose)
- {
- e.need_transpose = true;
- a->need_transpose = true;
- }
- break;
- }
- // Lerping
- case GLSLstd450FMix:
- case GLSLstd450IMix:
- {
- emit_mix_op(result_type, id, args[0], args[1], args[2]);
- break;
- }
- case GLSLstd450Step:
- emit_binary_func_op(result_type, id, args[0], args[1], "step");
- break;
- case GLSLstd450SmoothStep:
- emit_trinary_func_op(result_type, id, args[0], args[1], args[2], "smoothstep");
- break;
- // Packing
- case GLSLstd450Frexp:
- register_call_out_argument(args[1]);
- forced_temporaries.insert(id);
- emit_binary_func_op(result_type, id, args[0], args[1], "frexp");
- break;
- case GLSLstd450FrexpStruct:
- {
- auto &type = get<SPIRType>(result_type);
- emit_uninitialized_temporary_expression(result_type, id);
- statement(to_expression(id), ".", to_member_name(type, 0), " = ", "frexp(", to_expression(args[0]), ", ",
- to_expression(id), ".", to_member_name(type, 1), ");");
- break;
- }
- case GLSLstd450Ldexp:
- {
- bool forward = should_forward(args[0]) && should_forward(args[1]);
- auto op0 = to_unpacked_expression(args[0]);
- auto op1 = to_unpacked_expression(args[1]);
- auto &op1_type = expression_type(args[1]);
- if (op1_type.basetype != SPIRType::Int)
- {
- // Need a value cast here.
- auto target_type = op1_type;
- target_type.basetype = SPIRType::Int;
- op1 = join(type_to_glsl_constructor(target_type), "(", op1, ")");
- }
- auto expr = join("ldexp(", op0, ", ", op1, ")");
- emit_op(result_type, id, expr, forward);
- inherit_expression_dependencies(id, args[0]);
- inherit_expression_dependencies(id, args[1]);
- break;
- }
- case GLSLstd450PackSnorm4x8:
- emit_unary_func_op(result_type, id, args[0], "packSnorm4x8");
- break;
- case GLSLstd450PackUnorm4x8:
- emit_unary_func_op(result_type, id, args[0], "packUnorm4x8");
- break;
- case GLSLstd450PackSnorm2x16:
- emit_unary_func_op(result_type, id, args[0], "packSnorm2x16");
- break;
- case GLSLstd450PackUnorm2x16:
- emit_unary_func_op(result_type, id, args[0], "packUnorm2x16");
- break;
- case GLSLstd450PackHalf2x16:
- emit_unary_func_op(result_type, id, args[0], "packHalf2x16");
- break;
- case GLSLstd450UnpackSnorm4x8:
- emit_unary_func_op(result_type, id, args[0], "unpackSnorm4x8");
- break;
- case GLSLstd450UnpackUnorm4x8:
- emit_unary_func_op(result_type, id, args[0], "unpackUnorm4x8");
- break;
- case GLSLstd450UnpackSnorm2x16:
- emit_unary_func_op(result_type, id, args[0], "unpackSnorm2x16");
- break;
- case GLSLstd450UnpackUnorm2x16:
- emit_unary_func_op(result_type, id, args[0], "unpackUnorm2x16");
- break;
- case GLSLstd450UnpackHalf2x16:
- emit_unary_func_op(result_type, id, args[0], "unpackHalf2x16");
- break;
- case GLSLstd450PackDouble2x32:
- emit_unary_func_op(result_type, id, args[0], "packDouble2x32");
- break;
- case GLSLstd450UnpackDouble2x32:
- emit_unary_func_op(result_type, id, args[0], "unpackDouble2x32");
- break;
- // Vector math
- case GLSLstd450Length:
- emit_unary_func_op(result_type, id, args[0], "length");
- break;
- case GLSLstd450Distance:
- emit_binary_func_op(result_type, id, args[0], args[1], "distance");
- break;
- case GLSLstd450Cross:
- emit_binary_func_op(result_type, id, args[0], args[1], "cross");
- break;
- case GLSLstd450Normalize:
- emit_unary_func_op(result_type, id, args[0], "normalize");
- break;
- case GLSLstd450FaceForward:
- emit_trinary_func_op(result_type, id, args[0], args[1], args[2], "faceforward");
- break;
- case GLSLstd450Reflect:
- emit_binary_func_op(result_type, id, args[0], args[1], "reflect");
- break;
- case GLSLstd450Refract:
- emit_trinary_func_op(result_type, id, args[0], args[1], args[2], "refract");
- break;
- // Bit-fiddling
- case GLSLstd450FindILsb:
- // findLSB always returns int.
- emit_unary_func_op_cast(result_type, id, args[0], "findLSB", expression_type(args[0]).basetype, int_type);
- break;
- case GLSLstd450FindSMsb:
- emit_unary_func_op_cast(result_type, id, args[0], "findMSB", int_type, int_type);
- break;
- case GLSLstd450FindUMsb:
- emit_unary_func_op_cast(result_type, id, args[0], "findMSB", uint_type,
- int_type); // findMSB always returns int.
- break;
- // Multisampled varying
- case GLSLstd450InterpolateAtCentroid:
- emit_unary_func_op(result_type, id, args[0], "interpolateAtCentroid");
- break;
- case GLSLstd450InterpolateAtSample:
- emit_binary_func_op(result_type, id, args[0], args[1], "interpolateAtSample");
- break;
- case GLSLstd450InterpolateAtOffset:
- emit_binary_func_op(result_type, id, args[0], args[1], "interpolateAtOffset");
- break;
- case GLSLstd450NMin:
- case GLSLstd450NMax:
- {
- if (options.vulkan_semantics)
- {
- require_extension_internal("GL_EXT_spirv_intrinsics");
- bool relaxed = has_decoration(id, DecorationRelaxedPrecision);
- Polyfill poly = {};
- switch (get<SPIRType>(result_type).width)
- {
- case 16:
- poly = op == GLSLstd450NMin ? PolyfillNMin16 : PolyfillNMax16;
- break;
- case 32:
- poly = op == GLSLstd450NMin ? PolyfillNMin32 : PolyfillNMax32;
- break;
- case 64:
- poly = op == GLSLstd450NMin ? PolyfillNMin64 : PolyfillNMax64;
- break;
- default:
- SPIRV_CROSS_THROW("Invalid bit width for NMin/NMax.");
- }
- require_polyfill(poly, relaxed);
- // Function return decorations are broken, so need to do double polyfill.
- if (relaxed)
- require_polyfill(poly, false);
- const char *op_str;
- if (relaxed)
- op_str = op == GLSLstd450NMin ? "spvNMinRelaxed" : "spvNMaxRelaxed";
- else
- op_str = op == GLSLstd450NMin ? "spvNMin" : "spvNMax";
- emit_binary_func_op(result_type, id, args[0], args[1], op_str);
- }
- else
- {
- emit_nminmax_op(result_type, id, args[0], args[1], op);
- }
- break;
- }
- case GLSLstd450NClamp:
- {
- if (options.vulkan_semantics)
- {
- require_extension_internal("GL_EXT_spirv_intrinsics");
- bool relaxed = has_decoration(id, DecorationRelaxedPrecision);
- Polyfill poly = {};
- switch (get<SPIRType>(result_type).width)
- {
- case 16:
- poly = PolyfillNClamp16;
- break;
- case 32:
- poly = PolyfillNClamp32;
- break;
- case 64:
- poly = PolyfillNClamp64;
- break;
- default:
- SPIRV_CROSS_THROW("Invalid bit width for NMin/NMax.");
- }
- require_polyfill(poly, relaxed);
- // Function return decorations are broken, so need to do double polyfill.
- if (relaxed)
- require_polyfill(poly, false);
- emit_trinary_func_op(result_type, id, args[0], args[1], args[2], relaxed ? "spvNClampRelaxed" : "spvNClamp");
- }
- else
- {
- // Make sure we have a unique ID here to avoid aliasing the extra sub-expressions between clamp and NMin sub-op.
- // IDs cannot exceed 24 bits, so we can make use of the higher bits for some unique flags.
- uint32_t &max_id = extra_sub_expressions[id | EXTRA_SUB_EXPRESSION_TYPE_AUX];
- if (!max_id)
- max_id = ir.increase_bound_by(1);
- // Inherit precision qualifiers.
- ir.meta[max_id] = ir.meta[id];
- emit_nminmax_op(result_type, max_id, args[0], args[1], GLSLstd450NMax);
- emit_nminmax_op(result_type, id, max_id, args[2], GLSLstd450NMin);
- }
- break;
- }
- default:
- statement("// unimplemented GLSL op ", eop);
- break;
- }
- }
- void CompilerGLSL::emit_nminmax_op(uint32_t result_type, uint32_t id, uint32_t op0, uint32_t op1, GLSLstd450 op)
- {
- // Need to emulate this call.
- uint32_t &ids = extra_sub_expressions[id];
- if (!ids)
- {
- ids = ir.increase_bound_by(5);
- auto btype = get<SPIRType>(result_type);
- btype.basetype = SPIRType::Boolean;
- set<SPIRType>(ids, btype);
- }
- uint32_t btype_id = ids + 0;
- uint32_t left_nan_id = ids + 1;
- uint32_t right_nan_id = ids + 2;
- uint32_t tmp_id = ids + 3;
- uint32_t mixed_first_id = ids + 4;
- // Inherit precision qualifiers.
- ir.meta[tmp_id] = ir.meta[id];
- ir.meta[mixed_first_id] = ir.meta[id];
- if (!is_legacy())
- {
- emit_unary_func_op(btype_id, left_nan_id, op0, "isnan");
- emit_unary_func_op(btype_id, right_nan_id, op1, "isnan");
- }
- else if (expression_type(op0).vecsize > 1)
- {
- // If the number doesn't equal itself, it must be NaN
- emit_binary_func_op(btype_id, left_nan_id, op0, op0, "notEqual");
- emit_binary_func_op(btype_id, right_nan_id, op1, op1, "notEqual");
- }
- else
- {
- emit_binary_op(btype_id, left_nan_id, op0, op0, "!=");
- emit_binary_op(btype_id, right_nan_id, op1, op1, "!=");
- }
- emit_binary_func_op(result_type, tmp_id, op0, op1, op == GLSLstd450NMin ? "min" : "max");
- emit_mix_op(result_type, mixed_first_id, tmp_id, op1, left_nan_id);
- emit_mix_op(result_type, id, mixed_first_id, op0, right_nan_id);
- }
- void CompilerGLSL::emit_emulated_ahyper_op(uint32_t result_type, uint32_t id, uint32_t op0, GLSLstd450 op)
- {
- const char *one = backend.float_literal_suffix ? "1.0f" : "1.0";
- std::string expr;
- bool forward = should_forward(op0);
- switch (op)
- {
- case GLSLstd450Asinh:
- expr = join("log(", to_enclosed_expression(op0), " + sqrt(",
- to_enclosed_expression(op0), " * ", to_enclosed_expression(op0), " + ", one, "))");
- emit_op(result_type, id, expr, forward);
- break;
- case GLSLstd450Acosh:
- expr = join("log(", to_enclosed_expression(op0), " + sqrt(",
- to_enclosed_expression(op0), " * ", to_enclosed_expression(op0), " - ", one, "))");
- break;
- case GLSLstd450Atanh:
- expr = join("log((", one, " + ", to_enclosed_expression(op0), ") / "
- "(", one, " - ", to_enclosed_expression(op0), ")) * 0.5",
- backend.float_literal_suffix ? "f" : "");
- break;
- default:
- SPIRV_CROSS_THROW("Invalid op.");
- }
- emit_op(result_type, id, expr, forward);
- inherit_expression_dependencies(id, op0);
- }
- void CompilerGLSL::emit_spv_amd_shader_ballot_op(uint32_t result_type, uint32_t id, uint32_t eop, const uint32_t *args,
- uint32_t)
- {
- require_extension_internal("GL_AMD_shader_ballot");
- enum AMDShaderBallot
- {
- SwizzleInvocationsAMD = 1,
- SwizzleInvocationsMaskedAMD = 2,
- WriteInvocationAMD = 3,
- MbcntAMD = 4
- };
- auto op = static_cast<AMDShaderBallot>(eop);
- switch (op)
- {
- case SwizzleInvocationsAMD:
- emit_binary_func_op(result_type, id, args[0], args[1], "swizzleInvocationsAMD");
- register_control_dependent_expression(id);
- break;
- case SwizzleInvocationsMaskedAMD:
- emit_binary_func_op(result_type, id, args[0], args[1], "swizzleInvocationsMaskedAMD");
- register_control_dependent_expression(id);
- break;
- case WriteInvocationAMD:
- emit_trinary_func_op(result_type, id, args[0], args[1], args[2], "writeInvocationAMD");
- register_control_dependent_expression(id);
- break;
- case MbcntAMD:
- emit_unary_func_op(result_type, id, args[0], "mbcntAMD");
- register_control_dependent_expression(id);
- break;
- default:
- statement("// unimplemented SPV AMD shader ballot op ", eop);
- break;
- }
- }
- void CompilerGLSL::emit_spv_amd_shader_explicit_vertex_parameter_op(uint32_t result_type, uint32_t id, uint32_t eop,
- const uint32_t *args, uint32_t)
- {
- require_extension_internal("GL_AMD_shader_explicit_vertex_parameter");
- enum AMDShaderExplicitVertexParameter
- {
- InterpolateAtVertexAMD = 1
- };
- auto op = static_cast<AMDShaderExplicitVertexParameter>(eop);
- switch (op)
- {
- case InterpolateAtVertexAMD:
- emit_binary_func_op(result_type, id, args[0], args[1], "interpolateAtVertexAMD");
- break;
- default:
- statement("// unimplemented SPV AMD shader explicit vertex parameter op ", eop);
- break;
- }
- }
- void CompilerGLSL::emit_spv_amd_shader_trinary_minmax_op(uint32_t result_type, uint32_t id, uint32_t eop,
- const uint32_t *args, uint32_t)
- {
- require_extension_internal("GL_AMD_shader_trinary_minmax");
- enum AMDShaderTrinaryMinMax
- {
- FMin3AMD = 1,
- UMin3AMD = 2,
- SMin3AMD = 3,
- FMax3AMD = 4,
- UMax3AMD = 5,
- SMax3AMD = 6,
- FMid3AMD = 7,
- UMid3AMD = 8,
- SMid3AMD = 9
- };
- auto op = static_cast<AMDShaderTrinaryMinMax>(eop);
- switch (op)
- {
- case FMin3AMD:
- case UMin3AMD:
- case SMin3AMD:
- emit_trinary_func_op(result_type, id, args[0], args[1], args[2], "min3");
- break;
- case FMax3AMD:
- case UMax3AMD:
- case SMax3AMD:
- emit_trinary_func_op(result_type, id, args[0], args[1], args[2], "max3");
- break;
- case FMid3AMD:
- case UMid3AMD:
- case SMid3AMD:
- emit_trinary_func_op(result_type, id, args[0], args[1], args[2], "mid3");
- break;
- default:
- statement("// unimplemented SPV AMD shader trinary minmax op ", eop);
- break;
- }
- }
- void CompilerGLSL::emit_spv_amd_gcn_shader_op(uint32_t result_type, uint32_t id, uint32_t eop, const uint32_t *args,
- uint32_t)
- {
- require_extension_internal("GL_AMD_gcn_shader");
- enum AMDGCNShader
- {
- CubeFaceIndexAMD = 1,
- CubeFaceCoordAMD = 2,
- TimeAMD = 3
- };
- auto op = static_cast<AMDGCNShader>(eop);
- switch (op)
- {
- case CubeFaceIndexAMD:
- emit_unary_func_op(result_type, id, args[0], "cubeFaceIndexAMD");
- break;
- case CubeFaceCoordAMD:
- emit_unary_func_op(result_type, id, args[0], "cubeFaceCoordAMD");
- break;
- case TimeAMD:
- {
- string expr = "timeAMD()";
- emit_op(result_type, id, expr, true);
- register_control_dependent_expression(id);
- break;
- }
- default:
- statement("// unimplemented SPV AMD gcn shader op ", eop);
- break;
- }
- }
- void CompilerGLSL::emit_subgroup_op(const Instruction &i)
- {
- const uint32_t *ops = stream(i);
- auto op = static_cast<Op>(i.op);
- if (!options.vulkan_semantics && !is_supported_subgroup_op_in_opengl(op, ops))
- SPIRV_CROSS_THROW("This subgroup operation is only supported in Vulkan semantics.");
- // If we need to do implicit bitcasts, make sure we do it with the correct type.
- uint32_t integer_width = get_integer_width_for_instruction(i);
- auto int_type = to_signed_basetype(integer_width);
- auto uint_type = to_unsigned_basetype(integer_width);
- if (options.vulkan_semantics)
- {
- auto &return_type = get<SPIRType>(ops[0]);
- switch (return_type.basetype)
- {
- case SPIRType::SByte:
- case SPIRType::UByte:
- require_extension_internal("GL_EXT_shader_subgroup_extended_types_int8");
- break;
- case SPIRType::Short:
- case SPIRType::UShort:
- require_extension_internal("GL_EXT_shader_subgroup_extended_types_int16");
- break;
- case SPIRType::Half:
- require_extension_internal("GL_EXT_shader_subgroup_extended_types_float16");
- break;
- case SPIRType::Int64:
- case SPIRType::UInt64:
- require_extension_internal("GL_EXT_shader_subgroup_extended_types_int64");
- break;
- default:
- break;
- }
- }
- switch (op)
- {
- case OpGroupNonUniformElect:
- request_subgroup_feature(ShaderSubgroupSupportHelper::SubgroupElect);
- break;
- case OpGroupNonUniformBallotBitCount:
- {
- const GroupOperation operation = static_cast<GroupOperation>(ops[3]);
- if (operation == GroupOperationReduce)
- request_subgroup_feature(ShaderSubgroupSupportHelper::SubgroupBallotBitCount);
- else if (operation == GroupOperationInclusiveScan || operation == GroupOperationExclusiveScan)
- request_subgroup_feature(ShaderSubgroupSupportHelper::SubgroupInverseBallot_InclBitCount_ExclBitCout);
- }
- break;
- case OpGroupNonUniformBallotBitExtract:
- request_subgroup_feature(ShaderSubgroupSupportHelper::SubgroupBallotBitExtract);
- break;
- case OpGroupNonUniformInverseBallot:
- request_subgroup_feature(ShaderSubgroupSupportHelper::SubgroupInverseBallot_InclBitCount_ExclBitCout);
- break;
- case OpGroupNonUniformBallot:
- request_subgroup_feature(ShaderSubgroupSupportHelper::SubgroupBallot);
- break;
- case OpGroupNonUniformBallotFindLSB:
- case OpGroupNonUniformBallotFindMSB:
- request_subgroup_feature(ShaderSubgroupSupportHelper::SubgroupBallotFindLSB_MSB);
- break;
- case OpGroupNonUniformBroadcast:
- case OpGroupNonUniformBroadcastFirst:
- request_subgroup_feature(ShaderSubgroupSupportHelper::SubgroupBroadcast_First);
- break;
- case OpGroupNonUniformShuffle:
- case OpGroupNonUniformShuffleXor:
- require_extension_internal("GL_KHR_shader_subgroup_shuffle");
- break;
- case OpGroupNonUniformShuffleUp:
- case OpGroupNonUniformShuffleDown:
- require_extension_internal("GL_KHR_shader_subgroup_shuffle_relative");
- break;
- case OpGroupNonUniformRotateKHR:
- require_extension_internal("GL_KHR_shader_subgroup_rotate");
- break;
- case OpGroupNonUniformAll:
- case OpGroupNonUniformAny:
- case OpGroupNonUniformAllEqual:
- {
- const SPIRType &type = expression_type(ops[3]);
- if (type.basetype == SPIRType::BaseType::Boolean && type.vecsize == 1u)
- request_subgroup_feature(ShaderSubgroupSupportHelper::SubgroupAll_Any_AllEqualBool);
- else
- request_subgroup_feature(ShaderSubgroupSupportHelper::SubgroupAllEqualT);
- }
- break;
- // clang-format off
- #define GLSL_GROUP_OP(OP)\
- case OpGroupNonUniform##OP:\
- {\
- auto operation = static_cast<GroupOperation>(ops[3]);\
- if (operation == GroupOperationClusteredReduce)\
- require_extension_internal("GL_KHR_shader_subgroup_clustered");\
- else if (operation == GroupOperationReduce)\
- request_subgroup_feature(ShaderSubgroupSupportHelper::SubgroupArithmetic##OP##Reduce);\
- else if (operation == GroupOperationExclusiveScan)\
- request_subgroup_feature(ShaderSubgroupSupportHelper::SubgroupArithmetic##OP##ExclusiveScan);\
- else if (operation == GroupOperationInclusiveScan)\
- request_subgroup_feature(ShaderSubgroupSupportHelper::SubgroupArithmetic##OP##InclusiveScan);\
- else\
- SPIRV_CROSS_THROW("Invalid group operation.");\
- break;\
- }
- GLSL_GROUP_OP(IAdd)
- GLSL_GROUP_OP(FAdd)
- GLSL_GROUP_OP(IMul)
- GLSL_GROUP_OP(FMul)
- #undef GLSL_GROUP_OP
- // clang-format on
- case OpGroupNonUniformFMin:
- case OpGroupNonUniformFMax:
- case OpGroupNonUniformSMin:
- case OpGroupNonUniformSMax:
- case OpGroupNonUniformUMin:
- case OpGroupNonUniformUMax:
- case OpGroupNonUniformBitwiseAnd:
- case OpGroupNonUniformBitwiseOr:
- case OpGroupNonUniformBitwiseXor:
- case OpGroupNonUniformLogicalAnd:
- case OpGroupNonUniformLogicalOr:
- case OpGroupNonUniformLogicalXor:
- {
- auto operation = static_cast<GroupOperation>(ops[3]);
- if (operation == GroupOperationClusteredReduce)
- {
- require_extension_internal("GL_KHR_shader_subgroup_clustered");
- }
- else if (operation == GroupOperationExclusiveScan || operation == GroupOperationInclusiveScan ||
- operation == GroupOperationReduce)
- {
- require_extension_internal("GL_KHR_shader_subgroup_arithmetic");
- }
- else
- SPIRV_CROSS_THROW("Invalid group operation.");
- break;
- }
- case OpGroupNonUniformQuadSwap:
- case OpGroupNonUniformQuadBroadcast:
- require_extension_internal("GL_KHR_shader_subgroup_quad");
- break;
- case OpGroupNonUniformQuadAllKHR:
- case OpGroupNonUniformQuadAnyKHR:
- // Require both extensions to be enabled.
- require_extension_internal("GL_KHR_shader_subgroup_vote");
- require_extension_internal("GL_EXT_shader_quad_control");
- break;
- default:
- SPIRV_CROSS_THROW("Invalid opcode for subgroup.");
- }
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- // These quad ops do not have a scope parameter.
- if (op != OpGroupNonUniformQuadAllKHR && op != OpGroupNonUniformQuadAnyKHR)
- {
- auto scope = static_cast<Scope>(evaluate_constant_u32(ops[2]));
- if (scope != ScopeSubgroup)
- SPIRV_CROSS_THROW("Only subgroup scope is supported.");
- }
- switch (op)
- {
- case OpGroupNonUniformElect:
- emit_op(result_type, id, "subgroupElect()", true);
- break;
- case OpGroupNonUniformBroadcast:
- emit_binary_func_op(result_type, id, ops[3], ops[4], "subgroupBroadcast");
- break;
- case OpGroupNonUniformBroadcastFirst:
- emit_unary_func_op(result_type, id, ops[3], "subgroupBroadcastFirst");
- break;
- case OpGroupNonUniformBallot:
- emit_unary_func_op(result_type, id, ops[3], "subgroupBallot");
- break;
- case OpGroupNonUniformInverseBallot:
- emit_unary_func_op(result_type, id, ops[3], "subgroupInverseBallot");
- break;
- case OpGroupNonUniformBallotBitExtract:
- emit_binary_func_op(result_type, id, ops[3], ops[4], "subgroupBallotBitExtract");
- break;
- case OpGroupNonUniformBallotFindLSB:
- emit_unary_func_op(result_type, id, ops[3], "subgroupBallotFindLSB");
- break;
- case OpGroupNonUniformBallotFindMSB:
- emit_unary_func_op(result_type, id, ops[3], "subgroupBallotFindMSB");
- break;
- case OpGroupNonUniformBallotBitCount:
- {
- auto operation = static_cast<GroupOperation>(ops[3]);
- if (operation == GroupOperationReduce)
- emit_unary_func_op(result_type, id, ops[4], "subgroupBallotBitCount");
- else if (operation == GroupOperationInclusiveScan)
- emit_unary_func_op(result_type, id, ops[4], "subgroupBallotInclusiveBitCount");
- else if (operation == GroupOperationExclusiveScan)
- emit_unary_func_op(result_type, id, ops[4], "subgroupBallotExclusiveBitCount");
- else
- SPIRV_CROSS_THROW("Invalid BitCount operation.");
- break;
- }
- case OpGroupNonUniformShuffle:
- emit_binary_func_op(result_type, id, ops[3], ops[4], "subgroupShuffle");
- break;
- case OpGroupNonUniformShuffleXor:
- emit_binary_func_op(result_type, id, ops[3], ops[4], "subgroupShuffleXor");
- break;
- case OpGroupNonUniformShuffleUp:
- emit_binary_func_op(result_type, id, ops[3], ops[4], "subgroupShuffleUp");
- break;
- case OpGroupNonUniformShuffleDown:
- emit_binary_func_op(result_type, id, ops[3], ops[4], "subgroupShuffleDown");
- break;
- case OpGroupNonUniformRotateKHR:
- if (i.length > 5)
- emit_trinary_func_op(result_type, id, ops[3], ops[4], ops[5], "subgroupClusteredRotate");
- else
- emit_binary_func_op(result_type, id, ops[3], ops[4], "subgroupRotate");
- break;
- case OpGroupNonUniformAll:
- emit_unary_func_op(result_type, id, ops[3], "subgroupAll");
- break;
- case OpGroupNonUniformAny:
- emit_unary_func_op(result_type, id, ops[3], "subgroupAny");
- break;
- case OpGroupNonUniformAllEqual:
- emit_unary_func_op(result_type, id, ops[3], "subgroupAllEqual");
- break;
- // clang-format off
- #define GLSL_GROUP_OP(op, glsl_op) \
- case OpGroupNonUniform##op: \
- { \
- auto operation = static_cast<GroupOperation>(ops[3]); \
- if (operation == GroupOperationReduce) \
- emit_unary_func_op(result_type, id, ops[4], "subgroup" #glsl_op); \
- else if (operation == GroupOperationInclusiveScan) \
- emit_unary_func_op(result_type, id, ops[4], "subgroupInclusive" #glsl_op); \
- else if (operation == GroupOperationExclusiveScan) \
- emit_unary_func_op(result_type, id, ops[4], "subgroupExclusive" #glsl_op); \
- else if (operation == GroupOperationClusteredReduce) \
- emit_binary_func_op(result_type, id, ops[4], ops[5], "subgroupClustered" #glsl_op); \
- else \
- SPIRV_CROSS_THROW("Invalid group operation."); \
- break; \
- }
- #define GLSL_GROUP_OP_CAST(op, glsl_op, type) \
- case OpGroupNonUniform##op: \
- { \
- auto operation = static_cast<GroupOperation>(ops[3]); \
- if (operation == GroupOperationReduce) \
- emit_unary_func_op_cast(result_type, id, ops[4], "subgroup" #glsl_op, type, type); \
- else if (operation == GroupOperationInclusiveScan) \
- emit_unary_func_op_cast(result_type, id, ops[4], "subgroupInclusive" #glsl_op, type, type); \
- else if (operation == GroupOperationExclusiveScan) \
- emit_unary_func_op_cast(result_type, id, ops[4], "subgroupExclusive" #glsl_op, type, type); \
- else if (operation == GroupOperationClusteredReduce) \
- emit_binary_func_op_cast_clustered(result_type, id, ops[4], ops[5], "subgroupClustered" #glsl_op, type); \
- else \
- SPIRV_CROSS_THROW("Invalid group operation."); \
- break; \
- }
- GLSL_GROUP_OP(FAdd, Add)
- GLSL_GROUP_OP(FMul, Mul)
- GLSL_GROUP_OP(FMin, Min)
- GLSL_GROUP_OP(FMax, Max)
- GLSL_GROUP_OP(IAdd, Add)
- GLSL_GROUP_OP(IMul, Mul)
- GLSL_GROUP_OP_CAST(SMin, Min, int_type)
- GLSL_GROUP_OP_CAST(SMax, Max, int_type)
- GLSL_GROUP_OP_CAST(UMin, Min, uint_type)
- GLSL_GROUP_OP_CAST(UMax, Max, uint_type)
- GLSL_GROUP_OP(BitwiseAnd, And)
- GLSL_GROUP_OP(BitwiseOr, Or)
- GLSL_GROUP_OP(BitwiseXor, Xor)
- GLSL_GROUP_OP(LogicalAnd, And)
- GLSL_GROUP_OP(LogicalOr, Or)
- GLSL_GROUP_OP(LogicalXor, Xor)
- #undef GLSL_GROUP_OP
- #undef GLSL_GROUP_OP_CAST
- // clang-format on
- case OpGroupNonUniformQuadSwap:
- {
- uint32_t direction = evaluate_constant_u32(ops[4]);
- if (direction == 0)
- emit_unary_func_op(result_type, id, ops[3], "subgroupQuadSwapHorizontal");
- else if (direction == 1)
- emit_unary_func_op(result_type, id, ops[3], "subgroupQuadSwapVertical");
- else if (direction == 2)
- emit_unary_func_op(result_type, id, ops[3], "subgroupQuadSwapDiagonal");
- else
- SPIRV_CROSS_THROW("Invalid quad swap direction.");
- break;
- }
- case OpGroupNonUniformQuadBroadcast:
- {
- emit_binary_func_op(result_type, id, ops[3], ops[4], "subgroupQuadBroadcast");
- break;
- }
- case OpGroupNonUniformQuadAllKHR:
- emit_unary_func_op(result_type, id, ops[2], "subgroupQuadAll");
- break;
- case OpGroupNonUniformQuadAnyKHR:
- emit_unary_func_op(result_type, id, ops[2], "subgroupQuadAny");
- break;
- default:
- SPIRV_CROSS_THROW("Invalid opcode for subgroup.");
- }
- register_control_dependent_expression(id);
- }
- string CompilerGLSL::bitcast_glsl_op(const SPIRType &out_type, const SPIRType &in_type)
- {
- // OpBitcast can deal with pointers.
- if (out_type.pointer || in_type.pointer)
- {
- if (out_type.vecsize == 2 || in_type.vecsize == 2)
- require_extension_internal("GL_EXT_buffer_reference_uvec2");
- return type_to_glsl(out_type);
- }
- if (out_type.basetype == in_type.basetype)
- return "";
- assert(out_type.basetype != SPIRType::Boolean);
- assert(in_type.basetype != SPIRType::Boolean);
- bool integral_cast = type_is_integral(out_type) && type_is_integral(in_type);
- bool same_size_cast = out_type.width == in_type.width;
- // Trivial bitcast case, casts between integers.
- if (integral_cast && same_size_cast)
- return type_to_glsl(out_type);
- // Catch-all 8-bit arithmetic casts (GL_EXT_shader_explicit_arithmetic_types).
- if (out_type.width == 8 && in_type.width >= 16 && integral_cast && in_type.vecsize == 1)
- return "unpack8";
- else if (in_type.width == 8 && out_type.width == 16 && integral_cast && out_type.vecsize == 1)
- return "pack16";
- else if (in_type.width == 8 && out_type.width == 32 && integral_cast && out_type.vecsize == 1)
- return "pack32";
- // Floating <-> Integer special casts. Just have to enumerate all cases. :(
- // 16-bit, 32-bit and 64-bit floats.
- if (out_type.basetype == SPIRType::UInt && in_type.basetype == SPIRType::Float)
- {
- if (is_legacy_es())
- SPIRV_CROSS_THROW("Float -> Uint bitcast not supported on legacy ESSL.");
- else if (!options.es && options.version < 330)
- require_extension_internal("GL_ARB_shader_bit_encoding");
- return "floatBitsToUint";
- }
- else if (out_type.basetype == SPIRType::Int && in_type.basetype == SPIRType::Float)
- {
- if (is_legacy_es())
- SPIRV_CROSS_THROW("Float -> Int bitcast not supported on legacy ESSL.");
- else if (!options.es && options.version < 330)
- require_extension_internal("GL_ARB_shader_bit_encoding");
- return "floatBitsToInt";
- }
- else if (out_type.basetype == SPIRType::Float && in_type.basetype == SPIRType::UInt)
- {
- if (is_legacy_es())
- SPIRV_CROSS_THROW("Uint -> Float bitcast not supported on legacy ESSL.");
- else if (!options.es && options.version < 330)
- require_extension_internal("GL_ARB_shader_bit_encoding");
- return "uintBitsToFloat";
- }
- else if (out_type.basetype == SPIRType::Float && in_type.basetype == SPIRType::Int)
- {
- if (is_legacy_es())
- SPIRV_CROSS_THROW("Int -> Float bitcast not supported on legacy ESSL.");
- else if (!options.es && options.version < 330)
- require_extension_internal("GL_ARB_shader_bit_encoding");
- return "intBitsToFloat";
- }
- else if (out_type.basetype == SPIRType::Int64 && in_type.basetype == SPIRType::Double)
- return "doubleBitsToInt64";
- else if (out_type.basetype == SPIRType::UInt64 && in_type.basetype == SPIRType::Double)
- return "doubleBitsToUint64";
- else if (out_type.basetype == SPIRType::Double && in_type.basetype == SPIRType::Int64)
- return "int64BitsToDouble";
- else if (out_type.basetype == SPIRType::Double && in_type.basetype == SPIRType::UInt64)
- return "uint64BitsToDouble";
- else if (out_type.basetype == SPIRType::Short && in_type.basetype == SPIRType::Half)
- return "float16BitsToInt16";
- else if (out_type.basetype == SPIRType::UShort && in_type.basetype == SPIRType::Half)
- return "float16BitsToUint16";
- else if (out_type.basetype == SPIRType::Half && in_type.basetype == SPIRType::Short)
- return "int16BitsToFloat16";
- else if (out_type.basetype == SPIRType::Half && in_type.basetype == SPIRType::UShort)
- return "uint16BitsToFloat16";
- // And finally, some even more special purpose casts.
- if (out_type.basetype == SPIRType::UInt64 && in_type.basetype == SPIRType::UInt && in_type.vecsize == 2)
- return "packUint2x32";
- else if (out_type.basetype == SPIRType::UInt && in_type.basetype == SPIRType::UInt64 && out_type.vecsize == 2)
- return "unpackUint2x32";
- else if (out_type.basetype == SPIRType::Half && in_type.basetype == SPIRType::UInt && in_type.vecsize == 1)
- return "unpackFloat2x16";
- else if (out_type.basetype == SPIRType::UInt && in_type.basetype == SPIRType::Half && in_type.vecsize == 2)
- return "packFloat2x16";
- else if (out_type.basetype == SPIRType::Int && in_type.basetype == SPIRType::Short && in_type.vecsize == 2)
- return "packInt2x16";
- else if (out_type.basetype == SPIRType::Short && in_type.basetype == SPIRType::Int && in_type.vecsize == 1)
- return "unpackInt2x16";
- else if (out_type.basetype == SPIRType::UInt && in_type.basetype == SPIRType::UShort && in_type.vecsize == 2)
- return "packUint2x16";
- else if (out_type.basetype == SPIRType::UShort && in_type.basetype == SPIRType::UInt && in_type.vecsize == 1)
- return "unpackUint2x16";
- else if (out_type.basetype == SPIRType::Int64 && in_type.basetype == SPIRType::Short && in_type.vecsize == 4)
- return "packInt4x16";
- else if (out_type.basetype == SPIRType::Short && in_type.basetype == SPIRType::Int64 && in_type.vecsize == 1)
- return "unpackInt4x16";
- else if (out_type.basetype == SPIRType::UInt64 && in_type.basetype == SPIRType::UShort && in_type.vecsize == 4)
- return "packUint4x16";
- else if (out_type.basetype == SPIRType::UShort && in_type.basetype == SPIRType::UInt64 && in_type.vecsize == 1)
- return "unpackUint4x16";
- else if (out_type.basetype == SPIRType::BFloat16 && in_type.basetype == SPIRType::UShort)
- return "uintBitsToBFloat16EXT";
- else if (out_type.basetype == SPIRType::BFloat16 && in_type.basetype == SPIRType::Short)
- return "intBitsToBFloat16EXT";
- else if (out_type.basetype == SPIRType::UShort && in_type.basetype == SPIRType::BFloat16)
- return "bfloat16BitsToUintEXT";
- else if (out_type.basetype == SPIRType::Short && in_type.basetype == SPIRType::BFloat16)
- return "bfloat16BitsToIntEXT";
- else if (out_type.basetype == SPIRType::FloatE4M3 && in_type.basetype == SPIRType::UByte)
- return "uintBitsToFloate4m3EXT";
- else if (out_type.basetype == SPIRType::FloatE4M3 && in_type.basetype == SPIRType::SByte)
- return "intBitsToFloate4m3EXT";
- else if (out_type.basetype == SPIRType::UByte && in_type.basetype == SPIRType::FloatE4M3)
- return "floate4m3BitsToUintEXT";
- else if (out_type.basetype == SPIRType::SByte && in_type.basetype == SPIRType::FloatE4M3)
- return "floate4m3BitsToIntEXT";
- else if (out_type.basetype == SPIRType::FloatE5M2 && in_type.basetype == SPIRType::UByte)
- return "uintBitsToFloate5m2EXT";
- else if (out_type.basetype == SPIRType::FloatE5M2 && in_type.basetype == SPIRType::SByte)
- return "intBitsToFloate5m2EXT";
- else if (out_type.basetype == SPIRType::UByte && in_type.basetype == SPIRType::FloatE5M2)
- return "floate5m2BitsToUintEXT";
- else if (out_type.basetype == SPIRType::SByte && in_type.basetype == SPIRType::FloatE5M2)
- return "floate5m2BitsToIntEXT";
- return "";
- }
- string CompilerGLSL::bitcast_glsl(const SPIRType &result_type, uint32_t argument)
- {
- auto op = bitcast_glsl_op(result_type, expression_type(argument));
- if (op.empty())
- return to_enclosed_unpacked_expression(argument);
- else
- return join(op, "(", to_unpacked_expression(argument), ")");
- }
- std::string CompilerGLSL::bitcast_expression(SPIRType::BaseType target_type, uint32_t arg)
- {
- auto expr = to_expression(arg);
- auto &src_type = expression_type(arg);
- if (src_type.basetype != target_type)
- {
- auto target = src_type;
- target.basetype = target_type;
- expr = join(bitcast_glsl_op(target, src_type), "(", expr, ")");
- }
- return expr;
- }
- std::string CompilerGLSL::bitcast_expression(const SPIRType &target_type, SPIRType::BaseType expr_type,
- const std::string &expr)
- {
- if (target_type.basetype == expr_type)
- return expr;
- auto src_type = target_type;
- src_type.basetype = expr_type;
- return join(bitcast_glsl_op(target_type, src_type), "(", expr, ")");
- }
- string CompilerGLSL::builtin_to_glsl(BuiltIn builtin, StorageClass storage)
- {
- switch (builtin)
- {
- case BuiltInPosition:
- return "gl_Position";
- case BuiltInPointSize:
- return "gl_PointSize";
- case BuiltInClipDistance:
- {
- if (options.es)
- require_extension_internal("GL_EXT_clip_cull_distance");
- return "gl_ClipDistance";
- }
- case BuiltInCullDistance:
- {
- if (options.es)
- require_extension_internal("GL_EXT_clip_cull_distance");
- return "gl_CullDistance";
- }
- case BuiltInVertexId:
- if (options.vulkan_semantics)
- SPIRV_CROSS_THROW("Cannot implement gl_VertexID in Vulkan GLSL. This shader was created "
- "with GL semantics.");
- return "gl_VertexID";
- case BuiltInInstanceId:
- if (options.vulkan_semantics)
- {
- auto model = get_entry_point().model;
- switch (model)
- {
- case ExecutionModelIntersectionKHR:
- case ExecutionModelAnyHitKHR:
- case ExecutionModelClosestHitKHR:
- // gl_InstanceID is allowed in these shaders.
- break;
- default:
- SPIRV_CROSS_THROW("Cannot implement gl_InstanceID in Vulkan GLSL. This shader was "
- "created with GL semantics.");
- }
- }
- if (!options.es && options.version < 140)
- {
- require_extension_internal("GL_ARB_draw_instanced");
- }
- return "gl_InstanceID";
- case BuiltInVertexIndex:
- if (options.vulkan_semantics)
- return "gl_VertexIndex";
- else
- return "gl_VertexID"; // gl_VertexID already has the base offset applied.
- case BuiltInInstanceIndex:
- if (options.vulkan_semantics)
- return "gl_InstanceIndex";
- if (!options.es && options.version < 140)
- {
- require_extension_internal("GL_ARB_draw_instanced");
- }
- if (options.vertex.support_nonzero_base_instance)
- {
- if (!options.vulkan_semantics)
- {
- // This is a soft-enable. We will opt-in to using gl_BaseInstanceARB if supported.
- require_extension_internal("GL_ARB_shader_draw_parameters");
- }
- return "(gl_InstanceID + SPIRV_Cross_BaseInstance)"; // ... but not gl_InstanceID.
- }
- else
- return "gl_InstanceID";
- case BuiltInPrimitiveId:
- if (storage == StorageClassInput && get_entry_point().model == ExecutionModelGeometry)
- return "gl_PrimitiveIDIn";
- else
- return "gl_PrimitiveID";
- case BuiltInInvocationId:
- return "gl_InvocationID";
- case BuiltInLayer:
- {
- auto model = get_execution_model();
- if (model == ExecutionModelVertex || model == ExecutionModelTessellationEvaluation)
- {
- if (options.es)
- require_extension_internal("GL_NV_viewport_array2");
- else
- require_extension_internal("GL_ARB_shader_viewport_layer_array");
- }
- return "gl_Layer";
- }
- case BuiltInViewportIndex:
- return "gl_ViewportIndex";
- case BuiltInTessLevelOuter:
- return "gl_TessLevelOuter";
- case BuiltInTessLevelInner:
- return "gl_TessLevelInner";
- case BuiltInTessCoord:
- return "gl_TessCoord";
- case BuiltInPatchVertices:
- return "gl_PatchVerticesIn";
- case BuiltInFragCoord:
- return "gl_FragCoord";
- case BuiltInPointCoord:
- return "gl_PointCoord";
- case BuiltInFrontFacing:
- return "gl_FrontFacing";
- case BuiltInFragDepth:
- return "gl_FragDepth";
- case BuiltInNumWorkgroups:
- return "gl_NumWorkGroups";
- case BuiltInWorkgroupSize:
- return "gl_WorkGroupSize";
- case BuiltInWorkgroupId:
- return "gl_WorkGroupID";
- case BuiltInLocalInvocationId:
- return "gl_LocalInvocationID";
- case BuiltInGlobalInvocationId:
- return "gl_GlobalInvocationID";
- case BuiltInLocalInvocationIndex:
- return "gl_LocalInvocationIndex";
- case BuiltInHelperInvocation:
- return "gl_HelperInvocation";
- case BuiltInBaseVertex:
- if (options.es)
- SPIRV_CROSS_THROW("BaseVertex not supported in ES profile.");
- if (options.vulkan_semantics)
- {
- if (options.version < 460)
- {
- require_extension_internal("GL_ARB_shader_draw_parameters");
- return "gl_BaseVertexARB";
- }
- return "gl_BaseVertex";
- }
- // On regular GL, this is soft-enabled and we emit ifdefs in code.
- require_extension_internal("GL_ARB_shader_draw_parameters");
- return "SPIRV_Cross_BaseVertex";
- case BuiltInBaseInstance:
- if (options.es)
- SPIRV_CROSS_THROW("BaseInstance not supported in ES profile.");
- if (options.vulkan_semantics)
- {
- if (options.version < 460)
- {
- require_extension_internal("GL_ARB_shader_draw_parameters");
- return "gl_BaseInstanceARB";
- }
- return "gl_BaseInstance";
- }
- // On regular GL, this is soft-enabled and we emit ifdefs in code.
- require_extension_internal("GL_ARB_shader_draw_parameters");
- return "SPIRV_Cross_BaseInstance";
- case BuiltInDrawIndex:
- if (options.es)
- SPIRV_CROSS_THROW("DrawIndex not supported in ES profile.");
- if (options.vulkan_semantics)
- {
- if (options.version < 460)
- {
- require_extension_internal("GL_ARB_shader_draw_parameters");
- return "gl_DrawIDARB";
- }
- return "gl_DrawID";
- }
- // On regular GL, this is soft-enabled and we emit ifdefs in code.
- require_extension_internal("GL_ARB_shader_draw_parameters");
- return "gl_DrawIDARB";
- case BuiltInSampleId:
- if (is_legacy())
- SPIRV_CROSS_THROW("Sample variables not supported in legacy GLSL.");
- else if (options.es && options.version < 320)
- require_extension_internal("GL_OES_sample_variables");
- else if (!options.es && options.version < 400)
- require_extension_internal("GL_ARB_sample_shading");
- return "gl_SampleID";
- case BuiltInSampleMask:
- if (is_legacy())
- SPIRV_CROSS_THROW("Sample variables not supported in legacy GLSL.");
- else if (options.es && options.version < 320)
- require_extension_internal("GL_OES_sample_variables");
- else if (!options.es && options.version < 400)
- require_extension_internal("GL_ARB_sample_shading");
- if (storage == StorageClassInput)
- return "gl_SampleMaskIn";
- else
- return "gl_SampleMask";
- case BuiltInSamplePosition:
- if (is_legacy())
- SPIRV_CROSS_THROW("Sample variables not supported in legacy GLSL.");
- else if (options.es && options.version < 320)
- require_extension_internal("GL_OES_sample_variables");
- else if (!options.es && options.version < 400)
- require_extension_internal("GL_ARB_sample_shading");
- return "gl_SamplePosition";
- case BuiltInViewIndex:
- if (options.vulkan_semantics)
- return "gl_ViewIndex";
- else
- return "gl_ViewID_OVR";
- case BuiltInNumSubgroups:
- request_subgroup_feature(ShaderSubgroupSupportHelper::NumSubgroups);
- return "gl_NumSubgroups";
- case BuiltInSubgroupId:
- request_subgroup_feature(ShaderSubgroupSupportHelper::SubgroupID);
- return "gl_SubgroupID";
- case BuiltInSubgroupSize:
- request_subgroup_feature(ShaderSubgroupSupportHelper::SubgroupSize);
- return "gl_SubgroupSize";
- case BuiltInSubgroupLocalInvocationId:
- request_subgroup_feature(ShaderSubgroupSupportHelper::SubgroupInvocationID);
- return "gl_SubgroupInvocationID";
- case BuiltInSubgroupEqMask:
- request_subgroup_feature(ShaderSubgroupSupportHelper::SubgroupMask);
- return "gl_SubgroupEqMask";
- case BuiltInSubgroupGeMask:
- request_subgroup_feature(ShaderSubgroupSupportHelper::SubgroupMask);
- return "gl_SubgroupGeMask";
- case BuiltInSubgroupGtMask:
- request_subgroup_feature(ShaderSubgroupSupportHelper::SubgroupMask);
- return "gl_SubgroupGtMask";
- case BuiltInSubgroupLeMask:
- request_subgroup_feature(ShaderSubgroupSupportHelper::SubgroupMask);
- return "gl_SubgroupLeMask";
- case BuiltInSubgroupLtMask:
- request_subgroup_feature(ShaderSubgroupSupportHelper::SubgroupMask);
- return "gl_SubgroupLtMask";
- case BuiltInLaunchIdKHR:
- return ray_tracing_is_khr ? "gl_LaunchIDEXT" : "gl_LaunchIDNV";
- case BuiltInLaunchSizeKHR:
- return ray_tracing_is_khr ? "gl_LaunchSizeEXT" : "gl_LaunchSizeNV";
- case BuiltInWorldRayOriginKHR:
- return ray_tracing_is_khr ? "gl_WorldRayOriginEXT" : "gl_WorldRayOriginNV";
- case BuiltInWorldRayDirectionKHR:
- return ray_tracing_is_khr ? "gl_WorldRayDirectionEXT" : "gl_WorldRayDirectionNV";
- case BuiltInObjectRayOriginKHR:
- return ray_tracing_is_khr ? "gl_ObjectRayOriginEXT" : "gl_ObjectRayOriginNV";
- case BuiltInObjectRayDirectionKHR:
- return ray_tracing_is_khr ? "gl_ObjectRayDirectionEXT" : "gl_ObjectRayDirectionNV";
- case BuiltInRayTminKHR:
- return ray_tracing_is_khr ? "gl_RayTminEXT" : "gl_RayTminNV";
- case BuiltInRayTmaxKHR:
- return ray_tracing_is_khr ? "gl_RayTmaxEXT" : "gl_RayTmaxNV";
- case BuiltInInstanceCustomIndexKHR:
- return ray_tracing_is_khr ? "gl_InstanceCustomIndexEXT" : "gl_InstanceCustomIndexNV";
- case BuiltInObjectToWorldKHR:
- return ray_tracing_is_khr ? "gl_ObjectToWorldEXT" : "gl_ObjectToWorldNV";
- case BuiltInWorldToObjectKHR:
- return ray_tracing_is_khr ? "gl_WorldToObjectEXT" : "gl_WorldToObjectNV";
- case BuiltInHitTNV:
- // gl_HitTEXT is an alias of RayTMax in KHR.
- return "gl_HitTNV";
- case BuiltInHitKindKHR:
- return ray_tracing_is_khr ? "gl_HitKindEXT" : "gl_HitKindNV";
- case BuiltInIncomingRayFlagsKHR:
- return ray_tracing_is_khr ? "gl_IncomingRayFlagsEXT" : "gl_IncomingRayFlagsNV";
- case BuiltInBaryCoordKHR:
- {
- if (options.es && options.version < 320)
- SPIRV_CROSS_THROW("gl_BaryCoordEXT requires ESSL 320.");
- else if (!options.es && options.version < 450)
- SPIRV_CROSS_THROW("gl_BaryCoordEXT requires GLSL 450.");
- if (barycentric_is_nv)
- {
- require_extension_internal("GL_NV_fragment_shader_barycentric");
- return "gl_BaryCoordNV";
- }
- else
- {
- require_extension_internal("GL_EXT_fragment_shader_barycentric");
- return "gl_BaryCoordEXT";
- }
- }
- case BuiltInBaryCoordNoPerspNV:
- {
- if (options.es && options.version < 320)
- SPIRV_CROSS_THROW("gl_BaryCoordNoPerspEXT requires ESSL 320.");
- else if (!options.es && options.version < 450)
- SPIRV_CROSS_THROW("gl_BaryCoordNoPerspEXT requires GLSL 450.");
- if (barycentric_is_nv)
- {
- require_extension_internal("GL_NV_fragment_shader_barycentric");
- return "gl_BaryCoordNoPerspNV";
- }
- else
- {
- require_extension_internal("GL_EXT_fragment_shader_barycentric");
- return "gl_BaryCoordNoPerspEXT";
- }
- }
- case BuiltInFragStencilRefEXT:
- {
- if (!options.es)
- {
- require_extension_internal("GL_ARB_shader_stencil_export");
- return "gl_FragStencilRefARB";
- }
- else
- SPIRV_CROSS_THROW("Stencil export not supported in GLES.");
- }
- case BuiltInPrimitiveShadingRateKHR:
- {
- if (!options.vulkan_semantics)
- SPIRV_CROSS_THROW("Can only use PrimitiveShadingRateKHR in Vulkan GLSL.");
- require_extension_internal("GL_EXT_fragment_shading_rate");
- return "gl_PrimitiveShadingRateEXT";
- }
- case BuiltInShadingRateKHR:
- {
- if (!options.vulkan_semantics)
- SPIRV_CROSS_THROW("Can only use ShadingRateKHR in Vulkan GLSL.");
- require_extension_internal("GL_EXT_fragment_shading_rate");
- return "gl_ShadingRateEXT";
- }
- case BuiltInDeviceIndex:
- if (!options.vulkan_semantics)
- SPIRV_CROSS_THROW("Need Vulkan semantics for device group support.");
- require_extension_internal("GL_EXT_device_group");
- return "gl_DeviceIndex";
- case BuiltInFullyCoveredEXT:
- if (!options.es)
- require_extension_internal("GL_NV_conservative_raster_underestimation");
- else
- SPIRV_CROSS_THROW("Need desktop GL to use GL_NV_conservative_raster_underestimation.");
- return "gl_FragFullyCoveredNV";
- case BuiltInPrimitiveTriangleIndicesEXT:
- return "gl_PrimitiveTriangleIndicesEXT";
- case BuiltInPrimitiveLineIndicesEXT:
- return "gl_PrimitiveLineIndicesEXT";
- case BuiltInPrimitivePointIndicesEXT:
- return "gl_PrimitivePointIndicesEXT";
- case BuiltInCullPrimitiveEXT:
- return "gl_CullPrimitiveEXT";
- case BuiltInHitTriangleVertexPositionsKHR:
- {
- if (!options.vulkan_semantics)
- SPIRV_CROSS_THROW("Need Vulkan semantics for EXT_ray_tracing_position_fetch.");
- require_extension_internal("GL_EXT_ray_tracing_position_fetch");
- return "gl_HitTriangleVertexPositionsEXT";
- }
- case BuiltInClusterIDNV:
- {
- if (!options.vulkan_semantics)
- SPIRV_CROSS_THROW("Can only use ClusterIDNV in Vulkan GLSL.");
- require_extension_internal("GL_NV_cluster_acceleration_structure");
- return "gl_ClusterIDNV";
- }
- default:
- return join("gl_BuiltIn_", convert_to_string(builtin));
- }
- }
- const char *CompilerGLSL::index_to_swizzle(uint32_t index)
- {
- switch (index)
- {
- case 0:
- return "x";
- case 1:
- return "y";
- case 2:
- return "z";
- case 3:
- return "w";
- default:
- return "x"; // Don't crash, but engage the "undefined behavior" described for out-of-bounds logical addressing in spec.
- }
- }
- void CompilerGLSL::access_chain_internal_append_index(std::string &expr, uint32_t /*base*/, const SPIRType * /*type*/,
- AccessChainFlags flags, bool &access_chain_is_arrayed,
- uint32_t index)
- {
- bool index_is_literal = (flags & ACCESS_CHAIN_INDEX_IS_LITERAL_BIT) != 0;
- bool ptr_chain = (flags & ACCESS_CHAIN_PTR_CHAIN_BIT) != 0;
- bool register_expression_read = (flags & ACCESS_CHAIN_SKIP_REGISTER_EXPRESSION_READ_BIT) == 0;
- string idx_expr = index_is_literal ? convert_to_string(index) : to_unpacked_expression(index, register_expression_read);
- // For the case where the base of an OpPtrAccessChain already ends in [n],
- // we need to use the index as an offset to the existing index, otherwise,
- // we can just use the index directly.
- if (ptr_chain && access_chain_is_arrayed)
- {
- size_t split_pos = expr.find_last_of(']');
- size_t enclose_split = expr.find_last_of(')');
- // If we have already enclosed the expression, don't try to be clever, it will break.
- if (split_pos > enclose_split || enclose_split == string::npos)
- {
- string expr_front = expr.substr(0, split_pos);
- string expr_back = expr.substr(split_pos);
- expr = expr_front + " + " + enclose_expression(idx_expr) + expr_back;
- return;
- }
- }
- expr += "[";
- expr += idx_expr;
- expr += "]";
- }
- bool CompilerGLSL::access_chain_needs_stage_io_builtin_translation(uint32_t)
- {
- return true;
- }
- string CompilerGLSL::access_chain_internal(uint32_t base, const uint32_t *indices, uint32_t count,
- AccessChainFlags flags, AccessChainMeta *meta)
- {
- string expr;
- bool index_is_literal = (flags & ACCESS_CHAIN_INDEX_IS_LITERAL_BIT) != 0;
- bool msb_is_id = (flags & ACCESS_CHAIN_LITERAL_MSB_FORCE_ID) != 0;
- bool chain_only = (flags & ACCESS_CHAIN_CHAIN_ONLY_BIT) != 0;
- bool ptr_chain = (flags & ACCESS_CHAIN_PTR_CHAIN_BIT) != 0;
- bool register_expression_read = (flags & ACCESS_CHAIN_SKIP_REGISTER_EXPRESSION_READ_BIT) == 0;
- bool flatten_member_reference = (flags & ACCESS_CHAIN_FLATTEN_ALL_MEMBERS_BIT) != 0;
- if (!chain_only)
- {
- // We handle transpose explicitly, so don't resolve that here.
- auto *e = maybe_get<SPIRExpression>(base);
- bool old_transpose = e && e->need_transpose;
- if (e)
- e->need_transpose = false;
- expr = to_enclosed_expression(base, register_expression_read);
- if (e)
- e->need_transpose = old_transpose;
- }
- // Start traversing type hierarchy at the proper non-pointer types,
- // but keep type_id referencing the original pointer for use below.
- uint32_t type_id = expression_type_id(base);
- const auto *type = &get_pointee_type(type_id);
- if (!backend.native_pointers)
- {
- if (ptr_chain)
- SPIRV_CROSS_THROW("Backend does not support native pointers and does not support OpPtrAccessChain.");
- // Wrapped buffer reference pointer types will need to poke into the internal "value" member before
- // continuing the access chain.
- if (should_dereference(base))
- expr = dereference_expression(get<SPIRType>(type_id), expr);
- }
- else if (should_dereference(base) && type->basetype != SPIRType::Struct && !ptr_chain)
- expr = join("(", dereference_expression(*type, expr), ")");
- bool access_chain_is_arrayed = expr.find_first_of('[') != string::npos;
- bool row_major_matrix_needs_conversion = is_non_native_row_major_matrix(base);
- bool is_packed = has_extended_decoration(base, SPIRVCrossDecorationPhysicalTypePacked);
- uint32_t physical_type = get_extended_decoration(base, SPIRVCrossDecorationPhysicalTypeID);
- bool is_invariant = has_decoration(base, DecorationInvariant);
- bool relaxed_precision = has_decoration(base, DecorationRelaxedPrecision);
- bool pending_array_enclose = false;
- bool dimension_flatten = false;
- bool access_meshlet_position_y = false;
- bool chain_is_builtin = false;
- BuiltIn chained_builtin = {};
- if (auto *base_expr = maybe_get<SPIRExpression>(base))
- {
- access_meshlet_position_y = base_expr->access_meshlet_position_y;
- }
- // If we are translating access to a structured buffer, the first subscript '._m0' must be hidden
- bool hide_first_subscript = count > 1 && is_user_type_structured(base);
- const auto append_index = [&](uint32_t index, bool is_literal, bool is_ptr_chain = false) {
- AccessChainFlags mod_flags = flags;
- if (!is_literal)
- mod_flags &= ~ACCESS_CHAIN_INDEX_IS_LITERAL_BIT;
- if (!is_ptr_chain)
- mod_flags &= ~ACCESS_CHAIN_PTR_CHAIN_BIT;
- access_chain_internal_append_index(expr, base, type, mod_flags, access_chain_is_arrayed, index);
- if (check_physical_type_cast(expr, type, physical_type))
- physical_type = 0;
- };
- for (uint32_t i = 0; i < count; i++)
- {
- uint32_t index = indices[i];
- bool is_literal = index_is_literal;
- if (is_literal && msb_is_id && (index >> 31u) != 0u)
- {
- is_literal = false;
- index &= 0x7fffffffu;
- }
- bool ptr_chain_array_entry = ptr_chain && i == 0 && is_array(*type);
- if (ptr_chain_array_entry)
- {
- // This is highly unusual code, since normally we'd use plain AccessChain, but it's still allowed.
- // We are considered to have a pointer to array and one element shifts by one array at a time.
- // If we use normal array indexing, we'll first decay to pointer, and lose the array-ness,
- // so we have to take pointer to array explicitly.
- if (!should_dereference(base))
- expr = enclose_expression(address_of_expression(expr));
- }
- if (ptr_chain && i == 0)
- {
- // Pointer chains
- // If we are flattening multidimensional arrays, only create opening bracket on first
- // array index.
- if (options.flatten_multidimensional_arrays)
- {
- dimension_flatten = type->array.size() >= 1;
- pending_array_enclose = dimension_flatten;
- if (pending_array_enclose)
- expr += "[";
- }
- if (options.flatten_multidimensional_arrays && dimension_flatten)
- {
- // If we are flattening multidimensional arrays, do manual stride computation.
- if (is_literal)
- expr += convert_to_string(index);
- else
- expr += to_enclosed_expression(index, register_expression_read);
- for (auto j = uint32_t(type->array.size()); j; j--)
- {
- expr += " * ";
- expr += enclose_expression(to_array_size(*type, j - 1));
- }
- if (type->array.empty())
- pending_array_enclose = false;
- else
- expr += " + ";
- if (!pending_array_enclose)
- expr += "]";
- }
- else
- {
- if (flags & ACCESS_CHAIN_PTR_CHAIN_POINTER_ARITH_BIT)
- {
- SPIRType tmp_type(OpTypeInt);
- tmp_type.basetype = SPIRType::UInt64;
- tmp_type.width = 64;
- tmp_type.vecsize = 1;
- tmp_type.columns = 1;
- TypeID ptr_type_id = expression_type_id(base);
- const SPIRType &ptr_type = get<SPIRType>(ptr_type_id);
- const SPIRType &pointee_type = get_pointee_type(ptr_type);
- // This only runs in native pointer backends.
- // Can replace reinterpret_cast with a backend string if ever needed.
- // We expect this to count as a de-reference.
- // This leaks some MSL details, but feels slightly overkill to
- // add yet another virtual interface just for this.
- auto intptr_expr = join("reinterpret_cast<", type_to_glsl(tmp_type), ">(", expr, ")");
- intptr_expr += join(" + ", to_enclosed_unpacked_expression(index), " * ",
- get_decoration(ptr_type_id, DecorationArrayStride));
- if (flags & ACCESS_CHAIN_PTR_CHAIN_CAST_TO_SCALAR_BIT)
- {
- is_packed = true;
- expr = join("*reinterpret_cast<device packed_", type_to_glsl(pointee_type),
- " *>(", intptr_expr, ")");
- }
- else
- {
- expr = join("*reinterpret_cast<", type_to_glsl(ptr_type), ">(", intptr_expr, ")");
- }
- }
- else
- append_index(index, is_literal, true);
- }
- if (type->basetype == SPIRType::ControlPointArray)
- {
- type_id = type->parent_type;
- type = &get<SPIRType>(type_id);
- }
- access_chain_is_arrayed = true;
- // Explicitly enclose the expression if this is one of the weird pointer-to-array cases.
- // We don't want any future indexing to add to this array dereference.
- // Enclosing the expression blocks that and avoids any shenanigans with operand priority.
- if (ptr_chain_array_entry)
- expr = join("(", expr, ")");
- }
- // Arrays and OpTypeCooperativeVectorNV (aka fancy arrays)
- else if (!type->array.empty() || type->op == OpTypeCooperativeVectorNV)
- {
- // If we are flattening multidimensional arrays, only create opening bracket on first
- // array index.
- if (options.flatten_multidimensional_arrays && !pending_array_enclose)
- {
- dimension_flatten = type->array.size() > 1;
- pending_array_enclose = dimension_flatten;
- if (pending_array_enclose)
- expr += "[";
- }
- assert(type->parent_type);
- auto *var = maybe_get<SPIRVariable>(base);
- if (backend.force_gl_in_out_block && i == 0 && var && is_builtin_variable(*var) &&
- !has_decoration(type->self, DecorationBlock))
- {
- // This deals with scenarios for tesc/geom where arrays of gl_Position[] are declared.
- // Normally, these variables live in blocks when compiled from GLSL,
- // but HLSL seems to just emit straight arrays here.
- // We must pretend this access goes through gl_in/gl_out arrays
- // to be able to access certain builtins as arrays.
- // Similar concerns apply for mesh shaders where we have to redirect to gl_MeshVerticesEXT or MeshPrimitivesEXT.
- auto builtin = ir.meta[base].decoration.builtin_type;
- bool mesh_shader = get_execution_model() == ExecutionModelMeshEXT;
- chain_is_builtin = true;
- chained_builtin = builtin;
- switch (builtin)
- {
- case BuiltInCullDistance:
- case BuiltInClipDistance:
- if (type->array.size() == 1) // Red herring. Only consider block IO for two-dimensional arrays here.
- {
- append_index(index, is_literal);
- break;
- }
- // fallthrough
- case BuiltInPosition:
- case BuiltInPointSize:
- if (mesh_shader)
- expr = join("gl_MeshVerticesEXT[", to_expression(index, register_expression_read), "].", expr);
- else if (var->storage == StorageClassInput)
- expr = join("gl_in[", to_expression(index, register_expression_read), "].", expr);
- else if (var->storage == StorageClassOutput)
- expr = join("gl_out[", to_expression(index, register_expression_read), "].", expr);
- else
- append_index(index, is_literal);
- break;
- case BuiltInPrimitiveId:
- case BuiltInLayer:
- case BuiltInViewportIndex:
- case BuiltInCullPrimitiveEXT:
- case BuiltInPrimitiveShadingRateKHR:
- if (mesh_shader)
- expr = join("gl_MeshPrimitivesEXT[", to_expression(index, register_expression_read), "].", expr);
- else
- append_index(index, is_literal);
- break;
- default:
- append_index(index, is_literal);
- break;
- }
- }
- else if (backend.force_merged_mesh_block && i == 0 && var &&
- !is_builtin_variable(*var) && var->storage == StorageClassOutput)
- {
- if (is_per_primitive_variable(*var))
- expr = join("gl_MeshPrimitivesEXT[", to_expression(index, register_expression_read), "].", expr);
- else
- expr = join("gl_MeshVerticesEXT[", to_expression(index, register_expression_read), "].", expr);
- }
- else if (options.flatten_multidimensional_arrays && dimension_flatten)
- {
- // If we are flattening multidimensional arrays, do manual stride computation.
- auto &parent_type = get<SPIRType>(type->parent_type);
- if (is_literal)
- expr += convert_to_string(index);
- else
- expr += to_enclosed_expression(index, register_expression_read);
- for (auto j = uint32_t(parent_type.array.size()); j; j--)
- {
- expr += " * ";
- expr += enclose_expression(to_array_size(parent_type, j - 1));
- }
- if (parent_type.array.empty())
- pending_array_enclose = false;
- else
- expr += " + ";
- if (!pending_array_enclose)
- expr += "]";
- }
- else if (index_is_literal || !builtin_translates_to_nonarray(BuiltIn(get_decoration(base, DecorationBuiltIn))))
- {
- // Some builtins are arrays in SPIR-V but not in other languages, e.g. gl_SampleMask[] is an array in SPIR-V but not in Metal.
- // By throwing away the index, we imply the index was 0, which it must be for gl_SampleMask.
- // For literal indices we are working on composites, so we ignore this since we have already converted to proper array.
- append_index(index, is_literal);
- }
- if (var && has_decoration(var->self, DecorationBuiltIn) &&
- get_decoration(var->self, DecorationBuiltIn) == BuiltInPosition &&
- get_execution_model() == ExecutionModelMeshEXT)
- {
- access_meshlet_position_y = true;
- }
- type_id = type->parent_type;
- type = &get<SPIRType>(type_id);
- // If the physical type has an unnatural vecsize,
- // we must assume it's a faked struct where the .data member
- // is used for the real payload.
- if (physical_type && (is_vector(*type) || is_scalar(*type)))
- {
- auto &phys = get<SPIRType>(physical_type);
- if (phys.vecsize > 4)
- expr += ".data";
- }
- access_chain_is_arrayed = true;
- }
- // For structs, the index refers to a constant, which indexes into the members, possibly through a redirection mapping.
- // We also check if this member is a builtin, since we then replace the entire expression with the builtin one.
- else if (type->basetype == SPIRType::Struct)
- {
- if (!is_literal)
- index = evaluate_constant_u32(index);
- if (index < uint32_t(type->member_type_index_redirection.size()))
- index = type->member_type_index_redirection[index];
- if (index >= type->member_types.size())
- SPIRV_CROSS_THROW("Member index is out of bounds!");
- if (hide_first_subscript)
- {
- // First "._m0" subscript has been hidden, subsequent fields must be emitted even for structured buffers
- hide_first_subscript = false;
- }
- else
- {
- BuiltIn builtin = BuiltInMax;
- if (is_member_builtin(*type, index, &builtin) && access_chain_needs_stage_io_builtin_translation(base))
- {
- if (access_chain_is_arrayed)
- {
- expr += ".";
- expr += builtin_to_glsl(builtin, type->storage);
- }
- else
- expr = builtin_to_glsl(builtin, type->storage);
- if (builtin == BuiltInPosition && get_execution_model() == ExecutionModelMeshEXT)
- {
- access_meshlet_position_y = true;
- }
- chain_is_builtin = true;
- chained_builtin = builtin;
- }
- else
- {
- // If the member has a qualified name, use it as the entire chain
- string qual_mbr_name = get_member_qualified_name(type_id, index);
- if (!qual_mbr_name.empty())
- expr = qual_mbr_name;
- else if (flatten_member_reference)
- expr += join("_", to_member_name(*type, index));
- else
- {
- // Any pointer de-refences for values are handled in the first access chain.
- // For pointer chains, the pointer-ness is resolved through an array access.
- // The only time this is not true is when accessing array of SSBO/UBO.
- // This case is explicitly handled.
- expr += to_member_reference(base, *type, index, ptr_chain || i != 0);
- }
- }
- }
- if (has_member_decoration(type->self, index, DecorationInvariant))
- is_invariant = true;
- if (has_member_decoration(type->self, index, DecorationRelaxedPrecision))
- relaxed_precision = true;
- is_packed = member_is_packed_physical_type(*type, index);
- if (member_is_remapped_physical_type(*type, index))
- physical_type = get_extended_member_decoration(type->self, index, SPIRVCrossDecorationPhysicalTypeID);
- else
- physical_type = 0;
- row_major_matrix_needs_conversion = member_is_non_native_row_major_matrix(*type, index);
- type = &get<SPIRType>(type->member_types[index]);
- }
- // Matrix -> Vector
- else if (type->columns > 1)
- {
- // If we have a row-major matrix here, we need to defer any transpose in case this access chain
- // is used to store a column. We can resolve it right here and now if we access a scalar directly,
- // by flipping indexing order of the matrix.
- expr += "[";
- if (is_literal)
- expr += convert_to_string(index);
- else
- expr += to_unpacked_expression(index, register_expression_read);
- expr += "]";
- // If the physical type has an unnatural vecsize,
- // we must assume it's a faked struct where the .data member
- // is used for the real payload.
- if (physical_type)
- {
- auto &phys = get<SPIRType>(physical_type);
- if (phys.vecsize > 4 || phys.columns > 4)
- expr += ".data";
- }
- type_id = type->parent_type;
- type = &get<SPIRType>(type_id);
- }
- // Vector -> Scalar
- else if (type->op == OpTypeCooperativeMatrixKHR || type->vecsize > 1)
- {
- string deferred_index;
- if (row_major_matrix_needs_conversion)
- {
- // Flip indexing order.
- auto column_index = expr.find_last_of('[');
- if (column_index != string::npos)
- {
- deferred_index = expr.substr(column_index);
- auto end_deferred_index = deferred_index.find_last_of(']');
- if (end_deferred_index != string::npos && end_deferred_index + 1 != deferred_index.size())
- {
- // If we have any data member fixups, it must be transposed so that it refers to this index.
- // E.g. [0].data followed by [1] would be shuffled to [1][0].data which is wrong,
- // and needs to be [1].data[0] instead.
- end_deferred_index++;
- deferred_index = deferred_index.substr(end_deferred_index) +
- deferred_index.substr(0, end_deferred_index);
- }
- expr.resize(column_index);
- }
- }
- // Internally, access chain implementation can also be used on composites,
- // ignore scalar access workarounds in this case.
- StorageClass effective_storage = StorageClassGeneric;
- bool ignore_potential_sliced_writes = false;
- if ((flags & ACCESS_CHAIN_FORCE_COMPOSITE_BIT) == 0)
- {
- if (expression_type(base).pointer)
- effective_storage = get_expression_effective_storage_class(base);
- // Special consideration for control points.
- // Control points can only be written by InvocationID, so there is no need
- // to consider scalar access chains here.
- // Cleans up some cases where it's very painful to determine the accurate storage class
- // since blocks can be partially masked ...
- auto *var = maybe_get_backing_variable(base);
- if (var && var->storage == StorageClassOutput &&
- get_execution_model() == ExecutionModelTessellationControl &&
- !has_decoration(var->self, DecorationPatch))
- {
- ignore_potential_sliced_writes = true;
- }
- }
- else
- ignore_potential_sliced_writes = true;
- if (!row_major_matrix_needs_conversion && !ignore_potential_sliced_writes)
- {
- // On some backends, we might not be able to safely access individual scalars in a vector.
- // To work around this, we might have to cast the access chain reference to something which can,
- // like a pointer to scalar, which we can then index into.
- prepare_access_chain_for_scalar_access(expr, get<SPIRType>(type->parent_type), effective_storage,
- is_packed);
- }
- if (is_literal)
- {
- bool out_of_bounds = index >= type->vecsize && type->op != OpTypeCooperativeMatrixKHR;
- if (!is_packed && !row_major_matrix_needs_conversion && type->op != OpTypeCooperativeMatrixKHR)
- {
- expr += ".";
- expr += index_to_swizzle(out_of_bounds ? 0 : index);
- }
- else
- {
- // For packed vectors, we can only access them as an array, not by swizzle.
- expr += join("[", out_of_bounds ? 0 : index, "]");
- }
- }
- else if (ir.ids[index].get_type() == TypeConstant && !is_packed && !row_major_matrix_needs_conversion)
- {
- auto &c = get<SPIRConstant>(index);
- bool out_of_bounds = (c.scalar() >= type->vecsize);
- if (c.specialization)
- {
- // If the index is a spec constant, we cannot turn extract into a swizzle.
- expr += join("[", out_of_bounds ? "0" : to_expression(index), "]");
- }
- else
- {
- expr += ".";
- expr += index_to_swizzle(out_of_bounds ? 0 : c.scalar());
- }
- }
- else
- {
- expr += "[";
- expr += to_unpacked_expression(index, register_expression_read);
- expr += "]";
- }
- if (row_major_matrix_needs_conversion && !ignore_potential_sliced_writes)
- {
- if (prepare_access_chain_for_scalar_access(expr, get<SPIRType>(type->parent_type), effective_storage,
- is_packed))
- {
- // We're in a pointer context now, so just remove any member dereference.
- auto first_index = deferred_index.find_first_of('[');
- if (first_index != string::npos && first_index != 0)
- deferred_index = deferred_index.substr(first_index);
- }
- }
- if (access_meshlet_position_y)
- {
- if (is_literal)
- {
- access_meshlet_position_y = index == 1;
- }
- else
- {
- const auto *c = maybe_get<SPIRConstant>(index);
- if (c)
- access_meshlet_position_y = c->scalar() == 1;
- else
- {
- // We don't know, but we have to assume no.
- // Flip Y in mesh shaders is an opt-in horrible hack, so we'll have to assume shaders try to behave.
- access_meshlet_position_y = false;
- }
- }
- }
- expr += deferred_index;
- row_major_matrix_needs_conversion = false;
- is_packed = false;
- physical_type = 0;
- type_id = type->parent_type;
- type = &get<SPIRType>(type_id);
- }
- else if (!backend.allow_truncated_access_chain)
- SPIRV_CROSS_THROW("Cannot subdivide a scalar value!");
- }
- if (pending_array_enclose)
- {
- SPIRV_CROSS_THROW("Flattening of multidimensional arrays were enabled, "
- "but the access chain was terminated in the middle of a multidimensional array. "
- "This is not supported.");
- }
- if (meta)
- {
- meta->need_transpose = row_major_matrix_needs_conversion;
- meta->storage_is_packed = is_packed;
- meta->storage_is_invariant = is_invariant;
- meta->storage_physical_type = physical_type;
- meta->relaxed_precision = relaxed_precision;
- meta->access_meshlet_position_y = access_meshlet_position_y;
- meta->chain_is_builtin = chain_is_builtin;
- meta->builtin = chained_builtin;
- }
- return expr;
- }
- bool CompilerGLSL::check_physical_type_cast(std::string &, const SPIRType *, uint32_t)
- {
- return false;
- }
- bool CompilerGLSL::prepare_access_chain_for_scalar_access(std::string &, const SPIRType &, StorageClass, bool &)
- {
- return false;
- }
- string CompilerGLSL::to_flattened_struct_member(const string &basename, const SPIRType &type, uint32_t index)
- {
- auto ret = join(basename, "_", to_member_name(type, index));
- ParsedIR::sanitize_underscores(ret);
- return ret;
- }
- uint32_t CompilerGLSL::get_physical_type_stride(const SPIRType &) const
- {
- SPIRV_CROSS_THROW("Invalid to call get_physical_type_stride on a backend without native pointer support.");
- }
- string CompilerGLSL::access_chain(uint32_t base, const uint32_t *indices, uint32_t count, const SPIRType &target_type,
- AccessChainMeta *meta, bool ptr_chain)
- {
- if (flattened_buffer_blocks.count(base))
- {
- uint32_t matrix_stride = 0;
- uint32_t array_stride = 0;
- bool need_transpose = false;
- flattened_access_chain_offset(expression_type(base), indices, count, 0, 16, &need_transpose, &matrix_stride,
- &array_stride, ptr_chain);
- if (meta)
- {
- meta->need_transpose = target_type.columns > 1 && need_transpose;
- meta->storage_is_packed = false;
- }
- return flattened_access_chain(base, indices, count, target_type, 0, matrix_stride, array_stride,
- need_transpose);
- }
- else if (flattened_structs.count(base) && count > 0)
- {
- AccessChainFlags flags = ACCESS_CHAIN_CHAIN_ONLY_BIT | ACCESS_CHAIN_SKIP_REGISTER_EXPRESSION_READ_BIT;
- if (ptr_chain)
- flags |= ACCESS_CHAIN_PTR_CHAIN_BIT;
- if (flattened_structs[base])
- {
- flags |= ACCESS_CHAIN_FLATTEN_ALL_MEMBERS_BIT;
- if (meta)
- meta->flattened_struct = target_type.basetype == SPIRType::Struct;
- }
- auto chain = access_chain_internal(base, indices, count, flags, nullptr).substr(1);
- if (meta)
- {
- meta->need_transpose = false;
- meta->storage_is_packed = false;
- }
- auto basename = to_flattened_access_chain_expression(base);
- auto ret = join(basename, "_", chain);
- ParsedIR::sanitize_underscores(ret);
- return ret;
- }
- else
- {
- AccessChainFlags flags = ACCESS_CHAIN_SKIP_REGISTER_EXPRESSION_READ_BIT;
- if (ptr_chain)
- {
- flags |= ACCESS_CHAIN_PTR_CHAIN_BIT;
- // PtrAccessChain could get complicated.
- TypeID type_id = expression_type_id(base);
- if (backend.native_pointers && has_decoration(type_id, DecorationArrayStride))
- {
- // If there is a mismatch we have to go via 64-bit pointer arithmetic :'(
- // Using packed hacks only gets us so far, and is not designed to deal with pointer to
- // random values. It works for structs though.
- auto &pointee_type = get_pointee_type(get<SPIRType>(type_id));
- uint32_t physical_stride = get_physical_type_stride(pointee_type);
- uint32_t requested_stride = get_decoration(type_id, DecorationArrayStride);
- if (physical_stride != requested_stride)
- {
- flags |= ACCESS_CHAIN_PTR_CHAIN_POINTER_ARITH_BIT;
- if (is_vector(pointee_type))
- flags |= ACCESS_CHAIN_PTR_CHAIN_CAST_TO_SCALAR_BIT;
- }
- }
- }
- return access_chain_internal(base, indices, count, flags, meta);
- }
- }
- string CompilerGLSL::load_flattened_struct(const string &basename, const SPIRType &type)
- {
- auto expr = type_to_glsl_constructor(type);
- expr += '(';
- for (uint32_t i = 0; i < uint32_t(type.member_types.size()); i++)
- {
- if (i)
- expr += ", ";
- auto &member_type = get<SPIRType>(type.member_types[i]);
- if (member_type.basetype == SPIRType::Struct)
- expr += load_flattened_struct(to_flattened_struct_member(basename, type, i), member_type);
- else
- expr += to_flattened_struct_member(basename, type, i);
- }
- expr += ')';
- return expr;
- }
- std::string CompilerGLSL::to_flattened_access_chain_expression(uint32_t id)
- {
- // Do not use to_expression as that will unflatten access chains.
- string basename;
- if (const auto *var = maybe_get<SPIRVariable>(id))
- basename = to_name(var->self);
- else if (const auto *expr = maybe_get<SPIRExpression>(id))
- basename = expr->expression;
- else
- basename = to_expression(id);
- return basename;
- }
- void CompilerGLSL::store_flattened_struct(const string &basename, uint32_t rhs_id, const SPIRType &type,
- const SmallVector<uint32_t> &indices)
- {
- SmallVector<uint32_t> sub_indices = indices;
- sub_indices.push_back(0);
- auto *member_type = &type;
- for (auto &index : indices)
- member_type = &get<SPIRType>(member_type->member_types[index]);
- for (uint32_t i = 0; i < uint32_t(member_type->member_types.size()); i++)
- {
- sub_indices.back() = i;
- auto lhs = join(basename, "_", to_member_name(*member_type, i));
- ParsedIR::sanitize_underscores(lhs);
- if (get<SPIRType>(member_type->member_types[i]).basetype == SPIRType::Struct)
- {
- store_flattened_struct(lhs, rhs_id, type, sub_indices);
- }
- else
- {
- auto rhs = to_expression(rhs_id) + to_multi_member_reference(type, sub_indices);
- statement(lhs, " = ", rhs, ";");
- }
- }
- }
- void CompilerGLSL::store_flattened_struct(uint32_t lhs_id, uint32_t value)
- {
- auto &type = expression_type(lhs_id);
- auto basename = to_flattened_access_chain_expression(lhs_id);
- store_flattened_struct(basename, value, type, {});
- }
- std::string CompilerGLSL::flattened_access_chain(uint32_t base, const uint32_t *indices, uint32_t count,
- const SPIRType &target_type, uint32_t offset, uint32_t matrix_stride,
- uint32_t /* array_stride */, bool need_transpose)
- {
- if (!target_type.array.empty())
- SPIRV_CROSS_THROW("Access chains that result in an array can not be flattened");
- else if (target_type.basetype == SPIRType::Struct)
- return flattened_access_chain_struct(base, indices, count, target_type, offset);
- else if (target_type.columns > 1)
- return flattened_access_chain_matrix(base, indices, count, target_type, offset, matrix_stride, need_transpose);
- else
- return flattened_access_chain_vector(base, indices, count, target_type, offset, matrix_stride, need_transpose);
- }
- std::string CompilerGLSL::flattened_access_chain_struct(uint32_t base, const uint32_t *indices, uint32_t count,
- const SPIRType &target_type, uint32_t offset)
- {
- std::string expr;
- if (backend.can_declare_struct_inline)
- {
- expr += type_to_glsl_constructor(target_type);
- expr += "(";
- }
- else
- expr += "{";
- for (uint32_t i = 0; i < uint32_t(target_type.member_types.size()); ++i)
- {
- if (i != 0)
- expr += ", ";
- const SPIRType &member_type = get<SPIRType>(target_type.member_types[i]);
- uint32_t member_offset = type_struct_member_offset(target_type, i);
- // The access chain terminates at the struct, so we need to find matrix strides and row-major information
- // ahead of time.
- bool need_transpose = false;
- bool relaxed = false;
- uint32_t matrix_stride = 0;
- if (member_type.columns > 1)
- {
- auto decorations = combined_decoration_for_member(target_type, i);
- need_transpose = decorations.get(DecorationRowMajor);
- relaxed = decorations.get(DecorationRelaxedPrecision);
- matrix_stride = type_struct_member_matrix_stride(target_type, i);
- }
- auto tmp = flattened_access_chain(base, indices, count, member_type, offset + member_offset, matrix_stride,
- 0 /* array_stride */, need_transpose);
- // Cannot forward transpositions, so resolve them here.
- if (need_transpose)
- expr += convert_row_major_matrix(tmp, member_type, 0, false, relaxed);
- else
- expr += tmp;
- }
- expr += backend.can_declare_struct_inline ? ")" : "}";
- return expr;
- }
- std::string CompilerGLSL::flattened_access_chain_matrix(uint32_t base, const uint32_t *indices, uint32_t count,
- const SPIRType &target_type, uint32_t offset,
- uint32_t matrix_stride, bool need_transpose)
- {
- assert(matrix_stride);
- SPIRType tmp_type = target_type;
- if (need_transpose)
- swap(tmp_type.vecsize, tmp_type.columns);
- std::string expr;
- expr += type_to_glsl_constructor(tmp_type);
- expr += "(";
- for (uint32_t i = 0; i < tmp_type.columns; i++)
- {
- if (i != 0)
- expr += ", ";
- expr += flattened_access_chain_vector(base, indices, count, tmp_type, offset + i * matrix_stride, matrix_stride,
- /* need_transpose= */ false);
- }
- expr += ")";
- return expr;
- }
- std::string CompilerGLSL::flattened_access_chain_vector(uint32_t base, const uint32_t *indices, uint32_t count,
- const SPIRType &target_type, uint32_t offset,
- uint32_t matrix_stride, bool need_transpose)
- {
- auto result = flattened_access_chain_offset(expression_type(base), indices, count, offset, 16);
- auto buffer_name = to_name(expression_type(base).self);
- if (need_transpose)
- {
- std::string expr;
- if (target_type.vecsize > 1)
- {
- expr += type_to_glsl_constructor(target_type);
- expr += "(";
- }
- for (uint32_t i = 0; i < target_type.vecsize; ++i)
- {
- if (i != 0)
- expr += ", ";
- uint32_t component_offset = result.second + i * matrix_stride;
- assert(component_offset % (target_type.width / 8) == 0);
- uint32_t index = component_offset / (target_type.width / 8);
- expr += buffer_name;
- expr += "[";
- expr += result.first; // this is a series of N1 * k1 + N2 * k2 + ... that is either empty or ends with a +
- expr += convert_to_string(index / 4);
- expr += "]";
- expr += vector_swizzle(1, index % 4);
- }
- if (target_type.vecsize > 1)
- {
- expr += ")";
- }
- return expr;
- }
- else
- {
- assert(result.second % (target_type.width / 8) == 0);
- uint32_t index = result.second / (target_type.width / 8);
- std::string expr;
- expr += buffer_name;
- expr += "[";
- expr += result.first; // this is a series of N1 * k1 + N2 * k2 + ... that is either empty or ends with a +
- expr += convert_to_string(index / 4);
- expr += "]";
- expr += vector_swizzle(target_type.vecsize, index % 4);
- return expr;
- }
- }
- std::pair<std::string, uint32_t> CompilerGLSL::flattened_access_chain_offset(
- const SPIRType &basetype, const uint32_t *indices, uint32_t count, uint32_t offset, uint32_t word_stride,
- bool *need_transpose, uint32_t *out_matrix_stride, uint32_t *out_array_stride, bool ptr_chain)
- {
- // Start traversing type hierarchy at the proper non-pointer types.
- const auto *type = &get_pointee_type(basetype);
- std::string expr;
- // Inherit matrix information in case we are access chaining a vector which might have come from a row major layout.
- bool row_major_matrix_needs_conversion = need_transpose ? *need_transpose : false;
- uint32_t matrix_stride = out_matrix_stride ? *out_matrix_stride : 0;
- uint32_t array_stride = out_array_stride ? *out_array_stride : 0;
- for (uint32_t i = 0; i < count; i++)
- {
- uint32_t index = indices[i];
- // Pointers
- if (ptr_chain && i == 0)
- {
- // Here, the pointer type will be decorated with an array stride.
- array_stride = get_decoration(basetype.self, DecorationArrayStride);
- if (!array_stride)
- SPIRV_CROSS_THROW("SPIR-V does not define ArrayStride for buffer block.");
- auto *constant = maybe_get<SPIRConstant>(index);
- if (constant)
- {
- // Constant array access.
- offset += constant->scalar() * array_stride;
- }
- else
- {
- // Dynamic array access.
- if (array_stride % word_stride)
- {
- SPIRV_CROSS_THROW("Array stride for dynamic indexing must be divisible by the size "
- "of a 4-component vector. "
- "Likely culprit here is a float or vec2 array inside a push "
- "constant block which is std430. "
- "This cannot be flattened. Try using std140 layout instead.");
- }
- expr += to_enclosed_expression(index);
- expr += " * ";
- expr += convert_to_string(array_stride / word_stride);
- expr += " + ";
- }
- }
- // Arrays
- else if (!type->array.empty())
- {
- auto *constant = maybe_get<SPIRConstant>(index);
- if (constant)
- {
- // Constant array access.
- offset += constant->scalar() * array_stride;
- }
- else
- {
- // Dynamic array access.
- if (array_stride % word_stride)
- {
- SPIRV_CROSS_THROW("Array stride for dynamic indexing must be divisible by the size "
- "of a 4-component vector. "
- "Likely culprit here is a float or vec2 array inside a push "
- "constant block which is std430. "
- "This cannot be flattened. Try using std140 layout instead.");
- }
- expr += to_enclosed_expression(index, false);
- expr += " * ";
- expr += convert_to_string(array_stride / word_stride);
- expr += " + ";
- }
- uint32_t parent_type = type->parent_type;
- type = &get<SPIRType>(parent_type);
- if (!type->array.empty())
- array_stride = get_decoration(parent_type, DecorationArrayStride);
- }
- // For structs, the index refers to a constant, which indexes into the members.
- // We also check if this member is a builtin, since we then replace the entire expression with the builtin one.
- else if (type->basetype == SPIRType::Struct)
- {
- index = evaluate_constant_u32(index);
- if (index >= type->member_types.size())
- SPIRV_CROSS_THROW("Member index is out of bounds!");
- offset += type_struct_member_offset(*type, index);
- auto &struct_type = *type;
- type = &get<SPIRType>(type->member_types[index]);
- if (type->columns > 1)
- {
- matrix_stride = type_struct_member_matrix_stride(struct_type, index);
- row_major_matrix_needs_conversion =
- combined_decoration_for_member(struct_type, index).get(DecorationRowMajor);
- }
- else
- row_major_matrix_needs_conversion = false;
- if (!type->array.empty())
- array_stride = type_struct_member_array_stride(struct_type, index);
- }
- // Matrix -> Vector
- else if (type->columns > 1)
- {
- auto *constant = maybe_get<SPIRConstant>(index);
- if (constant)
- {
- index = evaluate_constant_u32(index);
- offset += index * (row_major_matrix_needs_conversion ? (type->width / 8) : matrix_stride);
- }
- else
- {
- uint32_t indexing_stride = row_major_matrix_needs_conversion ? (type->width / 8) : matrix_stride;
- // Dynamic array access.
- if (indexing_stride % word_stride)
- {
- SPIRV_CROSS_THROW("Matrix stride for dynamic indexing must be divisible by the size of a "
- "4-component vector. "
- "Likely culprit here is a row-major matrix being accessed dynamically. "
- "This cannot be flattened. Try using std140 layout instead.");
- }
- expr += to_enclosed_expression(index, false);
- expr += " * ";
- expr += convert_to_string(indexing_stride / word_stride);
- expr += " + ";
- }
- type = &get<SPIRType>(type->parent_type);
- }
- // Vector -> Scalar
- else if (type->vecsize > 1)
- {
- auto *constant = maybe_get<SPIRConstant>(index);
- if (constant)
- {
- index = evaluate_constant_u32(index);
- offset += index * (row_major_matrix_needs_conversion ? matrix_stride : (type->width / 8));
- }
- else
- {
- uint32_t indexing_stride = row_major_matrix_needs_conversion ? matrix_stride : (type->width / 8);
- // Dynamic array access.
- if (indexing_stride % word_stride)
- {
- SPIRV_CROSS_THROW("Stride for dynamic vector indexing must be divisible by the "
- "size of a 4-component vector. "
- "This cannot be flattened in legacy targets.");
- }
- expr += to_enclosed_expression(index, false);
- expr += " * ";
- expr += convert_to_string(indexing_stride / word_stride);
- expr += " + ";
- }
- type = &get<SPIRType>(type->parent_type);
- }
- else
- SPIRV_CROSS_THROW("Cannot subdivide a scalar value!");
- }
- if (need_transpose)
- *need_transpose = row_major_matrix_needs_conversion;
- if (out_matrix_stride)
- *out_matrix_stride = matrix_stride;
- if (out_array_stride)
- *out_array_stride = array_stride;
- return std::make_pair(expr, offset);
- }
- bool CompilerGLSL::should_dereference(uint32_t id)
- {
- const auto &type = expression_type(id);
- // Non-pointer expressions don't need to be dereferenced.
- if (!is_pointer(type))
- return false;
- // Handles shouldn't be dereferenced either.
- if (!expression_is_lvalue(id))
- return false;
- // If id is a variable but not a phi variable, we should not dereference it.
- // BDA passed around as parameters are always pointers.
- if (auto *var = maybe_get<SPIRVariable>(id))
- return (var->parameter && is_physical_or_buffer_pointer(type)) || var->phi_variable;
- if (auto *expr = maybe_get<SPIRExpression>(id))
- {
- // If id is an access chain, we should not dereference it.
- if (expr->access_chain)
- return false;
- // If id is a forwarded copy of a variable pointer, we should not dereference it.
- SPIRVariable *var = nullptr;
- while (expr->loaded_from && expression_is_forwarded(expr->self))
- {
- auto &src_type = expression_type(expr->loaded_from);
- // To be a copy, the pointer and its source expression must be the
- // same type. Can't check type.self, because for some reason that's
- // usually the base type with pointers stripped off. This check is
- // complex enough that I've hoisted it out of the while condition.
- if (src_type.pointer != type.pointer || src_type.pointer_depth != type.pointer_depth ||
- src_type.parent_type != type.parent_type)
- break;
- if ((var = maybe_get<SPIRVariable>(expr->loaded_from)))
- break;
- if (!(expr = maybe_get<SPIRExpression>(expr->loaded_from)))
- break;
- }
- return !var || var->phi_variable;
- }
- // Otherwise, we should dereference this pointer expression.
- return true;
- }
- bool CompilerGLSL::should_dereference_caller_param(uint32_t id)
- {
- const auto &type = expression_type(id);
- // BDA is always passed around as pointers. Similarly, we need to pass variable buffer pointers as pointers.
- if (is_physical_or_buffer_pointer(type))
- return false;
- return should_dereference(id);
- }
- bool CompilerGLSL::should_forward(uint32_t id) const
- {
- // If id is a variable we will try to forward it regardless of force_temporary check below
- // This is important because otherwise we'll get local sampler copies (highp sampler2D foo = bar) that are invalid in OpenGL GLSL
- auto *var = maybe_get<SPIRVariable>(id);
- if (var)
- {
- // Never forward volatile builtin variables, e.g. SPIR-V 1.6 HelperInvocation.
- return !(has_decoration(id, DecorationBuiltIn) && has_decoration(id, DecorationVolatile));
- }
- // For debugging emit temporary variables for all expressions
- if (options.force_temporary)
- return false;
- // If an expression carries enough dependencies we need to stop forwarding at some point,
- // or we explode compilers. There are usually limits to how much we can nest expressions.
- auto *expr = maybe_get<SPIRExpression>(id);
- const uint32_t max_expression_dependencies = 64;
- if (expr && expr->expression_dependencies.size() >= max_expression_dependencies)
- return false;
- if (expr && expr->loaded_from
- && has_decoration(expr->loaded_from, DecorationBuiltIn)
- && has_decoration(expr->loaded_from, DecorationVolatile))
- {
- // Never forward volatile builtin variables, e.g. SPIR-V 1.6 HelperInvocation.
- return false;
- }
- // Immutable expression can always be forwarded.
- if (is_immutable(id))
- return true;
- return false;
- }
- bool CompilerGLSL::should_suppress_usage_tracking(uint32_t id) const
- {
- // Used only by opcodes which don't do any real "work", they just swizzle data in some fashion.
- return !expression_is_forwarded(id) || expression_suppresses_usage_tracking(id);
- }
- void CompilerGLSL::track_expression_read(uint32_t id)
- {
- switch (ir.ids[id].get_type())
- {
- case TypeExpression:
- {
- auto &e = get<SPIRExpression>(id);
- for (auto implied_read : e.implied_read_expressions)
- track_expression_read(implied_read);
- break;
- }
- case TypeAccessChain:
- {
- auto &e = get<SPIRAccessChain>(id);
- for (auto implied_read : e.implied_read_expressions)
- track_expression_read(implied_read);
- break;
- }
- default:
- break;
- }
- // If we try to read a forwarded temporary more than once we will stamp out possibly complex code twice.
- // In this case, it's better to just bind the complex expression to the temporary and read that temporary twice.
- if (expression_is_forwarded(id) && !expression_suppresses_usage_tracking(id))
- {
- auto &v = expression_usage_counts[id];
- v++;
- // If we create an expression outside a loop,
- // but access it inside a loop, we're implicitly reading it multiple times.
- // If the expression in question is expensive, we should hoist it out to avoid relying on loop-invariant code motion
- // working inside the backend compiler.
- if (expression_read_implies_multiple_reads(id))
- v++;
- if (v >= 2)
- {
- //if (v == 2)
- // fprintf(stderr, "ID %u was forced to temporary due to more than 1 expression use!\n", id);
- // Force a recompile after this pass to avoid forwarding this variable.
- force_temporary_and_recompile(id);
- }
- }
- }
- bool CompilerGLSL::args_will_forward(uint32_t id, const uint32_t *args, uint32_t num_args, bool pure)
- {
- if (forced_temporaries.find(id) != end(forced_temporaries))
- return false;
- for (uint32_t i = 0; i < num_args; i++)
- if (!should_forward(args[i]))
- return false;
- // We need to forward globals as well.
- if (!pure)
- {
- for (auto global : global_variables)
- if (!should_forward(global))
- return false;
- for (auto aliased : aliased_variables)
- if (!should_forward(aliased))
- return false;
- }
- return true;
- }
- void CompilerGLSL::register_impure_function_call()
- {
- // Impure functions can modify globals and aliased variables, so invalidate them as well.
- for (auto global : global_variables)
- flush_dependees(get<SPIRVariable>(global));
- for (auto aliased : aliased_variables)
- flush_dependees(get<SPIRVariable>(aliased));
- }
- void CompilerGLSL::register_call_out_argument(uint32_t id)
- {
- register_write(id);
- auto *var = maybe_get<SPIRVariable>(id);
- if (var)
- flush_variable_declaration(var->self);
- }
- string CompilerGLSL::variable_decl_function_local(SPIRVariable &var)
- {
- // These variables are always function local,
- // so make sure we emit the variable without storage qualifiers.
- // Some backends will inject custom variables locally in a function
- // with a storage qualifier which is not function-local.
- auto old_storage = var.storage;
- var.storage = StorageClassFunction;
- auto expr = variable_decl(var);
- var.storage = old_storage;
- return expr;
- }
- void CompilerGLSL::emit_variable_temporary_copies(const SPIRVariable &var)
- {
- // Ensure that we declare phi-variable copies even if the original declaration isn't deferred
- if (var.allocate_temporary_copy && !flushed_phi_variables.count(var.self))
- {
- auto &type = get<SPIRType>(var.basetype);
- auto &flags = get_decoration_bitset(var.self);
- statement(flags_to_qualifiers_glsl(type, var.self, flags), variable_decl(type, join("_", var.self, "_copy")), ";");
- flushed_phi_variables.insert(var.self);
- }
- }
- void CompilerGLSL::flush_variable_declaration(uint32_t id)
- {
- // Ensure that we declare phi-variable copies even if the original declaration isn't deferred
- auto *var = maybe_get<SPIRVariable>(id);
- if (var && var->deferred_declaration)
- {
- string initializer;
- if (options.force_zero_initialized_variables &&
- (var->storage == StorageClassFunction || var->storage == StorageClassGeneric ||
- var->storage == StorageClassPrivate) &&
- !var->initializer && type_can_zero_initialize(get_variable_data_type(*var)))
- {
- initializer = join(" = ", to_zero_initialized_expression(get_variable_data_type_id(*var)));
- }
- statement(variable_decl_function_local(*var), initializer, ";");
- var->deferred_declaration = false;
- }
- if (var)
- {
- emit_variable_temporary_copies(*var);
- }
- }
- bool CompilerGLSL::remove_duplicate_swizzle(string &op)
- {
- auto pos = op.find_last_of('.');
- if (pos == string::npos || pos == 0)
- return false;
- string final_swiz = op.substr(pos + 1, string::npos);
- if (backend.swizzle_is_function)
- {
- if (final_swiz.size() < 2)
- return false;
- if (final_swiz.substr(final_swiz.size() - 2, string::npos) == "()")
- final_swiz.erase(final_swiz.size() - 2, string::npos);
- else
- return false;
- }
- // Check if final swizzle is of form .x, .xy, .xyz, .xyzw or similar.
- // If so, and previous swizzle is of same length,
- // we can drop the final swizzle altogether.
- for (uint32_t i = 0; i < final_swiz.size(); i++)
- {
- static const char expected[] = { 'x', 'y', 'z', 'w' };
- if (i >= 4 || final_swiz[i] != expected[i])
- return false;
- }
- auto prevpos = op.find_last_of('.', pos - 1);
- if (prevpos == string::npos)
- return false;
- prevpos++;
- // Make sure there are only swizzles here ...
- for (auto i = prevpos; i < pos; i++)
- {
- if (op[i] < 'w' || op[i] > 'z')
- {
- // If swizzles are foo.xyz() like in C++ backend for example, check for that.
- if (backend.swizzle_is_function && i + 2 == pos && op[i] == '(' && op[i + 1] == ')')
- break;
- return false;
- }
- }
- // If original swizzle is large enough, just carve out the components we need.
- // E.g. foobar.wyx.xy will turn into foobar.wy.
- if (pos - prevpos >= final_swiz.size())
- {
- op.erase(prevpos + final_swiz.size(), string::npos);
- // Add back the function call ...
- if (backend.swizzle_is_function)
- op += "()";
- }
- return true;
- }
- // Optimizes away vector swizzles where we have something like
- // vec3 foo;
- // foo.xyz <-- swizzle expression does nothing.
- // This is a very common pattern after OpCompositeCombine.
- bool CompilerGLSL::remove_unity_swizzle(uint32_t base, string &op)
- {
- auto pos = op.find_last_of('.');
- if (pos == string::npos || pos == 0)
- return false;
- string final_swiz = op.substr(pos + 1, string::npos);
- if (backend.swizzle_is_function)
- {
- if (final_swiz.size() < 2)
- return false;
- if (final_swiz.substr(final_swiz.size() - 2, string::npos) == "()")
- final_swiz.erase(final_swiz.size() - 2, string::npos);
- else
- return false;
- }
- // Check if final swizzle is of form .x, .xy, .xyz, .xyzw or similar.
- // If so, and previous swizzle is of same length,
- // we can drop the final swizzle altogether.
- for (uint32_t i = 0; i < final_swiz.size(); i++)
- {
- static const char expected[] = { 'x', 'y', 'z', 'w' };
- if (i >= 4 || final_swiz[i] != expected[i])
- return false;
- }
- auto &type = expression_type(base);
- // Sanity checking ...
- assert(type.columns == 1 && type.array.empty());
- if (type.vecsize == final_swiz.size())
- op.erase(pos, string::npos);
- return true;
- }
- string CompilerGLSL::build_composite_combiner(uint32_t return_type, const uint32_t *elems, uint32_t length)
- {
- ID base = 0;
- string op;
- string subop;
- // Can only merge swizzles for vectors.
- auto &type = get<SPIRType>(return_type);
- bool can_apply_swizzle_opt = type.basetype != SPIRType::Struct && type.array.empty() && type.columns == 1 &&
- type.op != OpTypeCooperativeMatrixKHR;
- bool swizzle_optimization = false;
- for (uint32_t i = 0; i < length; i++)
- {
- auto *e = maybe_get<SPIRExpression>(elems[i]);
- // If we're merging another scalar which belongs to the same base
- // object, just merge the swizzles to avoid triggering more than 1 expression read as much as possible!
- if (can_apply_swizzle_opt && e && e->base_expression && e->base_expression == base)
- {
- // Only supposed to be used for vector swizzle -> scalar.
- assert(!e->expression.empty() && e->expression.front() == '.');
- subop += e->expression.substr(1, string::npos);
- swizzle_optimization = true;
- }
- else
- {
- // We'll likely end up with duplicated swizzles, e.g.
- // foobar.xyz.xyz from patterns like
- // OpVectorShuffle
- // OpCompositeExtract x 3
- // OpCompositeConstruct 3x + other scalar.
- // Just modify op in-place.
- if (swizzle_optimization)
- {
- if (backend.swizzle_is_function)
- subop += "()";
- // Don't attempt to remove unity swizzling if we managed to remove duplicate swizzles.
- // The base "foo" might be vec4, while foo.xyz is vec3 (OpVectorShuffle) and looks like a vec3 due to the .xyz tacked on.
- // We only want to remove the swizzles if we're certain that the resulting base will be the same vecsize.
- // Essentially, we can only remove one set of swizzles, since that's what we have control over ...
- // Case 1:
- // foo.yxz.xyz: Duplicate swizzle kicks in, giving foo.yxz, we are done.
- // foo.yxz was the result of OpVectorShuffle and we don't know the type of foo.
- // Case 2:
- // foo.xyz: Duplicate swizzle won't kick in.
- // If foo is vec3, we can remove xyz, giving just foo.
- if (!remove_duplicate_swizzle(subop))
- remove_unity_swizzle(base, subop);
- // Strips away redundant parens if we created them during component extraction.
- strip_enclosed_expression(subop);
- swizzle_optimization = false;
- op += subop;
- }
- else
- op += subop;
- if (i)
- op += ", ";
- bool uses_buffer_offset =
- type.basetype == SPIRType::Struct && has_member_decoration(type.self, i, DecorationOffset);
- subop = to_composite_constructor_expression(type, elems[i], uses_buffer_offset);
- }
- base = e ? e->base_expression : ID(0);
- }
- if (swizzle_optimization)
- {
- if (backend.swizzle_is_function)
- subop += "()";
- if (!remove_duplicate_swizzle(subop))
- remove_unity_swizzle(base, subop);
- // Strips away redundant parens if we created them during component extraction.
- strip_enclosed_expression(subop);
- }
- op += subop;
- return op;
- }
- bool CompilerGLSL::skip_argument(uint32_t id) const
- {
- if (!combined_image_samplers.empty() || !options.vulkan_semantics)
- {
- auto &type = expression_type(id);
- if (type.basetype == SPIRType::Sampler || (type.basetype == SPIRType::Image && type.image.sampled == 1))
- return true;
- }
- return false;
- }
- bool CompilerGLSL::optimize_read_modify_write(const SPIRType &type, const string &lhs, const string &rhs)
- {
- // Do this with strings because we have a very clear pattern we can check for and it avoids
- // adding lots of special cases to the code emission.
- if (rhs.size() < lhs.size() + 3)
- return false;
- // Do not optimize matrices. They are a bit awkward to reason about in general
- // (in which order does operation happen?), and it does not work on MSL anyways.
- if (type.vecsize > 1 && type.columns > 1)
- return false;
- auto index = rhs.find(lhs);
- if (index != 0)
- return false;
- // TODO: Shift operators, but it's not important for now.
- auto op = rhs.find_first_of("+-/*%|&^", lhs.size() + 1);
- if (op != lhs.size() + 1)
- return false;
- // Check that the op is followed by space. This excludes && and ||.
- if (rhs[op + 1] != ' ')
- return false;
- char bop = rhs[op];
- auto expr = rhs.substr(lhs.size() + 3);
- // Avoids false positives where we get a = a * b + c.
- // Normally, these expressions are always enclosed, but unexpected code paths may end up hitting this.
- if (needs_enclose_expression(expr))
- return false;
- // Try to find increments and decrements. Makes it look neater as += 1, -= 1 is fairly rare to see in real code.
- // Find some common patterns which are equivalent.
- if ((bop == '+' || bop == '-') && (expr == "1" || expr == "uint(1)" || expr == "1u" || expr == "int(1u)"))
- statement(lhs, bop, bop, ";");
- else
- statement(lhs, " ", bop, "= ", expr, ";");
- return true;
- }
- void CompilerGLSL::register_control_dependent_expression(uint32_t expr)
- {
- if (forwarded_temporaries.find(expr) == end(forwarded_temporaries))
- return;
- assert(current_emitting_block);
- current_emitting_block->invalidate_expressions.push_back(expr);
- }
- void CompilerGLSL::emit_block_instructions(SPIRBlock &block)
- {
- current_emitting_block = █
- if (backend.requires_relaxed_precision_analysis)
- {
- // If PHI variables are consumed in unexpected precision contexts, copy them here.
- for (size_t i = 0, n = block.phi_variables.size(); i < n; i++)
- {
- auto &phi = block.phi_variables[i];
- // Ensure we only copy once. We know a-priori that this array will lay out
- // the same function variables together.
- if (i && block.phi_variables[i - 1].function_variable == phi.function_variable)
- continue;
- auto itr = temporary_to_mirror_precision_alias.find(phi.function_variable);
- if (itr != temporary_to_mirror_precision_alias.end())
- {
- // Explicitly, we don't want to inherit RelaxedPrecision state in this CopyObject,
- // so it helps to have handle_instruction_precision() on the outside of emit_instruction().
- EmbeddedInstruction inst;
- inst.op = OpCopyObject;
- inst.length = 3;
- inst.ops.push_back(expression_type_id(itr->first));
- inst.ops.push_back(itr->second);
- inst.ops.push_back(itr->first);
- emit_instruction(inst);
- }
- }
- }
- for (auto &op : block.ops)
- {
- auto temporary_copy = handle_instruction_precision(op);
- emit_instruction(op);
- if (temporary_copy.dst_id)
- {
- // Explicitly, we don't want to inherit RelaxedPrecision state in this CopyObject,
- // so it helps to have handle_instruction_precision() on the outside of emit_instruction().
- EmbeddedInstruction inst;
- inst.op = OpCopyObject;
- inst.length = 3;
- inst.ops.push_back(expression_type_id(temporary_copy.src_id));
- inst.ops.push_back(temporary_copy.dst_id);
- inst.ops.push_back(temporary_copy.src_id);
- // Never attempt to hoist mirrored temporaries.
- // They are hoisted in lock-step with their parents.
- block_temporary_hoisting = true;
- emit_instruction(inst);
- block_temporary_hoisting = false;
- }
- }
- current_emitting_block = nullptr;
- }
- void CompilerGLSL::disallow_forwarding_in_expression_chain(const SPIRExpression &expr)
- {
- // Allow trivially forwarded expressions like OpLoad or trivial shuffles,
- // these will be marked as having suppressed usage tracking.
- // Our only concern is to make sure arithmetic operations are done in similar ways.
- if (forced_invariant_temporaries.count(expr.self) == 0)
- {
- if (!expression_suppresses_usage_tracking(expr.self))
- force_temporary_and_recompile(expr.self);
- forced_invariant_temporaries.insert(expr.self);
- for (auto &dependent : expr.invariance_dependencies)
- disallow_forwarding_in_expression_chain(get<SPIRExpression>(dependent));
- }
- }
- void CompilerGLSL::handle_store_to_invariant_variable(uint32_t store_id, uint32_t value_id)
- {
- // Variables or access chains marked invariant are complicated. We will need to make sure the code-gen leading up to
- // this variable is consistent. The failure case for SPIRV-Cross is when an expression is forced to a temporary
- // in one translation unit, but not another, e.g. due to multiple use of an expression.
- // This causes variance despite the output variable being marked invariant, so the solution here is to force all dependent
- // expressions to be temporaries.
- // It is uncertain if this is enough to support invariant in all possible cases, but it should be good enough
- // for all reasonable uses of invariant.
- if (!has_decoration(store_id, DecorationInvariant))
- return;
- auto *expr = maybe_get<SPIRExpression>(value_id);
- if (!expr)
- return;
- disallow_forwarding_in_expression_chain(*expr);
- }
- void CompilerGLSL::emit_store_statement(uint32_t lhs_expression, uint32_t rhs_expression)
- {
- auto rhs = to_pointer_expression(rhs_expression);
- // Statements to OpStore may be empty if it is a struct with zero members. Just forward the store to /dev/null.
- if (!rhs.empty())
- {
- handle_store_to_invariant_variable(lhs_expression, rhs_expression);
- if (!unroll_array_to_complex_store(lhs_expression, rhs_expression))
- {
- auto lhs = to_dereferenced_expression(lhs_expression);
- if (has_decoration(lhs_expression, DecorationNonUniform))
- convert_non_uniform_expression(lhs, lhs_expression);
- // We might need to cast in order to store to a builtin.
- cast_to_variable_store(lhs_expression, rhs, expression_type(rhs_expression));
- // Tries to optimize assignments like "<lhs> = <lhs> op expr".
- // While this is purely cosmetic, this is important for legacy ESSL where loop
- // variable increments must be in either i++ or i += const-expr.
- // Without this, we end up with i = i + 1, which is correct GLSL, but not correct GLES 2.0.
- if (!optimize_read_modify_write(expression_type(rhs_expression), lhs, rhs))
- statement(lhs, " = ", rhs, ";");
- }
- register_write(lhs_expression);
- }
- }
- uint32_t CompilerGLSL::get_integer_width_for_instruction(const Instruction &instr) const
- {
- if (instr.length < 3)
- return 32;
- auto *ops = stream(instr);
- switch (instr.op)
- {
- case OpSConvert:
- case OpConvertSToF:
- case OpUConvert:
- case OpConvertUToF:
- case OpIEqual:
- case OpINotEqual:
- case OpSLessThan:
- case OpSLessThanEqual:
- case OpSGreaterThan:
- case OpSGreaterThanEqual:
- case OpULessThan:
- case OpULessThanEqual:
- case OpUGreaterThan:
- case OpUGreaterThanEqual:
- return expression_type(ops[2]).width;
- case OpSMulExtended:
- case OpUMulExtended:
- return get<SPIRType>(get<SPIRType>(ops[0]).member_types[0]).width;
- default:
- {
- // We can look at result type which is more robust.
- auto *type = maybe_get<SPIRType>(ops[0]);
- if (type && type_is_integral(*type))
- return type->width;
- else
- return 32;
- }
- }
- }
- uint32_t CompilerGLSL::get_integer_width_for_glsl_instruction(GLSLstd450 op, const uint32_t *ops, uint32_t length) const
- {
- if (length < 1)
- return 32;
- switch (op)
- {
- case GLSLstd450SAbs:
- case GLSLstd450SSign:
- case GLSLstd450UMin:
- case GLSLstd450SMin:
- case GLSLstd450UMax:
- case GLSLstd450SMax:
- case GLSLstd450UClamp:
- case GLSLstd450SClamp:
- case GLSLstd450FindSMsb:
- case GLSLstd450FindUMsb:
- return expression_type(ops[0]).width;
- default:
- {
- // We don't need to care about other opcodes, just return 32.
- return 32;
- }
- }
- }
- void CompilerGLSL::forward_relaxed_precision(uint32_t dst_id, const uint32_t *args, uint32_t length)
- {
- // Only GLSL supports RelaxedPrecision directly.
- // We cannot implement this in HLSL or MSL because it is tied to the type system.
- // In SPIR-V, everything must masquerade as 32-bit.
- if (!backend.requires_relaxed_precision_analysis)
- return;
- auto input_precision = analyze_expression_precision(args, length);
- // For expressions which are loaded or directly forwarded, we inherit mediump implicitly.
- // For dst_id to be analyzed properly, it must inherit any relaxed precision decoration from src_id.
- if (input_precision == Options::Mediump)
- set_decoration(dst_id, DecorationRelaxedPrecision);
- }
- CompilerGLSL::Options::Precision CompilerGLSL::analyze_expression_precision(const uint32_t *args, uint32_t length) const
- {
- // Now, analyze the precision at which the arguments would run.
- // GLSL rules are such that the precision used to evaluate an expression is equal to the highest precision
- // for the inputs. Constants do not have inherent precision and do not contribute to this decision.
- // If all inputs are constants, they inherit precision from outer expressions, including an l-value.
- // In this case, we'll have to force a temporary for dst_id so that we can bind the constant expression with
- // correct precision.
- bool expression_has_highp = false;
- bool expression_has_mediump = false;
- for (uint32_t i = 0; i < length; i++)
- {
- uint32_t arg = args[i];
- auto handle_type = ir.ids[arg].get_type();
- if (handle_type == TypeConstant || handle_type == TypeConstantOp || handle_type == TypeUndef)
- continue;
- if (has_decoration(arg, DecorationRelaxedPrecision))
- expression_has_mediump = true;
- else
- expression_has_highp = true;
- }
- if (expression_has_highp)
- return Options::Highp;
- else if (expression_has_mediump)
- return Options::Mediump;
- else
- return Options::DontCare;
- }
- void CompilerGLSL::analyze_precision_requirements(uint32_t type_id, uint32_t dst_id, uint32_t *args, uint32_t length)
- {
- if (!backend.requires_relaxed_precision_analysis)
- return;
- auto &type = get<SPIRType>(type_id);
- // RelaxedPrecision only applies to 32-bit values.
- if (type.basetype != SPIRType::Float && type.basetype != SPIRType::Int && type.basetype != SPIRType::UInt)
- return;
- bool operation_is_highp = !has_decoration(dst_id, DecorationRelaxedPrecision);
- auto input_precision = analyze_expression_precision(args, length);
- if (input_precision == Options::DontCare)
- {
- consume_temporary_in_precision_context(type_id, dst_id, input_precision);
- return;
- }
- // In SPIR-V and GLSL, the semantics are flipped for how relaxed precision is determined.
- // In SPIR-V, the operation itself marks RelaxedPrecision, meaning that inputs can be truncated to 16-bit.
- // However, if the expression is not, inputs must be expanded to 32-bit first,
- // since the operation must run at high precision.
- // This is the awkward part, because if we have mediump inputs, or expressions which derived from mediump,
- // we might have to forcefully bind the source IDs to highp temporaries. This is done by clearing decorations
- // and forcing temporaries. Similarly for mediump operations. We bind highp expressions to mediump variables.
- if ((operation_is_highp && input_precision == Options::Mediump) ||
- (!operation_is_highp && input_precision == Options::Highp))
- {
- auto precision = operation_is_highp ? Options::Highp : Options::Mediump;
- for (uint32_t i = 0; i < length; i++)
- {
- // Rewrites the opcode so that we consume an ID in correct precision context.
- // This is pretty hacky, but it's the most straight forward way of implementing this without adding
- // lots of extra passes to rewrite all code blocks.
- args[i] = consume_temporary_in_precision_context(expression_type_id(args[i]), args[i], precision);
- }
- }
- }
- // This is probably not exhaustive ...
- static bool opcode_is_precision_sensitive_operation(Op op)
- {
- switch (op)
- {
- case OpFAdd:
- case OpFSub:
- case OpFMul:
- case OpFNegate:
- case OpIAdd:
- case OpISub:
- case OpIMul:
- case OpSNegate:
- case OpFMod:
- case OpFDiv:
- case OpFRem:
- case OpSMod:
- case OpSDiv:
- case OpSRem:
- case OpUMod:
- case OpUDiv:
- case OpVectorTimesMatrix:
- case OpMatrixTimesVector:
- case OpMatrixTimesMatrix:
- case OpDPdx:
- case OpDPdy:
- case OpDPdxCoarse:
- case OpDPdyCoarse:
- case OpDPdxFine:
- case OpDPdyFine:
- case OpFwidth:
- case OpFwidthCoarse:
- case OpFwidthFine:
- case OpVectorTimesScalar:
- case OpMatrixTimesScalar:
- case OpOuterProduct:
- case OpFConvert:
- case OpSConvert:
- case OpUConvert:
- case OpConvertSToF:
- case OpConvertUToF:
- case OpConvertFToU:
- case OpConvertFToS:
- return true;
- default:
- return false;
- }
- }
- // Instructions which just load data but don't do any arithmetic operation should just inherit the decoration.
- // SPIR-V doesn't require this, but it's somewhat implied it has to work this way, relaxed precision is only
- // relevant when operating on the IDs, not when shuffling things around.
- static bool opcode_is_precision_forwarding_instruction(Op op, uint32_t &arg_count)
- {
- switch (op)
- {
- case OpLoad:
- case OpAccessChain:
- case OpInBoundsAccessChain:
- case OpCompositeExtract:
- case OpVectorExtractDynamic:
- case OpSampledImage:
- case OpImage:
- case OpCopyObject:
- case OpImageRead:
- case OpImageFetch:
- case OpImageSampleImplicitLod:
- case OpImageSampleProjImplicitLod:
- case OpImageSampleDrefImplicitLod:
- case OpImageSampleProjDrefImplicitLod:
- case OpImageSampleExplicitLod:
- case OpImageSampleProjExplicitLod:
- case OpImageSampleDrefExplicitLod:
- case OpImageSampleProjDrefExplicitLod:
- case OpImageGather:
- case OpImageDrefGather:
- case OpImageSparseRead:
- case OpImageSparseFetch:
- case OpImageSparseSampleImplicitLod:
- case OpImageSparseSampleProjImplicitLod:
- case OpImageSparseSampleDrefImplicitLod:
- case OpImageSparseSampleProjDrefImplicitLod:
- case OpImageSparseSampleExplicitLod:
- case OpImageSparseSampleProjExplicitLod:
- case OpImageSparseSampleDrefExplicitLod:
- case OpImageSparseSampleProjDrefExplicitLod:
- case OpImageSparseGather:
- case OpImageSparseDrefGather:
- arg_count = 1;
- return true;
- case OpVectorShuffle:
- arg_count = 2;
- return true;
- case OpCompositeConstruct:
- return true;
- default:
- break;
- }
- return false;
- }
- CompilerGLSL::TemporaryCopy CompilerGLSL::handle_instruction_precision(const Instruction &instruction)
- {
- auto ops = stream_mutable(instruction);
- auto opcode = static_cast<Op>(instruction.op);
- uint32_t length = instruction.length;
- if (backend.requires_relaxed_precision_analysis)
- {
- if (length > 2)
- {
- uint32_t forwarding_length = length - 2;
- if (opcode_is_precision_sensitive_operation(opcode))
- analyze_precision_requirements(ops[0], ops[1], &ops[2], forwarding_length);
- else if (opcode == OpExtInst && length >= 5 && get<SPIRExtension>(ops[2]).ext == SPIRExtension::GLSL)
- analyze_precision_requirements(ops[0], ops[1], &ops[4], forwarding_length - 2);
- else if (opcode_is_precision_forwarding_instruction(opcode, forwarding_length))
- forward_relaxed_precision(ops[1], &ops[2], forwarding_length);
- }
- uint32_t result_type = 0, result_id = 0;
- if (instruction_to_result_type(result_type, result_id, opcode, ops, length))
- {
- auto itr = temporary_to_mirror_precision_alias.find(ops[1]);
- if (itr != temporary_to_mirror_precision_alias.end())
- return { itr->second, itr->first };
- }
- }
- return {};
- }
- static pair<string, string> split_coopmat_pointer(const string &expr)
- {
- auto ptr_expr = expr;
- string index_expr;
- if (ptr_expr.back() != ']')
- SPIRV_CROSS_THROW("Access chain for coopmat must be indexed into an array.");
- // Strip the access chain.
- ptr_expr.pop_back();
- uint32_t counter = 1;
- while (counter && !ptr_expr.empty())
- {
- if (ptr_expr.back() == ']')
- counter++;
- else if (ptr_expr.back() == '[')
- counter--;
- ptr_expr.pop_back();
- }
- if (ptr_expr.empty())
- SPIRV_CROSS_THROW("Invalid pointer expression for coopmat.");
- index_expr = expr.substr(ptr_expr.size() + 1, expr.size() - (ptr_expr.size() + 1) - 1);
- return { std::move(ptr_expr), std::move(index_expr) };
- }
- void CompilerGLSL::emit_instruction(const Instruction &instruction)
- {
- auto ops = stream(instruction);
- auto opcode = static_cast<Op>(instruction.op);
- uint32_t length = instruction.length;
- #define GLSL_BOP(op) emit_binary_op(ops[0], ops[1], ops[2], ops[3], #op)
- #define GLSL_BOP_CAST(op, type) \
- emit_binary_op_cast(ops[0], ops[1], ops[2], ops[3], #op, type, \
- opcode_is_sign_invariant(opcode), implicit_integer_promotion)
- #define GLSL_UOP(op) emit_unary_op(ops[0], ops[1], ops[2], #op)
- #define GLSL_UOP_CAST(op) emit_unary_op_cast(ops[0], ops[1], ops[2], #op)
- #define GLSL_QFOP(op) emit_quaternary_func_op(ops[0], ops[1], ops[2], ops[3], ops[4], ops[5], #op)
- #define GLSL_TFOP(op) emit_trinary_func_op(ops[0], ops[1], ops[2], ops[3], ops[4], #op)
- #define GLSL_BFOP(op) emit_binary_func_op(ops[0], ops[1], ops[2], ops[3], #op)
- #define GLSL_BFOP_CAST(op, type) \
- emit_binary_func_op_cast(ops[0], ops[1], ops[2], ops[3], #op, type, opcode_is_sign_invariant(opcode))
- #define GLSL_BFOP(op) emit_binary_func_op(ops[0], ops[1], ops[2], ops[3], #op)
- #define GLSL_UFOP(op) emit_unary_func_op(ops[0], ops[1], ops[2], #op)
- // If we need to do implicit bitcasts, make sure we do it with the correct type.
- uint32_t integer_width = get_integer_width_for_instruction(instruction);
- auto int_type = to_signed_basetype(integer_width);
- auto uint_type = to_unsigned_basetype(integer_width);
- // Handle C implicit integer promotion rules.
- // If we get implicit promotion to int, need to make sure we cast by value to intended return type,
- // otherwise, future sign-dependent operations and bitcasts will break.
- bool implicit_integer_promotion = integer_width < 32 && backend.implicit_c_integer_promotion_rules &&
- opcode_can_promote_integer_implicitly(opcode) &&
- get<SPIRType>(ops[0]).vecsize == 1;
- opcode = get_remapped_spirv_op(opcode);
- switch (opcode)
- {
- // Dealing with memory
- case OpLoad:
- {
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- uint32_t ptr = ops[2];
- flush_variable_declaration(ptr);
- // If we're loading from memory that cannot be changed by the shader,
- // just forward the expression directly to avoid needless temporaries.
- // If an expression is mutable and forwardable, we speculate that it is immutable.
- bool forward = should_forward(ptr) && forced_temporaries.find(id) == end(forced_temporaries);
- // If loading a non-native row-major matrix, mark the expression as need_transpose.
- bool need_transpose = false;
- bool old_need_transpose = false;
- auto *ptr_expression = maybe_get<SPIRExpression>(ptr);
- if (forward)
- {
- // If we're forwarding the load, we're also going to forward transpose state, so don't transpose while
- // taking the expression.
- if (ptr_expression && ptr_expression->need_transpose)
- {
- old_need_transpose = true;
- ptr_expression->need_transpose = false;
- need_transpose = true;
- }
- else if (is_non_native_row_major_matrix(ptr))
- need_transpose = true;
- }
- // If we are forwarding this load,
- // don't register the read to access chain here, defer that to when we actually use the expression,
- // using the add_implied_read_expression mechanism.
- string expr;
- bool is_packed = has_extended_decoration(ptr, SPIRVCrossDecorationPhysicalTypePacked);
- bool is_remapped = has_extended_decoration(ptr, SPIRVCrossDecorationPhysicalTypeID);
- if (forward || (!is_packed && !is_remapped))
- {
- // For the simple case, we do not need to deal with repacking.
- expr = to_dereferenced_expression(ptr, false);
- }
- else
- {
- // If we are not forwarding the expression, we need to unpack and resolve any physical type remapping here before
- // storing the expression to a temporary.
- expr = to_unpacked_expression(ptr);
- }
- auto &type = get<SPIRType>(result_type);
- auto &expr_type = expression_type(ptr);
- // If the expression has more vector components than the result type, insert
- // a swizzle. This shouldn't happen normally on valid SPIR-V, but it might
- // happen with e.g. the MSL backend replacing the type of an input variable.
- if (expr_type.vecsize > type.vecsize)
- expr = enclose_expression(expr + vector_swizzle(type.vecsize, 0));
- if (forward && ptr_expression)
- ptr_expression->need_transpose = old_need_transpose;
- // We might need to cast in order to load from a builtin.
- cast_from_variable_load(ptr, expr, type);
- if (forward && ptr_expression)
- ptr_expression->need_transpose = false;
- // We might be trying to load a gl_Position[N], where we should be
- // doing float4[](gl_in[i].gl_Position, ...) instead.
- // Similar workarounds are required for input arrays in tessellation.
- // Also, loading from gl_SampleMask array needs special unroll.
- unroll_array_from_complex_load(id, ptr, expr);
- if (!type_is_opaque_value(type) && has_decoration(ptr, DecorationNonUniform))
- {
- // If we're loading something non-opaque, we need to handle non-uniform descriptor access.
- convert_non_uniform_expression(expr, ptr);
- }
- if (forward && ptr_expression)
- ptr_expression->need_transpose = old_need_transpose;
- bool flattened = ptr_expression && flattened_buffer_blocks.count(ptr_expression->loaded_from) != 0;
- if (backend.needs_row_major_load_workaround && !is_non_native_row_major_matrix(ptr) && !flattened)
- rewrite_load_for_wrapped_row_major(expr, result_type, ptr);
- // By default, suppress usage tracking since using same expression multiple times does not imply any extra work.
- // However, if we try to load a complex, composite object from a flattened buffer,
- // we should avoid emitting the same code over and over and lower the result to a temporary.
- bool usage_tracking = flattened && (type.basetype == SPIRType::Struct || (type.columns > 1));
- SPIRExpression *e = nullptr;
- if (!forward && expression_is_non_value_type_array(ptr))
- {
- // Complicated load case where we need to make a copy of ptr, but we cannot, because
- // it is an array, and our backend does not support arrays as value types.
- // Emit the temporary, and copy it explicitly.
- e = &emit_uninitialized_temporary_expression(result_type, id);
- emit_array_copy(nullptr, id, ptr, StorageClassFunction, get_expression_effective_storage_class(ptr));
- }
- else
- e = &emit_op(result_type, id, expr, forward, !usage_tracking);
- e->need_transpose = need_transpose;
- register_read(id, ptr, forward);
- if (forward)
- {
- // Pass through whether the result is of a packed type and the physical type ID.
- if (has_extended_decoration(ptr, SPIRVCrossDecorationPhysicalTypePacked))
- set_extended_decoration(id, SPIRVCrossDecorationPhysicalTypePacked);
- if (has_extended_decoration(ptr, SPIRVCrossDecorationPhysicalTypeID))
- {
- set_extended_decoration(id, SPIRVCrossDecorationPhysicalTypeID,
- get_extended_decoration(ptr, SPIRVCrossDecorationPhysicalTypeID));
- }
- }
- else
- {
- // This might have been set on an earlier compilation iteration, force it to be unset.
- unset_extended_decoration(id, SPIRVCrossDecorationPhysicalTypePacked);
- unset_extended_decoration(id, SPIRVCrossDecorationPhysicalTypeID);
- }
- inherit_expression_dependencies(id, ptr);
- if (forward)
- add_implied_read_expression(*e, ptr);
- break;
- }
- case OpInBoundsAccessChain:
- case OpAccessChain:
- case OpPtrAccessChain:
- {
- auto *var = maybe_get<SPIRVariable>(ops[2]);
- if (var)
- flush_variable_declaration(var->self);
- // If the base is immutable, the access chain pointer must also be.
- // If an expression is mutable and forwardable, we speculate that it is immutable.
- AccessChainMeta meta;
- bool ptr_chain = opcode == OpPtrAccessChain;
- auto &target_type = get<SPIRType>(ops[0]);
- auto e = access_chain(ops[2], &ops[3], length - 3, target_type, &meta, ptr_chain);
- // If the base is flattened UBO of struct type, the expression has to be a composite.
- // In that case, backends which do not support inline syntax need it to be bound to a temporary.
- // Otherwise, invalid expressions like ({UBO[0].xyz, UBO[0].w, UBO[1]}).member are emitted.
- bool requires_temporary = false;
- if (flattened_buffer_blocks.count(ops[2]) && target_type.basetype == SPIRType::Struct)
- requires_temporary = !backend.can_declare_struct_inline;
- auto &expr = requires_temporary ?
- emit_op(ops[0], ops[1], std::move(e), false) :
- set<SPIRExpression>(ops[1], std::move(e), ops[0], should_forward(ops[2]));
- auto *backing_variable = maybe_get_backing_variable(ops[2]);
- expr.loaded_from = backing_variable ? backing_variable->self : ID(ops[2]);
- expr.need_transpose = meta.need_transpose;
- expr.access_chain = true;
- expr.access_meshlet_position_y = meta.access_meshlet_position_y;
- // Mark the result as being packed. Some platforms handled packed vectors differently than non-packed.
- if (meta.storage_is_packed)
- set_extended_decoration(ops[1], SPIRVCrossDecorationPhysicalTypePacked);
- if (meta.storage_physical_type != 0)
- set_extended_decoration(ops[1], SPIRVCrossDecorationPhysicalTypeID, meta.storage_physical_type);
- if (meta.storage_is_invariant)
- set_decoration(ops[1], DecorationInvariant);
- if (meta.flattened_struct)
- flattened_structs[ops[1]] = true;
- if (meta.relaxed_precision && backend.requires_relaxed_precision_analysis)
- set_decoration(ops[1], DecorationRelaxedPrecision);
- if (meta.chain_is_builtin)
- set_decoration(ops[1], DecorationBuiltIn, meta.builtin);
- // If we have some expression dependencies in our access chain, this access chain is technically a forwarded
- // temporary which could be subject to invalidation.
- // Need to assume we're forwarded while calling inherit_expression_depdendencies.
- forwarded_temporaries.insert(ops[1]);
- // The access chain itself is never forced to a temporary, but its dependencies might.
- suppressed_usage_tracking.insert(ops[1]);
- for (uint32_t i = 2; i < length; i++)
- {
- inherit_expression_dependencies(ops[1], ops[i]);
- add_implied_read_expression(expr, ops[i]);
- }
- // If we have no dependencies after all, i.e., all indices in the access chain are immutable temporaries,
- // we're not forwarded after all.
- if (expr.expression_dependencies.empty())
- forwarded_temporaries.erase(ops[1]);
- break;
- }
- case OpStore:
- {
- auto *var = maybe_get<SPIRVariable>(ops[0]);
- if (var && var->statically_assigned)
- var->static_expression = ops[1];
- else if (var && var->loop_variable && !var->loop_variable_enable)
- var->static_expression = ops[1];
- else if (var && var->remapped_variable && var->static_expression)
- {
- // Skip the write.
- }
- else if (flattened_structs.count(ops[0]))
- {
- store_flattened_struct(ops[0], ops[1]);
- register_write(ops[0]);
- }
- else
- {
- emit_store_statement(ops[0], ops[1]);
- }
- // Storing a pointer results in a variable pointer, so we must conservatively assume
- // we can write through it.
- if (expression_type(ops[1]).pointer)
- register_write(ops[1]);
- break;
- }
- case OpArrayLength:
- {
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- auto e = access_chain_internal(ops[2], &ops[3], length - 3, ACCESS_CHAIN_INDEX_IS_LITERAL_BIT, nullptr);
- if (has_decoration(ops[2], DecorationNonUniform))
- convert_non_uniform_expression(e, ops[2]);
- set<SPIRExpression>(id, join(type_to_glsl(get<SPIRType>(result_type)), "(", e, ".length())"), result_type,
- true);
- break;
- }
- // Function calls
- case OpFunctionCall:
- {
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- uint32_t func = ops[2];
- const auto *arg = &ops[3];
- length -= 3;
- auto &callee = get<SPIRFunction>(func);
- auto &return_type = get<SPIRType>(callee.return_type);
- bool pure = function_is_pure(callee);
- bool control_dependent = function_is_control_dependent(callee);
- bool callee_has_out_variables = false;
- bool emit_return_value_as_argument = false;
- // Invalidate out variables passed to functions since they can be OpStore'd to.
- for (uint32_t i = 0; i < length; i++)
- {
- if (callee.arguments[i].write_count)
- {
- register_call_out_argument(arg[i]);
- callee_has_out_variables = true;
- }
- flush_variable_declaration(arg[i]);
- }
- if (!return_type.array.empty() && !backend.can_return_array)
- {
- callee_has_out_variables = true;
- emit_return_value_as_argument = true;
- }
- if (!pure)
- register_impure_function_call();
- string funexpr;
- SmallVector<string> arglist;
- funexpr += to_name(func) + "(";
- if (emit_return_value_as_argument)
- {
- statement(type_to_glsl(return_type), " ", to_name(id), type_to_array_glsl(return_type, 0), ";");
- arglist.push_back(to_name(id));
- }
- for (uint32_t i = 0; i < length; i++)
- {
- // Do not pass in separate images or samplers if we're remapping
- // to combined image samplers.
- if (skip_argument(arg[i]))
- continue;
- arglist.push_back(to_func_call_arg(callee.arguments[i], arg[i]));
- }
- for (auto &combined : callee.combined_parameters)
- {
- auto image_id = combined.global_image ? combined.image_id : VariableID(arg[combined.image_id]);
- auto sampler_id = combined.global_sampler ? combined.sampler_id : VariableID(arg[combined.sampler_id]);
- arglist.push_back(to_combined_image_sampler(image_id, sampler_id));
- }
- append_global_func_args(callee, length, arglist);
- funexpr += merge(arglist);
- funexpr += ")";
- // Check for function call constraints.
- check_function_call_constraints(arg, length);
- if (return_type.basetype != SPIRType::Void)
- {
- // If the function actually writes to an out variable,
- // take the conservative route and do not forward.
- // The problem is that we might not read the function
- // result (and emit the function) before an out variable
- // is read (common case when return value is ignored!
- // In order to avoid start tracking invalid variables,
- // just avoid the forwarding problem altogether.
- bool forward = args_will_forward(id, arg, length, pure) && !callee_has_out_variables && pure &&
- (forced_temporaries.find(id) == end(forced_temporaries));
- if (emit_return_value_as_argument)
- {
- statement(funexpr, ";");
- set<SPIRExpression>(id, to_name(id), result_type, true);
- }
- else
- emit_op(result_type, id, funexpr, forward);
- // Function calls are implicit loads from all variables in question.
- // Set dependencies for them.
- for (uint32_t i = 0; i < length; i++)
- register_read(id, arg[i], forward);
- // If we're going to forward the temporary result,
- // put dependencies on every variable that must not change.
- if (forward)
- register_global_read_dependencies(callee, id);
- }
- else
- statement(funexpr, ";");
- if (control_dependent)
- register_control_dependent_expression(id);
- break;
- }
- // Composite munging
- case OpCompositeConstruct:
- {
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- const auto *const elems = &ops[2];
- length -= 2;
- bool forward = true;
- for (uint32_t i = 0; i < length; i++)
- forward = forward && should_forward(elems[i]);
- auto &out_type = get<SPIRType>(result_type);
- auto *in_type = length > 0 ? &expression_type(elems[0]) : nullptr;
- // Only splat if we have vector constructors.
- // Arrays and structs must be initialized properly in full.
- bool composite = !out_type.array.empty() || out_type.basetype == SPIRType::Struct;
- bool splat = false;
- bool swizzle_splat = false;
- if (in_type)
- {
- splat = in_type->vecsize == 1 && in_type->columns == 1 && !composite && backend.use_constructor_splatting;
- swizzle_splat = in_type->vecsize == 1 && in_type->columns == 1 && backend.can_swizzle_scalar;
- if (ir.ids[elems[0]].get_type() == TypeConstant && !type_is_floating_point(*in_type))
- {
- // Cannot swizzle literal integers as a special case.
- swizzle_splat = false;
- }
- }
- if (splat || swizzle_splat)
- {
- uint32_t input = elems[0];
- for (uint32_t i = 0; i < length; i++)
- {
- if (input != elems[i])
- {
- splat = false;
- swizzle_splat = false;
- }
- }
- }
- if (out_type.basetype == SPIRType::Struct && !backend.can_declare_struct_inline)
- forward = false;
- if (!out_type.array.empty() && !backend.can_declare_arrays_inline)
- forward = false;
- if (type_is_empty(out_type) && !backend.supports_empty_struct)
- forward = false;
- string constructor_op;
- if (backend.use_initializer_list && composite)
- {
- bool needs_trailing_tracket = false;
- // Only use this path if we are building composites.
- // This path cannot be used for arithmetic.
- if (backend.use_typed_initializer_list && out_type.basetype == SPIRType::Struct && out_type.array.empty())
- constructor_op += type_to_glsl_constructor(get<SPIRType>(result_type));
- else if (backend.use_typed_initializer_list && backend.array_is_value_type && !out_type.array.empty())
- {
- // MSL path. Array constructor is baked into type here, do not use _constructor variant.
- constructor_op += type_to_glsl_constructor(get<SPIRType>(result_type)) + "(";
- needs_trailing_tracket = true;
- }
- constructor_op += "{ ";
- if (type_is_empty(out_type) && !backend.supports_empty_struct)
- constructor_op += "0";
- else if (splat)
- constructor_op += to_unpacked_expression(elems[0]);
- else
- constructor_op += build_composite_combiner(result_type, elems, length);
- constructor_op += " }";
- if (needs_trailing_tracket)
- constructor_op += ")";
- }
- else if (swizzle_splat && !composite)
- {
- constructor_op = remap_swizzle(get<SPIRType>(result_type), 1, to_unpacked_expression(elems[0]));
- }
- else
- {
- constructor_op = type_to_glsl_constructor(get<SPIRType>(result_type)) + "(";
- if (type_is_empty(out_type) && !backend.supports_empty_struct)
- constructor_op += "0";
- else if (splat)
- constructor_op += to_unpacked_expression(elems[0]);
- else
- constructor_op += build_composite_combiner(result_type, elems, length);
- constructor_op += ")";
- }
- if (!constructor_op.empty())
- {
- emit_op(result_type, id, constructor_op, forward);
- for (uint32_t i = 0; i < length; i++)
- inherit_expression_dependencies(id, elems[i]);
- }
- break;
- }
- case OpVectorInsertDynamic:
- {
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- uint32_t vec = ops[2];
- uint32_t comp = ops[3];
- uint32_t index = ops[4];
- flush_variable_declaration(vec);
- // Make a copy, then use access chain to store the variable.
- statement(declare_temporary(result_type, id), to_expression(vec), ";");
- set<SPIRExpression>(id, to_name(id), result_type, true);
- auto chain = access_chain_internal(id, &index, 1, 0, nullptr);
- statement(chain, " = ", to_unpacked_expression(comp), ";");
- break;
- }
- case OpVectorExtractDynamic:
- {
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- auto expr = access_chain_internal(ops[2], &ops[3], 1, 0, nullptr);
- emit_op(result_type, id, expr, should_forward(ops[2]));
- inherit_expression_dependencies(id, ops[2]);
- inherit_expression_dependencies(id, ops[3]);
- break;
- }
- case OpCompositeExtract:
- {
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- length -= 3;
- auto &type = get<SPIRType>(result_type);
- // We can only split the expression here if our expression is forwarded as a temporary.
- bool allow_base_expression = forced_temporaries.find(id) == end(forced_temporaries);
- // Do not allow base expression for struct members. We risk doing "swizzle" optimizations in this case.
- auto &composite_type = expression_type(ops[2]);
- bool composite_type_is_complex = composite_type.basetype == SPIRType::Struct || !composite_type.array.empty();
- if (composite_type_is_complex)
- allow_base_expression = false;
- if (composite_type.op == OpTypeCooperativeMatrixKHR)
- allow_base_expression = false;
- // Packed expressions or physical ID mapped expressions cannot be split up.
- if (has_extended_decoration(ops[2], SPIRVCrossDecorationPhysicalTypePacked) ||
- has_extended_decoration(ops[2], SPIRVCrossDecorationPhysicalTypeID))
- allow_base_expression = false;
- // Cannot use base expression for row-major matrix row-extraction since we need to interleave access pattern
- // into the base expression.
- if (is_non_native_row_major_matrix(ops[2]))
- allow_base_expression = false;
- AccessChainMeta meta;
- SPIRExpression *e = nullptr;
- auto *c = maybe_get<SPIRConstant>(ops[2]);
- if (c && !c->specialization && !composite_type_is_complex)
- {
- auto expr = to_extract_constant_composite_expression(result_type, *c, ops + 3, length);
- e = &emit_op(result_type, id, expr, true, true);
- }
- else if (allow_base_expression && should_forward(ops[2]) && type.vecsize == 1 && type.columns == 1 && length == 1)
- {
- // Only apply this optimization if result is scalar.
- // We want to split the access chain from the base.
- // This is so we can later combine different CompositeExtract results
- // with CompositeConstruct without emitting code like
- //
- // vec3 temp = texture(...).xyz
- // vec4(temp.x, temp.y, temp.z, 1.0).
- //
- // when we actually wanted to emit this
- // vec4(texture(...).xyz, 1.0).
- //
- // Including the base will prevent this and would trigger multiple reads
- // from expression causing it to be forced to an actual temporary in GLSL.
- auto expr = access_chain_internal(ops[2], &ops[3], length,
- ACCESS_CHAIN_INDEX_IS_LITERAL_BIT | ACCESS_CHAIN_CHAIN_ONLY_BIT |
- ACCESS_CHAIN_FORCE_COMPOSITE_BIT, &meta);
- e = &emit_op(result_type, id, expr, true, should_suppress_usage_tracking(ops[2]));
- inherit_expression_dependencies(id, ops[2]);
- e->base_expression = ops[2];
- if (meta.relaxed_precision && backend.requires_relaxed_precision_analysis)
- set_decoration(ops[1], DecorationRelaxedPrecision);
- }
- else
- {
- auto expr = access_chain_internal(ops[2], &ops[3], length,
- ACCESS_CHAIN_INDEX_IS_LITERAL_BIT | ACCESS_CHAIN_FORCE_COMPOSITE_BIT, &meta);
- e = &emit_op(result_type, id, expr, should_forward(ops[2]), should_suppress_usage_tracking(ops[2]));
- inherit_expression_dependencies(id, ops[2]);
- }
- // Pass through some meta information to the loaded expression.
- // We can still end up loading a buffer type to a variable, then CompositeExtract from it
- // instead of loading everything through an access chain.
- e->need_transpose = meta.need_transpose;
- if (meta.storage_is_packed)
- set_extended_decoration(id, SPIRVCrossDecorationPhysicalTypePacked);
- if (meta.storage_physical_type != 0)
- set_extended_decoration(id, SPIRVCrossDecorationPhysicalTypeID, meta.storage_physical_type);
- if (meta.storage_is_invariant)
- set_decoration(id, DecorationInvariant);
- break;
- }
- case OpCompositeInsert:
- {
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- uint32_t obj = ops[2];
- uint32_t composite = ops[3];
- const auto *elems = &ops[4];
- length -= 4;
- flush_variable_declaration(composite);
- // CompositeInsert requires a copy + modification, but this is very awkward code in HLL.
- // Speculate that the input composite is no longer used, and we can modify it in-place.
- // There are various scenarios where this is not possible to satisfy.
- bool can_modify_in_place = true;
- forced_temporaries.insert(id);
- // Cannot safely RMW PHI variables since they have no way to be invalidated,
- // forcing temporaries is not going to help.
- // This is similar for Constant and Undef inputs.
- // The only safe thing to RMW is SPIRExpression.
- // If the expression has already been used (i.e. used in a continue block), we have to keep using
- // that loop variable, since we won't be able to override the expression after the fact.
- // If the composite is hoisted, we might never be able to properly invalidate any usage
- // of that composite in a subsequent loop iteration.
- if (invalid_expressions.count(composite) ||
- block_composite_insert_overwrite.count(composite) ||
- hoisted_temporaries.count(id) || hoisted_temporaries.count(composite) ||
- maybe_get<SPIRExpression>(composite) == nullptr)
- {
- can_modify_in_place = false;
- }
- else if (backend.requires_relaxed_precision_analysis &&
- has_decoration(composite, DecorationRelaxedPrecision) !=
- has_decoration(id, DecorationRelaxedPrecision) &&
- get<SPIRType>(result_type).basetype != SPIRType::Struct)
- {
- // Similarly, if precision does not match for input and output,
- // we cannot alias them. If we write a composite into a relaxed precision
- // ID, we might get a false truncation.
- can_modify_in_place = false;
- }
- if (can_modify_in_place)
- {
- // Have to make sure the modified SSA value is bound to a temporary so we can modify it in-place.
- if (!forced_temporaries.count(composite))
- force_temporary_and_recompile(composite);
- auto chain = access_chain_internal(composite, elems, length, ACCESS_CHAIN_INDEX_IS_LITERAL_BIT, nullptr);
- statement(chain, " = ", to_unpacked_expression(obj), ";");
- set<SPIRExpression>(id, to_expression(composite), result_type, true);
- invalid_expressions.insert(composite);
- composite_insert_overwritten.insert(composite);
- }
- else
- {
- if (maybe_get<SPIRUndef>(composite) != nullptr)
- {
- emit_uninitialized_temporary_expression(result_type, id);
- }
- else
- {
- // Make a copy, then use access chain to store the variable.
- statement(declare_temporary(result_type, id), to_expression(composite), ";");
- set<SPIRExpression>(id, to_name(id), result_type, true);
- }
- auto chain = access_chain_internal(id, elems, length, ACCESS_CHAIN_INDEX_IS_LITERAL_BIT, nullptr);
- statement(chain, " = ", to_unpacked_expression(obj), ";");
- }
- break;
- }
- case OpCopyMemory:
- {
- uint32_t lhs = ops[0];
- uint32_t rhs = ops[1];
- if (lhs != rhs)
- {
- uint32_t &tmp_id = extra_sub_expressions[instruction.offset | EXTRA_SUB_EXPRESSION_TYPE_STREAM_OFFSET];
- if (!tmp_id)
- tmp_id = ir.increase_bound_by(1);
- uint32_t tmp_type_id = expression_type(rhs).parent_type;
- EmbeddedInstruction fake_load, fake_store;
- fake_load.op = OpLoad;
- fake_load.length = 3;
- fake_load.ops.push_back(tmp_type_id);
- fake_load.ops.push_back(tmp_id);
- fake_load.ops.push_back(rhs);
- fake_store.op = OpStore;
- fake_store.length = 2;
- fake_store.ops.push_back(lhs);
- fake_store.ops.push_back(tmp_id);
- // Load and Store do a *lot* of workarounds, and we'd like to reuse them as much as possible.
- // Synthesize a fake Load and Store pair for CopyMemory.
- emit_instruction(fake_load);
- emit_instruction(fake_store);
- }
- break;
- }
- case OpCopyLogical:
- {
- // This is used for copying object of different types, arrays and structs.
- // We need to unroll the copy, element-by-element.
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- uint32_t rhs = ops[2];
- emit_uninitialized_temporary_expression(result_type, id);
- emit_copy_logical_type(id, result_type, rhs, expression_type_id(rhs), {});
- break;
- }
- case OpCopyObject:
- {
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- uint32_t rhs = ops[2];
- bool pointer = get<SPIRType>(result_type).pointer;
- auto *chain = maybe_get<SPIRAccessChain>(rhs);
- auto *imgsamp = maybe_get<SPIRCombinedImageSampler>(rhs);
- if (chain)
- {
- // Cannot lower to a SPIRExpression, just copy the object.
- auto &e = set<SPIRAccessChain>(id, *chain);
- e.self = id;
- }
- else if (imgsamp)
- {
- // Cannot lower to a SPIRExpression, just copy the object.
- // GLSL does not currently use this type and will never get here, but MSL does.
- // Handled here instead of CompilerMSL for better integration and general handling,
- // and in case GLSL or other subclasses require it in the future.
- auto &e = set<SPIRCombinedImageSampler>(id, *imgsamp);
- e.self = id;
- }
- else if (expression_is_lvalue(rhs) && !pointer)
- {
- // Need a copy.
- // For pointer types, we copy the pointer itself.
- emit_op(result_type, id, to_unpacked_expression(rhs), false);
- }
- else
- {
- // RHS expression is immutable, so just forward it.
- // Copying these things really make no sense, but
- // seems to be allowed anyways.
- auto &e = emit_op(result_type, id, to_expression(rhs), true, true);
- if (pointer)
- {
- auto *var = maybe_get_backing_variable(rhs);
- e.loaded_from = var ? var->self : ID(0);
- }
- // If we're copying an access chain, need to inherit the read expressions.
- auto *rhs_expr = maybe_get<SPIRExpression>(rhs);
- if (rhs_expr)
- {
- e.implied_read_expressions = rhs_expr->implied_read_expressions;
- e.expression_dependencies = rhs_expr->expression_dependencies;
- }
- }
- break;
- }
- case OpVectorShuffle:
- {
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- uint32_t vec0 = ops[2];
- uint32_t vec1 = ops[3];
- const auto *elems = &ops[4];
- length -= 4;
- auto &type0 = expression_type(vec0);
- // If we have the undefined swizzle index -1, we need to swizzle in undefined data,
- // or in our case, T(0).
- bool shuffle = false;
- for (uint32_t i = 0; i < length; i++)
- if (elems[i] >= type0.vecsize || elems[i] == 0xffffffffu)
- shuffle = true;
- // Cannot use swizzles with packed expressions, force shuffle path.
- if (!shuffle && has_extended_decoration(vec0, SPIRVCrossDecorationPhysicalTypePacked))
- shuffle = true;
- string expr;
- bool should_fwd, trivial_forward;
- if (shuffle)
- {
- should_fwd = should_forward(vec0) && should_forward(vec1);
- trivial_forward = should_suppress_usage_tracking(vec0) && should_suppress_usage_tracking(vec1);
- // Constructor style and shuffling from two different vectors.
- SmallVector<string> args;
- for (uint32_t i = 0; i < length; i++)
- {
- if (elems[i] == 0xffffffffu)
- {
- // Use a constant 0 here.
- // We could use the first component or similar, but then we risk propagating
- // a value we might not need, and bog down codegen.
- SPIRConstant c;
- c.constant_type = type0.parent_type;
- assert(type0.parent_type != ID(0));
- args.push_back(constant_expression(c));
- }
- else if (elems[i] >= type0.vecsize)
- args.push_back(to_extract_component_expression(vec1, elems[i] - type0.vecsize));
- else
- args.push_back(to_extract_component_expression(vec0, elems[i]));
- }
- expr += join(type_to_glsl_constructor(get<SPIRType>(result_type)), "(", merge(args), ")");
- }
- else
- {
- should_fwd = should_forward(vec0);
- trivial_forward = should_suppress_usage_tracking(vec0);
- // We only source from first vector, so can use swizzle.
- // If the vector is packed, unpack it before applying a swizzle (needed for MSL)
- expr += to_enclosed_unpacked_expression(vec0);
- expr += ".";
- for (uint32_t i = 0; i < length; i++)
- {
- assert(elems[i] != 0xffffffffu);
- expr += index_to_swizzle(elems[i]);
- }
- if (backend.swizzle_is_function && length > 1)
- expr += "()";
- }
- // A shuffle is trivial in that it doesn't actually *do* anything.
- // We inherit the forwardedness from our arguments to avoid flushing out to temporaries when it's not really needed.
- emit_op(result_type, id, expr, should_fwd, trivial_forward);
- inherit_expression_dependencies(id, vec0);
- if (vec0 != vec1)
- inherit_expression_dependencies(id, vec1);
- break;
- }
- // ALU
- case OpIsNan:
- if (!is_legacy())
- GLSL_UFOP(isnan);
- else
- {
- // Check if the number doesn't equal itself
- auto &type = get<SPIRType>(ops[0]);
- if (type.vecsize > 1)
- emit_binary_func_op(ops[0], ops[1], ops[2], ops[2], "notEqual");
- else
- emit_binary_op(ops[0], ops[1], ops[2], ops[2], "!=");
- }
- break;
- case OpIsInf:
- if (!is_legacy())
- GLSL_UFOP(isinf);
- else
- {
- // inf * 2 == inf by IEEE 754 rules, note this also applies to 0.0
- // This is more reliable than checking if product with zero is NaN
- uint32_t result_type = ops[0];
- uint32_t result_id = ops[1];
- uint32_t operand = ops[2];
- auto &type = get<SPIRType>(result_type);
- std::string expr;
- if (type.vecsize > 1)
- {
- expr = type_to_glsl_constructor(type);
- expr += '(';
- for (uint32_t i = 0; i < type.vecsize; i++)
- {
- auto comp = to_extract_component_expression(operand, i);
- expr += join(comp, " != 0.0 && 2.0 * ", comp, " == ", comp);
- if (i + 1 < type.vecsize)
- expr += ", ";
- }
- expr += ')';
- }
- else
- {
- // Register an extra read to force writing out a temporary
- auto oper = to_enclosed_expression(operand);
- track_expression_read(operand);
- expr += join(oper, " != 0.0 && 2.0 * ", oper, " == ", oper);
- }
- emit_op(result_type, result_id, expr, should_forward(operand));
- inherit_expression_dependencies(result_id, operand);
- }
- break;
- case OpSNegate:
- if (implicit_integer_promotion || expression_type_id(ops[2]) != ops[0])
- GLSL_UOP_CAST(-);
- else
- GLSL_UOP(-);
- break;
- case OpFNegate:
- GLSL_UOP(-);
- break;
- case OpIAdd:
- {
- // For simple arith ops, prefer the output type if there's a mismatch to avoid extra bitcasts.
- auto type = get<SPIRType>(ops[0]).basetype;
- GLSL_BOP_CAST(+, type);
- break;
- }
- case OpFAdd:
- GLSL_BOP(+);
- break;
- case OpISub:
- {
- auto type = get<SPIRType>(ops[0]).basetype;
- GLSL_BOP_CAST(-, type);
- break;
- }
- case OpFSub:
- GLSL_BOP(-);
- break;
- case OpIMul:
- {
- auto type = get<SPIRType>(ops[0]).basetype;
- GLSL_BOP_CAST(*, type);
- break;
- }
- case OpVectorTimesMatrix:
- case OpMatrixTimesVector:
- {
- // If the matrix needs transpose, just flip the multiply order.
- auto *e = maybe_get<SPIRExpression>(ops[opcode == OpMatrixTimesVector ? 2 : 3]);
- if (e && e->need_transpose)
- {
- e->need_transpose = false;
- string expr;
- if (opcode == OpMatrixTimesVector)
- expr = join(to_enclosed_unpacked_expression(ops[3]), " * ",
- enclose_expression(to_unpacked_row_major_matrix_expression(ops[2])));
- else
- expr = join(enclose_expression(to_unpacked_row_major_matrix_expression(ops[3])), " * ",
- to_enclosed_unpacked_expression(ops[2]));
- bool forward = should_forward(ops[2]) && should_forward(ops[3]);
- emit_op(ops[0], ops[1], expr, forward);
- e->need_transpose = true;
- inherit_expression_dependencies(ops[1], ops[2]);
- inherit_expression_dependencies(ops[1], ops[3]);
- }
- else
- GLSL_BOP(*);
- break;
- }
- case OpMatrixTimesMatrix:
- {
- auto *a = maybe_get<SPIRExpression>(ops[2]);
- auto *b = maybe_get<SPIRExpression>(ops[3]);
- // If both matrices need transpose, we can multiply in flipped order and tag the expression as transposed.
- // a^T * b^T = (b * a)^T.
- if (a && b && a->need_transpose && b->need_transpose)
- {
- a->need_transpose = false;
- b->need_transpose = false;
- auto expr = join(enclose_expression(to_unpacked_row_major_matrix_expression(ops[3])), " * ",
- enclose_expression(to_unpacked_row_major_matrix_expression(ops[2])));
- bool forward = should_forward(ops[2]) && should_forward(ops[3]);
- emit_transposed_op(ops[0], ops[1], expr, forward);
- a->need_transpose = true;
- b->need_transpose = true;
- inherit_expression_dependencies(ops[1], ops[2]);
- inherit_expression_dependencies(ops[1], ops[3]);
- }
- else
- GLSL_BOP(*);
- break;
- }
- case OpMatrixTimesScalar:
- {
- auto *a = maybe_get<SPIRExpression>(ops[2]);
- // If the matrix need transpose, just mark the result as needing so.
- if (a && a->need_transpose)
- {
- a->need_transpose = false;
- auto expr = join(enclose_expression(to_unpacked_row_major_matrix_expression(ops[2])), " * ",
- to_enclosed_unpacked_expression(ops[3]));
- bool forward = should_forward(ops[2]) && should_forward(ops[3]);
- emit_transposed_op(ops[0], ops[1], expr, forward);
- a->need_transpose = true;
- inherit_expression_dependencies(ops[1], ops[2]);
- inherit_expression_dependencies(ops[1], ops[3]);
- }
- else
- GLSL_BOP(*);
- break;
- }
- case OpFMul:
- case OpVectorTimesScalar:
- GLSL_BOP(*);
- break;
- case OpOuterProduct:
- if (options.version < 120) // Matches GLSL 1.10 / ESSL 1.00
- {
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- uint32_t a = ops[2];
- uint32_t b = ops[3];
- auto &type = get<SPIRType>(result_type);
- string expr = type_to_glsl_constructor(type);
- expr += "(";
- for (uint32_t col = 0; col < type.columns; col++)
- {
- expr += to_enclosed_expression(a);
- expr += " * ";
- expr += to_extract_component_expression(b, col);
- if (col + 1 < type.columns)
- expr += ", ";
- }
- expr += ")";
- emit_op(result_type, id, expr, should_forward(a) && should_forward(b));
- inherit_expression_dependencies(id, a);
- inherit_expression_dependencies(id, b);
- }
- else
- GLSL_BFOP(outerProduct);
- break;
- case OpDot:
- GLSL_BFOP(dot);
- break;
- case OpTranspose:
- if (options.version < 120) // Matches GLSL 1.10 / ESSL 1.00
- {
- // transpose() is not available, so instead, flip need_transpose,
- // which can later be turned into an emulated transpose op by
- // convert_row_major_matrix(), if necessary.
- uint32_t result_type = ops[0];
- uint32_t result_id = ops[1];
- uint32_t input = ops[2];
- // Force need_transpose to false temporarily to prevent
- // to_expression() from doing the transpose.
- bool need_transpose = false;
- auto *input_e = maybe_get<SPIRExpression>(input);
- if (input_e)
- swap(need_transpose, input_e->need_transpose);
- bool forward = should_forward(input);
- auto &e = emit_op(result_type, result_id, to_expression(input), forward);
- e.need_transpose = !need_transpose;
- // Restore the old need_transpose flag.
- if (input_e)
- input_e->need_transpose = need_transpose;
- }
- else
- GLSL_UFOP(transpose);
- break;
- case OpSRem:
- {
- uint32_t result_type = ops[0];
- uint32_t result_id = ops[1];
- uint32_t op0 = ops[2];
- uint32_t op1 = ops[3];
- auto &out_type = get<SPIRType>(result_type);
- bool forward = should_forward(op0) && should_forward(op1);
- string cast_op0, cast_op1;
- auto expected_type = binary_op_bitcast_helper(cast_op0, cast_op1, int_type, op0, op1, false);
- // Needs special handling.
- auto expr = join(cast_op0, " - ", cast_op1, " * ", "(", cast_op0, " / ", cast_op1, ")");
- if (implicit_integer_promotion)
- {
- expr = join(type_to_glsl(get<SPIRType>(result_type)), '(', expr, ')');
- }
- else if (out_type.basetype != int_type)
- {
- expected_type.basetype = int_type;
- expr = join(bitcast_glsl_op(out_type, expected_type), '(', expr, ')');
- }
- emit_op(result_type, result_id, expr, forward);
- inherit_expression_dependencies(result_id, op0);
- inherit_expression_dependencies(result_id, op1);
- break;
- }
- case OpSDiv:
- GLSL_BOP_CAST(/, int_type);
- break;
- case OpUDiv:
- GLSL_BOP_CAST(/, uint_type);
- break;
- case OpIAddCarry:
- case OpISubBorrow:
- {
- if (options.es && options.version < 310)
- SPIRV_CROSS_THROW("Extended arithmetic is only available from ESSL 310.");
- else if (!options.es && options.version < 400)
- SPIRV_CROSS_THROW("Extended arithmetic is only available from GLSL 400.");
- uint32_t result_type = ops[0];
- uint32_t result_id = ops[1];
- uint32_t op0 = ops[2];
- uint32_t op1 = ops[3];
- auto &type = get<SPIRType>(result_type);
- emit_uninitialized_temporary_expression(result_type, result_id);
- const char *op = opcode == OpIAddCarry ? "uaddCarry" : "usubBorrow";
- statement(to_expression(result_id), ".", to_member_name(type, 0), " = ", op, "(", to_expression(op0), ", ",
- to_expression(op1), ", ", to_expression(result_id), ".", to_member_name(type, 1), ");");
- break;
- }
- case OpUMulExtended:
- case OpSMulExtended:
- {
- if (options.es && options.version < 310)
- SPIRV_CROSS_THROW("Extended arithmetic is only available from ESSL 310.");
- else if (!options.es && options.version < 400)
- SPIRV_CROSS_THROW("Extended arithmetic is only available from GLSL 4000.");
- uint32_t result_type = ops[0];
- uint32_t result_id = ops[1];
- uint32_t op0 = ops[2];
- uint32_t op1 = ops[3];
- auto &type = get<SPIRType>(result_type);
- emit_uninitialized_temporary_expression(result_type, result_id);
- const char *op = opcode == OpUMulExtended ? "umulExtended" : "imulExtended";
- statement(op, "(", to_expression(op0), ", ", to_expression(op1), ", ", to_expression(result_id), ".",
- to_member_name(type, 1), ", ", to_expression(result_id), ".", to_member_name(type, 0), ");");
- break;
- }
- case OpFDiv:
- GLSL_BOP(/);
- break;
- case OpShiftRightLogical:
- GLSL_BOP_CAST(>>, uint_type);
- break;
- case OpShiftRightArithmetic:
- GLSL_BOP_CAST(>>, int_type);
- break;
- case OpShiftLeftLogical:
- {
- auto type = get<SPIRType>(ops[0]).basetype;
- GLSL_BOP_CAST(<<, type);
- break;
- }
- case OpBitwiseOr:
- {
- auto type = get<SPIRType>(ops[0]).basetype;
- GLSL_BOP_CAST(|, type);
- break;
- }
- case OpBitwiseXor:
- {
- auto type = get<SPIRType>(ops[0]).basetype;
- GLSL_BOP_CAST(^, type);
- break;
- }
- case OpBitwiseAnd:
- {
- auto type = get<SPIRType>(ops[0]).basetype;
- GLSL_BOP_CAST(&, type);
- break;
- }
- case OpNot:
- if (implicit_integer_promotion || expression_type_id(ops[2]) != ops[0])
- GLSL_UOP_CAST(~);
- else
- GLSL_UOP(~);
- break;
- case OpUMod:
- GLSL_BOP_CAST(%, uint_type);
- break;
- case OpSMod:
- GLSL_BOP_CAST(%, int_type);
- break;
- case OpFMod:
- GLSL_BFOP(mod);
- break;
- case OpFRem:
- {
- uint32_t result_type = ops[0];
- uint32_t result_id = ops[1];
- uint32_t op0 = ops[2];
- uint32_t op1 = ops[3];
- // Needs special handling.
- bool forward = should_forward(op0) && should_forward(op1);
- std::string expr;
- if (!is_legacy())
- {
- expr = join(to_enclosed_expression(op0), " - ", to_enclosed_expression(op1), " * ", "trunc(",
- to_enclosed_expression(op0), " / ", to_enclosed_expression(op1), ")");
- }
- else
- {
- // Legacy GLSL has no trunc, emulate by casting to int and back
- auto &op0_type = expression_type(op0);
- auto via_type = op0_type;
- via_type.basetype = SPIRType::Int;
- expr = join(to_enclosed_expression(op0), " - ", to_enclosed_expression(op1), " * ",
- type_to_glsl(op0_type), "(", type_to_glsl(via_type), "(",
- to_enclosed_expression(op0), " / ", to_enclosed_expression(op1), "))");
- }
- emit_op(result_type, result_id, expr, forward);
- inherit_expression_dependencies(result_id, op0);
- inherit_expression_dependencies(result_id, op1);
- break;
- }
- // Relational
- case OpAny:
- GLSL_UFOP(any);
- break;
- case OpAll:
- GLSL_UFOP(all);
- break;
- case OpSelect:
- emit_mix_op(ops[0], ops[1], ops[4], ops[3], ops[2]);
- break;
- case OpLogicalOr:
- {
- // No vector variant in GLSL for logical OR.
- auto result_type = ops[0];
- auto id = ops[1];
- auto &type = get<SPIRType>(result_type);
- if (type.vecsize > 1)
- emit_unrolled_binary_op(result_type, id, ops[2], ops[3], "||", false, SPIRType::Unknown);
- else
- GLSL_BOP(||);
- break;
- }
- case OpLogicalAnd:
- {
- // No vector variant in GLSL for logical AND.
- auto result_type = ops[0];
- auto id = ops[1];
- auto &type = get<SPIRType>(result_type);
- if (type.vecsize > 1)
- emit_unrolled_binary_op(result_type, id, ops[2], ops[3], "&&", false, SPIRType::Unknown);
- else
- GLSL_BOP(&&);
- break;
- }
- case OpLogicalNot:
- {
- auto &type = get<SPIRType>(ops[0]);
- if (type.vecsize > 1)
- GLSL_UFOP(not );
- else
- GLSL_UOP(!);
- break;
- }
- case OpIEqual:
- {
- if (expression_type(ops[2]).vecsize > 1)
- GLSL_BFOP_CAST(equal, int_type);
- else
- GLSL_BOP_CAST(==, int_type);
- break;
- }
- case OpLogicalEqual:
- case OpFOrdEqual:
- {
- if (expression_type(ops[2]).vecsize > 1)
- GLSL_BFOP(equal);
- else
- GLSL_BOP(==);
- break;
- }
- case OpINotEqual:
- {
- if (expression_type(ops[2]).vecsize > 1)
- GLSL_BFOP_CAST(notEqual, int_type);
- else
- GLSL_BOP_CAST(!=, int_type);
- break;
- }
- case OpLogicalNotEqual:
- case OpFOrdNotEqual:
- case OpFUnordNotEqual:
- {
- // GLSL is fuzzy on what to do with ordered vs unordered not equal.
- // glslang started emitting UnorderedNotEqual some time ago to harmonize with IEEE,
- // but this means we have no easy way of implementing ordered not equal.
- if (expression_type(ops[2]).vecsize > 1)
- GLSL_BFOP(notEqual);
- else
- GLSL_BOP(!=);
- break;
- }
- case OpUGreaterThan:
- case OpSGreaterThan:
- {
- auto type = opcode == OpUGreaterThan ? uint_type : int_type;
- if (expression_type(ops[2]).vecsize > 1)
- GLSL_BFOP_CAST(greaterThan, type);
- else
- GLSL_BOP_CAST(>, type);
- break;
- }
- case OpFOrdGreaterThan:
- {
- if (expression_type(ops[2]).vecsize > 1)
- GLSL_BFOP(greaterThan);
- else
- GLSL_BOP(>);
- break;
- }
- case OpUGreaterThanEqual:
- case OpSGreaterThanEqual:
- {
- auto type = opcode == OpUGreaterThanEqual ? uint_type : int_type;
- if (expression_type(ops[2]).vecsize > 1)
- GLSL_BFOP_CAST(greaterThanEqual, type);
- else
- GLSL_BOP_CAST(>=, type);
- break;
- }
- case OpFOrdGreaterThanEqual:
- {
- if (expression_type(ops[2]).vecsize > 1)
- GLSL_BFOP(greaterThanEqual);
- else
- GLSL_BOP(>=);
- break;
- }
- case OpULessThan:
- case OpSLessThan:
- {
- auto type = opcode == OpULessThan ? uint_type : int_type;
- if (expression_type(ops[2]).vecsize > 1)
- GLSL_BFOP_CAST(lessThan, type);
- else
- GLSL_BOP_CAST(<, type);
- break;
- }
- case OpFOrdLessThan:
- {
- if (expression_type(ops[2]).vecsize > 1)
- GLSL_BFOP(lessThan);
- else
- GLSL_BOP(<);
- break;
- }
- case OpULessThanEqual:
- case OpSLessThanEqual:
- {
- auto type = opcode == OpULessThanEqual ? uint_type : int_type;
- if (expression_type(ops[2]).vecsize > 1)
- GLSL_BFOP_CAST(lessThanEqual, type);
- else
- GLSL_BOP_CAST(<=, type);
- break;
- }
- case OpFOrdLessThanEqual:
- {
- if (expression_type(ops[2]).vecsize > 1)
- GLSL_BFOP(lessThanEqual);
- else
- GLSL_BOP(<=);
- break;
- }
- // Conversion
- case OpSConvert:
- case OpConvertSToF:
- case OpUConvert:
- case OpConvertUToF:
- {
- auto input_type = opcode == OpSConvert || opcode == OpConvertSToF ? int_type : uint_type;
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- auto &type = get<SPIRType>(result_type);
- auto &arg_type = expression_type(ops[2]);
- auto func = type_to_glsl_constructor(type);
- if (arg_type.width < type.width || type_is_floating_point(type))
- emit_unary_func_op_cast(result_type, id, ops[2], func.c_str(), input_type, type.basetype);
- else
- emit_unary_func_op(result_type, id, ops[2], func.c_str());
- break;
- }
- case OpConvertFToU:
- case OpConvertFToS:
- {
- // Cast to expected arithmetic type, then potentially bitcast away to desired signedness.
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- auto &type = get<SPIRType>(result_type);
- auto expected_type = type;
- auto &float_type = expression_type(ops[2]);
- expected_type.basetype =
- opcode == OpConvertFToS ? to_signed_basetype(type.width) : to_unsigned_basetype(type.width);
- auto func = type_to_glsl_constructor(expected_type);
- emit_unary_func_op_cast(result_type, id, ops[2], func.c_str(), float_type.basetype, expected_type.basetype);
- break;
- }
- case OpCooperativeMatrixConvertNV:
- if (!options.vulkan_semantics)
- SPIRV_CROSS_THROW("CooperativeMatrixConvertNV requires vulkan semantics.");
- require_extension_internal("GL_NV_cooperative_matrix2");
- // fallthrough
- case OpFConvert:
- {
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- auto &type = get<SPIRType>(result_type);
- if (type.op == OpTypeCooperativeMatrixKHR && opcode == OpFConvert)
- {
- auto &expr_type = expression_type(ops[2]);
- if (get<SPIRConstant>(type.ext.cooperative.use_id).scalar() !=
- get<SPIRConstant>(expr_type.ext.cooperative.use_id).scalar())
- {
- // Somewhat questionable with spec constant uses.
- if (!options.vulkan_semantics)
- SPIRV_CROSS_THROW("NV_cooperative_matrix2 requires vulkan semantics.");
- require_extension_internal("GL_NV_cooperative_matrix2");
- }
- }
- if ((type.basetype == SPIRType::FloatE4M3 || type.basetype == SPIRType::FloatE5M2) &&
- has_decoration(id, DecorationSaturatedToLargestFloat8NormalConversionEXT))
- {
- emit_uninitialized_temporary_expression(result_type, id);
- statement("saturatedConvertEXT(", to_expression(id), ", ", to_unpacked_expression(ops[2]), ");");
- }
- else
- {
- auto func = type_to_glsl_constructor(type);
- emit_unary_func_op(result_type, id, ops[2], func.c_str());
- }
- break;
- }
- case OpBitcast:
- {
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- uint32_t arg = ops[2];
- if (!emit_complex_bitcast(result_type, id, arg))
- {
- auto op = bitcast_glsl_op(get<SPIRType>(result_type), expression_type(arg));
- emit_unary_func_op(result_type, id, arg, op.c_str());
- }
- break;
- }
- case OpQuantizeToF16:
- {
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- uint32_t arg = ops[2];
- string op;
- auto &type = get<SPIRType>(result_type);
- switch (type.vecsize)
- {
- case 1:
- op = join("unpackHalf2x16(packHalf2x16(vec2(", to_expression(arg), "))).x");
- break;
- case 2:
- op = join("unpackHalf2x16(packHalf2x16(", to_expression(arg), "))");
- break;
- case 3:
- {
- auto op0 = join("unpackHalf2x16(packHalf2x16(", to_expression(arg), ".xy))");
- auto op1 = join("unpackHalf2x16(packHalf2x16(", to_expression(arg), ".zz)).x");
- op = join("vec3(", op0, ", ", op1, ")");
- break;
- }
- case 4:
- {
- auto op0 = join("unpackHalf2x16(packHalf2x16(", to_expression(arg), ".xy))");
- auto op1 = join("unpackHalf2x16(packHalf2x16(", to_expression(arg), ".zw))");
- op = join("vec4(", op0, ", ", op1, ")");
- break;
- }
- default:
- SPIRV_CROSS_THROW("Illegal argument to OpQuantizeToF16.");
- }
- emit_op(result_type, id, op, should_forward(arg));
- inherit_expression_dependencies(id, arg);
- break;
- }
- // Derivatives
- case OpDPdx:
- GLSL_UFOP(dFdx);
- if (is_legacy_es())
- require_extension_internal("GL_OES_standard_derivatives");
- register_control_dependent_expression(ops[1]);
- break;
- case OpDPdy:
- GLSL_UFOP(dFdy);
- if (is_legacy_es())
- require_extension_internal("GL_OES_standard_derivatives");
- register_control_dependent_expression(ops[1]);
- break;
- case OpDPdxFine:
- GLSL_UFOP(dFdxFine);
- if (options.es)
- {
- SPIRV_CROSS_THROW("GL_ARB_derivative_control is unavailable in OpenGL ES.");
- }
- if (options.version < 450)
- require_extension_internal("GL_ARB_derivative_control");
- register_control_dependent_expression(ops[1]);
- break;
- case OpDPdyFine:
- GLSL_UFOP(dFdyFine);
- if (options.es)
- {
- SPIRV_CROSS_THROW("GL_ARB_derivative_control is unavailable in OpenGL ES.");
- }
- if (options.version < 450)
- require_extension_internal("GL_ARB_derivative_control");
- register_control_dependent_expression(ops[1]);
- break;
- case OpDPdxCoarse:
- if (options.es)
- {
- SPIRV_CROSS_THROW("GL_ARB_derivative_control is unavailable in OpenGL ES.");
- }
- GLSL_UFOP(dFdxCoarse);
- if (options.version < 450)
- require_extension_internal("GL_ARB_derivative_control");
- register_control_dependent_expression(ops[1]);
- break;
- case OpDPdyCoarse:
- GLSL_UFOP(dFdyCoarse);
- if (options.es)
- {
- SPIRV_CROSS_THROW("GL_ARB_derivative_control is unavailable in OpenGL ES.");
- }
- if (options.version < 450)
- require_extension_internal("GL_ARB_derivative_control");
- register_control_dependent_expression(ops[1]);
- break;
- case OpFwidth:
- GLSL_UFOP(fwidth);
- if (is_legacy_es())
- require_extension_internal("GL_OES_standard_derivatives");
- register_control_dependent_expression(ops[1]);
- break;
- case OpFwidthCoarse:
- GLSL_UFOP(fwidthCoarse);
- if (options.es)
- {
- SPIRV_CROSS_THROW("GL_ARB_derivative_control is unavailable in OpenGL ES.");
- }
- if (options.version < 450)
- require_extension_internal("GL_ARB_derivative_control");
- register_control_dependent_expression(ops[1]);
- break;
- case OpFwidthFine:
- GLSL_UFOP(fwidthFine);
- if (options.es)
- {
- SPIRV_CROSS_THROW("GL_ARB_derivative_control is unavailable in OpenGL ES.");
- }
- if (options.version < 450)
- require_extension_internal("GL_ARB_derivative_control");
- register_control_dependent_expression(ops[1]);
- break;
- // Bitfield
- case OpBitFieldInsert:
- {
- emit_bitfield_insert_op(ops[0], ops[1], ops[2], ops[3], ops[4], ops[5], "bitfieldInsert", SPIRType::Int);
- break;
- }
- case OpBitFieldSExtract:
- {
- emit_trinary_func_op_bitextract(ops[0], ops[1], ops[2], ops[3], ops[4], "bitfieldExtract", int_type, int_type,
- SPIRType::Int, SPIRType::Int);
- break;
- }
- case OpBitFieldUExtract:
- {
- emit_trinary_func_op_bitextract(ops[0], ops[1], ops[2], ops[3], ops[4], "bitfieldExtract", uint_type, uint_type,
- SPIRType::Int, SPIRType::Int);
- break;
- }
- case OpBitReverse:
- // BitReverse does not have issues with sign since result type must match input type.
- GLSL_UFOP(bitfieldReverse);
- break;
- case OpBitCount:
- {
- auto basetype = expression_type(ops[2]).basetype;
- emit_unary_func_op_cast(ops[0], ops[1], ops[2], "bitCount", basetype, int_type);
- break;
- }
- // Atomics
- case OpAtomicExchange:
- {
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- uint32_t ptr = ops[2];
- // Ignore semantics for now, probably only relevant to CL.
- uint32_t val = ops[5];
- const char *op = check_atomic_image(ptr) ? "imageAtomicExchange" : "atomicExchange";
- emit_atomic_func_op(result_type, id, ptr, val, op);
- break;
- }
- case OpAtomicCompareExchange:
- {
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- uint32_t ptr = ops[2];
- uint32_t val = ops[6];
- uint32_t comp = ops[7];
- const char *op = check_atomic_image(ptr) ? "imageAtomicCompSwap" : "atomicCompSwap";
- emit_atomic_func_op(result_type, id, ptr, comp, val, op);
- break;
- }
- case OpAtomicLoad:
- {
- // In plain GLSL, we have no atomic loads, so emulate this by fetch adding by 0 and hope compiler figures it out.
- // Alternatively, we could rely on KHR_memory_model, but that's not very helpful for GL.
- auto &type = expression_type(ops[2]);
- forced_temporaries.insert(ops[1]);
- bool atomic_image = check_atomic_image(ops[2]);
- bool unsigned_type = (type.basetype == SPIRType::UInt) ||
- (atomic_image && get<SPIRType>(type.image.type).basetype == SPIRType::UInt);
- const char *op = atomic_image ? "imageAtomicAdd" : "atomicAdd";
- const char *increment = unsigned_type ? "0u" : "0";
- emit_op(ops[0], ops[1],
- join(op, "(",
- to_atomic_ptr_expression(ops[2]), ", ", increment, ")"), false);
- flush_all_atomic_capable_variables();
- if (type.basetype == SPIRType::UInt64 || type.basetype == SPIRType::Int64)
- require_extension_internal("GL_EXT_shader_atomic_int64");
- break;
- }
- case OpAtomicStore:
- {
- // In plain GLSL, we have no atomic stores, so emulate this with an atomic exchange where we don't consume the result.
- // Alternatively, we could rely on KHR_memory_model, but that's not very helpful for GL.
- uint32_t ptr = ops[0];
- // Ignore semantics for now, probably only relevant to CL.
- uint32_t val = ops[3];
- const char *op = check_atomic_image(ptr) ? "imageAtomicExchange" : "atomicExchange";
- statement(op, "(", to_atomic_ptr_expression(ptr), ", ", to_expression(val), ");");
- flush_all_atomic_capable_variables();
- auto &type = expression_type(ptr);
- if (type.basetype == SPIRType::UInt64 || type.basetype == SPIRType::Int64)
- require_extension_internal("GL_EXT_shader_atomic_int64");
- break;
- }
- case OpAtomicIIncrement:
- case OpAtomicIDecrement:
- {
- forced_temporaries.insert(ops[1]);
- auto &type = expression_type(ops[2]);
- if (type.storage == StorageClassAtomicCounter)
- {
- // Legacy GLSL stuff, not sure if this is relevant to support.
- if (opcode == OpAtomicIIncrement)
- GLSL_UFOP(atomicCounterIncrement);
- else
- GLSL_UFOP(atomicCounterDecrement);
- }
- else
- {
- bool atomic_image = check_atomic_image(ops[2]);
- bool unsigned_type = (type.basetype == SPIRType::UInt) ||
- (atomic_image && get<SPIRType>(type.image.type).basetype == SPIRType::UInt);
- const char *op = atomic_image ? "imageAtomicAdd" : "atomicAdd";
- const char *increment = nullptr;
- if (opcode == OpAtomicIIncrement && unsigned_type)
- increment = "1u";
- else if (opcode == OpAtomicIIncrement)
- increment = "1";
- else if (unsigned_type)
- increment = "uint(-1)";
- else
- increment = "-1";
- emit_op(ops[0], ops[1],
- join(op, "(", to_atomic_ptr_expression(ops[2]), ", ", increment, ")"), false);
- if (type.basetype == SPIRType::UInt64 || type.basetype == SPIRType::Int64)
- require_extension_internal("GL_EXT_shader_atomic_int64");
- }
- flush_all_atomic_capable_variables();
- break;
- }
- case OpAtomicIAdd:
- case OpAtomicFAddEXT:
- {
- const char *op = check_atomic_image(ops[2]) ? "imageAtomicAdd" : "atomicAdd";
- emit_atomic_func_op(ops[0], ops[1], ops[2], ops[5], op);
- break;
- }
- case OpAtomicISub:
- {
- const char *op = check_atomic_image(ops[2]) ? "imageAtomicAdd" : "atomicAdd";
- forced_temporaries.insert(ops[1]);
- auto expr = join(op, "(", to_atomic_ptr_expression(ops[2]), ", -", to_enclosed_expression(ops[5]), ")");
- emit_op(ops[0], ops[1], expr, should_forward(ops[2]) && should_forward(ops[5]));
- flush_all_atomic_capable_variables();
- auto &type = get<SPIRType>(ops[0]);
- if (type.basetype == SPIRType::UInt64 || type.basetype == SPIRType::Int64)
- require_extension_internal("GL_EXT_shader_atomic_int64");
- break;
- }
- case OpAtomicSMin:
- case OpAtomicUMin:
- {
- const char *op = check_atomic_image(ops[2]) ? "imageAtomicMin" : "atomicMin";
- emit_atomic_func_op(ops[0], ops[1], ops[2], ops[5], op);
- break;
- }
- case OpAtomicSMax:
- case OpAtomicUMax:
- {
- const char *op = check_atomic_image(ops[2]) ? "imageAtomicMax" : "atomicMax";
- emit_atomic_func_op(ops[0], ops[1], ops[2], ops[5], op);
- break;
- }
- case OpAtomicAnd:
- {
- const char *op = check_atomic_image(ops[2]) ? "imageAtomicAnd" : "atomicAnd";
- emit_atomic_func_op(ops[0], ops[1], ops[2], ops[5], op);
- break;
- }
- case OpAtomicOr:
- {
- const char *op = check_atomic_image(ops[2]) ? "imageAtomicOr" : "atomicOr";
- emit_atomic_func_op(ops[0], ops[1], ops[2], ops[5], op);
- break;
- }
- case OpAtomicXor:
- {
- const char *op = check_atomic_image(ops[2]) ? "imageAtomicXor" : "atomicXor";
- emit_atomic_func_op(ops[0], ops[1], ops[2], ops[5], op);
- break;
- }
- // Geometry shaders
- case OpEmitVertex:
- statement("EmitVertex();");
- break;
- case OpEndPrimitive:
- statement("EndPrimitive();");
- break;
- case OpEmitStreamVertex:
- {
- if (options.es)
- SPIRV_CROSS_THROW("Multi-stream geometry shaders not supported in ES.");
- else if (!options.es && options.version < 400)
- SPIRV_CROSS_THROW("Multi-stream geometry shaders only supported in GLSL 400.");
- auto stream_expr = to_expression(ops[0]);
- if (expression_type(ops[0]).basetype != SPIRType::Int)
- stream_expr = join("int(", stream_expr, ")");
- statement("EmitStreamVertex(", stream_expr, ");");
- break;
- }
- case OpEndStreamPrimitive:
- {
- if (options.es)
- SPIRV_CROSS_THROW("Multi-stream geometry shaders not supported in ES.");
- else if (!options.es && options.version < 400)
- SPIRV_CROSS_THROW("Multi-stream geometry shaders only supported in GLSL 400.");
- auto stream_expr = to_expression(ops[0]);
- if (expression_type(ops[0]).basetype != SPIRType::Int)
- stream_expr = join("int(", stream_expr, ")");
- statement("EndStreamPrimitive(", stream_expr, ");");
- break;
- }
- // Textures
- case OpImageSampleExplicitLod:
- case OpImageSampleProjExplicitLod:
- case OpImageSampleDrefExplicitLod:
- case OpImageSampleProjDrefExplicitLod:
- case OpImageSampleImplicitLod:
- case OpImageSampleProjImplicitLod:
- case OpImageSampleDrefImplicitLod:
- case OpImageSampleProjDrefImplicitLod:
- case OpImageFetch:
- case OpImageGather:
- case OpImageDrefGather:
- // Gets a bit hairy, so move this to a separate instruction.
- emit_texture_op(instruction, false);
- break;
- case OpImageSparseSampleExplicitLod:
- case OpImageSparseSampleProjExplicitLod:
- case OpImageSparseSampleDrefExplicitLod:
- case OpImageSparseSampleProjDrefExplicitLod:
- case OpImageSparseSampleImplicitLod:
- case OpImageSparseSampleProjImplicitLod:
- case OpImageSparseSampleDrefImplicitLod:
- case OpImageSparseSampleProjDrefImplicitLod:
- case OpImageSparseFetch:
- case OpImageSparseGather:
- case OpImageSparseDrefGather:
- // Gets a bit hairy, so move this to a separate instruction.
- emit_texture_op(instruction, true);
- break;
- case OpImageSparseTexelsResident:
- if (options.es)
- SPIRV_CROSS_THROW("Sparse feedback is not supported in GLSL.");
- require_extension_internal("GL_ARB_sparse_texture2");
- emit_unary_func_op_cast(ops[0], ops[1], ops[2], "sparseTexelsResidentARB", int_type, SPIRType::Boolean);
- break;
- case OpImage:
- {
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- // Suppress usage tracking.
- auto &e = emit_op(result_type, id, to_expression(ops[2]), true, true);
- // When using the image, we need to know which variable it is actually loaded from.
- auto *var = maybe_get_backing_variable(ops[2]);
- e.loaded_from = var ? var->self : ID(0);
- break;
- }
- case OpImageQueryLod:
- {
- const char *op = nullptr;
- if (!options.es && options.version < 400)
- {
- require_extension_internal("GL_ARB_texture_query_lod");
- // For some reason, the ARB spec is all-caps.
- op = "textureQueryLOD";
- }
- else if (options.es)
- {
- if (options.version < 300)
- SPIRV_CROSS_THROW("textureQueryLod not supported in legacy ES");
- require_extension_internal("GL_EXT_texture_query_lod");
- op = "textureQueryLOD";
- }
- else
- op = "textureQueryLod";
- auto sampler_expr = to_expression(ops[2]);
- if (has_decoration(ops[2], DecorationNonUniform))
- {
- if (maybe_get_backing_variable(ops[2]))
- convert_non_uniform_expression(sampler_expr, ops[2]);
- else if (*backend.nonuniform_qualifier != '\0')
- sampler_expr = join(backend.nonuniform_qualifier, "(", sampler_expr, ")");
- }
- bool forward = should_forward(ops[3]);
- emit_op(ops[0], ops[1],
- join(op, "(", sampler_expr, ", ", to_unpacked_expression(ops[3]), ")"),
- forward);
- inherit_expression_dependencies(ops[1], ops[2]);
- inherit_expression_dependencies(ops[1], ops[3]);
- register_control_dependent_expression(ops[1]);
- break;
- }
- case OpImageQueryLevels:
- {
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- if (!options.es && options.version < 430)
- require_extension_internal("GL_ARB_texture_query_levels");
- if (options.es)
- SPIRV_CROSS_THROW("textureQueryLevels not supported in ES profile.");
- auto expr = join("textureQueryLevels(", convert_separate_image_to_expression(ops[2]), ")");
- auto &restype = get<SPIRType>(ops[0]);
- expr = bitcast_expression(restype, SPIRType::Int, expr);
- emit_op(result_type, id, expr, true);
- break;
- }
- case OpImageQuerySamples:
- {
- auto &type = expression_type(ops[2]);
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- if (options.es)
- SPIRV_CROSS_THROW("textureSamples and imageSamples not supported in ES profile.");
- else if (options.version < 450)
- require_extension_internal("GL_ARB_texture_query_samples");
- string expr;
- if (type.image.sampled == 2)
- expr = join("imageSamples(", to_non_uniform_aware_expression(ops[2]), ")");
- else
- expr = join("textureSamples(", convert_separate_image_to_expression(ops[2]), ")");
- auto &restype = get<SPIRType>(ops[0]);
- expr = bitcast_expression(restype, SPIRType::Int, expr);
- emit_op(result_type, id, expr, true);
- break;
- }
- case OpSampledImage:
- {
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- emit_sampled_image_op(result_type, id, ops[2], ops[3]);
- inherit_expression_dependencies(id, ops[2]);
- inherit_expression_dependencies(id, ops[3]);
- break;
- }
- case OpImageQuerySizeLod:
- {
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- uint32_t img = ops[2];
- auto &type = expression_type(img);
- auto &imgtype = get<SPIRType>(type.self);
- std::string fname = "textureSize";
- if (is_legacy_desktop())
- {
- fname = legacy_tex_op(fname, imgtype, img);
- }
- else if (is_legacy_es())
- SPIRV_CROSS_THROW("textureSize is not supported in ESSL 100.");
- auto expr = join(fname, "(", convert_separate_image_to_expression(img), ", ",
- bitcast_expression(SPIRType::Int, ops[3]), ")");
- // ES needs to emulate 1D images as 2D.
- if (type.image.dim == Dim1D && options.es)
- expr = join(expr, ".x");
- auto &restype = get<SPIRType>(ops[0]);
- expr = bitcast_expression(restype, SPIRType::Int, expr);
- emit_op(result_type, id, expr, true);
- break;
- }
- // Image load/store
- case OpImageRead:
- case OpImageSparseRead:
- {
- // We added Nonreadable speculatively to the OpImage variable due to glslangValidator
- // not adding the proper qualifiers.
- // If it turns out we need to read the image after all, remove the qualifier and recompile.
- auto *var = maybe_get_backing_variable(ops[2]);
- if (var)
- {
- auto &flags = get_decoration_bitset(var->self);
- if (flags.get(DecorationNonReadable))
- {
- unset_decoration(var->self, DecorationNonReadable);
- force_recompile();
- }
- }
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- bool pure;
- string imgexpr;
- auto &type = expression_type(ops[2]);
- if (var && var->remapped_variable) // Remapped input, just read as-is without any op-code
- {
- if (type.image.ms)
- SPIRV_CROSS_THROW("Trying to remap multisampled image to variable, this is not possible.");
- auto itr =
- find_if(begin(pls_inputs), end(pls_inputs), [var](const PlsRemap &pls) { return pls.id == var->self; });
- if (itr == end(pls_inputs))
- {
- // For non-PLS inputs, we rely on subpass type remapping information to get it right
- // since ImageRead always returns 4-component vectors and the backing type is opaque.
- if (!var->remapped_components)
- SPIRV_CROSS_THROW("subpassInput was remapped, but remap_components is not set correctly.");
- imgexpr = remap_swizzle(get<SPIRType>(result_type), var->remapped_components, to_expression(ops[2]));
- }
- else
- {
- // PLS input could have different number of components than what the SPIR expects, swizzle to
- // the appropriate vector size.
- uint32_t components = pls_format_to_components(itr->format);
- imgexpr = remap_swizzle(get<SPIRType>(result_type), components, to_expression(ops[2]));
- }
- pure = true;
- }
- else if (type.image.dim == DimSubpassData)
- {
- if (var && subpass_input_is_framebuffer_fetch(var->self))
- {
- imgexpr = to_expression(var->self);
- }
- else if (options.vulkan_semantics)
- {
- // With Vulkan semantics, use the proper Vulkan GLSL construct.
- if (type.image.ms)
- {
- uint32_t operands = ops[4];
- if (operands != ImageOperandsSampleMask || length != 6)
- SPIRV_CROSS_THROW("Multisampled image used in OpImageRead, but unexpected "
- "operand mask was used.");
- uint32_t samples = ops[5];
- imgexpr = join("subpassLoad(", to_non_uniform_aware_expression(ops[2]), ", ", to_expression(samples), ")");
- }
- else
- imgexpr = join("subpassLoad(", to_non_uniform_aware_expression(ops[2]), ")");
- }
- else
- {
- if (type.image.ms)
- {
- uint32_t operands = ops[4];
- if (operands != ImageOperandsSampleMask || length != 6)
- SPIRV_CROSS_THROW("Multisampled image used in OpImageRead, but unexpected "
- "operand mask was used.");
- uint32_t samples = ops[5];
- imgexpr = join("texelFetch(", to_non_uniform_aware_expression(ops[2]), ", ivec2(gl_FragCoord.xy), ",
- to_expression(samples), ")");
- }
- else
- {
- // Implement subpass loads via texture barrier style sampling.
- imgexpr = join("texelFetch(", to_non_uniform_aware_expression(ops[2]), ", ivec2(gl_FragCoord.xy), 0)");
- }
- }
- imgexpr = remap_swizzle(get<SPIRType>(result_type), 4, imgexpr);
- pure = true;
- }
- else
- {
- bool sparse = opcode == OpImageSparseRead;
- uint32_t sparse_code_id = 0;
- uint32_t sparse_texel_id = 0;
- if (sparse)
- emit_sparse_feedback_temporaries(ops[0], ops[1], sparse_code_id, sparse_texel_id);
- // imageLoad only accepts int coords, not uint.
- auto coord_expr = to_expression(ops[3]);
- auto target_coord_type = expression_type(ops[3]);
- target_coord_type.basetype = SPIRType::Int;
- coord_expr = bitcast_expression(target_coord_type, expression_type(ops[3]).basetype, coord_expr);
- // ES needs to emulate 1D images as 2D.
- if (type.image.dim == Dim1D && options.es)
- coord_expr = join("ivec2(", coord_expr, ", 0)");
- // Plain image load/store.
- if (sparse)
- {
- if (type.image.ms)
- {
- uint32_t operands = ops[4];
- if (operands != ImageOperandsSampleMask || length != 6)
- SPIRV_CROSS_THROW("Multisampled image used in OpImageRead, but unexpected "
- "operand mask was used.");
- uint32_t samples = ops[5];
- statement(to_expression(sparse_code_id), " = sparseImageLoadARB(", to_non_uniform_aware_expression(ops[2]), ", ",
- coord_expr, ", ", to_expression(samples), ", ", to_expression(sparse_texel_id), ");");
- }
- else
- {
- statement(to_expression(sparse_code_id), " = sparseImageLoadARB(", to_non_uniform_aware_expression(ops[2]), ", ",
- coord_expr, ", ", to_expression(sparse_texel_id), ");");
- }
- imgexpr = join(type_to_glsl(get<SPIRType>(result_type)), "(", to_expression(sparse_code_id), ", ",
- to_expression(sparse_texel_id), ")");
- }
- else
- {
- if (type.image.ms)
- {
- uint32_t operands = ops[4];
- if (operands != ImageOperandsSampleMask || length != 6)
- SPIRV_CROSS_THROW("Multisampled image used in OpImageRead, but unexpected "
- "operand mask was used.");
- uint32_t samples = ops[5];
- imgexpr =
- join("imageLoad(", to_non_uniform_aware_expression(ops[2]), ", ", coord_expr, ", ", to_expression(samples), ")");
- }
- else
- imgexpr = join("imageLoad(", to_non_uniform_aware_expression(ops[2]), ", ", coord_expr, ")");
- }
- if (!sparse)
- imgexpr = remap_swizzle(get<SPIRType>(result_type), 4, imgexpr);
- pure = false;
- }
- if (var)
- {
- bool forward = forced_temporaries.find(id) == end(forced_temporaries);
- auto &e = emit_op(result_type, id, imgexpr, forward);
- // We only need to track dependencies if we're reading from image load/store.
- if (!pure)
- {
- e.loaded_from = var->self;
- if (forward)
- var->dependees.push_back(id);
- }
- }
- else
- emit_op(result_type, id, imgexpr, false);
- inherit_expression_dependencies(id, ops[2]);
- if (type.image.ms)
- inherit_expression_dependencies(id, ops[5]);
- break;
- }
- case OpImageTexelPointer:
- {
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- auto coord_expr = to_expression(ops[3]);
- auto target_coord_type = expression_type(ops[3]);
- target_coord_type.basetype = SPIRType::Int;
- coord_expr = bitcast_expression(target_coord_type, expression_type(ops[3]).basetype, coord_expr);
- auto expr = join(to_expression(ops[2]), ", ", coord_expr);
- auto &e = set<SPIRExpression>(id, expr, result_type, true);
- // When using the pointer, we need to know which variable it is actually loaded from.
- auto *var = maybe_get_backing_variable(ops[2]);
- e.loaded_from = var ? var->self : ID(0);
- inherit_expression_dependencies(id, ops[3]);
- break;
- }
- case OpImageWrite:
- {
- // We added Nonwritable speculatively to the OpImage variable due to glslangValidator
- // not adding the proper qualifiers.
- // If it turns out we need to write to the image after all, remove the qualifier and recompile.
- auto *var = maybe_get_backing_variable(ops[0]);
- if (var)
- {
- if (has_decoration(var->self, DecorationNonWritable))
- {
- unset_decoration(var->self, DecorationNonWritable);
- force_recompile();
- }
- }
- auto &type = expression_type(ops[0]);
- auto &value_type = expression_type(ops[2]);
- auto store_type = value_type;
- store_type.vecsize = 4;
- // imageStore only accepts int coords, not uint.
- auto coord_expr = to_expression(ops[1]);
- auto target_coord_type = expression_type(ops[1]);
- target_coord_type.basetype = SPIRType::Int;
- coord_expr = bitcast_expression(target_coord_type, expression_type(ops[1]).basetype, coord_expr);
- // ES needs to emulate 1D images as 2D.
- if (type.image.dim == Dim1D && options.es)
- coord_expr = join("ivec2(", coord_expr, ", 0)");
- if (type.image.ms)
- {
- uint32_t operands = ops[3];
- if (operands != ImageOperandsSampleMask || length != 5)
- SPIRV_CROSS_THROW("Multisampled image used in OpImageWrite, but unexpected operand mask was used.");
- uint32_t samples = ops[4];
- statement("imageStore(", to_non_uniform_aware_expression(ops[0]), ", ", coord_expr, ", ", to_expression(samples), ", ",
- remap_swizzle(store_type, value_type.vecsize, to_expression(ops[2])), ");");
- }
- else
- statement("imageStore(", to_non_uniform_aware_expression(ops[0]), ", ", coord_expr, ", ",
- remap_swizzle(store_type, value_type.vecsize, to_expression(ops[2])), ");");
- if (var && variable_storage_is_aliased(*var))
- flush_all_aliased_variables();
- break;
- }
- case OpImageQuerySize:
- {
- auto &type = expression_type(ops[2]);
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- if (type.basetype == SPIRType::Image)
- {
- string expr;
- if (type.image.sampled == 2)
- {
- if (!options.es && options.version < 430)
- require_extension_internal("GL_ARB_shader_image_size");
- else if (options.es && options.version < 310)
- SPIRV_CROSS_THROW("At least ESSL 3.10 required for imageSize.");
- // The size of an image is always constant.
- expr = join("imageSize(", to_non_uniform_aware_expression(ops[2]), ")");
- }
- else
- {
- // This path is hit for samplerBuffers and multisampled images which do not have LOD.
- std::string fname = "textureSize";
- if (is_legacy())
- {
- auto &imgtype = get<SPIRType>(type.self);
- fname = legacy_tex_op(fname, imgtype, ops[2]);
- }
- expr = join(fname, "(", convert_separate_image_to_expression(ops[2]), ")");
- }
- auto &restype = get<SPIRType>(ops[0]);
- expr = bitcast_expression(restype, SPIRType::Int, expr);
- emit_op(result_type, id, expr, true);
- }
- else
- SPIRV_CROSS_THROW("Invalid type for OpImageQuerySize.");
- break;
- }
- case OpImageSampleWeightedQCOM:
- case OpImageBoxFilterQCOM:
- case OpImageBlockMatchSSDQCOM:
- case OpImageBlockMatchSADQCOM:
- {
- require_extension_internal("GL_QCOM_image_processing");
- uint32_t result_type_id = ops[0];
- uint32_t id = ops[1];
- string expr;
- switch (opcode)
- {
- case OpImageSampleWeightedQCOM:
- expr = "textureWeightedQCOM";
- break;
- case OpImageBoxFilterQCOM:
- expr = "textureBoxFilterQCOM";
- break;
- case OpImageBlockMatchSSDQCOM:
- expr = "textureBlockMatchSSDQCOM";
- break;
- case OpImageBlockMatchSADQCOM:
- expr = "textureBlockMatchSADQCOM";
- break;
- default:
- SPIRV_CROSS_THROW("Invalid opcode for QCOM_image_processing.");
- }
- expr += "(";
- bool forward = false;
- expr += to_expression(ops[2]);
- expr += ", " + to_expression(ops[3]);
- switch (opcode)
- {
- case OpImageSampleWeightedQCOM:
- expr += ", " + to_non_uniform_aware_expression(ops[4]);
- break;
- case OpImageBoxFilterQCOM:
- expr += ", " + to_expression(ops[4]);
- break;
- case OpImageBlockMatchSSDQCOM:
- case OpImageBlockMatchSADQCOM:
- expr += ", " + to_non_uniform_aware_expression(ops[4]);
- expr += ", " + to_expression(ops[5]);
- expr += ", " + to_expression(ops[6]);
- break;
- default:
- SPIRV_CROSS_THROW("Invalid opcode for QCOM_image_processing.");
- }
- expr += ")";
- emit_op(result_type_id, id, expr, forward);
- inherit_expression_dependencies(id, ops[3]);
- if (opcode == OpImageBlockMatchSSDQCOM || opcode == OpImageBlockMatchSADQCOM)
- inherit_expression_dependencies(id, ops[5]);
- break;
- }
- case OpImageBlockMatchWindowSSDQCOM:
- case OpImageBlockMatchWindowSADQCOM:
- case OpImageBlockMatchGatherSSDQCOM:
- case OpImageBlockMatchGatherSADQCOM:
- {
- require_extension_internal("GL_QCOM_image_processing2");
- uint32_t result_type_id = ops[0];
- uint32_t id = ops[1];
- string expr;
- switch (opcode)
- {
- case OpImageBlockMatchWindowSSDQCOM:
- expr = "textureBlockMatchWindowSSDQCOM";
- break;
- case OpImageBlockMatchWindowSADQCOM:
- expr = "textureBlockMatchWindowSADQCOM";
- break;
- case OpImageBlockMatchGatherSSDQCOM:
- expr = "textureBlockMatchGatherSSDQCOM";
- break;
- case OpImageBlockMatchGatherSADQCOM:
- expr = "textureBlockMatchGatherSADQCOM";
- break;
- default:
- SPIRV_CROSS_THROW("Invalid opcode for QCOM_image_processing2.");
- }
- expr += "(";
- bool forward = false;
- expr += to_expression(ops[2]);
- expr += ", " + to_expression(ops[3]);
- expr += ", " + to_non_uniform_aware_expression(ops[4]);
- expr += ", " + to_expression(ops[5]);
- expr += ", " + to_expression(ops[6]);
- expr += ")";
- emit_op(result_type_id, id, expr, forward);
- inherit_expression_dependencies(id, ops[3]);
- inherit_expression_dependencies(id, ops[5]);
- break;
- }
- // Compute
- case OpControlBarrier:
- case OpMemoryBarrier:
- {
- uint32_t execution_scope = 0;
- uint32_t memory;
- uint32_t semantics;
- if (opcode == OpMemoryBarrier)
- {
- memory = evaluate_constant_u32(ops[0]);
- semantics = evaluate_constant_u32(ops[1]);
- }
- else
- {
- execution_scope = evaluate_constant_u32(ops[0]);
- memory = evaluate_constant_u32(ops[1]);
- semantics = evaluate_constant_u32(ops[2]);
- }
- if (execution_scope == ScopeSubgroup || memory == ScopeSubgroup)
- {
- // OpControlBarrier with ScopeSubgroup is subgroupBarrier()
- if (opcode != OpControlBarrier)
- {
- request_subgroup_feature(ShaderSubgroupSupportHelper::SubgroupMemBarrier);
- }
- else
- {
- request_subgroup_feature(ShaderSubgroupSupportHelper::SubgroupBarrier);
- }
- }
- if (execution_scope != ScopeSubgroup && get_entry_point().model == ExecutionModelTessellationControl)
- {
- // Control shaders only have barriers, and it implies memory barriers.
- if (opcode == OpControlBarrier)
- statement("barrier();");
- break;
- }
- // We only care about these flags, acquire/release and friends are not relevant to GLSL.
- semantics = mask_relevant_memory_semantics(semantics);
- if (opcode == OpMemoryBarrier)
- {
- // If we are a memory barrier, and the next instruction is a control barrier, check if that memory barrier
- // does what we need, so we avoid redundant barriers.
- const Instruction *next = get_next_instruction_in_block(instruction);
- if (next && next->op == OpControlBarrier)
- {
- auto *next_ops = stream(*next);
- uint32_t next_memory = evaluate_constant_u32(next_ops[1]);
- uint32_t next_semantics = evaluate_constant_u32(next_ops[2]);
- next_semantics = mask_relevant_memory_semantics(next_semantics);
- bool memory_scope_covered = false;
- if (next_memory == memory)
- memory_scope_covered = true;
- else if (next_semantics == MemorySemanticsWorkgroupMemoryMask)
- {
- // If we only care about workgroup memory, either Device or Workgroup scope is fine,
- // scope does not have to match.
- if ((next_memory == ScopeDevice || next_memory == ScopeWorkgroup) &&
- (memory == ScopeDevice || memory == ScopeWorkgroup))
- {
- memory_scope_covered = true;
- }
- }
- else if (memory == ScopeWorkgroup && next_memory == ScopeDevice)
- {
- // The control barrier has device scope, but the memory barrier just has workgroup scope.
- memory_scope_covered = true;
- }
- // If we have the same memory scope, and all memory types are covered, we're good.
- if (memory_scope_covered && (semantics & next_semantics) == semantics)
- break;
- }
- }
- // We are synchronizing some memory or syncing execution,
- // so we cannot forward any loads beyond the memory barrier.
- if (semantics || opcode == OpControlBarrier)
- {
- assert(current_emitting_block);
- flush_control_dependent_expressions(current_emitting_block->self);
- flush_all_active_variables();
- }
- if (memory == ScopeWorkgroup) // Only need to consider memory within a group
- {
- if (semantics == MemorySemanticsWorkgroupMemoryMask)
- {
- // OpControlBarrier implies a memory barrier for shared memory as well.
- bool implies_shared_barrier = opcode == OpControlBarrier && execution_scope == ScopeWorkgroup;
- if (!implies_shared_barrier)
- statement("memoryBarrierShared();");
- }
- else if (semantics != 0)
- statement("groupMemoryBarrier();");
- }
- else if (memory == ScopeSubgroup)
- {
- const uint32_t all_barriers =
- MemorySemanticsWorkgroupMemoryMask | MemorySemanticsUniformMemoryMask | MemorySemanticsImageMemoryMask;
- if (semantics & (MemorySemanticsCrossWorkgroupMemoryMask | MemorySemanticsSubgroupMemoryMask))
- {
- // These are not relevant for GLSL, but assume it means memoryBarrier().
- // memoryBarrier() does everything, so no need to test anything else.
- statement("subgroupMemoryBarrier();");
- }
- else if ((semantics & all_barriers) == all_barriers)
- {
- // Short-hand instead of emitting 3 barriers.
- statement("subgroupMemoryBarrier();");
- }
- else
- {
- // Pick out individual barriers.
- if (semantics & MemorySemanticsWorkgroupMemoryMask)
- statement("subgroupMemoryBarrierShared();");
- if (semantics & MemorySemanticsUniformMemoryMask)
- statement("subgroupMemoryBarrierBuffer();");
- if (semantics & MemorySemanticsImageMemoryMask)
- statement("subgroupMemoryBarrierImage();");
- }
- }
- else
- {
- const uint32_t all_barriers =
- MemorySemanticsWorkgroupMemoryMask | MemorySemanticsUniformMemoryMask | MemorySemanticsImageMemoryMask;
- if (semantics & (MemorySemanticsCrossWorkgroupMemoryMask | MemorySemanticsSubgroupMemoryMask))
- {
- // These are not relevant for GLSL, but assume it means memoryBarrier().
- // memoryBarrier() does everything, so no need to test anything else.
- statement("memoryBarrier();");
- }
- else if ((semantics & all_barriers) == all_barriers)
- {
- // Short-hand instead of emitting 4 barriers.
- statement("memoryBarrier();");
- }
- else
- {
- // Pick out individual barriers.
- if (semantics & MemorySemanticsWorkgroupMemoryMask)
- statement("memoryBarrierShared();");
- if (semantics & MemorySemanticsUniformMemoryMask)
- statement("memoryBarrierBuffer();");
- if (semantics & MemorySemanticsImageMemoryMask)
- statement("memoryBarrierImage();");
- }
- }
- if (opcode == OpControlBarrier)
- {
- if (execution_scope == ScopeSubgroup)
- statement("subgroupBarrier();");
- else
- statement("barrier();");
- }
- break;
- }
- case OpExtInstWithForwardRefsKHR:
- {
- uint32_t extension_set = ops[2];
- auto ext = get<SPIRExtension>(extension_set).ext;
- if (ext != SPIRExtension::SPV_debug_info &&
- ext != SPIRExtension::NonSemanticShaderDebugInfo &&
- ext != SPIRExtension::NonSemanticGeneric)
- {
- SPIRV_CROSS_THROW("Unexpected use of ExtInstWithForwardRefsKHR.");
- }
- break;
- }
- case OpExtInst:
- {
- uint32_t extension_set = ops[2];
- auto ext = get<SPIRExtension>(extension_set).ext;
- if (ext == SPIRExtension::GLSL)
- {
- emit_glsl_op(ops[0], ops[1], ops[3], &ops[4], length - 4);
- }
- else if (ext == SPIRExtension::SPV_AMD_shader_ballot)
- {
- emit_spv_amd_shader_ballot_op(ops[0], ops[1], ops[3], &ops[4], length - 4);
- }
- else if (ext == SPIRExtension::SPV_AMD_shader_explicit_vertex_parameter)
- {
- emit_spv_amd_shader_explicit_vertex_parameter_op(ops[0], ops[1], ops[3], &ops[4], length - 4);
- }
- else if (ext == SPIRExtension::SPV_AMD_shader_trinary_minmax)
- {
- emit_spv_amd_shader_trinary_minmax_op(ops[0], ops[1], ops[3], &ops[4], length - 4);
- }
- else if (ext == SPIRExtension::SPV_AMD_gcn_shader)
- {
- emit_spv_amd_gcn_shader_op(ops[0], ops[1], ops[3], &ops[4], length - 4);
- }
- else if (ext == SPIRExtension::NonSemanticShaderDebugInfo)
- {
- emit_non_semantic_shader_debug_info(ops[0], ops[1], ops[3], &ops[4], length - 4);
- }
- else if (ext == SPIRExtension::SPV_debug_info ||
- ext == SPIRExtension::NonSemanticGeneric)
- {
- break; // Ignore SPIR-V debug information extended instructions.
- }
- else if (ext == SPIRExtension::NonSemanticDebugPrintf)
- {
- // Operation 1 is printf.
- if (ops[3] == 1)
- {
- if (!options.vulkan_semantics)
- SPIRV_CROSS_THROW("Debug printf is only supported in Vulkan GLSL.\n");
- require_extension_internal("GL_EXT_debug_printf");
- auto &format_string = get<SPIRString>(ops[4]).str;
- string expr = join(backend.printf_function, "(\"", format_string, "\"");
- for (uint32_t i = 5; i < length; i++)
- {
- expr += ", ";
- expr += to_expression(ops[i]);
- }
- statement(expr, ");");
- }
- }
- else
- {
- statement("// unimplemented ext op ", instruction.op);
- break;
- }
- break;
- }
- // Legacy sub-group stuff ...
- case OpSubgroupBallotKHR:
- {
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- string expr;
- expr = join("uvec4(unpackUint2x32(ballotARB(" + to_expression(ops[2]) + ")), 0u, 0u)");
- emit_op(result_type, id, expr, should_forward(ops[2]));
- require_extension_internal("GL_ARB_shader_ballot");
- inherit_expression_dependencies(id, ops[2]);
- register_control_dependent_expression(ops[1]);
- break;
- }
- case OpSubgroupFirstInvocationKHR:
- {
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- emit_unary_func_op(result_type, id, ops[2], "readFirstInvocationARB");
- require_extension_internal("GL_ARB_shader_ballot");
- register_control_dependent_expression(ops[1]);
- break;
- }
- case OpSubgroupReadInvocationKHR:
- {
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- emit_binary_func_op(result_type, id, ops[2], ops[3], "readInvocationARB");
- require_extension_internal("GL_ARB_shader_ballot");
- register_control_dependent_expression(ops[1]);
- break;
- }
- case OpSubgroupAllKHR:
- {
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- emit_unary_func_op(result_type, id, ops[2], "allInvocationsARB");
- require_extension_internal("GL_ARB_shader_group_vote");
- register_control_dependent_expression(ops[1]);
- break;
- }
- case OpSubgroupAnyKHR:
- {
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- emit_unary_func_op(result_type, id, ops[2], "anyInvocationARB");
- require_extension_internal("GL_ARB_shader_group_vote");
- register_control_dependent_expression(ops[1]);
- break;
- }
- case OpSubgroupAllEqualKHR:
- {
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- emit_unary_func_op(result_type, id, ops[2], "allInvocationsEqualARB");
- require_extension_internal("GL_ARB_shader_group_vote");
- register_control_dependent_expression(ops[1]);
- break;
- }
- case OpGroupIAddNonUniformAMD:
- case OpGroupFAddNonUniformAMD:
- {
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- emit_unary_func_op(result_type, id, ops[4], "addInvocationsNonUniformAMD");
- require_extension_internal("GL_AMD_shader_ballot");
- register_control_dependent_expression(ops[1]);
- break;
- }
- case OpGroupFMinNonUniformAMD:
- case OpGroupUMinNonUniformAMD:
- case OpGroupSMinNonUniformAMD:
- {
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- emit_unary_func_op(result_type, id, ops[4], "minInvocationsNonUniformAMD");
- require_extension_internal("GL_AMD_shader_ballot");
- register_control_dependent_expression(ops[1]);
- break;
- }
- case OpGroupFMaxNonUniformAMD:
- case OpGroupUMaxNonUniformAMD:
- case OpGroupSMaxNonUniformAMD:
- {
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- emit_unary_func_op(result_type, id, ops[4], "maxInvocationsNonUniformAMD");
- require_extension_internal("GL_AMD_shader_ballot");
- register_control_dependent_expression(ops[1]);
- break;
- }
- case OpFragmentMaskFetchAMD:
- {
- auto &type = expression_type(ops[2]);
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- if (type.image.dim == DimSubpassData)
- {
- emit_unary_func_op(result_type, id, ops[2], "fragmentMaskFetchAMD");
- }
- else
- {
- emit_binary_func_op(result_type, id, ops[2], ops[3], "fragmentMaskFetchAMD");
- }
- require_extension_internal("GL_AMD_shader_fragment_mask");
- break;
- }
- case OpFragmentFetchAMD:
- {
- auto &type = expression_type(ops[2]);
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- if (type.image.dim == DimSubpassData)
- {
- emit_binary_func_op(result_type, id, ops[2], ops[4], "fragmentFetchAMD");
- }
- else
- {
- emit_trinary_func_op(result_type, id, ops[2], ops[3], ops[4], "fragmentFetchAMD");
- }
- require_extension_internal("GL_AMD_shader_fragment_mask");
- break;
- }
- // Vulkan 1.1 sub-group stuff ...
- case OpGroupNonUniformElect:
- case OpGroupNonUniformBroadcast:
- case OpGroupNonUniformBroadcastFirst:
- case OpGroupNonUniformBallot:
- case OpGroupNonUniformInverseBallot:
- case OpGroupNonUniformBallotBitExtract:
- case OpGroupNonUniformBallotBitCount:
- case OpGroupNonUniformBallotFindLSB:
- case OpGroupNonUniformBallotFindMSB:
- case OpGroupNonUniformShuffle:
- case OpGroupNonUniformShuffleXor:
- case OpGroupNonUniformShuffleUp:
- case OpGroupNonUniformShuffleDown:
- case OpGroupNonUniformAll:
- case OpGroupNonUniformAny:
- case OpGroupNonUniformAllEqual:
- case OpGroupNonUniformFAdd:
- case OpGroupNonUniformIAdd:
- case OpGroupNonUniformFMul:
- case OpGroupNonUniformIMul:
- case OpGroupNonUniformFMin:
- case OpGroupNonUniformFMax:
- case OpGroupNonUniformSMin:
- case OpGroupNonUniformSMax:
- case OpGroupNonUniformUMin:
- case OpGroupNonUniformUMax:
- case OpGroupNonUniformBitwiseAnd:
- case OpGroupNonUniformBitwiseOr:
- case OpGroupNonUniformBitwiseXor:
- case OpGroupNonUniformLogicalAnd:
- case OpGroupNonUniformLogicalOr:
- case OpGroupNonUniformLogicalXor:
- case OpGroupNonUniformQuadSwap:
- case OpGroupNonUniformQuadBroadcast:
- case OpGroupNonUniformQuadAllKHR:
- case OpGroupNonUniformQuadAnyKHR:
- case OpGroupNonUniformRotateKHR:
- emit_subgroup_op(instruction);
- break;
- case OpFUnordEqual:
- case OpFUnordLessThan:
- case OpFUnordGreaterThan:
- case OpFUnordLessThanEqual:
- case OpFUnordGreaterThanEqual:
- {
- // GLSL doesn't specify if floating point comparisons are ordered or unordered,
- // but glslang always emits ordered floating point compares for GLSL.
- // To get unordered compares, we can test the opposite thing and invert the result.
- // This way, we force true when there is any NaN present.
- uint32_t op0 = ops[2];
- uint32_t op1 = ops[3];
- string expr;
- if (expression_type(op0).vecsize > 1)
- {
- const char *comp_op = nullptr;
- switch (opcode)
- {
- case OpFUnordEqual:
- comp_op = "notEqual";
- break;
- case OpFUnordLessThan:
- comp_op = "greaterThanEqual";
- break;
- case OpFUnordLessThanEqual:
- comp_op = "greaterThan";
- break;
- case OpFUnordGreaterThan:
- comp_op = "lessThanEqual";
- break;
- case OpFUnordGreaterThanEqual:
- comp_op = "lessThan";
- break;
- default:
- assert(0);
- break;
- }
- expr = join("not(", comp_op, "(", to_unpacked_expression(op0), ", ", to_unpacked_expression(op1), "))");
- }
- else
- {
- const char *comp_op = nullptr;
- switch (opcode)
- {
- case OpFUnordEqual:
- comp_op = " != ";
- break;
- case OpFUnordLessThan:
- comp_op = " >= ";
- break;
- case OpFUnordLessThanEqual:
- comp_op = " > ";
- break;
- case OpFUnordGreaterThan:
- comp_op = " <= ";
- break;
- case OpFUnordGreaterThanEqual:
- comp_op = " < ";
- break;
- default:
- assert(0);
- break;
- }
- expr = join("!(", to_enclosed_unpacked_expression(op0), comp_op, to_enclosed_unpacked_expression(op1), ")");
- }
- emit_op(ops[0], ops[1], expr, should_forward(op0) && should_forward(op1));
- inherit_expression_dependencies(ops[1], op0);
- inherit_expression_dependencies(ops[1], op1);
- break;
- }
- case OpReportIntersectionKHR:
- // NV is same opcode.
- forced_temporaries.insert(ops[1]);
- if (ray_tracing_is_khr)
- GLSL_BFOP(reportIntersectionEXT);
- else
- GLSL_BFOP(reportIntersectionNV);
- flush_control_dependent_expressions(current_emitting_block->self);
- break;
- case OpIgnoreIntersectionNV:
- // KHR variant is a terminator.
- statement("ignoreIntersectionNV();");
- flush_control_dependent_expressions(current_emitting_block->self);
- break;
- case OpTerminateRayNV:
- // KHR variant is a terminator.
- statement("terminateRayNV();");
- flush_control_dependent_expressions(current_emitting_block->self);
- break;
- case OpTraceNV:
- statement("traceNV(", to_non_uniform_aware_expression(ops[0]), ", ", to_expression(ops[1]), ", ", to_expression(ops[2]), ", ",
- to_expression(ops[3]), ", ", to_expression(ops[4]), ", ", to_expression(ops[5]), ", ",
- to_expression(ops[6]), ", ", to_expression(ops[7]), ", ", to_expression(ops[8]), ", ",
- to_expression(ops[9]), ", ", to_expression(ops[10]), ");");
- flush_control_dependent_expressions(current_emitting_block->self);
- break;
- case OpTraceRayKHR:
- if (!has_decoration(ops[10], DecorationLocation))
- SPIRV_CROSS_THROW("A memory declaration object must be used in TraceRayKHR.");
- statement("traceRayEXT(", to_non_uniform_aware_expression(ops[0]), ", ", to_expression(ops[1]), ", ", to_expression(ops[2]), ", ",
- to_expression(ops[3]), ", ", to_expression(ops[4]), ", ", to_expression(ops[5]), ", ",
- to_expression(ops[6]), ", ", to_expression(ops[7]), ", ", to_expression(ops[8]), ", ",
- to_expression(ops[9]), ", ", get_decoration(ops[10], DecorationLocation), ");");
- flush_control_dependent_expressions(current_emitting_block->self);
- break;
- case OpExecuteCallableNV:
- statement("executeCallableNV(", to_expression(ops[0]), ", ", to_expression(ops[1]), ");");
- flush_control_dependent_expressions(current_emitting_block->self);
- break;
- case OpExecuteCallableKHR:
- if (!has_decoration(ops[1], DecorationLocation))
- SPIRV_CROSS_THROW("A memory declaration object must be used in ExecuteCallableKHR.");
- statement("executeCallableEXT(", to_expression(ops[0]), ", ", get_decoration(ops[1], DecorationLocation), ");");
- flush_control_dependent_expressions(current_emitting_block->self);
- break;
- // Don't bother forwarding temporaries. Avoids having to test expression invalidation with ray query objects.
- case OpRayQueryInitializeKHR:
- flush_variable_declaration(ops[0]);
- statement("rayQueryInitializeEXT(",
- to_expression(ops[0]), ", ", to_expression(ops[1]), ", ",
- to_expression(ops[2]), ", ", to_expression(ops[3]), ", ",
- to_expression(ops[4]), ", ", to_expression(ops[5]), ", ",
- to_expression(ops[6]), ", ", to_expression(ops[7]), ");");
- break;
- case OpRayQueryProceedKHR:
- flush_variable_declaration(ops[0]);
- emit_op(ops[0], ops[1], join("rayQueryProceedEXT(", to_expression(ops[2]), ")"), false);
- break;
- case OpRayQueryTerminateKHR:
- flush_variable_declaration(ops[0]);
- statement("rayQueryTerminateEXT(", to_expression(ops[0]), ");");
- break;
- case OpRayQueryGenerateIntersectionKHR:
- flush_variable_declaration(ops[0]);
- statement("rayQueryGenerateIntersectionEXT(", to_expression(ops[0]), ", ", to_expression(ops[1]), ");");
- break;
- case OpRayQueryConfirmIntersectionKHR:
- flush_variable_declaration(ops[0]);
- statement("rayQueryConfirmIntersectionEXT(", to_expression(ops[0]), ");");
- break;
- case OpRayQueryGetIntersectionTriangleVertexPositionsKHR:
- flush_variable_declaration(ops[1]);
- emit_uninitialized_temporary_expression(ops[0], ops[1]);
- statement("rayQueryGetIntersectionTriangleVertexPositionsEXT(", to_expression(ops[2]), ", bool(", to_expression(ops[3]), "), ", to_expression(ops[1]), ");");
- break;
- #define GLSL_RAY_QUERY_GET_OP(op) \
- case OpRayQueryGet##op##KHR: \
- flush_variable_declaration(ops[2]); \
- emit_op(ops[0], ops[1], join("rayQueryGet" #op "EXT(", to_expression(ops[2]), ")"), false); \
- break
- #define GLSL_RAY_QUERY_GET_OP2(op) \
- case OpRayQueryGet##op##KHR: \
- flush_variable_declaration(ops[2]); \
- emit_op(ops[0], ops[1], join("rayQueryGet" #op "EXT(", to_expression(ops[2]), ", ", "bool(", to_expression(ops[3]), "))"), false); \
- break
- GLSL_RAY_QUERY_GET_OP(RayTMin);
- GLSL_RAY_QUERY_GET_OP(RayFlags);
- GLSL_RAY_QUERY_GET_OP(WorldRayOrigin);
- GLSL_RAY_QUERY_GET_OP(WorldRayDirection);
- GLSL_RAY_QUERY_GET_OP(IntersectionCandidateAABBOpaque);
- GLSL_RAY_QUERY_GET_OP2(IntersectionType);
- GLSL_RAY_QUERY_GET_OP2(IntersectionT);
- GLSL_RAY_QUERY_GET_OP2(IntersectionInstanceCustomIndex);
- GLSL_RAY_QUERY_GET_OP2(IntersectionInstanceId);
- GLSL_RAY_QUERY_GET_OP2(IntersectionInstanceShaderBindingTableRecordOffset);
- GLSL_RAY_QUERY_GET_OP2(IntersectionGeometryIndex);
- GLSL_RAY_QUERY_GET_OP2(IntersectionPrimitiveIndex);
- GLSL_RAY_QUERY_GET_OP2(IntersectionBarycentrics);
- GLSL_RAY_QUERY_GET_OP2(IntersectionFrontFace);
- GLSL_RAY_QUERY_GET_OP2(IntersectionObjectRayDirection);
- GLSL_RAY_QUERY_GET_OP2(IntersectionObjectRayOrigin);
- GLSL_RAY_QUERY_GET_OP2(IntersectionObjectToWorld);
- GLSL_RAY_QUERY_GET_OP2(IntersectionWorldToObject);
- #undef GLSL_RAY_QUERY_GET_OP
- #undef GLSL_RAY_QUERY_GET_OP2
- case OpRayQueryGetClusterIdNV:
- flush_variable_declaration(ops[2]);
- emit_op(ops[0], ops[1], join("rayQueryGetIntersectionClusterIdNV(", to_expression(ops[2]), ", ", "bool(", to_expression(ops[3]), "))"), false);
- break;
- case OpTensorQuerySizeARM:
- flush_variable_declaration(ops[1]);
- // tensorSizeARM(tensor, dimension)
- emit_binary_func_op(ops[0], ops[1], ops[2], ops[3], "tensorSizeARM");
- break;
- case OpTensorReadARM:
- {
- flush_variable_declaration(ops[1]);
- emit_uninitialized_temporary_expression(ops[0], ops[1]);
- SmallVector<std::string> args {
- to_expression(ops[2]), // tensor
- to_expression(ops[3]), // coordinates
- to_expression(ops[1]), // out value
- };
- if (length > 4)
- {
- std::string tensor_operands;
- if (ops[4] == 0)
- tensor_operands = "0x0u";
- else if (ops[4] == TensorOperandsNontemporalARMMask)
- tensor_operands = "gl_TensorOperandsNonTemporalARM";
- else if (ops[4] == TensorOperandsOutOfBoundsValueARMMask)
- tensor_operands = "gl_TensorOperandsOutOfBoundsValueARM";
- else if (ops[4] == (TensorOperandsNontemporalARMMask | TensorOperandsOutOfBoundsValueARMMask))
- tensor_operands = "gl_TensorOperandsNonTemporalARM | gl_TensorOperandsOutOfBoundsValueARM";
- else
- SPIRV_CROSS_THROW("Invalid tensorOperands for tensorReadARM.");
- if ((ops[4] & TensorOperandsOutOfBoundsValueARMMask) && length != 6)
- SPIRV_CROSS_THROW("gl_TensorOperandsOutOfBoundsValueARM requires an outOfBoundsValue argument.");
- args.push_back(tensor_operands); // tensorOperands
- }
- if (length >= 6)
- {
- if ((length > 6) || (ops[4] & TensorOperandsOutOfBoundsValueARMMask) == 0)
- SPIRV_CROSS_THROW("Too many arguments to tensorReadARM.");
- args.push_back(to_expression(ops[5])); // outOfBoundsValue
- }
- // tensorRead(tensor, sizeof(type), coordinates, value, operand, ...)
- statement("tensorReadARM(", merge(args), ");");
- break;
- }
- case OpTensorWriteARM:
- {
- flush_variable_declaration(ops[0]);
- SmallVector<std::string> args {
- to_expression(ops[0]), // tensor
- to_expression(ops[1]), // coordinates
- to_expression(ops[2]), // out value
- };
- if (length > 3)
- {
- std::string tensor_operands;
- if (ops[3] == 0)
- tensor_operands = "0x0u";
- else if (ops[3] == TensorOperandsNontemporalARMMask)
- tensor_operands = "gl_TensorOperandsNonTemporalARM";
- else
- SPIRV_CROSS_THROW("Invalid tensorOperands for tensorWriteARM.");
- args.push_back(tensor_operands); // tensorOperands
- }
- if (length > 4)
- SPIRV_CROSS_THROW("Too many arguments to tensorWriteARM.");
- // tensorWrite(tensor, sizeof(type), coordinates, value)
- statement("tensorWriteARM(", merge(args), ");");
- break;
- }
- case OpConvertUToAccelerationStructureKHR:
- {
- require_extension_internal("GL_EXT_ray_tracing");
- bool elide_temporary = should_forward(ops[2]) && forced_temporaries.count(ops[1]) == 0 &&
- !hoisted_temporaries.count(ops[1]);
- if (elide_temporary)
- {
- GLSL_UFOP(accelerationStructureEXT);
- }
- else
- {
- // Force this path in subsequent iterations.
- forced_temporaries.insert(ops[1]);
- // We cannot declare a temporary acceleration structure in GLSL.
- // If we get to this point, we'll have to emit a temporary uvec2,
- // and cast to RTAS on demand.
- statement(declare_temporary(expression_type_id(ops[2]), ops[1]), to_unpacked_expression(ops[2]), ";");
- // Use raw SPIRExpression interface to block all usage tracking.
- set<SPIRExpression>(ops[1], join("accelerationStructureEXT(", to_name(ops[1]), ")"), ops[0], true);
- }
- break;
- }
- case OpConvertUToPtr:
- {
- auto &type = get<SPIRType>(ops[0]);
- if (type.storage != StorageClassPhysicalStorageBuffer)
- SPIRV_CROSS_THROW("Only StorageClassPhysicalStorageBuffer is supported by OpConvertUToPtr.");
- auto &in_type = expression_type(ops[2]);
- if (in_type.vecsize == 2)
- require_extension_internal("GL_EXT_buffer_reference_uvec2");
- auto op = type_to_glsl(type);
- emit_unary_func_op(ops[0], ops[1], ops[2], op.c_str());
- break;
- }
- case OpConvertPtrToU:
- {
- auto &type = get<SPIRType>(ops[0]);
- auto &ptr_type = expression_type(ops[2]);
- if (ptr_type.storage != StorageClassPhysicalStorageBuffer)
- SPIRV_CROSS_THROW("Only StorageClassPhysicalStorageBuffer is supported by OpConvertPtrToU.");
- if (type.vecsize == 2)
- require_extension_internal("GL_EXT_buffer_reference_uvec2");
- auto op = type_to_glsl(type);
- emit_unary_func_op(ops[0], ops[1], ops[2], op.c_str());
- break;
- }
- case OpUndef:
- // Undefined value has been declared.
- break;
- case OpLine:
- {
- emit_line_directive(ops[0], ops[1]);
- break;
- }
- case OpNoLine:
- break;
- case OpDemoteToHelperInvocationEXT:
- if (!options.vulkan_semantics)
- SPIRV_CROSS_THROW("GL_EXT_demote_to_helper_invocation is only supported in Vulkan GLSL.");
- require_extension_internal("GL_EXT_demote_to_helper_invocation");
- statement(backend.demote_literal, ";");
- break;
- case OpIsHelperInvocationEXT:
- if (!options.vulkan_semantics)
- SPIRV_CROSS_THROW("GL_EXT_demote_to_helper_invocation is only supported in Vulkan GLSL.");
- require_extension_internal("GL_EXT_demote_to_helper_invocation");
- // Helper lane state with demote is volatile by nature.
- // Do not forward this.
- emit_op(ops[0], ops[1], "helperInvocationEXT()", false);
- break;
- case OpBeginInvocationInterlockEXT:
- // If the interlock is complex, we emit this elsewhere.
- if (!interlocked_is_complex)
- {
- statement("SPIRV_Cross_beginInvocationInterlock();");
- flush_all_active_variables();
- // Make sure forwarding doesn't propagate outside interlock region.
- }
- break;
- case OpEndInvocationInterlockEXT:
- // If the interlock is complex, we emit this elsewhere.
- if (!interlocked_is_complex)
- {
- statement("SPIRV_Cross_endInvocationInterlock();");
- flush_all_active_variables();
- // Make sure forwarding doesn't propagate outside interlock region.
- }
- break;
- case OpSetMeshOutputsEXT:
- statement("SetMeshOutputsEXT(", to_unpacked_expression(ops[0]), ", ", to_unpacked_expression(ops[1]), ");");
- break;
- case OpReadClockKHR:
- {
- auto &type = get<SPIRType>(ops[0]);
- auto scope = static_cast<Scope>(evaluate_constant_u32(ops[2]));
- const char *op = nullptr;
- // Forwarding clock statements leads to a scenario where an SSA value can take on different
- // values every time it's evaluated. Block any forwarding attempt.
- // We also might want to invalidate all expressions to function as a sort of optimization
- // barrier, but might be overkill for now.
- if (scope == ScopeDevice)
- {
- require_extension_internal("GL_EXT_shader_realtime_clock");
- if (type.basetype == SPIRType::BaseType::UInt64)
- op = "clockRealtimeEXT()";
- else if (type.basetype == SPIRType::BaseType::UInt && type.vecsize == 2)
- op = "clockRealtime2x32EXT()";
- else
- SPIRV_CROSS_THROW("Unsupported result type for OpReadClockKHR opcode.");
- }
- else if (scope == ScopeSubgroup)
- {
- require_extension_internal("GL_ARB_shader_clock");
- if (type.basetype == SPIRType::BaseType::UInt64)
- op = "clockARB()";
- else if (type.basetype == SPIRType::BaseType::UInt && type.vecsize == 2)
- op = "clock2x32ARB()";
- else
- SPIRV_CROSS_THROW("Unsupported result type for OpReadClockKHR opcode.");
- }
- else
- SPIRV_CROSS_THROW("Unsupported scope for OpReadClockKHR opcode.");
- emit_op(ops[0], ops[1], op, false);
- break;
- }
- case OpCooperativeVectorLoadNV:
- {
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- emit_uninitialized_temporary_expression(result_type, id);
- statement("coopVecLoadNV(", to_expression(id), ", ", to_expression(ops[2]), ", ", to_expression(ops[3]), ");");
- register_read(id, ops[2], false);
- break;
- }
- case OpCooperativeVectorStoreNV:
- {
- uint32_t id = ops[0];
- statement("coopVecStoreNV(", to_expression(ops[2]), ", ", to_expression(id), ", ", to_expression(ops[1]), ");");
- register_write(ops[2]);
- break;
- }
- case OpCooperativeVectorOuterProductAccumulateNV:
- {
- auto buf = ops[0];
- auto offset = ops[1];
- auto v1 = ops[2];
- auto v2 = ops[3];
- auto matrix_layout_id = ops[4];
- auto matrix_iterpretation_id = ops[5];
- auto matrix_stride_id = length >= 6 ? ops[6] : 0;
- statement(join("coopVecOuterProductAccumulateNV(", to_expression(v1), ", ", to_expression(v2), ", ",
- to_expression(buf), ", ", to_expression(offset), ", ",
- matrix_stride_id ? to_expression(matrix_stride_id) : "0",
- ", ", to_pretty_expression_if_int_constant(
- matrix_layout_id, std::begin(CoopVecMatrixLayoutNames), std::end(CoopVecMatrixLayoutNames)),
- ", ", to_pretty_expression_if_int_constant(
- matrix_iterpretation_id, std::begin(CoopVecComponentTypeNames), std::end(CoopVecComponentTypeNames)),
- ");"));
- register_write(ops[0]);
- break;
- }
- case OpCooperativeVectorReduceSumAccumulateNV:
- {
- auto buf = ops[0];
- auto offset = ops[1];
- auto v1 = ops[2];
- statement(join("coopVecReduceSumAccumulateNV(", to_expression(v1), ", ", to_expression(buf), ", ",
- to_expression(offset), ");"));
- register_write(ops[0]);
- break;
- }
- case OpCooperativeVectorMatrixMulNV:
- case OpCooperativeVectorMatrixMulAddNV:
- {
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- emit_uninitialized_temporary_expression(result_type, id);
- std::string stmt;
- switch (opcode)
- {
- case OpCooperativeVectorMatrixMulAddNV:
- stmt += "coopVecMatMulAddNV(";
- break;
- case OpCooperativeVectorMatrixMulNV:
- stmt += "coopVecMatMulNV(";
- break;
- default:
- SPIRV_CROSS_THROW("Invalid op code for coopvec instruction.");
- }
- for (uint32_t i = 1; i < length; i++)
- {
- // arguments 3, 6 and in case of MulAddNv also 9 use component type int constants
- if (i == 3 || i == 6 || (i == 9 && opcode == OpCooperativeVectorMatrixMulAddNV))
- {
- stmt += to_pretty_expression_if_int_constant(
- ops[i], std::begin(CoopVecComponentTypeNames), std::end(CoopVecComponentTypeNames));
- }
- else if ((i == 12 && opcode == OpCooperativeVectorMatrixMulAddNV) ||
- (i == 9 && opcode == OpCooperativeVectorMatrixMulNV))
- {
- stmt += to_pretty_expression_if_int_constant(
- ops[i], std::begin(CoopVecMatrixLayoutNames), std::end(CoopVecMatrixLayoutNames));
- }
- else
- stmt += to_expression(ops[i]);
- if (i < length - 1)
- stmt += ", ";
- }
- stmt += ");";
- statement(stmt);
- break;
- }
- case OpCooperativeMatrixLengthKHR:
- {
- // Need to synthesize a dummy temporary, since the SPIR-V opcode is based on the type.
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- set<SPIRExpression>(
- id, join(type_to_glsl(get<SPIRType>(result_type)),
- "(", type_to_glsl(get<SPIRType>(ops[2])), "(0).length())"),
- result_type, true);
- break;
- }
- case OpCooperativeMatrixLoadKHR:
- {
- // Spec contradicts itself if stride is optional or not.
- if (length < 5)
- SPIRV_CROSS_THROW("Stride is not provided.");
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- emit_uninitialized_temporary_expression(result_type, id);
- auto expr = to_expression(ops[2]);
- pair<string, string> split_expr;
- if (!is_forcing_recompilation())
- split_expr = split_coopmat_pointer(expr);
- string layout_expr = to_pretty_expression_if_int_constant(
- ops[3], std::begin(CoopMatMatrixLayoutNames), std::end(CoopMatMatrixLayoutNames));
- statement("coopMatLoad(", to_expression(id), ", ", split_expr.first, ", ", split_expr.second, ", ",
- to_expression(ops[4]), ", ", layout_expr, ");");
- register_read(id, ops[2], false);
- break;
- }
- case OpCooperativeMatrixStoreKHR:
- {
- // Spec contradicts itself if stride is optional or not.
- if (length < 4)
- SPIRV_CROSS_THROW("Stride is not provided.");
- // SPIR-V and GLSL don't agree how to pass the expression.
- // In SPIR-V it's a pointer, but in GLSL it's reference to array + index.
- auto expr = to_expression(ops[0]);
- pair<string, string> split_expr;
- if (!is_forcing_recompilation())
- split_expr = split_coopmat_pointer(expr);
- string layout_expr = to_pretty_expression_if_int_constant(
- ops[2], std::begin(CoopMatMatrixLayoutNames), std::end(CoopMatMatrixLayoutNames));
- statement("coopMatStore(", to_expression(ops[1]), ", ", split_expr.first, ", ", split_expr.second, ", ",
- to_expression(ops[3]), ", ", layout_expr, ");");
- // TODO: Do we care about memory operands?
- register_write(ops[0]);
- break;
- }
- case OpCooperativeMatrixMulAddKHR:
- {
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- uint32_t A = ops[2];
- uint32_t B = ops[3];
- uint32_t C = ops[4];
- bool forward = should_forward(A) && should_forward(B) && should_forward(C);
- emit_op(result_type, id,
- join("coopMatMulAdd(",
- to_unpacked_expression(A), ", ",
- to_unpacked_expression(B), ", ",
- to_unpacked_expression(C), ", ",
- (length >= 6 ? ops[5] : 0),
- ")"),
- forward);
- inherit_expression_dependencies(id, A);
- inherit_expression_dependencies(id, B);
- inherit_expression_dependencies(id, C);
- break;
- }
- case OpCompositeConstructReplicateEXT:
- {
- uint32_t result_type = ops[0];
- uint32_t id = ops[1];
- auto &type = get<SPIRType>(result_type);
- auto value_to_replicate = to_expression(ops[2]);
- std::string rhs;
- // Matrices don't have a replicating constructor for vectors. Need to manually replicate
- if (type.op == OpTypeMatrix || type.op == OpTypeArray)
- {
- if (type.op == OpTypeArray && type.array.size() != 1)
- {
- SPIRV_CROSS_THROW(
- "Multi-dimensional arrays currently not supported for OpCompositeConstructReplicateEXT");
- }
- uint32_t num_elements = type.op == OpTypeMatrix ? type.columns : type.array[0];
- if (backend.use_initializer_list && type.op == OpTypeArray)
- {
- rhs += "{";
- }
- else
- {
- rhs += type_to_glsl_constructor(type);
- rhs += "(";
- }
- for (uint32_t i = 0; i < num_elements; i++)
- {
- rhs += value_to_replicate;
- if (i < num_elements - 1)
- rhs += ", ";
- }
- if (backend.use_initializer_list && type.op == OpTypeArray)
- rhs += "}";
- else
- rhs += ")";
- }
- else
- {
- rhs = join(type_to_glsl(type), "(", to_expression(ops[2]), ")");
- }
- emit_op(result_type, id, rhs, true);
- break;
- }
- default:
- statement("// unimplemented op ", instruction.op);
- break;
- }
- }
- // Appends function arguments, mapped from global variables, beyond the specified arg index.
- // This is used when a function call uses fewer arguments than the function defines.
- // This situation may occur if the function signature has been dynamically modified to
- // extract global variables referenced from within the function, and convert them to
- // function arguments. This is necessary for shader languages that do not support global
- // access to shader input content from within a function (eg. Metal). Each additional
- // function args uses the name of the global variable. Function nesting will modify the
- // functions and function calls all the way up the nesting chain.
- void CompilerGLSL::append_global_func_args(const SPIRFunction &func, uint32_t index, SmallVector<string> &arglist)
- {
- auto &args = func.arguments;
- uint32_t arg_cnt = uint32_t(args.size());
- for (uint32_t arg_idx = index; arg_idx < arg_cnt; arg_idx++)
- {
- auto &arg = args[arg_idx];
- assert(arg.alias_global_variable);
- // If the underlying variable needs to be declared
- // (ie. a local variable with deferred declaration), do so now.
- uint32_t var_id = get<SPIRVariable>(arg.id).basevariable;
- if (var_id)
- flush_variable_declaration(var_id);
- arglist.push_back(to_func_call_arg(arg, arg.id));
- }
- }
- string CompilerGLSL::to_member_name(const SPIRType &type, uint32_t index)
- {
- if (type.type_alias != TypeID(0) &&
- !has_extended_decoration(type.type_alias, SPIRVCrossDecorationBufferBlockRepacked))
- {
- return to_member_name(get<SPIRType>(type.type_alias), index);
- }
- auto &memb = ir.meta[type.self].members;
- if (index < memb.size() && !memb[index].alias.empty())
- return memb[index].alias;
- else
- return join("_m", index);
- }
- string CompilerGLSL::to_member_reference(uint32_t, const SPIRType &type, uint32_t index, bool)
- {
- return join(".", to_member_name(type, index));
- }
- string CompilerGLSL::to_multi_member_reference(const SPIRType &type, const SmallVector<uint32_t> &indices)
- {
- string ret;
- auto *member_type = &type;
- for (auto &index : indices)
- {
- ret += join(".", to_member_name(*member_type, index));
- member_type = &get<SPIRType>(member_type->member_types[index]);
- }
- return ret;
- }
- void CompilerGLSL::add_member_name(SPIRType &type, uint32_t index)
- {
- auto &memb = ir.meta[type.self].members;
- if (index < memb.size() && !memb[index].alias.empty())
- {
- auto &name = memb[index].alias;
- if (name.empty())
- return;
- ParsedIR::sanitize_identifier(name, true, true);
- update_name_cache(type.member_name_cache, name);
- }
- }
- // Checks whether the ID is a row_major matrix that requires conversion before use
- bool CompilerGLSL::is_non_native_row_major_matrix(uint32_t id)
- {
- // Natively supported row-major matrices do not need to be converted.
- // Legacy targets do not support row major.
- if (backend.native_row_major_matrix && !is_legacy())
- return false;
- auto *e = maybe_get<SPIRExpression>(id);
- if (e)
- return e->need_transpose;
- else
- return has_decoration(id, DecorationRowMajor);
- }
- // Checks whether the member is a row_major matrix that requires conversion before use
- bool CompilerGLSL::member_is_non_native_row_major_matrix(const SPIRType &type, uint32_t index)
- {
- // Natively supported row-major matrices do not need to be converted.
- if (backend.native_row_major_matrix && !is_legacy())
- return false;
- // Non-matrix or column-major matrix types do not need to be converted.
- if (!has_member_decoration(type.self, index, DecorationRowMajor))
- return false;
- // Only square row-major matrices can be converted at this time.
- // Converting non-square matrices will require defining custom GLSL function that
- // swaps matrix elements while retaining the original dimensional form of the matrix.
- const auto mbr_type = get<SPIRType>(type.member_types[index]);
- if (mbr_type.columns != mbr_type.vecsize)
- SPIRV_CROSS_THROW("Row-major matrices must be square on this platform.");
- return true;
- }
- // Checks if we need to remap physical type IDs when declaring the type in a buffer.
- bool CompilerGLSL::member_is_remapped_physical_type(const SPIRType &type, uint32_t index) const
- {
- return has_extended_member_decoration(type.self, index, SPIRVCrossDecorationPhysicalTypeID);
- }
- // Checks whether the member is in packed data type, that might need to be unpacked.
- bool CompilerGLSL::member_is_packed_physical_type(const SPIRType &type, uint32_t index) const
- {
- return has_extended_member_decoration(type.self, index, SPIRVCrossDecorationPhysicalTypePacked);
- }
- // Wraps the expression string in a function call that converts the
- // row_major matrix result of the expression to a column_major matrix.
- // Base implementation uses the standard library transpose() function.
- // Subclasses may override to use a different function.
- string CompilerGLSL::convert_row_major_matrix(string exp_str, const SPIRType &exp_type, uint32_t /* physical_type_id */,
- bool /*is_packed*/, bool relaxed)
- {
- strip_enclosed_expression(exp_str);
- if (!is_matrix(exp_type))
- {
- auto column_index = exp_str.find_last_of('[');
- if (column_index == string::npos)
- return exp_str;
- auto column_expr = exp_str.substr(column_index);
- exp_str.resize(column_index);
- auto end_deferred_index = column_expr.find_last_of(']');
- if (end_deferred_index != string::npos && end_deferred_index + 1 != column_expr.size())
- {
- // If we have any data member fixups, it must be transposed so that it refers to this index.
- // E.g. [0].data followed by [1] would be shuffled to [1][0].data which is wrong,
- // and needs to be [1].data[0] instead.
- end_deferred_index++;
- column_expr = column_expr.substr(end_deferred_index) +
- column_expr.substr(0, end_deferred_index);
- }
- auto transposed_expr = type_to_glsl_constructor(exp_type) + "(";
- // Loading a column from a row-major matrix. Unroll the load.
- for (uint32_t c = 0; c < exp_type.vecsize; c++)
- {
- transposed_expr += join(exp_str, '[', c, ']', column_expr);
- if (c + 1 < exp_type.vecsize)
- transposed_expr += ", ";
- }
- transposed_expr += ")";
- return transposed_expr;
- }
- else if (options.version < 120)
- {
- // GLSL 110, ES 100 do not have transpose(), so emulate it. Note that
- // these GLSL versions do not support non-square matrices.
- if (exp_type.vecsize == 2 && exp_type.columns == 2)
- require_polyfill(PolyfillTranspose2x2, relaxed);
- else if (exp_type.vecsize == 3 && exp_type.columns == 3)
- require_polyfill(PolyfillTranspose3x3, relaxed);
- else if (exp_type.vecsize == 4 && exp_type.columns == 4)
- require_polyfill(PolyfillTranspose4x4, relaxed);
- else
- SPIRV_CROSS_THROW("Non-square matrices are not supported in legacy GLSL, cannot transpose.");
- return join("spvTranspose", (options.es && relaxed) ? "MP" : "", "(", exp_str, ")");
- }
- else
- return join("transpose(", exp_str, ")");
- }
- string CompilerGLSL::variable_decl(const SPIRType &type, const string &name, uint32_t id)
- {
- string type_name = type_to_glsl(type, id);
- remap_variable_type_name(type, name, type_name);
- return join(type_name, " ", name, type_to_array_glsl(type, id));
- }
- bool CompilerGLSL::variable_decl_is_remapped_storage(const SPIRVariable &var, StorageClass storage) const
- {
- return var.storage == storage;
- }
- // Emit a structure member. Subclasses may override to modify output,
- // or to dynamically add a padding member if needed.
- void CompilerGLSL::emit_struct_member(const SPIRType &type, uint32_t member_type_id, uint32_t index,
- const string &qualifier, uint32_t)
- {
- auto &membertype = get<SPIRType>(member_type_id);
- Bitset memberflags;
- auto &memb = ir.meta[type.self].members;
- if (index < memb.size())
- memberflags = memb[index].decoration_flags;
- string qualifiers;
- bool is_block = ir.meta[type.self].decoration.decoration_flags.get(DecorationBlock) ||
- ir.meta[type.self].decoration.decoration_flags.get(DecorationBufferBlock);
- if (is_block)
- qualifiers = to_interpolation_qualifiers(memberflags);
- statement(layout_for_member(type, index), qualifiers, qualifier, flags_to_qualifiers_glsl(membertype, 0, memberflags),
- variable_decl(membertype, to_member_name(type, index)), ";");
- }
- void CompilerGLSL::emit_struct_padding_target(const SPIRType &)
- {
- }
- string CompilerGLSL::flags_to_qualifiers_glsl(const SPIRType &type, uint32_t id, const Bitset &flags)
- {
- // GL_EXT_buffer_reference variables can be marked as restrict.
- if (flags.get(DecorationRestrictPointerEXT))
- return "restrict ";
- string qual;
- if (type_is_floating_point(type) &&
- (flags.get(DecorationNoContraction) || (type.self && has_legacy_nocontract(type.self, id))) &&
- backend.support_precise_qualifier)
- {
- qual = "precise ";
- }
- // Structs do not have precision qualifiers, neither do doubles (desktop only anyways, so no mediump/highp).
- bool type_supports_precision =
- type.basetype == SPIRType::Float || type.basetype == SPIRType::Int || type.basetype == SPIRType::UInt ||
- type.basetype == SPIRType::Image || type.basetype == SPIRType::SampledImage ||
- type.basetype == SPIRType::Sampler;
- if (!type_supports_precision)
- return qual;
- if (options.es)
- {
- auto &execution = get_entry_point();
- if (type.basetype == SPIRType::UInt && is_legacy_es())
- {
- // HACK: This is a bool. See comment in type_to_glsl().
- qual += "lowp ";
- }
- else if (flags.get(DecorationRelaxedPrecision))
- {
- bool implied_fmediump = type.basetype == SPIRType::Float &&
- options.fragment.default_float_precision == Options::Mediump &&
- execution.model == ExecutionModelFragment;
- bool implied_imediump = (type.basetype == SPIRType::Int || type.basetype == SPIRType::UInt) &&
- options.fragment.default_int_precision == Options::Mediump &&
- execution.model == ExecutionModelFragment;
- qual += (implied_fmediump || implied_imediump) ? "" : "mediump ";
- }
- else
- {
- bool implied_fhighp =
- type.basetype == SPIRType::Float && ((options.fragment.default_float_precision == Options::Highp &&
- execution.model == ExecutionModelFragment) ||
- (execution.model != ExecutionModelFragment));
- bool implied_ihighp = (type.basetype == SPIRType::Int || type.basetype == SPIRType::UInt) &&
- ((options.fragment.default_int_precision == Options::Highp &&
- execution.model == ExecutionModelFragment) ||
- (execution.model != ExecutionModelFragment));
- qual += (implied_fhighp || implied_ihighp) ? "" : "highp ";
- }
- }
- else if (backend.allow_precision_qualifiers)
- {
- // Vulkan GLSL supports precision qualifiers, even in desktop profiles, which is convenient.
- // The default is highp however, so only emit mediump in the rare case that a shader has these.
- if (flags.get(DecorationRelaxedPrecision))
- qual += "mediump ";
- }
- return qual;
- }
- string CompilerGLSL::to_precision_qualifiers_glsl(uint32_t id)
- {
- auto &type = expression_type(id);
- bool use_precision_qualifiers = backend.allow_precision_qualifiers;
- if (use_precision_qualifiers && (type.basetype == SPIRType::Image || type.basetype == SPIRType::SampledImage))
- {
- // Force mediump for the sampler type. We cannot declare 16-bit or smaller image types.
- auto &result_type = get<SPIRType>(type.image.type);
- if (result_type.width < 32)
- return "mediump ";
- }
- return flags_to_qualifiers_glsl(type, id, ir.meta[id].decoration.decoration_flags);
- }
- void CompilerGLSL::fixup_io_block_patch_primitive_qualifiers(const SPIRVariable &var)
- {
- // Works around weird behavior in glslangValidator where
- // a patch out block is translated to just block members getting the decoration.
- // To make glslang not complain when we compile again, we have to transform this back to a case where
- // the variable itself has Patch decoration, and not members.
- // Same for perprimitiveEXT.
- auto &type = get<SPIRType>(var.basetype);
- if (has_decoration(type.self, DecorationBlock))
- {
- uint32_t member_count = uint32_t(type.member_types.size());
- Decoration promoted_decoration = {};
- bool do_promote_decoration = false;
- for (uint32_t i = 0; i < member_count; i++)
- {
- if (has_member_decoration(type.self, i, DecorationPatch))
- {
- promoted_decoration = DecorationPatch;
- do_promote_decoration = true;
- break;
- }
- else if (has_member_decoration(type.self, i, DecorationPerPrimitiveEXT))
- {
- promoted_decoration = DecorationPerPrimitiveEXT;
- do_promote_decoration = true;
- break;
- }
- }
- if (do_promote_decoration)
- {
- set_decoration(var.self, promoted_decoration);
- for (uint32_t i = 0; i < member_count; i++)
- unset_member_decoration(type.self, i, promoted_decoration);
- }
- }
- }
- string CompilerGLSL::to_qualifiers_glsl(uint32_t id)
- {
- auto &flags = get_decoration_bitset(id);
- string res;
- auto *var = maybe_get<SPIRVariable>(id);
- if (var && var->storage == StorageClassWorkgroup && !backend.shared_is_implied)
- res += "shared ";
- else if (var && var->storage == StorageClassTaskPayloadWorkgroupEXT && !backend.shared_is_implied)
- res += "taskPayloadSharedEXT ";
- res += to_interpolation_qualifiers(flags);
- if (var)
- res += to_storage_qualifiers_glsl(*var);
- auto &type = expression_type(id);
- if (type.image.dim != DimSubpassData && type.image.sampled == 2)
- {
- if (flags.get(DecorationCoherent))
- res += "coherent ";
- if (flags.get(DecorationRestrict))
- res += "restrict ";
- if (flags.get(DecorationNonWritable))
- res += "readonly ";
- bool formatted_load = type.image.format == ImageFormatUnknown;
- if (flags.get(DecorationNonReadable))
- {
- res += "writeonly ";
- formatted_load = false;
- }
- if (formatted_load)
- {
- if (!options.es)
- require_extension_internal("GL_EXT_shader_image_load_formatted");
- else
- SPIRV_CROSS_THROW("Cannot use GL_EXT_shader_image_load_formatted in ESSL.");
- }
- }
- else if (type.basetype == SPIRType::Tensor)
- {
- if (flags.get(DecorationNonWritable))
- res += "readonly ";
- if (flags.get(DecorationNonReadable))
- res += "writeonly ";
- }
- res += to_precision_qualifiers_glsl(id);
- return res;
- }
- string CompilerGLSL::argument_decl(const SPIRFunction::Parameter &arg)
- {
- // glslangValidator seems to make all arguments pointer no matter what which is rather bizarre ...
- auto &type = expression_type(arg.id);
- const char *direction = "";
- if (is_pointer(type) &&
- (type.storage == StorageClassFunction ||
- type.storage == StorageClassPrivate ||
- type.storage == StorageClassOutput))
- {
- // If we're passing around block types to function, we really mean reference in a pointer sense,
- // but DXC does not like inout for mesh blocks, so workaround that. out is technically not correct,
- // but it works in practice due to legalization. It's ... not great, but you gotta do what you gotta do.
- // GLSL will never hit this case since it's not valid.
- if (type.storage == StorageClassOutput && get_execution_model() == ExecutionModelMeshEXT &&
- has_decoration(type.self, DecorationBlock) && is_builtin_type(type) && arg.write_count)
- {
- direction = "out ";
- }
- else if (arg.write_count && arg.read_count)
- direction = "inout ";
- else if (arg.write_count)
- direction = "out ";
- }
- return join(direction, to_qualifiers_glsl(arg.id), variable_decl(type, to_name(arg.id), arg.id));
- }
- string CompilerGLSL::to_initializer_expression(const SPIRVariable &var)
- {
- return to_unpacked_expression(var.initializer);
- }
- string CompilerGLSL::to_zero_initialized_expression(uint32_t type_id)
- {
- #ifndef NDEBUG
- auto &type = get<SPIRType>(type_id);
- assert(type.storage == StorageClassPrivate || type.storage == StorageClassFunction ||
- type.storage == StorageClassGeneric);
- #endif
- uint32_t id = ir.increase_bound_by(1);
- ir.make_constant_null(id, type_id, false);
- return constant_expression(get<SPIRConstant>(id));
- }
- bool CompilerGLSL::type_can_zero_initialize(const SPIRType &type) const
- {
- if (type.pointer)
- return false;
- if (!type.array.empty() && options.flatten_multidimensional_arrays)
- return false;
- for (auto &literal : type.array_size_literal)
- if (!literal)
- return false;
- for (auto &memb : type.member_types)
- if (!type_can_zero_initialize(get<SPIRType>(memb)))
- return false;
- return true;
- }
- string CompilerGLSL::variable_decl(const SPIRVariable &variable)
- {
- // Ignore the pointer type since GLSL doesn't have pointers.
- auto &type = get_variable_data_type(variable);
- if (type.pointer_depth > 1 && !backend.support_pointer_to_pointer)
- SPIRV_CROSS_THROW("Cannot declare pointer-to-pointer types.");
- auto res = join(to_qualifiers_glsl(variable.self), variable_decl(type, to_name(variable.self), variable.self));
- if (variable.loop_variable && variable.static_expression)
- {
- uint32_t expr = variable.static_expression;
- if (ir.ids[expr].get_type() != TypeUndef)
- res += join(" = ", to_unpacked_expression(variable.static_expression));
- else if (options.force_zero_initialized_variables && type_can_zero_initialize(type))
- res += join(" = ", to_zero_initialized_expression(get_variable_data_type_id(variable)));
- }
- else if (variable.initializer)
- {
- if (!variable_decl_is_remapped_storage(variable, StorageClassWorkgroup))
- {
- uint32_t expr = variable.initializer;
- if (ir.ids[expr].get_type() != TypeUndef)
- res += join(" = ", to_initializer_expression(variable));
- else if (options.force_zero_initialized_variables && type_can_zero_initialize(type))
- res += join(" = ", to_zero_initialized_expression(get_variable_data_type_id(variable)));
- }
- else
- {
- // Workgroup memory requires special handling. First, it can only be Null-Initialized.
- // GLSL will handle this with null initializer, while others require more work after the decl
- require_extension_internal("GL_EXT_null_initializer");
- if (!backend.constant_null_initializer.empty())
- res += join(" = ", backend.constant_null_initializer);
- }
- }
- return res;
- }
- const char *CompilerGLSL::to_pls_qualifiers_glsl(const SPIRVariable &variable)
- {
- auto &flags = get_decoration_bitset(variable.self);
- if (flags.get(DecorationRelaxedPrecision))
- return "mediump ";
- else
- return "highp ";
- }
- string CompilerGLSL::pls_decl(const PlsRemap &var)
- {
- auto &variable = get<SPIRVariable>(var.id);
- auto op_and_basetype = pls_format_to_basetype(var.format);
- SPIRType type { op_and_basetype.first };
- type.basetype = op_and_basetype.second;
- auto vecsize = pls_format_to_components(var.format);
- if (vecsize > 1)
- {
- type.op = OpTypeVector;
- type.vecsize = vecsize;
- }
- return join(to_pls_layout(var.format), to_pls_qualifiers_glsl(variable), type_to_glsl(type), " ",
- to_name(variable.self));
- }
- uint32_t CompilerGLSL::to_array_size_literal(const SPIRType &type) const
- {
- return to_array_size_literal(type, uint32_t(type.array.size() - 1));
- }
- uint32_t CompilerGLSL::to_array_size_literal(const SPIRType &type, uint32_t index) const
- {
- assert(type.array.size() == type.array_size_literal.size());
- if (type.array_size_literal[index])
- {
- return type.array[index];
- }
- else
- {
- // Use the default spec constant value.
- // This is the best we can do.
- return evaluate_constant_u32(type.array[index]);
- }
- }
- string CompilerGLSL::to_array_size(const SPIRType &type, uint32_t index)
- {
- assert(type.array.size() == type.array_size_literal.size());
- auto &size = type.array[index];
- if (!type.array_size_literal[index])
- return to_expression(size);
- else if (size)
- return convert_to_string(size);
- else if (!backend.unsized_array_supported)
- {
- // For runtime-sized arrays, we can work around
- // lack of standard support for this by simply having
- // a single element array.
- //
- // Runtime length arrays must always be the last element
- // in an interface block.
- return "1";
- }
- else
- return "";
- }
- string CompilerGLSL::type_to_array_glsl(const SPIRType &type, uint32_t)
- {
- if (type.pointer && type.storage == StorageClassPhysicalStorageBuffer && type.basetype != SPIRType::Struct)
- {
- // We are using a wrapped pointer type, and we should not emit any array declarations here.
- return "";
- }
- if (type.array.empty())
- return "";
- if (options.flatten_multidimensional_arrays)
- {
- string res;
- res += "[";
- for (auto i = uint32_t(type.array.size()); i; i--)
- {
- res += enclose_expression(to_array_size(type, i - 1));
- if (i > 1)
- res += " * ";
- }
- res += "]";
- return res;
- }
- else
- {
- if (type.array.size() > 1)
- {
- if (!options.es && options.version < 430)
- require_extension_internal("GL_ARB_arrays_of_arrays");
- else if (options.es && options.version < 310)
- SPIRV_CROSS_THROW("Arrays of arrays not supported before ESSL version 310. "
- "Try using --flatten-multidimensional-arrays or set "
- "options.flatten_multidimensional_arrays to true.");
- }
- string res;
- for (auto i = uint32_t(type.array.size()); i; i--)
- {
- res += "[";
- res += to_array_size(type, i - 1);
- res += "]";
- }
- return res;
- }
- }
- string CompilerGLSL::image_type_glsl(const SPIRType &type, uint32_t id, bool /*member*/)
- {
- auto &imagetype = get<SPIRType>(type.image.type);
- string res;
- switch (imagetype.basetype)
- {
- case SPIRType::Int64:
- res = "i64";
- require_extension_internal("GL_EXT_shader_image_int64");
- break;
- case SPIRType::UInt64:
- res = "u64";
- require_extension_internal("GL_EXT_shader_image_int64");
- break;
- case SPIRType::Int:
- case SPIRType::Short:
- case SPIRType::SByte:
- res = "i";
- break;
- case SPIRType::UInt:
- case SPIRType::UShort:
- case SPIRType::UByte:
- res = "u";
- break;
- default:
- break;
- }
- // For half image types, we will force mediump for the sampler, and cast to f16 after any sampling operation.
- // We cannot express a true half texture type in GLSL. Neither for short integer formats for that matter.
- if (type.basetype == SPIRType::Image && type.image.dim == DimSubpassData && options.vulkan_semantics)
- return res + "subpassInput" + (type.image.ms ? "MS" : "");
- else if (type.basetype == SPIRType::Image && type.image.dim == DimSubpassData &&
- subpass_input_is_framebuffer_fetch(id))
- {
- SPIRType sampled_type = get<SPIRType>(type.image.type);
- sampled_type.vecsize = 4;
- return type_to_glsl(sampled_type);
- }
- // If we're emulating subpassInput with samplers, force sampler2D
- // so we don't have to specify format.
- if (type.basetype == SPIRType::Image && type.image.dim != DimSubpassData)
- {
- // Sampler buffers are always declared as samplerBuffer even though they might be separate images in the SPIR-V.
- if (type.image.dim == DimBuffer && type.image.sampled == 1)
- res += "sampler";
- else
- res += type.image.sampled == 2 ? "image" : "texture";
- }
- else
- res += "sampler";
- switch (type.image.dim)
- {
- case Dim1D:
- // ES doesn't support 1D. Fake it with 2D.
- res += options.es ? "2D" : "1D";
- break;
- case Dim2D:
- res += "2D";
- break;
- case Dim3D:
- res += "3D";
- break;
- case DimCube:
- res += "Cube";
- break;
- case DimRect:
- if (options.es)
- SPIRV_CROSS_THROW("Rectangle textures are not supported on OpenGL ES.");
- if (is_legacy_desktop())
- require_extension_internal("GL_ARB_texture_rectangle");
- res += "2DRect";
- break;
- case DimBuffer:
- if (options.es && options.version < 320)
- require_extension_internal("GL_EXT_texture_buffer");
- else if (!options.es && options.version < 140)
- require_extension_internal("GL_EXT_texture_buffer_object");
- res += "Buffer";
- break;
- case DimSubpassData:
- res += "2D";
- break;
- default:
- SPIRV_CROSS_THROW("Only 1D, 2D, 2DRect, 3D, Buffer, InputTarget and Cube textures supported.");
- }
- if (type.image.ms)
- res += "MS";
- if (type.image.arrayed)
- {
- if (is_legacy_desktop())
- require_extension_internal("GL_EXT_texture_array");
- res += "Array";
- }
- // "Shadow" state in GLSL only exists for samplers and combined image samplers.
- if (((type.basetype == SPIRType::SampledImage) || (type.basetype == SPIRType::Sampler)) &&
- is_depth_image(type, id))
- {
- res += "Shadow";
- if (type.image.dim == DimCube && is_legacy())
- {
- if (!options.es)
- require_extension_internal("GL_EXT_gpu_shader4");
- else
- {
- require_extension_internal("GL_NV_shadow_samplers_cube");
- res += "NV";
- }
- }
- }
- return res;
- }
- string CompilerGLSL::type_to_glsl_constructor(const SPIRType &type)
- {
- if (backend.use_array_constructor && type.array.size() > 1)
- {
- if (options.flatten_multidimensional_arrays)
- SPIRV_CROSS_THROW("Cannot flatten constructors of multidimensional array constructors, "
- "e.g. float[][]().");
- else if (!options.es && options.version < 430)
- require_extension_internal("GL_ARB_arrays_of_arrays");
- else if (options.es && options.version < 310)
- SPIRV_CROSS_THROW("Arrays of arrays not supported before ESSL version 310.");
- }
- auto e = type_to_glsl(type);
- if (backend.use_array_constructor)
- {
- for (uint32_t i = 0; i < type.array.size(); i++)
- e += "[]";
- }
- return e;
- }
- // The optional id parameter indicates the object whose type we are trying
- // to find the description for. It is optional. Most type descriptions do not
- // depend on a specific object's use of that type.
- string CompilerGLSL::type_to_glsl(const SPIRType &type, uint32_t id)
- {
- if (is_physical_pointer(type) && !is_physical_pointer_to_buffer_block(type))
- {
- // Need to create a magic type name which compacts the entire type information.
- auto *parent = &get_pointee_type(type);
- string name = type_to_glsl(*parent);
- uint32_t array_stride = get_decoration(type.parent_type, DecorationArrayStride);
- // Resolve all array dimensions in one go since once we lose the pointer type,
- // array information is left to to_array_type_glsl. The base type loses array information.
- while (is_array(*parent))
- {
- if (parent->array_size_literal.back())
- name += join(type.array.back(), "_");
- else
- name += join("id", type.array.back(), "_");
- name += "stride_" + std::to_string(array_stride);
- array_stride = get_decoration(parent->parent_type, DecorationArrayStride);
- parent = &get<SPIRType>(parent->parent_type);
- }
- name += "Pointer";
- return name;
- }
- switch (type.basetype)
- {
- case SPIRType::Struct:
- // Need OpName lookup here to get a "sensible" name for a struct.
- if (backend.explicit_struct_type)
- return join("struct ", to_name(type.self));
- else
- return to_name(type.self);
- case SPIRType::Image:
- case SPIRType::SampledImage:
- return image_type_glsl(type, id);
- case SPIRType::Sampler:
- // The depth field is set by calling code based on the variable ID of the sampler, effectively reintroducing
- // this distinction into the type system.
- return comparison_ids.count(id) ? "samplerShadow" : "sampler";
- case SPIRType::AccelerationStructure:
- return ray_tracing_is_khr ? "accelerationStructureEXT" : "accelerationStructureNV";
- case SPIRType::RayQuery:
- return "rayQueryEXT";
- case SPIRType::Tensor:
- if (type.ext.tensor.rank == 0)
- SPIRV_CROSS_THROW("GLSL tensors must have a Rank.");
- if (type.ext.tensor.shape != 0)
- SPIRV_CROSS_THROW("GLSL tensors cannot have a Shape.");
- return join("tensorARM<", type_to_glsl(get<SPIRType>(type.ext.tensor.type)), ", ",
- to_expression(type.ext.tensor.rank), ">");
- case SPIRType::Void:
- return "void";
- default:
- break;
- }
- if (type.basetype == SPIRType::UInt && is_legacy())
- {
- if (options.es)
- // HACK: spirv-cross changes bools into uints and generates code which compares them to
- // zero. Input code will have already been validated as not to have contained any uints,
- // so any remaining uints must in fact be bools. However, simply returning "bool" here
- // will result in invalid code. Instead, return an int.
- return backend.basic_int_type;
- else
- require_extension_internal("GL_EXT_gpu_shader4");
- }
- if (type.basetype == SPIRType::AtomicCounter)
- {
- if (options.es && options.version < 310)
- SPIRV_CROSS_THROW("At least ESSL 3.10 required for atomic counters.");
- else if (!options.es && options.version < 420)
- require_extension_internal("GL_ARB_shader_atomic_counters");
- }
- if (type.op == OpTypeCooperativeVectorNV)
- {
- require_extension_internal("GL_NV_cooperative_vector");
- if (!options.vulkan_semantics)
- SPIRV_CROSS_THROW("Cooperative vector NV only available in Vulkan.");
- std::string component_type_str = type_to_glsl(get<SPIRType>(type.ext.coopVecNV.component_type_id));
- return join("coopvecNV<", component_type_str, ", ", to_expression(type.ext.coopVecNV.component_count_id), ">");
- }
- const SPIRType *coop_type = &type;
- while (is_pointer(*coop_type) || is_array(*coop_type))
- coop_type = &get<SPIRType>(coop_type->parent_type);
- if (coop_type->op == OpTypeCooperativeMatrixKHR)
- {
- require_extension_internal("GL_KHR_cooperative_matrix");
- if (!options.vulkan_semantics)
- SPIRV_CROSS_THROW("Cooperative matrix only available in Vulkan.");
- // GLSL doesn't support this as spec constant, which makes sense ...
- uint32_t use_type = get<SPIRConstant>(coop_type->ext.cooperative.use_id).scalar();
- const char *use = nullptr;
- switch (use_type)
- {
- case CooperativeMatrixUseMatrixAKHR:
- use = "gl_MatrixUseA";
- break;
- case CooperativeMatrixUseMatrixBKHR:
- use = "gl_MatrixUseB";
- break;
- case CooperativeMatrixUseMatrixAccumulatorKHR:
- use = "gl_MatrixUseAccumulator";
- break;
- default:
- SPIRV_CROSS_THROW("Invalid matrix use.");
- }
- string scope_expr;
- if (const auto *scope = maybe_get<SPIRConstant>(coop_type->ext.cooperative.scope_id))
- {
- if (!scope->specialization)
- {
- require_extension_internal("GL_KHR_memory_scope_semantics");
- if (scope->scalar() == ScopeSubgroup)
- scope_expr = "gl_ScopeSubgroup";
- else if (scope->scalar() == ScopeWorkgroup)
- scope_expr = "gl_ScopeWorkgroup";
- else
- SPIRV_CROSS_THROW("Invalid scope for cooperative matrix.");
- }
- }
- if (scope_expr.empty())
- scope_expr = to_expression(coop_type->ext.cooperative.scope_id);
- return join("coopmat<", type_to_glsl(get<SPIRType>(coop_type->parent_type)), ", ",
- scope_expr, ", ",
- to_expression(coop_type->ext.cooperative.rows_id), ", ",
- to_expression(coop_type->ext.cooperative.columns_id), ", ", use, ">");
- }
- if (type.vecsize == 1 && type.columns == 1) // Scalar builtin
- {
- switch (type.basetype)
- {
- case SPIRType::Boolean:
- return "bool";
- case SPIRType::SByte:
- return backend.basic_int8_type;
- case SPIRType::UByte:
- return backend.basic_uint8_type;
- case SPIRType::Short:
- return backend.basic_int16_type;
- case SPIRType::UShort:
- return backend.basic_uint16_type;
- case SPIRType::Int:
- return backend.basic_int_type;
- case SPIRType::UInt:
- return backend.basic_uint_type;
- case SPIRType::AtomicCounter:
- return "atomic_uint";
- case SPIRType::Half:
- return "float16_t";
- case SPIRType::BFloat16:
- if (!options.vulkan_semantics)
- SPIRV_CROSS_THROW("bfloat16 requires Vulkan semantics.");
- require_extension_internal("GL_EXT_bfloat16");
- return "bfloat16_t";
- case SPIRType::FloatE4M3:
- if (!options.vulkan_semantics)
- SPIRV_CROSS_THROW("floate4m3_t requires Vulkan semantics.");
- require_extension_internal("GL_EXT_float_e4m3");
- return "floate4m3_t";
- case SPIRType::FloatE5M2:
- if (!options.vulkan_semantics)
- SPIRV_CROSS_THROW("floate5m2_t requires Vulkan semantics.");
- require_extension_internal("GL_EXT_float_e5m2");
- return "floate5m2_t";
- case SPIRType::Float:
- return "float";
- case SPIRType::Double:
- return "double";
- case SPIRType::Int64:
- return "int64_t";
- case SPIRType::UInt64:
- return "uint64_t";
- default:
- return "???";
- }
- }
- else if (type.vecsize > 1 && type.columns == 1) // Vector builtin
- {
- switch (type.basetype)
- {
- case SPIRType::Boolean:
- return join("bvec", type.vecsize);
- case SPIRType::SByte:
- return join("i8vec", type.vecsize);
- case SPIRType::UByte:
- return join("u8vec", type.vecsize);
- case SPIRType::Short:
- return join("i16vec", type.vecsize);
- case SPIRType::UShort:
- return join("u16vec", type.vecsize);
- case SPIRType::Int:
- return join("ivec", type.vecsize);
- case SPIRType::UInt:
- return join("uvec", type.vecsize);
- case SPIRType::Half:
- return join("f16vec", type.vecsize);
- case SPIRType::BFloat16:
- if (!options.vulkan_semantics)
- SPIRV_CROSS_THROW("bfloat16 requires Vulkan semantics.");
- require_extension_internal("GL_EXT_bfloat16");
- return join("bf16vec", type.vecsize);
- case SPIRType::FloatE4M3:
- if (!options.vulkan_semantics)
- SPIRV_CROSS_THROW("floate4m3_t requires Vulkan semantics.");
- require_extension_internal("GL_EXT_float_e4m3");
- return join("fe4m3vec", type.vecsize);
- case SPIRType::FloatE5M2:
- if (!options.vulkan_semantics)
- SPIRV_CROSS_THROW("floate5m2_t requires Vulkan semantics.");
- require_extension_internal("GL_EXT_float_e5m2");
- return join("fe5m2vec", type.vecsize);
- case SPIRType::Float:
- return join("vec", type.vecsize);
- case SPIRType::Double:
- return join("dvec", type.vecsize);
- case SPIRType::Int64:
- return join("i64vec", type.vecsize);
- case SPIRType::UInt64:
- return join("u64vec", type.vecsize);
- default:
- return "???";
- }
- }
- else if (type.vecsize == type.columns) // Simple Matrix builtin
- {
- switch (type.basetype)
- {
- case SPIRType::Boolean:
- return join("bmat", type.vecsize);
- case SPIRType::Int:
- return join("imat", type.vecsize);
- case SPIRType::UInt:
- return join("umat", type.vecsize);
- case SPIRType::Half:
- return join("f16mat", type.vecsize);
- case SPIRType::Float:
- return join("mat", type.vecsize);
- case SPIRType::Double:
- return join("dmat", type.vecsize);
- // Matrix types not supported for int64/uint64.
- default:
- return "???";
- }
- }
- else
- {
- switch (type.basetype)
- {
- case SPIRType::Boolean:
- return join("bmat", type.columns, "x", type.vecsize);
- case SPIRType::Int:
- return join("imat", type.columns, "x", type.vecsize);
- case SPIRType::UInt:
- return join("umat", type.columns, "x", type.vecsize);
- case SPIRType::Half:
- return join("f16mat", type.columns, "x", type.vecsize);
- case SPIRType::Float:
- return join("mat", type.columns, "x", type.vecsize);
- case SPIRType::Double:
- return join("dmat", type.columns, "x", type.vecsize);
- // Matrix types not supported for int64/uint64.
- default:
- return "???";
- }
- }
- }
- void CompilerGLSL::add_variable(unordered_set<string> &variables_primary,
- const unordered_set<string> &variables_secondary, string &name)
- {
- if (name.empty())
- return;
- ParsedIR::sanitize_underscores(name);
- if (ParsedIR::is_globally_reserved_identifier(name, true))
- {
- name.clear();
- return;
- }
- update_name_cache(variables_primary, variables_secondary, name);
- }
- void CompilerGLSL::add_local_variable_name(uint32_t id)
- {
- add_variable(local_variable_names, block_names, ir.meta[id].decoration.alias);
- }
- void CompilerGLSL::add_resource_name(uint32_t id)
- {
- add_variable(resource_names, block_names, ir.meta[id].decoration.alias);
- }
- void CompilerGLSL::add_header_line(const std::string &line)
- {
- header_lines.push_back(line);
- }
- bool CompilerGLSL::has_extension(const std::string &ext) const
- {
- auto itr = find(begin(forced_extensions), end(forced_extensions), ext);
- return itr != end(forced_extensions);
- }
- void CompilerGLSL::require_extension(const std::string &ext)
- {
- if (!has_extension(ext))
- forced_extensions.push_back(ext);
- }
- const SmallVector<std::string> &CompilerGLSL::get_required_extensions() const
- {
- return forced_extensions;
- }
- void CompilerGLSL::require_extension_internal(const string &ext)
- {
- if (backend.supports_extensions && !has_extension(ext))
- {
- forced_extensions.push_back(ext);
- force_recompile();
- }
- }
- void CompilerGLSL::flatten_buffer_block(VariableID id)
- {
- auto &var = get<SPIRVariable>(id);
- auto &type = get<SPIRType>(var.basetype);
- auto name = to_name(type.self, false);
- auto &flags = get_decoration_bitset(type.self);
- if (!type.array.empty())
- SPIRV_CROSS_THROW(name + " is an array of UBOs.");
- if (type.basetype != SPIRType::Struct)
- SPIRV_CROSS_THROW(name + " is not a struct.");
- if (!flags.get(DecorationBlock))
- SPIRV_CROSS_THROW(name + " is not a block.");
- if (type.member_types.empty())
- SPIRV_CROSS_THROW(name + " is an empty struct.");
- flattened_buffer_blocks.insert(id);
- }
- bool CompilerGLSL::builtin_translates_to_nonarray(BuiltIn /*builtin*/) const
- {
- return false; // GLSL itself does not need to translate array builtin types to non-array builtin types
- }
- bool CompilerGLSL::is_user_type_structured(uint32_t /*id*/) const
- {
- return false; // GLSL itself does not have structured user type, but HLSL does with StructuredBuffer and RWStructuredBuffer resources.
- }
- bool CompilerGLSL::check_atomic_image(uint32_t id)
- {
- auto &type = expression_type(id);
- if (type.storage == StorageClassImage)
- {
- if (options.es && options.version < 320)
- require_extension_internal("GL_OES_shader_image_atomic");
- auto *var = maybe_get_backing_variable(id);
- if (var)
- {
- if (has_decoration(var->self, DecorationNonWritable) || has_decoration(var->self, DecorationNonReadable))
- {
- unset_decoration(var->self, DecorationNonWritable);
- unset_decoration(var->self, DecorationNonReadable);
- force_recompile();
- }
- }
- return true;
- }
- else
- return false;
- }
- void CompilerGLSL::add_function_overload(const SPIRFunction &func)
- {
- Hasher hasher;
- for (auto &arg : func.arguments)
- {
- // Parameters can vary with pointer type or not,
- // but that will not change the signature in GLSL/HLSL,
- // so strip the pointer type before hashing.
- uint32_t type_id = get_pointee_type_id(arg.type);
- // Workaround glslang bug. It seems to only consider the base type when resolving overloads.
- if (get<SPIRType>(type_id).op == OpTypeCooperativeMatrixKHR)
- type_id = get<SPIRType>(type_id).parent_type;
- auto &type = get<SPIRType>(type_id);
- if (!combined_image_samplers.empty())
- {
- // If we have combined image samplers, we cannot really trust the image and sampler arguments
- // we pass down to callees, because they may be shuffled around.
- // Ignore these arguments, to make sure that functions need to differ in some other way
- // to be considered different overloads.
- if (type.basetype == SPIRType::SampledImage ||
- (type.basetype == SPIRType::Image && type.image.sampled == 1) || type.basetype == SPIRType::Sampler)
- {
- continue;
- }
- }
- hasher.u32(type_id);
- }
- uint64_t types_hash = hasher.get();
- auto function_name = to_name(func.self);
- auto itr = function_overloads.find(function_name);
- if (itr != end(function_overloads))
- {
- // There exists a function with this name already.
- auto &overloads = itr->second;
- if (overloads.count(types_hash) != 0)
- {
- // Overload conflict, assign a new name.
- add_resource_name(func.self);
- function_overloads[to_name(func.self)].insert(types_hash);
- }
- else
- {
- // Can reuse the name.
- overloads.insert(types_hash);
- }
- }
- else
- {
- // First time we see this function name.
- add_resource_name(func.self);
- function_overloads[to_name(func.self)].insert(types_hash);
- }
- }
- void CompilerGLSL::emit_function_prototype(SPIRFunction &func, const Bitset &return_flags)
- {
- if (func.self != ir.default_entry_point)
- add_function_overload(func);
- // Avoid shadow declarations.
- local_variable_names = resource_names;
- string decl;
- auto &type = get<SPIRType>(func.return_type);
- decl += flags_to_qualifiers_glsl(type, 0, return_flags);
- decl += type_to_glsl(type);
- decl += type_to_array_glsl(type, 0);
- decl += " ";
- if (func.self == ir.default_entry_point)
- {
- // If we need complex fallback in GLSL, we just wrap main() in a function
- // and interlock the entire shader ...
- if (interlocked_is_complex)
- decl += "spvMainInterlockedBody";
- else
- decl += "main";
- processing_entry_point = true;
- }
- else
- decl += to_name(func.self);
- decl += "(";
- SmallVector<string> arglist;
- for (auto &arg : func.arguments)
- {
- // Do not pass in separate images or samplers if we're remapping
- // to combined image samplers.
- if (skip_argument(arg.id))
- continue;
- // Might change the variable name if it already exists in this function.
- // SPIRV OpName doesn't have any semantic effect, so it's valid for an implementation
- // to use same name for variables.
- // Since we want to make the GLSL debuggable and somewhat sane, use fallback names for variables which are duplicates.
- add_local_variable_name(arg.id);
- arglist.push_back(argument_decl(arg));
- // Hold a pointer to the parameter so we can invalidate the readonly field if needed.
- auto *var = maybe_get<SPIRVariable>(arg.id);
- if (var)
- var->parameter = &arg;
- }
- for (auto &arg : func.shadow_arguments)
- {
- // Might change the variable name if it already exists in this function.
- // SPIRV OpName doesn't have any semantic effect, so it's valid for an implementation
- // to use same name for variables.
- // Since we want to make the GLSL debuggable and somewhat sane, use fallback names for variables which are duplicates.
- add_local_variable_name(arg.id);
- arglist.push_back(argument_decl(arg));
- // Hold a pointer to the parameter so we can invalidate the readonly field if needed.
- auto *var = maybe_get<SPIRVariable>(arg.id);
- if (var)
- var->parameter = &arg;
- }
- decl += merge(arglist);
- decl += ")";
- statement(decl);
- }
- void CompilerGLSL::emit_function(SPIRFunction &func, const Bitset &return_flags)
- {
- // Avoid potential cycles.
- if (func.active)
- return;
- func.active = true;
- // If we depend on a function, emit that function before we emit our own function.
- for (auto block : func.blocks)
- {
- auto &b = get<SPIRBlock>(block);
- for (auto &i : b.ops)
- {
- auto ops = stream(i);
- auto op = static_cast<Op>(i.op);
- if (op == OpFunctionCall)
- {
- // Recursively emit functions which are called.
- uint32_t id = ops[2];
- emit_function(get<SPIRFunction>(id), ir.meta[ops[1]].decoration.decoration_flags);
- }
- }
- }
- if (func.entry_line.file_id != 0)
- emit_line_directive(func.entry_line.file_id, func.entry_line.line_literal);
- emit_function_prototype(func, return_flags);
- begin_scope();
- if (func.self == ir.default_entry_point)
- emit_entry_point_declarations();
- current_function = &func;
- auto &entry_block = get<SPIRBlock>(func.entry_block);
- sort(begin(func.constant_arrays_needed_on_stack), end(func.constant_arrays_needed_on_stack));
- for (auto &array : func.constant_arrays_needed_on_stack)
- {
- auto &c = get<SPIRConstant>(array);
- auto &type = get<SPIRType>(c.constant_type);
- statement(variable_decl(type, join("_", array, "_array_copy")), " = ", constant_expression(c), ";");
- }
- for (auto &v : func.local_variables)
- {
- auto &var = get<SPIRVariable>(v);
- var.deferred_declaration = false;
- if (var.storage == StorageClassTaskPayloadWorkgroupEXT)
- continue;
- if (variable_decl_is_remapped_storage(var, StorageClassWorkgroup))
- {
- // Special variable type which cannot have initializer,
- // need to be declared as standalone variables.
- // Comes from MSL which can push global variables as local variables in main function.
- add_local_variable_name(var.self);
- statement(variable_decl(var), ";");
- // "Real" workgroup variables in compute shaders needs extra caretaking.
- // They need to be initialized with an extra routine as they come in arbitrary form.
- if (var.storage == StorageClassWorkgroup && var.initializer)
- emit_workgroup_initialization(var);
- var.deferred_declaration = false;
- }
- else if (var.storage == StorageClassPrivate)
- {
- // These variables will not have had their CFG usage analyzed, so move it to the entry block.
- // Comes from MSL which can push global variables as local variables in main function.
- // We could just declare them right now, but we would miss out on an important initialization case which is
- // LUT declaration in MSL.
- // If we don't declare the variable when it is assigned we're forced to go through a helper function
- // which copies elements one by one.
- add_local_variable_name(var.self);
- if (var.initializer)
- {
- statement(variable_decl(var), ";");
- var.deferred_declaration = false;
- }
- else
- {
- auto &dominated = entry_block.dominated_variables;
- if (find(begin(dominated), end(dominated), var.self) == end(dominated))
- entry_block.dominated_variables.push_back(var.self);
- var.deferred_declaration = true;
- }
- }
- else if (var.storage == StorageClassFunction && var.remapped_variable && var.static_expression)
- {
- // No need to declare this variable, it has a static expression.
- var.deferred_declaration = false;
- }
- else if (expression_is_lvalue(v))
- {
- add_local_variable_name(var.self);
- // Loop variables should never be declared early, they are explicitly emitted in a loop.
- if (var.initializer && !var.loop_variable)
- statement(variable_decl_function_local(var), ";");
- else
- {
- // Don't declare variable until first use to declutter the GLSL output quite a lot.
- // If we don't touch the variable before first branch,
- // declare it then since we need variable declaration to be in top scope.
- var.deferred_declaration = true;
- }
- }
- else
- {
- // HACK: SPIR-V in older glslang output likes to use samplers and images as local variables, but GLSL does not allow this.
- // For these types (non-lvalue), we enforce forwarding through a shadowed variable.
- // This means that when we OpStore to these variables, we just write in the expression ID directly.
- // This breaks any kind of branching, since the variable must be statically assigned.
- // Branching on samplers and images would be pretty much impossible to fake in GLSL.
- var.statically_assigned = true;
- }
- var.loop_variable_enable = false;
- // Loop variables are never declared outside their for-loop, so block any implicit declaration.
- if (var.loop_variable)
- {
- var.deferred_declaration = false;
- // Need to reset the static expression so we can fallback to initializer if need be.
- var.static_expression = 0;
- }
- }
- // Enforce declaration order for regression testing purposes.
- for (auto &block_id : func.blocks)
- {
- auto &block = get<SPIRBlock>(block_id);
- sort(begin(block.dominated_variables), end(block.dominated_variables));
- }
- for (auto &line : current_function->fixup_hooks_in)
- line();
- emit_block_chain(entry_block);
- end_scope();
- processing_entry_point = false;
- statement("");
- // Make sure deferred declaration state for local variables is cleared when we are done with function.
- // We risk declaring Private/Workgroup variables in places we are not supposed to otherwise.
- for (auto &v : func.local_variables)
- {
- auto &var = get<SPIRVariable>(v);
- var.deferred_declaration = false;
- }
- }
- void CompilerGLSL::emit_fixup()
- {
- if (is_vertex_like_shader())
- {
- if (options.vertex.fixup_clipspace)
- {
- const char *suffix = backend.float_literal_suffix ? "f" : "";
- statement("gl_Position.z = 2.0", suffix, " * gl_Position.z - gl_Position.w;");
- }
- if (options.vertex.flip_vert_y)
- statement("gl_Position.y = -gl_Position.y;");
- }
- }
- void CompilerGLSL::emit_workgroup_initialization(const SPIRVariable &)
- {
- }
- void CompilerGLSL::flush_phi(BlockID from, BlockID to)
- {
- auto &child = get<SPIRBlock>(to);
- if (child.ignore_phi_from_block == from)
- return;
- unordered_set<uint32_t> temporary_phi_variables;
- for (auto itr = begin(child.phi_variables); itr != end(child.phi_variables); ++itr)
- {
- auto &phi = *itr;
- if (phi.parent == from)
- {
- auto &var = get<SPIRVariable>(phi.function_variable);
- // A Phi variable might be a loop variable, so flush to static expression.
- if (var.loop_variable && !var.loop_variable_enable)
- var.static_expression = phi.local_variable;
- else
- {
- flush_variable_declaration(phi.function_variable);
- // Check if we are going to write to a Phi variable that another statement will read from
- // as part of another Phi node in our target block.
- // For this case, we will need to copy phi.function_variable to a temporary, and use that for future reads.
- // This is judged to be extremely rare, so deal with it here using a simple, but suboptimal algorithm.
- bool need_saved_temporary =
- find_if(itr + 1, end(child.phi_variables), [&](const SPIRBlock::Phi &future_phi) -> bool {
- return future_phi.local_variable == ID(phi.function_variable) && future_phi.parent == from;
- }) != end(child.phi_variables);
- if (need_saved_temporary)
- {
- // Need to make sure we declare the phi variable with a copy at the right scope.
- // We cannot safely declare a temporary here since we might be inside a continue block.
- if (!var.allocate_temporary_copy)
- {
- var.allocate_temporary_copy = true;
- force_recompile();
- }
- statement("_", phi.function_variable, "_copy", " = ", to_name(phi.function_variable), ";");
- temporary_phi_variables.insert(phi.function_variable);
- }
- // This might be called in continue block, so make sure we
- // use this to emit ESSL 1.0 compliant increments/decrements.
- auto lhs = to_expression(phi.function_variable);
- string rhs;
- if (temporary_phi_variables.count(phi.local_variable))
- rhs = join("_", phi.local_variable, "_copy");
- else
- rhs = to_pointer_expression(phi.local_variable);
- if (!optimize_read_modify_write(get<SPIRType>(var.basetype), lhs, rhs))
- statement(lhs, " = ", rhs, ";");
- }
- register_write(phi.function_variable);
- }
- }
- }
- void CompilerGLSL::branch_to_continue(BlockID from, BlockID to)
- {
- auto &to_block = get<SPIRBlock>(to);
- if (from == to)
- return;
- assert(is_continue(to));
- if (to_block.complex_continue)
- {
- // Just emit the whole block chain as is.
- auto usage_counts = expression_usage_counts;
- emit_block_chain(to_block);
- // Expression usage counts are moot after returning from the continue block.
- expression_usage_counts = usage_counts;
- }
- else
- {
- auto &from_block = get<SPIRBlock>(from);
- bool outside_control_flow = false;
- uint32_t loop_dominator = 0;
- // FIXME: Refactor this to not use the old loop_dominator tracking.
- if (from_block.merge_block)
- {
- // If we are a loop header, we don't set the loop dominator,
- // so just use "self" here.
- loop_dominator = from;
- }
- else if (from_block.loop_dominator != BlockID(SPIRBlock::NoDominator))
- {
- loop_dominator = from_block.loop_dominator;
- }
- if (loop_dominator != 0)
- {
- auto &cfg = get_cfg_for_current_function();
- // For non-complex continue blocks, we implicitly branch to the continue block
- // by having the continue block be part of the loop header in for (; ; continue-block).
- outside_control_flow = cfg.node_terminates_control_flow_in_sub_graph(loop_dominator, from);
- }
- // Some simplification for for-loops. We always end up with a useless continue;
- // statement since we branch to a loop block.
- // Walk the CFG, if we unconditionally execute the block calling continue assuming we're in the loop block,
- // we can avoid writing out an explicit continue statement.
- // Similar optimization to return statements if we know we're outside flow control.
- if (!outside_control_flow)
- statement("continue;");
- }
- }
- void CompilerGLSL::branch(BlockID from, BlockID to)
- {
- flush_phi(from, to);
- flush_control_dependent_expressions(from);
- bool to_is_continue = is_continue(to);
- // This is only a continue if we branch to our loop dominator.
- if ((ir.block_meta[to] & ParsedIR::BLOCK_META_LOOP_HEADER_BIT) != 0 && get<SPIRBlock>(from).loop_dominator == to)
- {
- // This can happen if we had a complex continue block which was emitted.
- // Once the continue block tries to branch to the loop header, just emit continue;
- // and end the chain here.
- statement("continue;");
- }
- else if (from != to && is_break(to))
- {
- // We cannot break to ourselves, so check explicitly for from != to.
- // This case can trigger if a loop header is all three of these things:
- // - Continue block
- // - Loop header
- // - Break merge target all at once ...
- // Very dirty workaround.
- // Switch constructs are able to break, but they cannot break out of a loop at the same time,
- // yet SPIR-V allows it.
- // Only sensible solution is to make a ladder variable, which we declare at the top of the switch block,
- // write to the ladder here, and defer the break.
- // The loop we're breaking out of must dominate the switch block, or there is no ladder breaking case.
- if (is_loop_break(to))
- {
- for (size_t n = current_emitting_switch_stack.size(); n; n--)
- {
- auto *current_emitting_switch = current_emitting_switch_stack[n - 1];
- if (current_emitting_switch &&
- current_emitting_switch->loop_dominator != BlockID(SPIRBlock::NoDominator) &&
- get<SPIRBlock>(current_emitting_switch->loop_dominator).merge_block == to)
- {
- if (!current_emitting_switch->need_ladder_break)
- {
- force_recompile();
- current_emitting_switch->need_ladder_break = true;
- }
- statement("_", current_emitting_switch->self, "_ladder_break = true;");
- }
- else
- break;
- }
- }
- statement("break;");
- }
- else if (to_is_continue || from == to)
- {
- // For from == to case can happen for a do-while loop which branches into itself.
- // We don't mark these cases as continue blocks, but the only possible way to branch into
- // ourselves is through means of continue blocks.
- // If we are merging to a continue block, there is no need to emit the block chain for continue here.
- // We can branch to the continue block after we merge execution.
- // Here we make use of structured control flow rules from spec:
- // 2.11: - the merge block declared by a header block cannot be a merge block declared by any other header block
- // - each header block must strictly dominate its merge block, unless the merge block is unreachable in the CFG
- // If we are branching to a merge block, we must be inside a construct which dominates the merge block.
- auto &block_meta = ir.block_meta[to];
- bool branching_to_merge =
- (block_meta & (ParsedIR::BLOCK_META_SELECTION_MERGE_BIT | ParsedIR::BLOCK_META_MULTISELECT_MERGE_BIT |
- ParsedIR::BLOCK_META_LOOP_MERGE_BIT)) != 0;
- if (!to_is_continue || !branching_to_merge)
- branch_to_continue(from, to);
- }
- else if (!is_conditional(to))
- emit_block_chain(get<SPIRBlock>(to));
- // It is important that we check for break before continue.
- // A block might serve two purposes, a break block for the inner scope, and
- // a continue block in the outer scope.
- // Inner scope always takes precedence.
- }
- void CompilerGLSL::branch(BlockID from, uint32_t cond, BlockID true_block, BlockID false_block)
- {
- auto &from_block = get<SPIRBlock>(from);
- BlockID merge_block = from_block.merge == SPIRBlock::MergeSelection ? from_block.next_block : BlockID(0);
- // If we branch directly to our selection merge target, we don't need a code path.
- bool true_block_needs_code = true_block != merge_block || flush_phi_required(from, true_block);
- bool false_block_needs_code = false_block != merge_block || flush_phi_required(from, false_block);
- if (!true_block_needs_code && !false_block_needs_code)
- return;
- // We might have a loop merge here. Only consider selection flattening constructs.
- // Loop hints are handled explicitly elsewhere.
- if (from_block.hint == SPIRBlock::HintFlatten || from_block.hint == SPIRBlock::HintDontFlatten)
- emit_block_hints(from_block);
- if (true_block_needs_code)
- {
- statement("if (", to_expression(cond), ")");
- begin_scope();
- branch(from, true_block);
- end_scope();
- if (false_block_needs_code)
- {
- statement("else");
- begin_scope();
- branch(from, false_block);
- end_scope();
- }
- }
- else if (false_block_needs_code)
- {
- // Only need false path, use negative conditional.
- statement("if (!", to_enclosed_expression(cond), ")");
- begin_scope();
- branch(from, false_block);
- end_scope();
- }
- }
- // FIXME: This currently cannot handle complex continue blocks
- // as in do-while.
- // This should be seen as a "trivial" continue block.
- string CompilerGLSL::emit_continue_block(uint32_t continue_block, bool follow_true_block, bool follow_false_block)
- {
- auto *block = &get<SPIRBlock>(continue_block);
- // While emitting the continue block, declare_temporary will check this
- // if we have to emit temporaries.
- current_continue_block = block;
- SmallVector<string> statements;
- // Capture all statements into our list.
- auto *old = redirect_statement;
- redirect_statement = &statements;
- // Stamp out all blocks one after each other.
- while ((ir.block_meta[block->self] & ParsedIR::BLOCK_META_LOOP_HEADER_BIT) == 0)
- {
- // Write out all instructions we have in this block.
- emit_block_instructions(*block);
- // For plain branchless for/while continue blocks.
- if (block->next_block)
- {
- flush_phi(continue_block, block->next_block);
- block = &get<SPIRBlock>(block->next_block);
- }
- // For do while blocks. The last block will be a select block.
- else if (block->true_block && follow_true_block)
- {
- flush_phi(continue_block, block->true_block);
- block = &get<SPIRBlock>(block->true_block);
- }
- else if (block->false_block && follow_false_block)
- {
- flush_phi(continue_block, block->false_block);
- block = &get<SPIRBlock>(block->false_block);
- }
- else
- {
- SPIRV_CROSS_THROW("Invalid continue block detected!");
- }
- }
- // Restore old pointer.
- redirect_statement = old;
- // Somewhat ugly, strip off the last ';' since we use ',' instead.
- // Ideally, we should select this behavior in statement().
- for (auto &s : statements)
- {
- if (!s.empty() && s.back() == ';')
- s.erase(s.size() - 1, 1);
- }
- current_continue_block = nullptr;
- return merge(statements);
- }
- void CompilerGLSL::emit_while_loop_initializers(const SPIRBlock &block)
- {
- // While loops do not take initializers, so declare all of them outside.
- for (auto &loop_var : block.loop_variables)
- {
- auto &var = get<SPIRVariable>(loop_var);
- statement(variable_decl(var), ";");
- }
- }
- string CompilerGLSL::emit_for_loop_initializers(const SPIRBlock &block)
- {
- if (block.loop_variables.empty())
- return "";
- bool same_types = for_loop_initializers_are_same_type(block);
- // We can only declare for loop initializers if all variables are of same type.
- // If we cannot do this, declare individual variables before the loop header.
- // We might have a loop variable candidate which was not assigned to for some reason.
- uint32_t missing_initializers = 0;
- for (auto &variable : block.loop_variables)
- {
- uint32_t expr = get<SPIRVariable>(variable).static_expression;
- // Sometimes loop variables are initialized with OpUndef, but we can just declare
- // a plain variable without initializer in this case.
- if (expr == 0 || ir.ids[expr].get_type() == TypeUndef)
- missing_initializers++;
- }
- if (block.loop_variables.size() == 1 && missing_initializers == 0)
- {
- return variable_decl(get<SPIRVariable>(block.loop_variables.front()));
- }
- else if (!same_types || missing_initializers == uint32_t(block.loop_variables.size()))
- {
- for (auto &loop_var : block.loop_variables)
- statement(variable_decl(get<SPIRVariable>(loop_var)), ";");
- return "";
- }
- else
- {
- // We have a mix of loop variables, either ones with a clear initializer, or ones without.
- // Separate the two streams.
- string expr;
- for (auto &loop_var : block.loop_variables)
- {
- uint32_t static_expr = get<SPIRVariable>(loop_var).static_expression;
- if (static_expr == 0 || ir.ids[static_expr].get_type() == TypeUndef)
- {
- statement(variable_decl(get<SPIRVariable>(loop_var)), ";");
- }
- else
- {
- auto &var = get<SPIRVariable>(loop_var);
- auto &type = get_variable_data_type(var);
- if (expr.empty())
- {
- // For loop initializers are of the form <type id = value, id = value, id = value, etc ...
- expr = join(to_qualifiers_glsl(var.self), type_to_glsl(type), " ");
- }
- else
- {
- expr += ", ";
- // In MSL, being based on C++, the asterisk marking a pointer
- // binds to the identifier, not the type.
- if (type.pointer)
- expr += "* ";
- }
- expr += join(to_name(loop_var), " = ", to_pointer_expression(var.static_expression));
- }
- }
- return expr;
- }
- }
- bool CompilerGLSL::for_loop_initializers_are_same_type(const SPIRBlock &block)
- {
- if (block.loop_variables.size() <= 1)
- return true;
- uint32_t expected = 0;
- Bitset expected_flags;
- for (auto &var : block.loop_variables)
- {
- // Don't care about uninitialized variables as they will not be part of the initializers.
- uint32_t expr = get<SPIRVariable>(var).static_expression;
- if (expr == 0 || ir.ids[expr].get_type() == TypeUndef)
- continue;
- if (expected == 0)
- {
- expected = get<SPIRVariable>(var).basetype;
- expected_flags = get_decoration_bitset(var);
- }
- else if (expected != get<SPIRVariable>(var).basetype)
- return false;
- // Precision flags and things like that must also match.
- if (expected_flags != get_decoration_bitset(var))
- return false;
- }
- return true;
- }
- void CompilerGLSL::emit_block_instructions_with_masked_debug(SPIRBlock &block)
- {
- // Have to block debug instructions such as OpLine here, since it will be treated as a statement otherwise,
- // which breaks loop optimizations.
- // Any line directive would be declared outside the loop body, which would just be confusing either way.
- bool old_block_debug_directives = block_debug_directives;
- block_debug_directives = true;
- emit_block_instructions(block);
- block_debug_directives = old_block_debug_directives;
- }
- bool CompilerGLSL::attempt_emit_loop_header(SPIRBlock &block, SPIRBlock::Method method)
- {
- SPIRBlock::ContinueBlockType continue_type = continue_block_type(get<SPIRBlock>(block.continue_block));
- if (method == SPIRBlock::MergeToSelectForLoop || method == SPIRBlock::MergeToSelectContinueForLoop)
- {
- uint32_t current_count = statement_count;
- // If we're trying to create a true for loop,
- // we need to make sure that all opcodes before branch statement do not actually emit any code.
- // We can then take the condition expression and create a for (; cond ; ) { body; } structure instead.
- emit_block_instructions_with_masked_debug(block);
- bool condition_is_temporary = forced_temporaries.find(block.condition) == end(forced_temporaries);
- bool flushes_phi = flush_phi_required(block.self, block.true_block) ||
- flush_phi_required(block.self, block.false_block);
- // This can work! We only did trivial things which could be forwarded in block body!
- if (!flushes_phi && current_count == statement_count && condition_is_temporary)
- {
- switch (continue_type)
- {
- case SPIRBlock::ForLoop:
- {
- // This block may be a dominating block, so make sure we flush undeclared variables before building the for loop header.
- flush_undeclared_variables(block);
- // Important that we do this in this order because
- // emitting the continue block can invalidate the condition expression.
- auto initializer = emit_for_loop_initializers(block);
- auto condition = to_expression(block.condition);
- // Condition might have to be inverted.
- if (execution_is_noop(get<SPIRBlock>(block.true_block), get<SPIRBlock>(block.merge_block)))
- condition = join("!", enclose_expression(condition));
- emit_block_hints(block);
- if (method != SPIRBlock::MergeToSelectContinueForLoop)
- {
- auto continue_block = emit_continue_block(block.continue_block, false, false);
- statement("for (", initializer, "; ", condition, "; ", continue_block, ")");
- }
- else
- statement("for (", initializer, "; ", condition, "; )");
- break;
- }
- case SPIRBlock::WhileLoop:
- {
- // This block may be a dominating block, so make sure we flush undeclared variables before building the while loop header.
- flush_undeclared_variables(block);
- emit_while_loop_initializers(block);
- emit_block_hints(block);
- auto condition = to_expression(block.condition);
- // Condition might have to be inverted.
- if (execution_is_noop(get<SPIRBlock>(block.true_block), get<SPIRBlock>(block.merge_block)))
- condition = join("!", enclose_expression(condition));
- statement("while (", condition, ")");
- break;
- }
- default:
- block.disable_block_optimization = true;
- force_recompile();
- begin_scope(); // We'll see an end_scope() later.
- return false;
- }
- begin_scope();
- return true;
- }
- else
- {
- block.disable_block_optimization = true;
- force_recompile();
- begin_scope(); // We'll see an end_scope() later.
- return false;
- }
- }
- else if (method == SPIRBlock::MergeToDirectForLoop)
- {
- auto &child = get<SPIRBlock>(block.next_block);
- // This block may be a dominating block, so make sure we flush undeclared variables before building the for loop header.
- flush_undeclared_variables(child);
- uint32_t current_count = statement_count;
- // If we're trying to create a true for loop,
- // we need to make sure that all opcodes before branch statement do not actually emit any code.
- // We can then take the condition expression and create a for (; cond ; ) { body; } structure instead.
- emit_block_instructions_with_masked_debug(child);
- bool condition_is_temporary = forced_temporaries.find(child.condition) == end(forced_temporaries);
- bool flushes_phi = flush_phi_required(child.self, child.true_block) ||
- flush_phi_required(child.self, child.false_block);
- if (!flushes_phi && current_count == statement_count && condition_is_temporary)
- {
- uint32_t target_block = child.true_block;
- switch (continue_type)
- {
- case SPIRBlock::ForLoop:
- {
- // Important that we do this in this order because
- // emitting the continue block can invalidate the condition expression.
- auto initializer = emit_for_loop_initializers(block);
- auto condition = to_expression(child.condition);
- // Condition might have to be inverted.
- if (execution_is_noop(get<SPIRBlock>(child.true_block), get<SPIRBlock>(block.merge_block)))
- {
- condition = join("!", enclose_expression(condition));
- target_block = child.false_block;
- }
- auto continue_block = emit_continue_block(block.continue_block, false, false);
- emit_block_hints(block);
- statement("for (", initializer, "; ", condition, "; ", continue_block, ")");
- break;
- }
- case SPIRBlock::WhileLoop:
- {
- emit_while_loop_initializers(block);
- emit_block_hints(block);
- auto condition = to_expression(child.condition);
- // Condition might have to be inverted.
- if (execution_is_noop(get<SPIRBlock>(child.true_block), get<SPIRBlock>(block.merge_block)))
- {
- condition = join("!", enclose_expression(condition));
- target_block = child.false_block;
- }
- statement("while (", condition, ")");
- break;
- }
- default:
- block.disable_block_optimization = true;
- force_recompile();
- begin_scope(); // We'll see an end_scope() later.
- return false;
- }
- begin_scope();
- branch(child.self, target_block);
- return true;
- }
- else
- {
- block.disable_block_optimization = true;
- force_recompile();
- begin_scope(); // We'll see an end_scope() later.
- return false;
- }
- }
- else
- return false;
- }
- void CompilerGLSL::flush_undeclared_variables(SPIRBlock &block)
- {
- for (auto &v : block.dominated_variables)
- flush_variable_declaration(v);
- }
- void CompilerGLSL::emit_hoisted_temporaries(SmallVector<pair<TypeID, ID>> &temporaries)
- {
- // If we need to force temporaries for certain IDs due to continue blocks, do it before starting loop header.
- // Need to sort these to ensure that reference output is stable.
- sort(begin(temporaries), end(temporaries),
- [](const pair<TypeID, ID> &a, const pair<TypeID, ID> &b) { return a.second < b.second; });
- for (auto &tmp : temporaries)
- {
- auto &type = get<SPIRType>(tmp.first);
- // There are some rare scenarios where we are asked to declare pointer types as hoisted temporaries.
- // This should be ignored unless we're doing actual variable pointers and backend supports it.
- // Access chains cannot normally be lowered to temporaries in GLSL and HLSL.
- if (type.pointer && !backend.native_pointers)
- continue;
- add_local_variable_name(tmp.second);
- auto &flags = get_decoration_bitset(tmp.second);
- // Not all targets support pointer literals, so don't bother with that case.
- string initializer;
- if (options.force_zero_initialized_variables && type_can_zero_initialize(type))
- initializer = join(" = ", to_zero_initialized_expression(tmp.first));
- statement(flags_to_qualifiers_glsl(type, tmp.second, flags), variable_decl(type, to_name(tmp.second)), initializer, ";");
- hoisted_temporaries.insert(tmp.second);
- forced_temporaries.insert(tmp.second);
- // The temporary might be read from before it's assigned, set up the expression now.
- set<SPIRExpression>(tmp.second, to_name(tmp.second), tmp.first, true);
- // If we have hoisted temporaries in multi-precision contexts, emit that here too ...
- // We will not be able to analyze hoisted-ness for dependent temporaries that we hallucinate here.
- auto mirrored_precision_itr = temporary_to_mirror_precision_alias.find(tmp.second);
- if (mirrored_precision_itr != temporary_to_mirror_precision_alias.end())
- {
- uint32_t mirror_id = mirrored_precision_itr->second;
- auto &mirror_flags = get_decoration_bitset(mirror_id);
- statement(flags_to_qualifiers_glsl(type, mirror_id, mirror_flags),
- variable_decl(type, to_name(mirror_id)),
- initializer, ";");
- // The temporary might be read from before it's assigned, set up the expression now.
- set<SPIRExpression>(mirror_id, to_name(mirror_id), tmp.first, true);
- hoisted_temporaries.insert(mirror_id);
- }
- }
- }
- void CompilerGLSL::emit_block_chain(SPIRBlock &block)
- {
- SmallVector<BlockID> cleanup_stack;
- BlockID next_block = emit_block_chain_inner(block);
- while (next_block != 0)
- {
- cleanup_stack.push_back(next_block);
- next_block = emit_block_chain_inner(get<SPIRBlock>(next_block));
- }
- while (!cleanup_stack.empty())
- {
- emit_block_chain_cleanup(get<SPIRBlock>(cleanup_stack.back()));
- cleanup_stack.pop_back();
- }
- emit_block_chain_cleanup(block);
- }
- BlockID CompilerGLSL::emit_block_chain_inner(SPIRBlock &block)
- {
- bool select_branch_to_true_block = false;
- bool select_branch_to_false_block = false;
- bool skip_direct_branch = false;
- bool emitted_loop_header_variables = false;
- bool force_complex_continue_block = false;
- ValueSaver<uint32_t> loop_level_saver(current_loop_level);
- if (block.merge == SPIRBlock::MergeLoop)
- add_loop_level();
- // If we're emitting PHI variables with precision aliases, we have to emit them as hoisted temporaries.
- for (auto var_id : block.dominated_variables)
- {
- auto &var = get<SPIRVariable>(var_id);
- if (var.phi_variable)
- {
- auto mirrored_precision_itr = temporary_to_mirror_precision_alias.find(var_id);
- if (mirrored_precision_itr != temporary_to_mirror_precision_alias.end() &&
- find_if(block.declare_temporary.begin(), block.declare_temporary.end(),
- [mirrored_precision_itr](const std::pair<TypeID, VariableID> &p) {
- return p.second == mirrored_precision_itr->second;
- }) == block.declare_temporary.end())
- {
- block.declare_temporary.push_back({ var.basetype, mirrored_precision_itr->second });
- }
- }
- }
- emit_hoisted_temporaries(block.declare_temporary);
- SPIRBlock::ContinueBlockType continue_type = SPIRBlock::ContinueNone;
- if (block.continue_block)
- {
- continue_type = continue_block_type(get<SPIRBlock>(block.continue_block));
- // If we know we cannot emit a loop, mark the block early as a complex loop so we don't force unnecessary recompiles.
- if (continue_type == SPIRBlock::ComplexLoop)
- block.complex_continue = true;
- }
- // If we have loop variables, stop masking out access to the variable now.
- for (auto var_id : block.loop_variables)
- {
- auto &var = get<SPIRVariable>(var_id);
- var.loop_variable_enable = true;
- // We're not going to declare the variable directly, so emit a copy here.
- emit_variable_temporary_copies(var);
- }
- // Remember deferred declaration state. We will restore it before returning.
- assert(block.rearm_dominated_variables.empty());
- block.rearm_dominated_variables.resize(block.dominated_variables.size());
- for (size_t i = 0; i < block.dominated_variables.size(); i++)
- {
- uint32_t var_id = block.dominated_variables[i];
- auto &var = get<SPIRVariable>(var_id);
- block.rearm_dominated_variables[i] = var.deferred_declaration;
- }
- // This is the method often used by spirv-opt to implement loops.
- // The loop header goes straight into the continue block.
- // However, don't attempt this on ESSL 1.0, because if a loop variable is used in a continue block,
- // it *MUST* be used in the continue block. This loop method will not work.
- if (!is_legacy_es() && block_is_loop_candidate(block, SPIRBlock::MergeToSelectContinueForLoop))
- {
- flush_undeclared_variables(block);
- if (attempt_emit_loop_header(block, SPIRBlock::MergeToSelectContinueForLoop))
- {
- if (execution_is_noop(get<SPIRBlock>(block.true_block), get<SPIRBlock>(block.merge_block)))
- select_branch_to_false_block = true;
- else
- select_branch_to_true_block = true;
- emitted_loop_header_variables = true;
- force_complex_continue_block = true;
- }
- }
- // This is the older loop behavior in glslang which branches to loop body directly from the loop header.
- else if (block_is_loop_candidate(block, SPIRBlock::MergeToSelectForLoop))
- {
- flush_undeclared_variables(block);
- if (attempt_emit_loop_header(block, SPIRBlock::MergeToSelectForLoop))
- {
- // The body of while, is actually just the true (or false) block, so always branch there unconditionally.
- if (execution_is_noop(get<SPIRBlock>(block.true_block), get<SPIRBlock>(block.merge_block)))
- select_branch_to_false_block = true;
- else
- select_branch_to_true_block = true;
- emitted_loop_header_variables = true;
- }
- }
- // This is the newer loop behavior in glslang which branches from Loop header directly to
- // a new block, which in turn has a OpBranchSelection without a selection merge.
- else if (block_is_loop_candidate(block, SPIRBlock::MergeToDirectForLoop))
- {
- flush_undeclared_variables(block);
- if (attempt_emit_loop_header(block, SPIRBlock::MergeToDirectForLoop))
- {
- skip_direct_branch = true;
- emitted_loop_header_variables = true;
- }
- }
- else if (continue_type == SPIRBlock::DoWhileLoop)
- {
- flush_undeclared_variables(block);
- emit_while_loop_initializers(block);
- emitted_loop_header_variables = true;
- // We have some temporaries where the loop header is the dominator.
- // We risk a case where we have code like:
- // for (;;) { create-temporary; break; } consume-temporary;
- // so force-declare temporaries here.
- emit_hoisted_temporaries(block.potential_declare_temporary);
- statement("do");
- begin_scope();
- emit_block_instructions(block);
- }
- else if (block.merge == SPIRBlock::MergeLoop)
- {
- flush_undeclared_variables(block);
- emit_while_loop_initializers(block);
- emitted_loop_header_variables = true;
- // We have a generic loop without any distinguishable pattern like for, while or do while.
- get<SPIRBlock>(block.continue_block).complex_continue = true;
- continue_type = SPIRBlock::ComplexLoop;
- // We have some temporaries where the loop header is the dominator.
- // We risk a case where we have code like:
- // for (;;) { create-temporary; break; } consume-temporary;
- // so force-declare temporaries here.
- emit_hoisted_temporaries(block.potential_declare_temporary);
- emit_block_hints(block);
- statement("for (;;)");
- begin_scope();
- emit_block_instructions(block);
- }
- else
- {
- emit_block_instructions(block);
- }
- // If we didn't successfully emit a loop header and we had loop variable candidates, we have a problem
- // as writes to said loop variables might have been masked out, we need a recompile.
- if (!emitted_loop_header_variables && !block.loop_variables.empty())
- {
- force_recompile_guarantee_forward_progress();
- for (auto var : block.loop_variables)
- get<SPIRVariable>(var).loop_variable = false;
- block.loop_variables.clear();
- }
- flush_undeclared_variables(block);
- bool emit_next_block = true;
- // Handle end of block.
- switch (block.terminator)
- {
- case SPIRBlock::Direct:
- // True when emitting complex continue block.
- if (block.loop_dominator == block.next_block)
- {
- branch(block.self, block.next_block);
- emit_next_block = false;
- }
- // True if MergeToDirectForLoop succeeded.
- else if (skip_direct_branch)
- emit_next_block = false;
- else if (is_continue(block.next_block) || is_break(block.next_block) || is_conditional(block.next_block))
- {
- branch(block.self, block.next_block);
- emit_next_block = false;
- }
- break;
- case SPIRBlock::Select:
- // True if MergeToSelectForLoop or MergeToSelectContinueForLoop succeeded.
- if (select_branch_to_true_block)
- {
- if (force_complex_continue_block)
- {
- assert(block.true_block == block.continue_block);
- // We're going to emit a continue block directly here, so make sure it's marked as complex.
- auto &complex_continue = get<SPIRBlock>(block.continue_block).complex_continue;
- bool old_complex = complex_continue;
- complex_continue = true;
- branch(block.self, block.true_block);
- complex_continue = old_complex;
- }
- else
- branch(block.self, block.true_block);
- }
- else if (select_branch_to_false_block)
- {
- if (force_complex_continue_block)
- {
- assert(block.false_block == block.continue_block);
- // We're going to emit a continue block directly here, so make sure it's marked as complex.
- auto &complex_continue = get<SPIRBlock>(block.continue_block).complex_continue;
- bool old_complex = complex_continue;
- complex_continue = true;
- branch(block.self, block.false_block);
- complex_continue = old_complex;
- }
- else
- branch(block.self, block.false_block);
- }
- else
- branch(block.self, block.condition, block.true_block, block.false_block);
- break;
- case SPIRBlock::MultiSelect:
- {
- auto &type = expression_type(block.condition);
- bool unsigned_case = type.basetype == SPIRType::UInt || type.basetype == SPIRType::UShort ||
- type.basetype == SPIRType::UByte || type.basetype == SPIRType::UInt64;
- if (block.merge == SPIRBlock::MergeNone)
- SPIRV_CROSS_THROW("Switch statement is not structured");
- if (!backend.support_64bit_switch && (type.basetype == SPIRType::UInt64 || type.basetype == SPIRType::Int64))
- {
- // SPIR-V spec suggests this is allowed, but we cannot support it in higher level languages.
- SPIRV_CROSS_THROW("Cannot use 64-bit switch selectors.");
- }
- const char *label_suffix = "";
- if (type.basetype == SPIRType::UInt && backend.uint32_t_literal_suffix)
- label_suffix = "u";
- else if (type.basetype == SPIRType::Int64 && backend.support_64bit_switch)
- label_suffix = "l";
- else if (type.basetype == SPIRType::UInt64 && backend.support_64bit_switch)
- label_suffix = "ul";
- else if (type.basetype == SPIRType::UShort)
- label_suffix = backend.uint16_t_literal_suffix;
- else if (type.basetype == SPIRType::Short)
- label_suffix = backend.int16_t_literal_suffix;
- current_emitting_switch_stack.push_back(&block);
- if (block.need_ladder_break)
- statement("bool _", block.self, "_ladder_break = false;");
- // Find all unique case constructs.
- unordered_map<uint32_t, SmallVector<uint64_t>> case_constructs;
- SmallVector<uint32_t> block_declaration_order;
- SmallVector<uint64_t> literals_to_merge;
- // If a switch case branches to the default block for some reason, we can just remove that literal from consideration
- // and let the default: block handle it.
- // 2.11 in SPIR-V spec states that for fall-through cases, there is a very strict declaration order which we can take advantage of here.
- // We only need to consider possible fallthrough if order[i] branches to order[i + 1].
- auto &cases = get_case_list(block);
- for (auto &c : cases)
- {
- if (c.block != block.next_block && c.block != block.default_block)
- {
- if (!case_constructs.count(c.block))
- block_declaration_order.push_back(c.block);
- case_constructs[c.block].push_back(c.value);
- }
- else if (c.block == block.next_block && block.default_block != block.next_block)
- {
- // We might have to flush phi inside specific case labels.
- // If we can piggyback on default:, do so instead.
- literals_to_merge.push_back(c.value);
- }
- }
- // Empty literal array -> default.
- if (block.default_block != block.next_block)
- {
- auto &default_block = get<SPIRBlock>(block.default_block);
- // We need to slide in the default block somewhere in this chain
- // if there are fall-through scenarios since the default is declared separately in OpSwitch.
- // Only consider trivial fall-through cases here.
- size_t num_blocks = block_declaration_order.size();
- bool injected_block = false;
- for (size_t i = 0; i < num_blocks; i++)
- {
- auto &case_block = get<SPIRBlock>(block_declaration_order[i]);
- if (execution_is_direct_branch(case_block, default_block))
- {
- // Fallthrough to default block, we must inject the default block here.
- block_declaration_order.insert(begin(block_declaration_order) + i + 1, block.default_block);
- injected_block = true;
- break;
- }
- else if (execution_is_direct_branch(default_block, case_block))
- {
- // Default case is falling through to another case label, we must inject the default block here.
- block_declaration_order.insert(begin(block_declaration_order) + i, block.default_block);
- injected_block = true;
- break;
- }
- }
- // Order does not matter.
- if (!injected_block)
- block_declaration_order.push_back(block.default_block);
- else if (is_legacy_es())
- SPIRV_CROSS_THROW("Default case label fallthrough to other case label is not supported in ESSL 1.0.");
- case_constructs[block.default_block] = {};
- }
- size_t num_blocks = block_declaration_order.size();
- const auto to_case_label = [](uint64_t literal, uint32_t width, bool is_unsigned_case) -> string
- {
- if (is_unsigned_case)
- return convert_to_string(literal);
- // For smaller cases, the literals are compiled as 32 bit wide
- // literals so we don't need to care for all sizes specifically.
- if (width <= 32)
- {
- return convert_to_string(int64_t(int32_t(literal)));
- }
- return convert_to_string(int64_t(literal));
- };
- const auto to_legacy_case_label = [&](uint32_t condition, const SmallVector<uint64_t> &labels,
- const char *suffix) -> string {
- string ret;
- size_t count = labels.size();
- for (size_t i = 0; i < count; i++)
- {
- if (i)
- ret += " || ";
- ret += join(count > 1 ? "(" : "", to_enclosed_expression(condition), " == ", labels[i], suffix,
- count > 1 ? ")" : "");
- }
- return ret;
- };
- // We need to deal with a complex scenario for OpPhi. If we have case-fallthrough and Phi in the picture,
- // we need to flush phi nodes outside the switch block in a branch,
- // and skip any Phi handling inside the case label to make fall-through work as expected.
- // This kind of code-gen is super awkward and it's a last resort. Normally we would want to handle this
- // inside the case label if at all possible.
- for (size_t i = 1; backend.support_case_fallthrough && i < num_blocks; i++)
- {
- if (flush_phi_required(block.self, block_declaration_order[i]) &&
- flush_phi_required(block_declaration_order[i - 1], block_declaration_order[i]))
- {
- uint32_t target_block = block_declaration_order[i];
- // Make sure we flush Phi, it might have been marked to be ignored earlier.
- get<SPIRBlock>(target_block).ignore_phi_from_block = 0;
- auto &literals = case_constructs[target_block];
- if (literals.empty())
- {
- // Oh boy, gotta make a complete negative test instead! o.o
- // Find all possible literals that would *not* make us enter the default block.
- // If none of those literals match, we flush Phi ...
- SmallVector<string> conditions;
- for (size_t j = 0; j < num_blocks; j++)
- {
- auto &negative_literals = case_constructs[block_declaration_order[j]];
- for (auto &case_label : negative_literals)
- conditions.push_back(join(to_enclosed_expression(block.condition),
- " != ", to_case_label(case_label, type.width, unsigned_case)));
- }
- statement("if (", merge(conditions, " && "), ")");
- begin_scope();
- flush_phi(block.self, target_block);
- end_scope();
- }
- else
- {
- SmallVector<string> conditions;
- conditions.reserve(literals.size());
- for (auto &case_label : literals)
- conditions.push_back(join(to_enclosed_expression(block.condition),
- " == ", to_case_label(case_label, type.width, unsigned_case)));
- statement("if (", merge(conditions, " || "), ")");
- begin_scope();
- flush_phi(block.self, target_block);
- end_scope();
- }
- // Mark the block so that we don't flush Phi from header to case label.
- get<SPIRBlock>(target_block).ignore_phi_from_block = block.self;
- }
- }
- // If there is only one default block, and no cases, this is a case where SPIRV-opt decided to emulate
- // non-structured exits with the help of a switch block.
- // This is buggy on FXC, so just emit the logical equivalent of a do { } while(false), which is more idiomatic.
- bool block_like_switch = cases.empty();
- // If this is true, the switch is completely meaningless, and we should just avoid it.
- bool collapsed_switch = block_like_switch && block.default_block == block.next_block;
- if (!collapsed_switch)
- {
- if (block_like_switch || is_legacy())
- {
- // ESSL 1.0 is not guaranteed to support do/while.
- if (is_legacy_es())
- {
- uint32_t counter = statement_count;
- statement("for (int spvDummy", counter, " = 0; spvDummy", counter, " < 1; spvDummy", counter,
- "++)");
- }
- else
- statement("do");
- }
- else
- {
- emit_block_hints(block);
- statement("switch (", to_unpacked_expression(block.condition), ")");
- }
- begin_scope();
- }
- for (size_t i = 0; i < num_blocks; i++)
- {
- uint32_t target_block = block_declaration_order[i];
- auto &literals = case_constructs[target_block];
- if (literals.empty())
- {
- // Default case.
- if (!block_like_switch)
- {
- if (is_legacy())
- statement("else");
- else
- statement("default:");
- }
- }
- else
- {
- if (is_legacy())
- {
- statement((i ? "else " : ""), "if (", to_legacy_case_label(block.condition, literals, label_suffix),
- ")");
- }
- else
- {
- for (auto &case_literal : literals)
- {
- // The case label value must be sign-extended properly in SPIR-V, so we can assume 32-bit values here.
- statement("case ", to_case_label(case_literal, type.width, unsigned_case), label_suffix, ":");
- }
- }
- }
- auto &case_block = get<SPIRBlock>(target_block);
- if (backend.support_case_fallthrough && i + 1 < num_blocks &&
- execution_is_direct_branch(case_block, get<SPIRBlock>(block_declaration_order[i + 1])))
- {
- // We will fall through here, so just terminate the block chain early.
- // We still need to deal with Phi potentially.
- // No need for a stack-like thing here since we only do fall-through when there is a
- // single trivial branch to fall-through target..
- current_emitting_switch_fallthrough = true;
- }
- else
- current_emitting_switch_fallthrough = false;
- if (!block_like_switch)
- begin_scope();
- branch(block.self, target_block);
- if (!block_like_switch)
- end_scope();
- current_emitting_switch_fallthrough = false;
- }
- // Might still have to flush phi variables if we branch from loop header directly to merge target.
- // This is supposed to emit all cases where we branch from header to merge block directly.
- // There are two main scenarios where cannot rely on default fallthrough.
- // - There is an explicit default: label already.
- // In this case, literals_to_merge need to form their own "default" case, so that we avoid executing that block.
- // - Header -> Merge requires flushing PHI. In this case, we need to collect all cases and flush PHI there.
- bool header_merge_requires_phi = flush_phi_required(block.self, block.next_block);
- bool need_fallthrough_block = block.default_block == block.next_block || !literals_to_merge.empty();
- if (!collapsed_switch && ((header_merge_requires_phi && need_fallthrough_block) || !literals_to_merge.empty()))
- {
- for (auto &case_literal : literals_to_merge)
- statement("case ", to_case_label(case_literal, type.width, unsigned_case), label_suffix, ":");
- if (block.default_block == block.next_block)
- {
- if (is_legacy())
- statement("else");
- else
- statement("default:");
- }
- begin_scope();
- flush_phi(block.self, block.next_block);
- statement("break;");
- end_scope();
- }
- if (!collapsed_switch)
- {
- if ((block_like_switch || is_legacy()) && !is_legacy_es())
- end_scope_decl("while(false)");
- else
- end_scope();
- }
- else
- flush_phi(block.self, block.next_block);
- if (block.need_ladder_break)
- {
- statement("if (_", block.self, "_ladder_break)");
- begin_scope();
- statement("break;");
- end_scope();
- }
- current_emitting_switch_stack.pop_back();
- break;
- }
- case SPIRBlock::Return:
- {
- for (auto &line : current_function->fixup_hooks_out)
- line();
- if (processing_entry_point)
- emit_fixup();
- auto &cfg = get_cfg_for_current_function();
- if (block.return_value)
- {
- auto &type = expression_type(block.return_value);
- if (!type.array.empty() && !backend.can_return_array)
- {
- // If we cannot return arrays, we will have a special out argument we can write to instead.
- // The backend is responsible for setting this up, and redirection the return values as appropriate.
- if (ir.ids[block.return_value].get_type() != TypeUndef)
- {
- emit_array_copy("spvReturnValue", 0, block.return_value, StorageClassFunction,
- get_expression_effective_storage_class(block.return_value));
- }
- if (!cfg.node_terminates_control_flow_in_sub_graph(current_function->entry_block, block.self) ||
- block.loop_dominator != BlockID(SPIRBlock::NoDominator))
- {
- statement("return;");
- }
- }
- else
- {
- // OpReturnValue can return Undef, so don't emit anything for this case.
- if (ir.ids[block.return_value].get_type() != TypeUndef)
- statement("return ", to_unpacked_expression(block.return_value), ";");
- }
- }
- else if (!cfg.node_terminates_control_flow_in_sub_graph(current_function->entry_block, block.self) ||
- block.loop_dominator != BlockID(SPIRBlock::NoDominator))
- {
- // If this block is the very final block and not called from control flow,
- // we do not need an explicit return which looks out of place. Just end the function here.
- // In the very weird case of for(;;) { return; } executing return is unconditional,
- // but we actually need a return here ...
- statement("return;");
- }
- break;
- }
- // If the Kill is terminating a block with a (probably synthetic) return value, emit a return value statement.
- case SPIRBlock::Kill:
- statement(backend.discard_literal, ";");
- if (block.return_value)
- statement("return ", to_unpacked_expression(block.return_value), ";");
- break;
- case SPIRBlock::Unreachable:
- {
- // If the entry point ends with unreachable and has a return value, insert a return
- // statement to avoid potential compiler errors from non-void functions without a return value.
- if (block.return_value)
- {
- statement("return ", to_unpacked_expression(block.return_value), ";");
- break;
- }
- // Avoid emitting false fallthrough, which can happen for
- // if (cond) break; else discard; inside a case label.
- // Discard is not always implementable as a terminator.
- auto &cfg = get_cfg_for_current_function();
- bool inner_dominator_is_switch = false;
- ID id = block.self;
- while (id)
- {
- auto &iter_block = get<SPIRBlock>(id);
- if (iter_block.terminator == SPIRBlock::MultiSelect ||
- iter_block.merge == SPIRBlock::MergeLoop)
- {
- ID next_block = iter_block.merge == SPIRBlock::MergeLoop ?
- iter_block.merge_block : iter_block.next_block;
- bool outside_construct = next_block && cfg.find_common_dominator(next_block, block.self) == next_block;
- if (!outside_construct)
- {
- inner_dominator_is_switch = iter_block.terminator == SPIRBlock::MultiSelect;
- break;
- }
- }
- if (cfg.get_preceding_edges(id).empty())
- break;
- id = cfg.get_immediate_dominator(id);
- }
- if (inner_dominator_is_switch)
- statement("break; // unreachable workaround");
- emit_next_block = false;
- break;
- }
- case SPIRBlock::IgnoreIntersection:
- statement("ignoreIntersectionEXT;");
- break;
- case SPIRBlock::TerminateRay:
- statement("terminateRayEXT;");
- break;
- case SPIRBlock::EmitMeshTasks:
- emit_mesh_tasks(block);
- break;
- default:
- SPIRV_CROSS_THROW("Unimplemented block terminator.");
- }
- BlockID trailing_block_id = 0;
- if (block.next_block && emit_next_block)
- {
- // If we hit this case, we're dealing with an unconditional branch, which means we will output
- // that block after this. If we had selection merge, we already flushed phi variables.
- if (block.merge != SPIRBlock::MergeSelection)
- {
- flush_phi(block.self, block.next_block);
- // For a direct branch, need to remember to invalidate expressions in the next linear block instead.
- get<SPIRBlock>(block.next_block).invalidate_expressions.clear();
- std::swap(get<SPIRBlock>(block.next_block).invalidate_expressions, block.invalidate_expressions);
- }
- // For switch fallthrough cases, we terminate the chain here, but we still need to handle Phi.
- if (!current_emitting_switch_fallthrough)
- {
- // For merge selects we might have ignored the fact that a merge target
- // could have been a break; or continue;
- // We will need to deal with it here.
- if (is_loop_break(block.next_block))
- {
- // Cannot check for just break, because switch statements will also use break.
- assert(block.merge == SPIRBlock::MergeSelection);
- statement("break;");
- }
- else if (is_continue(block.next_block))
- {
- assert(block.merge == SPIRBlock::MergeSelection);
- branch_to_continue(block.self, block.next_block);
- }
- else if (BlockID(block.self) != block.next_block)
- {
- // Recursing here is quite scary since it's quite easy to stack overflow if
- // the SPIR-V is constructed a particular way.
- // We have to simulate the tail call ourselves.
- if (block.merge != SPIRBlock::MergeLoop)
- trailing_block_id = block.next_block;
- else
- emit_block_chain(get<SPIRBlock>(block.next_block));
- }
- }
- }
- if (block.merge == SPIRBlock::MergeLoop)
- {
- if (continue_type == SPIRBlock::DoWhileLoop)
- {
- // Make sure that we run the continue block to get the expressions set, but this
- // should become an empty string.
- // We have no fallbacks if we cannot forward everything to temporaries ...
- const auto &continue_block = get<SPIRBlock>(block.continue_block);
- bool positive_test = execution_is_noop(get<SPIRBlock>(continue_block.true_block),
- get<SPIRBlock>(continue_block.loop_dominator));
- uint32_t current_count = statement_count;
- auto statements = emit_continue_block(block.continue_block, positive_test, !positive_test);
- if (statement_count != current_count)
- {
- // The DoWhile block has side effects, force ComplexLoop pattern next pass.
- get<SPIRBlock>(block.continue_block).complex_continue = true;
- force_recompile();
- }
- // Might have to invert the do-while test here.
- auto condition = to_expression(continue_block.condition);
- if (!positive_test)
- condition = join("!", enclose_expression(condition));
- end_scope_decl(join("while (", condition, ")"));
- }
- else
- end_scope();
- loop_level_saver.release();
- // We cannot break out of two loops at once, so don't check for break; here.
- // Using block.self as the "from" block isn't quite right, but it has the same scope
- // and dominance structure, so it's fine.
- if (is_continue(block.merge_block))
- branch_to_continue(block.self, block.merge_block);
- else
- trailing_block_id = block.merge_block;
- }
- return trailing_block_id;
- }
- void CompilerGLSL::emit_block_chain_cleanup(SPIRBlock &block)
- {
- // Forget about control dependent expressions now.
- block.invalidate_expressions.clear();
- // After we return, we must be out of scope, so if we somehow have to re-emit this block,
- // re-declare variables if necessary.
- // We only need one array here for rearm_dominated_variables,
- // since it should be impossible for the same block to be remitted in the same chain twice.
- assert(block.rearm_dominated_variables.size() == block.dominated_variables.size());
- for (size_t i = 0; i < block.dominated_variables.size(); i++)
- {
- uint32_t var = block.dominated_variables[i];
- get<SPIRVariable>(var).deferred_declaration = block.rearm_dominated_variables[i];
- }
- block.rearm_dominated_variables.clear();
- // Just like for deferred declaration, we need to forget about loop variable enable
- // if our block chain is reinstantiated later.
- for (auto &var_id : block.loop_variables)
- get<SPIRVariable>(var_id).loop_variable_enable = false;
- }
- void CompilerGLSL::begin_scope()
- {
- statement("{");
- indent++;
- }
- void CompilerGLSL::end_scope()
- {
- if (!indent)
- SPIRV_CROSS_THROW("Popping empty indent stack.");
- indent--;
- statement("}");
- }
- void CompilerGLSL::end_scope(const string &trailer)
- {
- if (!indent)
- SPIRV_CROSS_THROW("Popping empty indent stack.");
- indent--;
- statement("}", trailer);
- }
- void CompilerGLSL::end_scope_decl()
- {
- if (!indent)
- SPIRV_CROSS_THROW("Popping empty indent stack.");
- indent--;
- statement("};");
- }
- void CompilerGLSL::end_scope_decl(const string &decl)
- {
- if (!indent)
- SPIRV_CROSS_THROW("Popping empty indent stack.");
- indent--;
- statement("} ", decl, ";");
- }
- void CompilerGLSL::check_function_call_constraints(const uint32_t *args, uint32_t length)
- {
- // If our variable is remapped, and we rely on type-remapping information as
- // well, then we cannot pass the variable as a function parameter.
- // Fixing this is non-trivial without stamping out variants of the same function,
- // so for now warn about this and suggest workarounds instead.
- for (uint32_t i = 0; i < length; i++)
- {
- auto *var = maybe_get<SPIRVariable>(args[i]);
- if (!var || !var->remapped_variable)
- continue;
- auto &type = get<SPIRType>(var->basetype);
- if (type.basetype == SPIRType::Image && type.image.dim == DimSubpassData)
- {
- SPIRV_CROSS_THROW("Tried passing a remapped subpassInput variable to a function. "
- "This will not work correctly because type-remapping information is lost. "
- "To workaround, please consider not passing the subpass input as a function parameter, "
- "or use in/out variables instead which do not need type remapping information.");
- }
- }
- }
- const Instruction *CompilerGLSL::get_next_instruction_in_block(const Instruction &instr)
- {
- // FIXME: This is kind of hacky. There should be a cleaner way.
- auto offset = uint32_t(&instr - current_emitting_block->ops.data());
- if ((offset + 1) < current_emitting_block->ops.size())
- return ¤t_emitting_block->ops[offset + 1];
- else
- return nullptr;
- }
- uint32_t CompilerGLSL::mask_relevant_memory_semantics(uint32_t semantics)
- {
- return semantics & (MemorySemanticsAtomicCounterMemoryMask | MemorySemanticsImageMemoryMask |
- MemorySemanticsWorkgroupMemoryMask | MemorySemanticsUniformMemoryMask |
- MemorySemanticsCrossWorkgroupMemoryMask | MemorySemanticsSubgroupMemoryMask);
- }
- bool CompilerGLSL::emit_array_copy(const char *expr, uint32_t lhs_id, uint32_t rhs_id, StorageClass, StorageClass)
- {
- string lhs;
- if (expr)
- lhs = expr;
- else
- lhs = to_expression(lhs_id);
- statement(lhs, " = ", to_expression(rhs_id), ";");
- return true;
- }
- bool CompilerGLSL::unroll_array_to_complex_store(uint32_t target_id, uint32_t source_id)
- {
- if (!backend.force_gl_in_out_block)
- return false;
- // This path is only relevant for GL backends.
- auto *var = maybe_get<SPIRVariable>(target_id);
- if (!var || var->storage != StorageClassOutput)
- return false;
- if (!is_builtin_variable(*var) || BuiltIn(get_decoration(var->self, DecorationBuiltIn)) != BuiltInSampleMask)
- return false;
- auto &type = expression_type(source_id);
- string array_expr;
- if (type.array_size_literal.back())
- {
- array_expr = convert_to_string(type.array.back());
- if (type.array.back() == 0)
- SPIRV_CROSS_THROW("Cannot unroll an array copy from unsized array.");
- }
- else
- array_expr = to_expression(type.array.back());
- SPIRType target_type { OpTypeInt };
- target_type.basetype = SPIRType::Int;
- statement("for (int i = 0; i < int(", array_expr, "); i++)");
- begin_scope();
- statement(to_expression(target_id), "[i] = ",
- bitcast_expression(target_type, type.basetype, join(to_expression(source_id), "[i]")),
- ";");
- end_scope();
- return true;
- }
- void CompilerGLSL::unroll_array_from_complex_load(uint32_t target_id, uint32_t source_id, std::string &expr)
- {
- if (!backend.force_gl_in_out_block)
- return;
- // This path is only relevant for GL backends.
- auto *var = maybe_get<SPIRVariable>(source_id);
- if (!var)
- return;
- if (var->storage != StorageClassInput && var->storage != StorageClassOutput)
- return;
- auto &type = get_variable_data_type(*var);
- if (type.array.empty())
- return;
- auto builtin = BuiltIn(get_decoration(var->self, DecorationBuiltIn));
- bool is_builtin = is_builtin_variable(*var) &&
- (builtin == BuiltInPointSize ||
- builtin == BuiltInPosition ||
- builtin == BuiltInSampleMask);
- bool is_tess = is_tessellation_shader();
- bool is_patch = has_decoration(var->self, DecorationPatch);
- bool is_sample_mask = is_builtin && builtin == BuiltInSampleMask;
- // Tessellation input arrays are special in that they are unsized, so we cannot directly copy from it.
- // We must unroll the array load.
- // For builtins, we couldn't catch this case normally,
- // because this is resolved in the OpAccessChain in most cases.
- // If we load the entire array, we have no choice but to unroll here.
- if (!is_patch && (is_builtin || is_tess))
- {
- auto new_expr = join("_", target_id, "_unrolled");
- statement(variable_decl(type, new_expr, target_id), ";");
- string array_expr;
- if (type.array_size_literal.back())
- {
- array_expr = convert_to_string(type.array.back());
- if (type.array.back() == 0)
- SPIRV_CROSS_THROW("Cannot unroll an array copy from unsized array.");
- }
- else
- array_expr = to_expression(type.array.back());
- // The array size might be a specialization constant, so use a for-loop instead.
- statement("for (int i = 0; i < int(", array_expr, "); i++)");
- begin_scope();
- if (is_builtin && !is_sample_mask)
- statement(new_expr, "[i] = gl_in[i].", expr, ";");
- else if (is_sample_mask)
- {
- SPIRType target_type { OpTypeInt };
- target_type.basetype = SPIRType::Int;
- statement(new_expr, "[i] = ", bitcast_expression(target_type, type.basetype, join(expr, "[i]")), ";");
- }
- else
- statement(new_expr, "[i] = ", expr, "[i];");
- end_scope();
- expr = std::move(new_expr);
- }
- }
- void CompilerGLSL::cast_from_variable_load(uint32_t source_id, std::string &expr, const SPIRType &expr_type)
- {
- // We will handle array cases elsewhere.
- if (!expr_type.array.empty())
- return;
- auto *var = maybe_get_backing_variable(source_id);
- if (var)
- source_id = var->self;
- // Only interested in standalone builtin variables.
- if (!has_decoration(source_id, DecorationBuiltIn))
- {
- // Except for int attributes in legacy GLSL, which are cast from float.
- if (is_legacy() && expr_type.basetype == SPIRType::Int && var && var->storage == StorageClassInput)
- expr = join(type_to_glsl(expr_type), "(", expr, ")");
- return;
- }
- auto builtin = static_cast<BuiltIn>(get_decoration(source_id, DecorationBuiltIn));
- auto expected_type = expr_type.basetype;
- // TODO: Fill in for more builtins.
- switch (builtin)
- {
- case BuiltInLayer:
- case BuiltInPrimitiveId:
- case BuiltInViewportIndex:
- case BuiltInInstanceId:
- case BuiltInInstanceIndex:
- case BuiltInVertexId:
- case BuiltInVertexIndex:
- case BuiltInSampleId:
- case BuiltInBaseVertex:
- case BuiltInBaseInstance:
- case BuiltInDrawIndex:
- case BuiltInFragStencilRefEXT:
- case BuiltInInstanceCustomIndexNV:
- case BuiltInSampleMask:
- case BuiltInPrimitiveShadingRateKHR:
- case BuiltInShadingRateKHR:
- expected_type = SPIRType::Int;
- break;
- case BuiltInGlobalInvocationId:
- case BuiltInLocalInvocationId:
- case BuiltInWorkgroupId:
- case BuiltInLocalInvocationIndex:
- case BuiltInWorkgroupSize:
- case BuiltInNumWorkgroups:
- case BuiltInIncomingRayFlagsNV:
- case BuiltInLaunchIdNV:
- case BuiltInLaunchSizeNV:
- case BuiltInPrimitiveTriangleIndicesEXT:
- case BuiltInPrimitiveLineIndicesEXT:
- case BuiltInPrimitivePointIndicesEXT:
- expected_type = SPIRType::UInt;
- break;
- default:
- break;
- }
- if (expected_type != expr_type.basetype)
- expr = bitcast_expression(expr_type, expected_type, expr);
- }
- SPIRType::BaseType CompilerGLSL::get_builtin_basetype(BuiltIn builtin, SPIRType::BaseType default_type)
- {
- // TODO: Fill in for more builtins.
- switch (builtin)
- {
- case BuiltInLayer:
- case BuiltInPrimitiveId:
- case BuiltInViewportIndex:
- case BuiltInFragStencilRefEXT:
- case BuiltInSampleMask:
- case BuiltInPrimitiveShadingRateKHR:
- case BuiltInShadingRateKHR:
- return SPIRType::Int;
- default:
- return default_type;
- }
- }
- void CompilerGLSL::cast_to_variable_store(uint32_t target_id, std::string &expr, const SPIRType &expr_type)
- {
- auto *var = maybe_get_backing_variable(target_id);
- if (var)
- target_id = var->self;
- // Only interested in standalone builtin variables.
- if (!has_decoration(target_id, DecorationBuiltIn))
- return;
- auto builtin = static_cast<BuiltIn>(get_decoration(target_id, DecorationBuiltIn));
- auto expected_type = get_builtin_basetype(builtin, expr_type.basetype);
- if (expected_type != expr_type.basetype)
- {
- auto type = expr_type;
- type.basetype = expected_type;
- expr = bitcast_expression(type, expr_type.basetype, expr);
- }
- }
- void CompilerGLSL::convert_non_uniform_expression(string &expr, uint32_t ptr_id)
- {
- if (*backend.nonuniform_qualifier == '\0')
- return;
- auto *var = maybe_get_backing_variable(ptr_id);
- if (!var)
- return;
- if (var->storage != StorageClassUniformConstant &&
- var->storage != StorageClassStorageBuffer &&
- var->storage != StorageClassUniform)
- return;
- auto &backing_type = get<SPIRType>(var->basetype);
- if (backing_type.array.empty())
- return;
- // If we get here, we know we're accessing an arrayed resource which
- // might require nonuniform qualifier.
- auto start_array_index = expr.find_first_of('[');
- if (start_array_index == string::npos)
- return;
- // We've opened a bracket, track expressions until we can close the bracket.
- // This must be our resource index.
- size_t end_array_index = string::npos;
- unsigned bracket_count = 1;
- for (size_t index = start_array_index + 1; index < expr.size(); index++)
- {
- if (expr[index] == ']')
- {
- if (--bracket_count == 0)
- {
- end_array_index = index;
- break;
- }
- }
- else if (expr[index] == '[')
- bracket_count++;
- }
- assert(bracket_count == 0);
- // Doesn't really make sense to declare a non-arrayed image with nonuniformEXT, but there's
- // nothing we can do here to express that.
- if (start_array_index == string::npos || end_array_index == string::npos || end_array_index < start_array_index)
- return;
- start_array_index++;
- expr = join(expr.substr(0, start_array_index), backend.nonuniform_qualifier, "(",
- expr.substr(start_array_index, end_array_index - start_array_index), ")",
- expr.substr(end_array_index, string::npos));
- }
- void CompilerGLSL::emit_block_hints(const SPIRBlock &block)
- {
- if ((options.es && options.version < 310) || (!options.es && options.version < 140))
- return;
- switch (block.hint)
- {
- case SPIRBlock::HintFlatten:
- require_extension_internal("GL_EXT_control_flow_attributes");
- statement("SPIRV_CROSS_FLATTEN");
- break;
- case SPIRBlock::HintDontFlatten:
- require_extension_internal("GL_EXT_control_flow_attributes");
- statement("SPIRV_CROSS_BRANCH");
- break;
- case SPIRBlock::HintUnroll:
- require_extension_internal("GL_EXT_control_flow_attributes");
- statement("SPIRV_CROSS_UNROLL");
- break;
- case SPIRBlock::HintDontUnroll:
- require_extension_internal("GL_EXT_control_flow_attributes");
- statement("SPIRV_CROSS_LOOP");
- break;
- default:
- break;
- }
- }
- void CompilerGLSL::preserve_alias_on_reset(uint32_t id)
- {
- preserved_aliases[id] = get_name(id);
- }
- void CompilerGLSL::reset_name_caches()
- {
- for (auto &preserved : preserved_aliases)
- set_name(preserved.first, preserved.second);
- preserved_aliases.clear();
- resource_names.clear();
- block_input_names.clear();
- block_output_names.clear();
- block_ubo_names.clear();
- block_ssbo_names.clear();
- block_names.clear();
- function_overloads.clear();
- }
- void CompilerGLSL::fixup_anonymous_struct_names(std::unordered_set<uint32_t> &visited, const SPIRType &type)
- {
- if (visited.count(type.self))
- return;
- visited.insert(type.self);
- for (uint32_t i = 0; i < uint32_t(type.member_types.size()); i++)
- {
- auto &mbr_type = get<SPIRType>(type.member_types[i]);
- if (mbr_type.basetype == SPIRType::Struct)
- {
- // If there are multiple aliases, the output might be somewhat unpredictable,
- // but the only real alternative in that case is to do nothing, which isn't any better.
- // This check should be fine in practice.
- if (get_name(mbr_type.self).empty() && !get_member_name(type.self, i).empty())
- {
- auto anon_name = join("anon_", get_member_name(type.self, i));
- ParsedIR::sanitize_underscores(anon_name);
- set_name(mbr_type.self, anon_name);
- }
- fixup_anonymous_struct_names(visited, mbr_type);
- }
- }
- }
- void CompilerGLSL::fixup_anonymous_struct_names()
- {
- // HLSL codegen can often end up emitting anonymous structs inside blocks, which
- // breaks GL linking since all names must match ...
- // Try to emit sensible code, so attempt to find such structs and emit anon_$member.
- // Breaks exponential explosion with weird type trees.
- std::unordered_set<uint32_t> visited;
- ir.for_each_typed_id<SPIRType>([&](uint32_t, SPIRType &type) {
- if (type.basetype == SPIRType::Struct &&
- (has_decoration(type.self, DecorationBlock) ||
- has_decoration(type.self, DecorationBufferBlock)))
- {
- fixup_anonymous_struct_names(visited, type);
- }
- });
- }
- void CompilerGLSL::fixup_type_alias()
- {
- // Due to how some backends work, the "master" type of type_alias must be a block-like type if it exists.
- ir.for_each_typed_id<SPIRType>([&](uint32_t self, SPIRType &type) {
- if (!type.type_alias)
- return;
- if (has_decoration(type.self, DecorationBlock) || has_decoration(type.self, DecorationBufferBlock))
- {
- // Top-level block types should never alias anything else.
- type.type_alias = 0;
- }
- else if (type_is_block_like(type) && type.self == ID(self))
- {
- // A block-like type is any type which contains Offset decoration, but not top-level blocks,
- // i.e. blocks which are placed inside buffers.
- // Become the master.
- ir.for_each_typed_id<SPIRType>([&](uint32_t other_id, SPIRType &other_type) {
- if (other_id == self)
- return;
- if (other_type.type_alias == type.type_alias)
- other_type.type_alias = self;
- });
- this->get<SPIRType>(type.type_alias).type_alias = self;
- type.type_alias = 0;
- }
- });
- }
- void CompilerGLSL::reorder_type_alias()
- {
- // Reorder declaration of types so that the master of the type alias is always emitted first.
- // We need this in case a type B depends on type A (A must come before in the vector), but A is an alias of a type Abuffer, which
- // means declaration of A doesn't happen (yet), and order would be B, ABuffer and not ABuffer, B. Fix this up here.
- auto loop_lock = ir.create_loop_hard_lock();
- auto &type_ids = ir.ids_for_type[TypeType];
- for (auto alias_itr = begin(type_ids); alias_itr != end(type_ids); ++alias_itr)
- {
- auto &type = get<SPIRType>(*alias_itr);
- if (type.type_alias != TypeID(0) &&
- !has_extended_decoration(type.type_alias, SPIRVCrossDecorationBufferBlockRepacked))
- {
- // We will skip declaring this type, so make sure the type_alias type comes before.
- auto master_itr = find(begin(type_ids), end(type_ids), ID(type.type_alias));
- assert(master_itr != end(type_ids));
- if (alias_itr < master_itr)
- {
- // Must also swap the type order for the constant-type joined array.
- auto &joined_types = ir.ids_for_constant_undef_or_type;
- auto alt_alias_itr = find(begin(joined_types), end(joined_types), *alias_itr);
- auto alt_master_itr = find(begin(joined_types), end(joined_types), *master_itr);
- assert(alt_alias_itr != end(joined_types));
- assert(alt_master_itr != end(joined_types));
- swap(*alias_itr, *master_itr);
- swap(*alt_alias_itr, *alt_master_itr);
- }
- }
- }
- }
- void CompilerGLSL::emit_line_directive(uint32_t file_id, uint32_t line_literal)
- {
- // If we are redirecting statements, ignore the line directive.
- // Common case here is continue blocks.
- if (redirect_statement)
- return;
- // If we're emitting code in a sensitive context such as condition blocks in for loops, don't emit
- // any line directives, because it's not possible.
- if (block_debug_directives)
- return;
- if (options.emit_line_directives)
- {
- require_extension_internal("GL_GOOGLE_cpp_style_line_directive");
- statement_no_indent("#line ", line_literal, " \"", get<SPIRString>(file_id).str, "\"");
- }
- }
- void CompilerGLSL::emit_non_semantic_shader_debug_info(uint32_t, uint32_t result_id, uint32_t eop,
- const uint32_t *args, uint32_t)
- {
- if (!options.emit_line_directives)
- return;
- switch (eop)
- {
- case SPIRExtension::DebugLine:
- {
- // We're missing line end and columns here, but I don't think we can emit those in any meaningful way.
- emit_line_directive(args[0], get<SPIRConstant>(args[1]).scalar());
- break;
- }
- case SPIRExtension::DebugSource:
- {
- // Forward the string declaration here. We ignore the optional text operand.
- auto &str = get<SPIRString>(args[0]).str;
- set<SPIRString>(result_id, str);
- break;
- }
- default:
- break;
- }
- }
- void CompilerGLSL::emit_copy_logical_type(uint32_t lhs_id, uint32_t lhs_type_id, uint32_t rhs_id, uint32_t rhs_type_id,
- SmallVector<uint32_t> chain)
- {
- // Fully unroll all member/array indices one by one.
- auto &lhs_type = get<SPIRType>(lhs_type_id);
- auto &rhs_type = get<SPIRType>(rhs_type_id);
- if (!lhs_type.array.empty())
- {
- // Could use a loop here to support specialization constants, but it gets rather complicated with nested array types,
- // and this is a rather obscure opcode anyways, keep it simple unless we are forced to.
- uint32_t array_size = to_array_size_literal(lhs_type);
- chain.push_back(0);
- for (uint32_t i = 0; i < array_size; i++)
- {
- chain.back() = i;
- emit_copy_logical_type(lhs_id, lhs_type.parent_type, rhs_id, rhs_type.parent_type, chain);
- }
- }
- else if (lhs_type.basetype == SPIRType::Struct)
- {
- chain.push_back(0);
- uint32_t member_count = uint32_t(lhs_type.member_types.size());
- for (uint32_t i = 0; i < member_count; i++)
- {
- chain.back() = i;
- emit_copy_logical_type(lhs_id, lhs_type.member_types[i], rhs_id, rhs_type.member_types[i], chain);
- }
- }
- else
- {
- // Need to handle unpack/packing fixups since this can differ wildly between the logical types,
- // particularly in MSL.
- // To deal with this, we emit access chains and go through emit_store_statement
- // to deal with all the special cases we can encounter.
- AccessChainMeta lhs_meta, rhs_meta;
- auto lhs = access_chain_internal(lhs_id, chain.data(), uint32_t(chain.size()),
- ACCESS_CHAIN_INDEX_IS_LITERAL_BIT, &lhs_meta);
- auto rhs = access_chain_internal(rhs_id, chain.data(), uint32_t(chain.size()),
- ACCESS_CHAIN_INDEX_IS_LITERAL_BIT, &rhs_meta);
- uint32_t id = ir.increase_bound_by(2);
- lhs_id = id;
- rhs_id = id + 1;
- {
- auto &lhs_expr = set<SPIRExpression>(lhs_id, std::move(lhs), lhs_type_id, true);
- lhs_expr.need_transpose = lhs_meta.need_transpose;
- if (lhs_meta.storage_is_packed)
- set_extended_decoration(lhs_id, SPIRVCrossDecorationPhysicalTypePacked);
- if (lhs_meta.storage_physical_type != 0)
- set_extended_decoration(lhs_id, SPIRVCrossDecorationPhysicalTypeID, lhs_meta.storage_physical_type);
- forwarded_temporaries.insert(lhs_id);
- suppressed_usage_tracking.insert(lhs_id);
- }
- {
- auto &rhs_expr = set<SPIRExpression>(rhs_id, std::move(rhs), rhs_type_id, true);
- rhs_expr.need_transpose = rhs_meta.need_transpose;
- if (rhs_meta.storage_is_packed)
- set_extended_decoration(rhs_id, SPIRVCrossDecorationPhysicalTypePacked);
- if (rhs_meta.storage_physical_type != 0)
- set_extended_decoration(rhs_id, SPIRVCrossDecorationPhysicalTypeID, rhs_meta.storage_physical_type);
- forwarded_temporaries.insert(rhs_id);
- suppressed_usage_tracking.insert(rhs_id);
- }
- emit_store_statement(lhs_id, rhs_id);
- }
- }
- bool CompilerGLSL::subpass_input_is_framebuffer_fetch(uint32_t id) const
- {
- if (!has_decoration(id, DecorationInputAttachmentIndex))
- return false;
- uint32_t input_attachment_index = get_decoration(id, DecorationInputAttachmentIndex);
- for (auto &remap : subpass_to_framebuffer_fetch_attachment)
- if (remap.first == input_attachment_index)
- return true;
- return false;
- }
- const SPIRVariable *CompilerGLSL::find_subpass_input_by_attachment_index(uint32_t index) const
- {
- const SPIRVariable *ret = nullptr;
- ir.for_each_typed_id<SPIRVariable>([&](uint32_t, const SPIRVariable &var) {
- if (has_decoration(var.self, DecorationInputAttachmentIndex) &&
- get_decoration(var.self, DecorationInputAttachmentIndex) == index)
- {
- ret = &var;
- }
- });
- return ret;
- }
- const SPIRVariable *CompilerGLSL::find_color_output_by_location(uint32_t location) const
- {
- const SPIRVariable *ret = nullptr;
- ir.for_each_typed_id<SPIRVariable>([&](uint32_t, const SPIRVariable &var) {
- if (var.storage == StorageClassOutput && get_decoration(var.self, DecorationLocation) == location)
- ret = &var;
- });
- return ret;
- }
- void CompilerGLSL::emit_inout_fragment_outputs_copy_to_subpass_inputs()
- {
- for (auto &remap : subpass_to_framebuffer_fetch_attachment)
- {
- auto *subpass_var = find_subpass_input_by_attachment_index(remap.first);
- auto *output_var = find_color_output_by_location(remap.second);
- if (!subpass_var)
- continue;
- if (!output_var)
- SPIRV_CROSS_THROW("Need to declare the corresponding fragment output variable to be able "
- "to read from it.");
- if (is_array(get<SPIRType>(output_var->basetype)))
- SPIRV_CROSS_THROW("Cannot use GL_EXT_shader_framebuffer_fetch with arrays of color outputs.");
- auto &func = get<SPIRFunction>(get_entry_point().self);
- func.fixup_hooks_in.push_back([=]() {
- if (is_legacy())
- {
- statement(to_expression(subpass_var->self), " = ", "gl_LastFragData[",
- get_decoration(output_var->self, DecorationLocation), "];");
- }
- else
- {
- uint32_t num_rt_components = this->get<SPIRType>(output_var->basetype).vecsize;
- statement(to_expression(subpass_var->self), vector_swizzle(num_rt_components, 0), " = ",
- to_expression(output_var->self), ";");
- }
- });
- }
- }
- bool CompilerGLSL::variable_is_depth_or_compare(VariableID id) const
- {
- return is_depth_image(get<SPIRType>(get<SPIRVariable>(id).basetype), id);
- }
- const char *CompilerGLSL::ShaderSubgroupSupportHelper::get_extension_name(Candidate c)
- {
- static const char *const retval[CandidateCount] = { "GL_KHR_shader_subgroup_ballot",
- "GL_KHR_shader_subgroup_basic",
- "GL_KHR_shader_subgroup_vote",
- "GL_KHR_shader_subgroup_arithmetic",
- "GL_NV_gpu_shader_5",
- "GL_NV_shader_thread_group",
- "GL_NV_shader_thread_shuffle",
- "GL_ARB_shader_ballot",
- "GL_ARB_shader_group_vote",
- "GL_AMD_gcn_shader" };
- return retval[c];
- }
- SmallVector<std::string> CompilerGLSL::ShaderSubgroupSupportHelper::get_extra_required_extension_names(Candidate c)
- {
- switch (c)
- {
- case ARB_shader_ballot:
- return { "GL_ARB_shader_int64" };
- case AMD_gcn_shader:
- return { "GL_AMD_gpu_shader_int64", "GL_NV_gpu_shader5" };
- default:
- return {};
- }
- }
- const char *CompilerGLSL::ShaderSubgroupSupportHelper::get_extra_required_extension_predicate(Candidate c)
- {
- switch (c)
- {
- case ARB_shader_ballot:
- return "defined(GL_ARB_shader_int64)";
- case AMD_gcn_shader:
- return "(defined(GL_AMD_gpu_shader_int64) || defined(GL_NV_gpu_shader5))";
- default:
- return "";
- }
- }
- CompilerGLSL::ShaderSubgroupSupportHelper::FeatureVector CompilerGLSL::ShaderSubgroupSupportHelper::
- get_feature_dependencies(Feature feature)
- {
- switch (feature)
- {
- case SubgroupAllEqualT:
- return { SubgroupBroadcast_First, SubgroupAll_Any_AllEqualBool };
- case SubgroupElect:
- return { SubgroupBallotFindLSB_MSB, SubgroupBallot, SubgroupInvocationID };
- case SubgroupInverseBallot_InclBitCount_ExclBitCout:
- return { SubgroupMask };
- case SubgroupBallotBitCount:
- return { SubgroupBallot };
- case SubgroupArithmeticIAddReduce:
- case SubgroupArithmeticIAddInclusiveScan:
- case SubgroupArithmeticFAddReduce:
- case SubgroupArithmeticFAddInclusiveScan:
- case SubgroupArithmeticIMulReduce:
- case SubgroupArithmeticIMulInclusiveScan:
- case SubgroupArithmeticFMulReduce:
- case SubgroupArithmeticFMulInclusiveScan:
- return { SubgroupSize, SubgroupBallot, SubgroupBallotBitCount, SubgroupMask, SubgroupBallotBitExtract };
- case SubgroupArithmeticIAddExclusiveScan:
- case SubgroupArithmeticFAddExclusiveScan:
- case SubgroupArithmeticIMulExclusiveScan:
- case SubgroupArithmeticFMulExclusiveScan:
- return { SubgroupSize, SubgroupBallot, SubgroupBallotBitCount,
- SubgroupMask, SubgroupElect, SubgroupBallotBitExtract };
- default:
- return {};
- }
- }
- CompilerGLSL::ShaderSubgroupSupportHelper::FeatureMask CompilerGLSL::ShaderSubgroupSupportHelper::
- get_feature_dependency_mask(Feature feature)
- {
- return build_mask(get_feature_dependencies(feature));
- }
- bool CompilerGLSL::ShaderSubgroupSupportHelper::can_feature_be_implemented_without_extensions(Feature feature)
- {
- static const bool retval[FeatureCount] = {
- false, false, false, false, false, false,
- true, // SubgroupBalloFindLSB_MSB
- false, false, false, false,
- true, // SubgroupMemBarrier - replaced with workgroup memory barriers
- false, false, true, false,
- false, false, false, false, false, false, // iadd, fadd
- false, false, false, false, false, false, // imul , fmul
- };
- return retval[feature];
- }
- CompilerGLSL::ShaderSubgroupSupportHelper::Candidate CompilerGLSL::ShaderSubgroupSupportHelper::
- get_KHR_extension_for_feature(Feature feature)
- {
- static const Candidate extensions[FeatureCount] = {
- KHR_shader_subgroup_ballot, KHR_shader_subgroup_basic, KHR_shader_subgroup_basic, KHR_shader_subgroup_basic,
- KHR_shader_subgroup_basic, KHR_shader_subgroup_ballot, KHR_shader_subgroup_ballot, KHR_shader_subgroup_vote,
- KHR_shader_subgroup_vote, KHR_shader_subgroup_basic, KHR_shader_subgroup_basic, KHR_shader_subgroup_basic,
- KHR_shader_subgroup_ballot, KHR_shader_subgroup_ballot, KHR_shader_subgroup_ballot, KHR_shader_subgroup_ballot,
- KHR_shader_subgroup_arithmetic, KHR_shader_subgroup_arithmetic, KHR_shader_subgroup_arithmetic,
- KHR_shader_subgroup_arithmetic, KHR_shader_subgroup_arithmetic, KHR_shader_subgroup_arithmetic,
- KHR_shader_subgroup_arithmetic, KHR_shader_subgroup_arithmetic, KHR_shader_subgroup_arithmetic,
- KHR_shader_subgroup_arithmetic, KHR_shader_subgroup_arithmetic, KHR_shader_subgroup_arithmetic,
- };
- return extensions[feature];
- }
- void CompilerGLSL::ShaderSubgroupSupportHelper::request_feature(Feature feature)
- {
- feature_mask |= (FeatureMask(1) << feature) | get_feature_dependency_mask(feature);
- }
- bool CompilerGLSL::ShaderSubgroupSupportHelper::is_feature_requested(Feature feature) const
- {
- return (feature_mask & (1u << feature)) != 0;
- }
- CompilerGLSL::ShaderSubgroupSupportHelper::Result CompilerGLSL::ShaderSubgroupSupportHelper::resolve() const
- {
- Result res;
- for (uint32_t i = 0u; i < FeatureCount; ++i)
- {
- if (feature_mask & (1u << i))
- {
- auto feature = static_cast<Feature>(i);
- std::unordered_set<uint32_t> unique_candidates;
- auto candidates = get_candidates_for_feature(feature);
- unique_candidates.insert(candidates.begin(), candidates.end());
- auto deps = get_feature_dependencies(feature);
- for (Feature d : deps)
- {
- candidates = get_candidates_for_feature(d);
- if (!candidates.empty())
- unique_candidates.insert(candidates.begin(), candidates.end());
- }
- for (uint32_t c : unique_candidates)
- ++res.weights[static_cast<Candidate>(c)];
- }
- }
- return res;
- }
- CompilerGLSL::ShaderSubgroupSupportHelper::CandidateVector CompilerGLSL::ShaderSubgroupSupportHelper::
- get_candidates_for_feature(Feature ft, const Result &r)
- {
- auto c = get_candidates_for_feature(ft);
- auto cmp = [&r](Candidate a, Candidate b) {
- if (r.weights[a] == r.weights[b])
- return a < b; // Prefer candidates with lower enum value
- return r.weights[a] > r.weights[b];
- };
- std::sort(c.begin(), c.end(), cmp);
- return c;
- }
- CompilerGLSL::ShaderSubgroupSupportHelper::CandidateVector CompilerGLSL::ShaderSubgroupSupportHelper::
- get_candidates_for_feature(Feature feature)
- {
- switch (feature)
- {
- case SubgroupMask:
- return { KHR_shader_subgroup_ballot, NV_shader_thread_group, ARB_shader_ballot };
- case SubgroupSize:
- return { KHR_shader_subgroup_basic, NV_shader_thread_group, AMD_gcn_shader, ARB_shader_ballot };
- case SubgroupInvocationID:
- return { KHR_shader_subgroup_basic, NV_shader_thread_group, ARB_shader_ballot };
- case SubgroupID:
- return { KHR_shader_subgroup_basic, NV_shader_thread_group };
- case NumSubgroups:
- return { KHR_shader_subgroup_basic, NV_shader_thread_group };
- case SubgroupBroadcast_First:
- return { KHR_shader_subgroup_ballot, NV_shader_thread_shuffle, ARB_shader_ballot };
- case SubgroupBallotFindLSB_MSB:
- return { KHR_shader_subgroup_ballot, NV_shader_thread_group };
- case SubgroupAll_Any_AllEqualBool:
- return { KHR_shader_subgroup_vote, NV_gpu_shader_5, ARB_shader_group_vote, AMD_gcn_shader };
- case SubgroupAllEqualT:
- return {}; // depends on other features only
- case SubgroupElect:
- return {}; // depends on other features only
- case SubgroupBallot:
- return { KHR_shader_subgroup_ballot, NV_shader_thread_group, ARB_shader_ballot };
- case SubgroupBarrier:
- return { KHR_shader_subgroup_basic, NV_shader_thread_group, ARB_shader_ballot, AMD_gcn_shader };
- case SubgroupMemBarrier:
- return { KHR_shader_subgroup_basic };
- case SubgroupInverseBallot_InclBitCount_ExclBitCout:
- return {};
- case SubgroupBallotBitExtract:
- return { NV_shader_thread_group };
- case SubgroupBallotBitCount:
- return {};
- case SubgroupArithmeticIAddReduce:
- case SubgroupArithmeticIAddExclusiveScan:
- case SubgroupArithmeticIAddInclusiveScan:
- case SubgroupArithmeticFAddReduce:
- case SubgroupArithmeticFAddExclusiveScan:
- case SubgroupArithmeticFAddInclusiveScan:
- case SubgroupArithmeticIMulReduce:
- case SubgroupArithmeticIMulExclusiveScan:
- case SubgroupArithmeticIMulInclusiveScan:
- case SubgroupArithmeticFMulReduce:
- case SubgroupArithmeticFMulExclusiveScan:
- case SubgroupArithmeticFMulInclusiveScan:
- return { KHR_shader_subgroup_arithmetic, NV_shader_thread_shuffle };
- default:
- return {};
- }
- }
- CompilerGLSL::ShaderSubgroupSupportHelper::FeatureMask CompilerGLSL::ShaderSubgroupSupportHelper::build_mask(
- const SmallVector<Feature> &features)
- {
- FeatureMask mask = 0;
- for (Feature f : features)
- mask |= FeatureMask(1) << f;
- return mask;
- }
- CompilerGLSL::ShaderSubgroupSupportHelper::Result::Result()
- {
- for (auto &weight : weights)
- weight = 0;
- // Make sure KHR_shader_subgroup extensions are always prefered.
- const uint32_t big_num = FeatureCount;
- weights[KHR_shader_subgroup_ballot] = big_num;
- weights[KHR_shader_subgroup_basic] = big_num;
- weights[KHR_shader_subgroup_vote] = big_num;
- weights[KHR_shader_subgroup_arithmetic] = big_num;
- }
- void CompilerGLSL::request_workaround_wrapper_overload(TypeID id)
- {
- // Must be ordered to maintain deterministic output, so vector is appropriate.
- if (find(begin(workaround_ubo_load_overload_types), end(workaround_ubo_load_overload_types), id) ==
- end(workaround_ubo_load_overload_types))
- {
- force_recompile();
- workaround_ubo_load_overload_types.push_back(id);
- }
- }
- void CompilerGLSL::rewrite_load_for_wrapped_row_major(std::string &expr, TypeID loaded_type, ID ptr)
- {
- // Loading row-major matrices from UBOs on older AMD Windows OpenGL drivers is problematic.
- // To load these types correctly, we must first wrap them in a dummy function which only purpose is to
- // ensure row_major decoration is actually respected.
- auto *var = maybe_get_backing_variable(ptr);
- if (!var)
- return;
- auto &backing_type = get<SPIRType>(var->basetype);
- bool is_ubo = backing_type.basetype == SPIRType::Struct && backing_type.storage == StorageClassUniform &&
- has_decoration(backing_type.self, DecorationBlock);
- if (!is_ubo)
- return;
- auto *type = &get<SPIRType>(loaded_type);
- bool rewrite = false;
- bool relaxed = options.es;
- if (is_matrix(*type))
- {
- // To avoid adding a lot of unnecessary meta tracking to forward the row_major state,
- // we will simply look at the base struct itself. It is exceptionally rare to mix and match row-major/col-major state.
- // If there is any row-major action going on, we apply the workaround.
- // It is harmless to apply the workaround to column-major matrices, so this is still a valid solution.
- // If an access chain occurred, the workaround is not required, so loading vectors or scalars don't need workaround.
- type = &backing_type;
- }
- else
- {
- // If we're loading a composite, we don't have overloads like these.
- relaxed = false;
- }
- if (type->basetype == SPIRType::Struct)
- {
- // If we're loading a struct where any member is a row-major matrix, apply the workaround.
- for (uint32_t i = 0; i < uint32_t(type->member_types.size()); i++)
- {
- auto decorations = combined_decoration_for_member(*type, i);
- if (decorations.get(DecorationRowMajor))
- rewrite = true;
- // Since we decide on a per-struct basis, only use mediump wrapper if all candidates are mediump.
- if (!decorations.get(DecorationRelaxedPrecision))
- relaxed = false;
- }
- }
- if (rewrite)
- {
- request_workaround_wrapper_overload(loaded_type);
- expr = join("spvWorkaroundRowMajor", (relaxed ? "MP" : ""), "(", expr, ")");
- }
- }
- void CompilerGLSL::mask_stage_output_by_location(uint32_t location, uint32_t component)
- {
- masked_output_locations.insert({ location, component });
- }
- void CompilerGLSL::mask_stage_output_by_builtin(BuiltIn builtin)
- {
- masked_output_builtins.insert(builtin);
- }
- bool CompilerGLSL::is_stage_output_variable_masked(const SPIRVariable &var) const
- {
- auto &type = get<SPIRType>(var.basetype);
- bool is_block = has_decoration(type.self, DecorationBlock);
- // Blocks by themselves are never masked. Must be masked per-member.
- if (is_block)
- return false;
- bool is_builtin = has_decoration(var.self, DecorationBuiltIn);
- if (is_builtin)
- {
- return is_stage_output_builtin_masked(BuiltIn(get_decoration(var.self, DecorationBuiltIn)));
- }
- else
- {
- if (!has_decoration(var.self, DecorationLocation))
- return false;
- return is_stage_output_location_masked(
- get_decoration(var.self, DecorationLocation),
- get_decoration(var.self, DecorationComponent));
- }
- }
- bool CompilerGLSL::is_stage_output_block_member_masked(const SPIRVariable &var, uint32_t index, bool strip_array) const
- {
- auto &type = get<SPIRType>(var.basetype);
- bool is_block = has_decoration(type.self, DecorationBlock);
- if (!is_block)
- return false;
- BuiltIn builtin = BuiltInMax;
- if (is_member_builtin(type, index, &builtin))
- {
- return is_stage_output_builtin_masked(builtin);
- }
- else
- {
- uint32_t location = get_declared_member_location(var, index, strip_array);
- uint32_t component = get_member_decoration(type.self, index, DecorationComponent);
- return is_stage_output_location_masked(location, component);
- }
- }
- bool CompilerGLSL::is_per_primitive_variable(const SPIRVariable &var) const
- {
- if (has_decoration(var.self, DecorationPerPrimitiveEXT))
- return true;
- auto &type = get<SPIRType>(var.basetype);
- if (!has_decoration(type.self, DecorationBlock))
- return false;
- for (uint32_t i = 0, n = uint32_t(type.member_types.size()); i < n; i++)
- if (!has_member_decoration(type.self, i, DecorationPerPrimitiveEXT))
- return false;
- return true;
- }
- bool CompilerGLSL::is_stage_output_location_masked(uint32_t location, uint32_t component) const
- {
- return masked_output_locations.count({ location, component }) != 0;
- }
- bool CompilerGLSL::is_stage_output_builtin_masked(BuiltIn builtin) const
- {
- return masked_output_builtins.count(builtin) != 0;
- }
- uint32_t CompilerGLSL::get_declared_member_location(const SPIRVariable &var, uint32_t mbr_idx, bool strip_array) const
- {
- auto &block_type = get<SPIRType>(var.basetype);
- if (has_member_decoration(block_type.self, mbr_idx, DecorationLocation))
- return get_member_decoration(block_type.self, mbr_idx, DecorationLocation);
- else
- return get_accumulated_member_location(var, mbr_idx, strip_array);
- }
- uint32_t CompilerGLSL::get_accumulated_member_location(const SPIRVariable &var, uint32_t mbr_idx, bool strip_array) const
- {
- auto &type = strip_array ? get_variable_element_type(var) : get_variable_data_type(var);
- uint32_t location = get_decoration(var.self, DecorationLocation);
- for (uint32_t i = 0; i < mbr_idx; i++)
- {
- auto &mbr_type = get<SPIRType>(type.member_types[i]);
- // Start counting from any place we have a new location decoration.
- if (has_member_decoration(type.self, mbr_idx, DecorationLocation))
- location = get_member_decoration(type.self, mbr_idx, DecorationLocation);
- uint32_t location_count = type_to_location_count(mbr_type);
- location += location_count;
- }
- return location;
- }
- StorageClass CompilerGLSL::get_expression_effective_storage_class(uint32_t ptr)
- {
- auto *var = maybe_get_backing_variable(ptr);
- // If the expression has been lowered to a temporary, we need to use the Generic storage class.
- // We're looking for the effective storage class of a given expression.
- // An access chain or forwarded OpLoads from such access chains
- // will generally have the storage class of the underlying variable, but if the load was not forwarded
- // we have lost any address space qualifiers.
- bool forced_temporary = ir.ids[ptr].get_type() == TypeExpression && !get<SPIRExpression>(ptr).access_chain &&
- (forced_temporaries.count(ptr) != 0 || forwarded_temporaries.count(ptr) == 0);
- if (var && !forced_temporary)
- {
- if (variable_decl_is_remapped_storage(*var, StorageClassWorkgroup))
- return StorageClassWorkgroup;
- if (variable_decl_is_remapped_storage(*var, StorageClassStorageBuffer))
- return StorageClassStorageBuffer;
- // Normalize SSBOs to StorageBuffer here.
- if (var->storage == StorageClassUniform &&
- has_decoration(get<SPIRType>(var->basetype).self, DecorationBufferBlock))
- return StorageClassStorageBuffer;
- else
- return var->storage;
- }
- else
- return expression_type(ptr).storage;
- }
- uint32_t CompilerGLSL::type_to_location_count(const SPIRType &type) const
- {
- uint32_t count;
- if (type.basetype == SPIRType::Struct)
- {
- uint32_t mbr_count = uint32_t(type.member_types.size());
- count = 0;
- for (uint32_t i = 0; i < mbr_count; i++)
- count += type_to_location_count(get<SPIRType>(type.member_types[i]));
- }
- else
- {
- count = type.columns > 1 ? type.columns : 1;
- }
- uint32_t dim_count = uint32_t(type.array.size());
- for (uint32_t i = 0; i < dim_count; i++)
- count *= to_array_size_literal(type, i);
- return count;
- }
- std::string CompilerGLSL::format_float(float value) const
- {
- if (float_formatter)
- return float_formatter->format_float(value);
- // default behavior
- return convert_to_string(value, current_locale_radix_character);
- }
- std::string CompilerGLSL::format_double(double value) const
- {
- if (float_formatter)
- return float_formatter->format_double(value);
- // default behavior
- return convert_to_string(value, current_locale_radix_character);
- }
- std::string CompilerGLSL::to_pretty_expression_if_int_constant(
- uint32_t id,
- const GlslConstantNameMapping *mapping_start, const GlslConstantNameMapping *mapping_end,
- bool register_expression_read)
- {
- auto *c = maybe_get<SPIRConstant>(id);
- if (c && !c->specialization)
- {
- auto value = c->scalar();
- auto pretty_name = std::find_if(mapping_start, mapping_end,
- [value](const GlslConstantNameMapping &mapping) { return mapping.value == value; });
- if (pretty_name != mapping_end)
- return pretty_name->alias;
- }
- return join("int(", to_expression(id, register_expression_read), ")");
- }
- uint32_t CompilerGLSL::get_fp_fast_math_flags_for_op(uint32_t result_type, uint32_t id) const
- {
- uint32_t fp_flags = ~0;
- if (!type_is_floating_point(get<SPIRType>(result_type)))
- return fp_flags;
- auto &ep = get_entry_point();
- // Per-operation flag supersedes all defaults.
- if (id != 0 && has_decoration(id, DecorationFPFastMathMode))
- return get_decoration(id, DecorationFPFastMathMode);
- // Handle float_controls1 execution modes.
- uint32_t width = get<SPIRType>(result_type).width;
- bool szinp = false;
- switch (width)
- {
- case 8:
- szinp = ep.signed_zero_inf_nan_preserve_8;
- break;
- case 16:
- szinp = ep.signed_zero_inf_nan_preserve_16;
- break;
- case 32:
- szinp = ep.signed_zero_inf_nan_preserve_32;
- break;
- case 64:
- szinp = ep.signed_zero_inf_nan_preserve_64;
- break;
- default:
- break;
- }
- if (szinp)
- fp_flags &= ~(FPFastMathModeNSZMask | FPFastMathModeNotInfMask | FPFastMathModeNotNaNMask);
- // Legacy NoContraction deals with any kind of transform to the expression.
- if (id != 0 && has_decoration(id, DecorationNoContraction))
- fp_flags &= ~(FPFastMathModeAllowContractMask | FPFastMathModeAllowTransformMask | FPFastMathModeAllowReassocMask);
- // Handle float_controls2 execution modes.
- bool found_default = false;
- for (auto &fp_pair : ep.fp_fast_math_defaults)
- {
- if (get<SPIRType>(fp_pair.first).width == width && fp_pair.second)
- {
- fp_flags &= get<SPIRConstant>(fp_pair.second).scalar();
- found_default = true;
- }
- }
- // From SPV_KHR_float_controls2:
- // "This definition implies that, if the entry point set any FPFastMathDefault execution mode
- // then any type for which a default is not set uses no fast math flags
- // (although this can still be overridden on a per-operation basis).
- // Modules must not mix setting fast math modes explicitly using this extension and relying on older API defaults."
- if (!found_default && !ep.fp_fast_math_defaults.empty())
- fp_flags = 0;
- return fp_flags;
- }
- bool CompilerGLSL::has_legacy_nocontract(uint32_t result_type, uint32_t id) const
- {
- const auto fp_flags = FPFastMathModeAllowContractMask |
- FPFastMathModeAllowTransformMask |
- FPFastMathModeAllowReassocMask;
- return (get_fp_fast_math_flags_for_op(result_type, id) & fp_flags) != fp_flags;
- }
|