Lulucat

ચૉક ટૂલની કામગીરી: સંપૂર્ણ-સ્ક્રીન પાસથી scissor લંબચોરસ સુધી

Gaoge ZhangGaoge Zhang

Lulucat Notesનું ચૉક ટૂલ ગાઢ હસ્તલેખનવાળા વિસ્તારોમાં ધીમું પડતું હતું. મુખ્ય અડચણ 3,571 ઇનપુટ નમૂનાઓ નહોતી — તે દર ફ્રેમે થતી 70 સંપૂર્ણ-સ્ક્રીન scratch પાસ હતી. નકારવામાં આવેલી નીચા રિઝોલ્યુશનની કૅશ અને દરેક સ્ટ્રોક માટેનું scissor લંબચોરસ બાકીની વાત સમજાવે છે.

255% ઝૂમ પર iPadમાં Lulucat Notesનો કાપેલો દેખાવ: લાલ અને વાદળી ચૉકથી લખાયેલો શાસ્ત્રીય ચીની પાઠ અને ઍપના ટૂલબારનો થોડો ભાગ દેખાય છે.

ઉપકરણ માટેની અંતિમ બિલ્ડમાં લાલ અને વાદળી ચૉક, 255% ઝૂમ પર 155 સ્ટ્રોક્સ.

Lulucat Notesના ચૉક ટૂલમાં કામગીરીની એક ખાસ સમસ્યા હતી: ખાલી વિસ્તારમાં લખવું સરળ લાગતું, પરંતુ પહેલેથી ચૉક સ્ટ્રોક્સ ભરેલા વિસ્તારમાં જતાં પેનની અણી પાછળ રહી જતી. એ જ વિસ્તારમાં લખવાનું ચાલુ રાખવાથી કૅનવાસ ખસેડવાનું પણ ધીમે ધીમે મંદ થતું.

સામાન્ય હસ્તલેખનનું એક જ પાનું સમસ્યા શરૂ કરવા માટે પૂરતું હતું: 300% ઝૂમ, સ્થાનિક વિસ્તારમાં દેખાતા 70 ચૉક સ્ટ્રોક્સ અને કુલ 3,571 ઇનપુટ નમૂના-બિંદુઓ. ખાલી વિસ્તારો સરળ રહ્યા; માત્ર જ્યાં એ સ્ટ્રોક્સ ભેગા થયા હતા તે વિસ્તાર ધીમો પડ્યો.

સુધારા પછી, એ જ પાને 255% ઝૂમ પર નવું લખાણ ચાલુ રાખી શકાય છે, અને પેન સ્ક્રીન પર મૂક્યા પછી તેમજ કૅનવાસ ખસેડતી વખતે જૂના સ્ટ્રોક્સ તેમની સંપૂર્ણ સ્પષ્ટતા જાળવે છે.

255% ઝૂમ પર iPadમાં Lulucat Notes, લાલ અને વાદળી ચૉકનું હસ્તલેખન દર્શાવે છે. લખાણ "天行健,君子以自强不息;地势坤,君子以厚德载物" છે — શાસ્ત્રીય ચીની પાઠ. ઉપર જમણી બાજુ વાદળી Lulucat માસ્કોટ છે. નીચેના ટૂલબારમાં 155 સ્ટ્રોક્સની સંખ્યા, Save, Clear અને 255% ઝૂમ સ્લાઇડર દેખાય છે.

અંતિમ ઉપકરણ સ્ક્રીનશૉટ, કુલ 155 સ્ટ્રોક્સ. આ ઝૂમ સ્તરે પેન સ્ક્રીન પર મૂક્યા પછી કે કૅનવાસ ખસેડતી વખતે જૂના સ્ટ્રોક્સની સ્પષ્ટતા થોડા સમય માટે પણ બદલાતી નથી.

ચૉકને scratch ટેક્સ્ચર શા માટે જોઈએ

સામાન્ય પેન દરેક ગોળ stampને source-over blending વડે ink ટેક્સ્ચર પર સીધું composite કરી શકે છે. ચૉક grain-gatingનું એક સ્તર ઉમેરે છે: રેન્ડરર પહેલાં આખા સ્ટ્રોકનું body coverage અને ઊંડાઈ એકત્ર કરે છે, પછી સ્થિર grain ટેક્સ્ચરથી નક્કી કરે છે કે કયા સ્થાનો પર ચૉકની ધૂળ મળશે, અને અંતે પરિણામને પહેલેથી રહેલી શાહી પર composite કરે છે.

આ scratch ટેક્સ્ચર એક ચૉક સ્ટ્રોકને અલગ રાખે છે. આ અલગાવ મહત્વનું છે, કારણ કે એક જ સ્ટ્રોકની અંદરના stamps ભારે overlap થાય છે; જો દરેક stampને અલગથી grain-gated કરવામાં આવે, તો સ્ટ્રોકની મધ્યરેખા પર રંગ વારંવાર એકઠો થાય અને ચૉકના છિદ્રો નમૂના લેવાની ઘનતા સાથે ખસે.

વધુ ઝૂમ પર Lulucat Notes હાલના viewportમાં દેખાતા vector સ્ટ્રોક્સને ફરી દોરે છે. જૂનું અમલીકરણ દરેક દેખાતા ચૉક સ્ટ્રોક માટે આ પગલાં કરતું હતું:

  1. મુખ્ય render encoder સમાપ્ત કરવો;
  2. scratch ટેક્સ્ચર સાફ કરવું;
  3. આ એક ચૉક સ્ટ્રોકને scratchમાં દોરવો;
  4. મુખ્ય render encoder ફરી ખોલવો;
  5. સંપૂર્ણ-સ્ક્રીન ત્રિકોણ વડે scratchને drawable પર પાછું composite કરવું.

iPadનું drawable 2732×2048 હતું — અંદાજે 5.6 મિલિયન પિક્સેલ. દરેક ચૉક સ્ટ્રોક એક scratch પાસ અને એક સંપૂર્ણ-સ્ક્રીન composite શરૂ કરતો. 70 ચૉક સ્ટ્રોક્સ એટલે આશરે 141 render encoders અને 70 સંપૂર્ણ-સ્ક્રીન composites.

દેખાતા ચૉક સ્ટ્રોક્સની સંખ્યા અને drawable પિક્સેલ્સની સંખ્યા ગણીએ. માત્ર પિક્સેલ આવરણ સાથે વધતા કામને ધ્યાનમાં લઈએ તો જૂનું અમલીકરણ લગભગ

ની નજીક હતી. દરેક ચૉક સ્ટ્રોક સાથે સ્થિર render-pass overhead પણ આવતો, એટલે એ ખર્ચ પણ સાથે રેખીય રીતે વધતો. 3,571 ઇનપુટ બિંદુઓનો ફાળો માત્ર ગૌણ ખર્ચ હતો. સ્થાનિક સ્ટ્રોક્સની સંખ્યા સાથે જે વધતું હતું તે દરેક સ્ટ્રોકથી શરૂ થતું સંપૂર્ણ-સ્ક્રીન કામ હતું.

માપન 12.9-inch iPad Pro (5th generation, M1) પર iPadOS 18.6.2 સાથે કરવામાં આવ્યું. અમે ફેરફાર પહેલાં અને પછી એ જ viewportના GPU સમયચિહ્નોની તુલના કરી; આ ખાસ iPad પરના એ જ Debug device buildમાં command-buffer સમયચિહ્નો વાપર્યા — નીચે તેને LucasPad કહેવાયું છે. નીચેની શ્રેણીઓ અનેક ફ્રેમના લૉગમાં દેખાતી સામાન્ય વધઘટ છે, કોઈ પ્રકાશિત આવૃત્તિ માટેની frame-rate ખાતરી નથી. 70 દેખાતા ચૉક સ્ટ્રોક્સ પર એક ફ્રેમ માટે સામાન્ય રીતે 52–60 ms GPU સમય લાગતો; આશરે 120 સ્ટ્રોક્સવાળા વિસ્તારમાં GPU સમય વધીને 77–80 ms થયો.

scratch અને composite પાસના સંપૂર્ણ-સ્ક્રીન લંબચોરસોના ક્ષેત્રફળ પરથી અંદાજ કરતાં, દર ફ્રેમનું સૈદ્ધાંતિક કાર્યક્ષેત્ર આશરે 783 મિલિયન પિક્સેલથી વધીને 1.34 બિલિયન પિક્સેલ થયું. આ આંકડો લંબચોરસોના ક્ષેત્રફળનો સરવાળો છે; તે fragment invocation counts, video-memory read/write bytes કે GPU hardware counters સમાન નથી. Metalનું fast clear, attachment load/store અને pass switching GPU તથા driverના નિયંત્રણ હેઠળ જ રહે છે.

આથી એ પણ સમજાય છે કે ખાલી વિસ્તારો સરળ કેમ રહ્યા. દૃશ્યતા આધારિત છટણી viewportની બહારના સ્ટ્રોક્સને છોડી દે છે; ખાલી વિસ્તારમાં લગભગ શૂન્ય હોય છે, જ્યારે ગીચ વિસ્તારમાં સતત વધે છે.

0.85 ms પર ખોટો જવાબ

ઍપમાં પહેલેથી જ આખા પાનાની ink ટેક્સ્ચર હતી, જે પ્રતિ બિંદુ બે પિક્સેલના રિઝોલ્યુશન પર baked હતી. લખતી વખતે, કૅનવાસ ખસેડતી વખતે અને ઝૂમ કરતી વખતે આ ટેક્સ્ચર સીધી બતાવવાનો અને માત્ર હાલના Apple Pencil સ્ટ્રોકને live vector રાખવાનો પ્રયાસ કર્યો; interaction પૂરી થયા પછી એક વધારાની ફ્રેમ ઉચ્ચ-રિઝોલ્યુશન vector પરિણામ ફરી render કરતી.

આ રીતની કામગીરી ખૂબ સારી હતી. એ જ ગીચ વિસ્તારમાં 300% ઝૂમ પર GPU સમય ઘટીને 0.84–0.85 ms થયો અને જૂના ચૉક સ્ટ્રોક્સની સંખ્યા સાથે વધ્યો નહીં.

વાસ્તવિક ઉપકરણ પરની સમસ્યા એટલી જ સ્પષ્ટ હતી. 300% ઝૂમ પર આશરે છ screen pixels per point જરૂરી હતા, પરંતુ કૅશ માત્ર બે આપતી હતી. Apple Pencil સ્ક્રીનને અડતાની સાથે જ બધા જૂના સ્ટ્રોક્સ નરમ, નીચા રિઝોલ્યુશનની છબી બની જતા; Pencil ઊંચકતાં જ તેઓ સંપૂર્ણ સ્પષ્ટતા પર પાછા આવી જતા.

પરીક્ષણ કરનારે એક વાત કહી: “હું લખું ત્યારે આખું કૅનવાસ ઝાંખું થઈ જાય છે. Pencil છોડું એટલે ફરી સ્પષ્ટ થઈ જાય છે.”

આ ઑપ્ટિમાઇઝેશન કાઢી નાખવામાં આવ્યું. 0.85 ms સૌથી ઓછું માપેલું પરિણામ હતું, પરંતુ તે સ્વીકાર્ય ચૉક ટૂલ નહોતું. જૂના સ્ટ્રોક્સ લખાણના પ્રતિસાદનો ભાગ છે; પેન સ્ક્રીન પર મૂકતાં તેમની સ્પષ્ટતા બદલાઈ શકતી નથી.

દરેક ચૉક સ્ટ્રોકને તેના પોતાના લંબચોરસ સુધી મર્યાદિત કરવો

અંતિમ સુધારાએ દરેક સ્ટ્રોક માટેની scratch અને દરેક સ્ટ્રોક માટેનું compositing જાળવી રાખ્યું અને માત્ર પિક્સેલ કામનું ક્ષેત્ર ઘટાડ્યું. દરેક સ્ટ્રોક પાસે પહેલેથી જ કૅનવાસનો bounding box હતો, જે તેના બધા stamp radiiના unionમાંથી મળ્યો હતો. રેન્ડરર આ bounding boxને હાલના viewportના drawable coordinatesમાં ફેરવે છે અને antialiasing margin માટે તેમાં બે પિક્સેલ ઉમેરે છે:

એ જ scissor લંબચોરસ પછી ત્રણ કામ માટે વપરાય છે: scratch સાફ કરવી, સ્ટ્રોક દોરવો અને પરિણામને મુખ્ય સપાટી પર પાછું composite કરવું.

let rect = displayScissorRect(for: stroke.bounds, viewport: viewport)

scratchEncoder.setScissorRect(rect)
clearScratchExplicitly()
drawStrokeIntoScratch(stroke)

mainEncoder.setScissorRect(rect)
compositeChalkFromScratch(stroke)
mainEncoder.setScissorRect(fullDrawable)

આ જ તર્ક 4096² ink ટેક્સ્ચર તૈયાર કરવા અને આંશિક ફરી ચલાવવા માટે પણ વપરાય છે, તેથી ઉચ્ચ-ઝૂમનું દૃશ્ય અને સ્થિર થયેલી ink layer ચૉકના બે અલગ વર્તન ઉત્પન્ન કરતા નથી.

અહીં બે વિગતો સહેલાઈથી ચૂકી શકાય છે.

પહેલું, render passનું loadAction = .clear attachment load stage દરમિયાન થાય છે અને rasterization scissorથી મર્યાદિત નથી. તેનો ઉપયોગ ચાલુ રાખવાથી આખી scratch ટેક્સ્ચર હજી પણ સાફ થાત. સુધારેલો pass .dontCare વાપરે છે, પછી scissorની અંદર clear_fragment દોરે છે. આ લંબચોરસ સંપૂર્ણપણે લખાય છે અને composite માત્ર એ જ લંબચોરસ વાંચે છે, તેથી જૂનું attachment content load કરવાની જરૂર રહેતી નથી.

બીજું, દરેક ચૉક સ્ટ્રોકનું composite પૂરું થયા પછી બહારનું scissor પાછું ગોઠવવું જરૂરી છે. જો state restorationની આ લાઇન છોડી દેવામાં આવે, તો પછીના પેન, ચિત્રો અથવા selections અગાઉના ચૉક સ્ટ્રોકની સીમાઓથી clip થતા રહેશે અને ગુમ થયેલા સ્ટ્રોક્સ કે ચિત્રો જેવા દેખાશે.

ચૉકનું grain હજુ પણ લંબચોરસની અંદરના local UVsમાંથી નહીં, પરંતુ absolute canvas coordinatesમાંથી sample થાય છે. Scissor ખસેડવાથી GPU કયા પિક્સેલ્સ process કરે છે તે જ બદલાય છે; દરેક પિક્સેલ grain ટેક્સ્ચરના કયા સ્થાનેથી વાંચે છે તે બદલાતું નથી. તેથી બાજુબાજુના લંબચોરસો ટેક્સ્ચર seams બનાવતા નથી અને કૅનવાસ ખેંચવાથી grain drift થતો નથી.

માત્ર પિક્સેલ workload ધ્યાનમાં લઈએ તો નવું કાર્યક્ષેત્ર લગભગ

ની નજીક છે, જ્યાં હાલની સ્ક્રીન પરના મા ચૉક સ્ટ્રોકના axis-aligned bounding-boxનું ક્ષેત્રફળ છે. Render encodersની સંખ્યા ઘટી નથી, પરંતુ હવે દરેક clear અને composite સ્ટ્રોકના screen bounding box સુધી મર્યાદિત છે.

સમાન રંગના ચૉક સ્ટ્રોક્સને એકસાથે કેમ ન દોર્યા

પાનાના મોટા ભાગના ચૉક સ્ટ્રોક્સનો રંગ અને ઘનતા એકસરખી છે, તેથી ડઝનો સ્ટ્રોક્સ scratchમાં એકસાથે દોરીને માત્ર એક વાર composite કરવાનું મન થાય. તે render passesની સંખ્યા વધુ ઘટાડે, પરંતુ overlapping વિસ્તારોમાં રંગ અને grainની અર્થવ્યવસ્થા બદલી નાખે.

ચાલો જાણબૂઝીને સરળ બનાવેલી પરિસ્થિતિ લઈએ: કોઈ પિક્સેલ પર બે સ્ટ્રોક્સ બરાબર એક જ grain-gate value ધરાવે છે, અને તેમની body coverages અને છે. વાસ્તવિક shaderમાં gate દરેક strokeની pressure depth પર પણ આધાર રાખે છે; batching સામાન્ય રીતે equivalent નથી તે બતાવવા માટે આ વધુ સરળ પરિસ્થિતિ પૂરતી છે. હાલનું દરેક-સ્ટ્રોક compositing આપે છે

જ્યારે પહેલાં bodyને ભેગું કરીને પછી એક જ gate લાગુ કરીએ તો મળે છે

તફાવત છે. બે સ્ટ્રોક્સ overlap થાય અને grain gate ન તો સંપૂર્ણ શૂન્ય હોય ન સંપૂર્ણ એક, ત્યારે પરિણામો અલગ પડે છે. સીધું batching કરવાથી ક્રોસિંગ પર ચૉકની ધૂળ કેવી રીતે બેસે છે તે બદલાઈ જશે.

ચોક્કસ batching માટે સાબિત કરવું પડશે કે સ્ટ્રોક્સના પિક્સેલ્સ એકબીજાથી સંપૂર્ણપણે અલગ છે, અથવા દરેક સ્ટ્રોક માટે સ્વતંત્ર atlas વિસ્તાર ફાળવીને મૂળ ક્રમમાં composite કરવું પડશે. ઉપકરણ પરના અંતિમ સ્વીકારમાં scissor રીત જ રાખવામાં આવી, તેથી આ તબક્કે atlas કે તેને સંભાળવાની જટિલતા ઉમેરાઈ નહીં.

એક અબજ પિક્સેલથી પાછા થોડા મિલિયન સુધી

ઉપકરણ પરના અંતિમ માપન:

પરિસ્થિતિસુધારા પહેલાંચોક્કસ scissor
70 દેખાતા ચૉક સ્ટ્રોક્સ, 300% ઝૂમ, લખતી વખતેGPU 52–60 ms≈ 9–10 ms
≈ 121 દેખાતા ચૉક સ્ટ્રોક્સ, 300% ઝૂમGPU 77–80 ms13.7–15.6 ms
દર ફ્રેમનું સૈદ્ધાંતિક લંબચોરસ કાર્યક્ષેત્ર (scratch + composite)783 M–1.34 B પિક્સેલ≈ 1.7 M–3 M પિક્સેલ

ઉપકરણના એક દસ્તાવેજમાંથી આવેલા 3,452 સ્ટ્રોક્સ અને 202,710 નમૂના-બિંદુઓનો ઉપયોગ કરીને clipping bounds પણ ચકાસ્યા. 0.5×, 1×, 2×, 3×, 5× અને 8× ઝૂમ પર 186,408 viewport કેસ બનાવવામાં આવ્યા; શૂન્યથી અલગ આવરણ પેદા કરી શકે એવો દરેક point sprite ગણવામાં આવેલા scissorની અંદર હતો. આ ચકાસણીએ કૅનવાસની ધારો, viewportની ધારો અને offsetના વિવિધ સંયોજનો આવરી લીધા.

અંતિમ કોડ ઇન્ટરૅક્શનની સ્થિતિના આધારે નીચા રિઝોલ્યુશનના LOD પર બદલાતો નથી. ઓછા ઝૂમ પર આખા પાનાની ink ટેક્સ્ચર હજી પણ દેખાય છે; વધુ ઝૂમ પર દેખાતા સ્ટ્રોક્સને હજી પણ વેક્ટર તરીકે ફરી દોરવામાં આવે છે. થ્રેશોલ્ડની એક જ બાજુએ પેન સ્ક્રીન પર મૂકવાથી કે કૅનવાસ ખસેડવાથી જૂના સ્ટ્રોક્સને અલગ સ્પષ્ટતા-સ્તરથી બદલવામાં આવતા નથી. વધુ ઝૂમના વેક્ટર રીડ્રૉ દરમિયાન દરેક ચૉક સ્ટ્રોકનું scratch clear અને composite માત્ર તેના પોતાના screen bounding boxને આવરે છે.

GPU સમય સ્પષ્ટતા માપી શક્યો નહીં

GPU કામગીરીની સમસ્યાઓ data structureમાં સૌથી દેખાતી સંખ્યા સાથે જ વધે એવું જરૂરી નથી. આ કિસ્સામાં 3,571 ઇનપુટ પોઇન્ટ્સ સહેલો શંકાસ્પદ લાગતા હતા; ફ્રેમ સમય નક્કી કરતું કામ દરેક 70 ચૉક સ્ટ્રોકથી શરૂ થતું સંપૂર્ણ-સ્ક્રીન કામ અને તેની સાથેનું render-pass switching હતું.

દૃશ્ય અર્થવ્યવસ્થાએ ઉપલબ્ધ ઑપ્ટિમાઇઝેશનને પણ મર્યાદિત કર્યા. દરેક સ્ટ્રોકની scratch, મૂળ compositing order અને કૅનવાસના absolute grain coordinatesને મનસ્વી રીતે દૂર કરી શકાય તેમ નહોતું. સમાન રંગ અને સમાન ઘનતા માત્ર એટલું કહે છે કે પરિમાણો સરખાં છે — overlapping પરિણામોને ભેગાં કરી શકાય છે તે સાબિત થતું નથી.

વાસ્તવિક ઉપકરણના પ્રતિસાદે સૌથી ઓછો GPU સમય ધરાવતી આવૃત્તિ નકારી. “આખું કૅનવાસ ઝાંખું થઈ જાય છે” એવી ટિપ્પણીએ માપન એકલું વ્યક્ત ન કરી શકતું ઉત્પાદન-બંધન બતાવ્યું: Apple Pencil સ્ક્રીનને અડે ત્યારે વપરાશકર્તાઓ જૂના સ્ટ્રોક્સ પણ જોઈ રહ્યા હોય છે.

અંતે ચકાસવામાં આવેલા અમલીકરણમાં નવી ઇન્ટરૅક્શન કૅશ સ્તર ઉમેરવામાં આવી નથી અને સ્પષ્ટતા ઘટાડવામાં આવી નથી. વધુ ઝૂમનું વેક્ટર રીડ્રૉ દરેક ચૉક સ્ટ્રોકનું કામ તેના પોતાના screen bounding box સુધી મર્યાદિત કરે છે. LucasPad પર ફરી લોડ કર્યા પછી પ્રતિસાદ હતો: “ખૂબ સરસ લાગે છે.”