చాక్ టూల్ పనితీరు: Full-screen passల నుంచి scissor rectangleల వరకు
Lulucat Notesలోని చాక్ టూల్ దట్టమైన చేతిరాత ప్రాంతాల్లో నెమ్మదించింది. ప్రధాన అడ్డంకి 3,571 input samples కాదు — ప్రతి frameలో జరిగే 70 full-screen scratch passలు. తిరస్కరించిన low-resolution cache, ప్రతి strokeకు ఒక scissor rectangle మిగతా కథను చెబుతాయి.

చివరి device buildలో ఎరుపు, నీలం చాక్; 255% zoomలో 155 strokes.
Lulucat Notesలోని చాక్ టూల్కు ఒక నిర్దిష్టమైన పనితీరు సమస్య ఉంది: ఖాళీ ప్రాంతంలో రాయడం సాఫీగా అనిపించేది, కానీ ఇప్పటికే చాక్ strokesతో నిండిన ప్రాంతంలోకి వెళ్లగానే pen tip వెనుకబడేది. అదే ప్రాంతంలో రాయడం కొనసాగిస్తే canvas panning కూడా క్రమంగా నెమ్మదించేది.
సాధారణ చేతిరాత ఉన్న ఒక్క పేజీ ఈ సమస్యను రేపడానికి సరిపోయేది: 300% zoom, స్థానిక ప్రాంతంలో 70 visible chalk strokes, మొత్తం 3,571 input sample points. ఖాళీ ప్రాంతాలు సాఫీగానే ఉండేవి; ఆ strokes గుమికూడిన ప్రాంతం మాత్రమే నెమ్మదించేది.
సరిచేసిన తర్వాత, అదే పేజీ 255% zoomలో కొత్త రాతను స్వీకరిస్తూనే ఉంటుంది; pen-down మరియు panning రెండింటిలోనూ ఉన్న strokes పూర్తి స్పష్టతను నిలుపుకుంటాయి.

చివరి device screenshot, మొత్తం 155 strokes. ఈ zoom levelలో pen-down లేదా panning సమయంలో ఉన్న strokes స్పష్టత తాత్కాలికంగా మారదు.
చాక్కు scratch texture ఎందుకు అవసరం
సాధారణ pen ప్రతి వృత్తాకార stampను source-over blendingతో ink textureపై నేరుగా composite చేయగలదు. చాక్ ఒక grain-gating layerను జోడిస్తుంది: renderer ముందుగా మొత్తం stroke కోసం body coverage మరియు depthను కూడబెడుతుంది, తర్వాత ఏ స్థానాలకు చాక్ దూది చేరాలో నిర్ణయించడానికి fixed grain textureను ఉపయోగిస్తుంది, చివరగా ఫలితాన్ని ఉన్న inkపై composite చేస్తుంది.
ఈ scratch texture ఒక చాక్ strokeను వేరుగా ఉంచుతుంది. ఇది ముఖ్యం, ఎందుకంటే ఒకే strokeలోని stamps బలంగా overlap అవుతాయి; ప్రతి stampను విడిగా grain-gate చేస్తే stroke మధ్యరేఖ repeated colorను కూడబెట్టుకుంటుంది, చాక్ pores కూడా sampling densityతో మారతాయి.
High zoom levelsలో Lulucat Notes ప్రస్తుత viewportలో కనిపించే vector strokesను మళ్లీ గీస్తుంది. పాత implementation ప్రతి visible chalk stroke కోసం ఈ దశలను చేసేది:
- Main render encoderను ముగించడం;
- Scratch textureను clear చేయడం;
- ఈ chalk strokeను scratchలో draw చేయడం;
- Main render encoderను మళ్లీ తెరవడం;
- ఒక full-screen triangleతో scratchను drawableకు తిరిగి composite చేయడం.
ఒక stroke యొక్క rendering semantics సరైనవే, కానీ పని పరిధి చాలా పెద్దది. iPad drawable 2732×2048 — సుమారు 5.6 million pixels. ప్రతి chalk stroke ఒక scratch pass, ఒక full-screen compositeను trigger చేసేది. 70 chalk strokes అంటే సుమారు 141 render encoders మరియు 70 full-screen composites.
Visible chalk strokes సంఖ్యను
కు సమీపంగా ఉండేది.
ప్రతి chalk strokeకు ఒక స్థిరమైన render-pass overhead కూడా ఉండేది; కాబట్టి ఆ ఖర్చు కూడా
కొలతలు iPadOS 18.6.2 నడుస్తున్న 12.9-inch iPad Pro (5th generation, M1)పై తీసుకున్నాం. మార్పుకు ముందు, తర్వాత అదే viewport నుంచి GPU timestampsను పోల్చాం; ఈ నిర్దిష్ట iPadలోని అదే Debug device buildలో command-buffer timestampsను ఉపయోగించాం — ఇక దీనిని LucasPad అని పిలుస్తాం. దిగువ పరిధులు అనేక frameల logsలో కనిపించిన సాధారణ హెచ్చుతగ్గులు; విడుదలయ్యే version frame-rate హామీలు కావు. 70 visible chalk strokes ఉన్నప్పుడు ఒక frameకు సాధారణంగా 52–60 ms GPU time అవసరమయ్యేది; సుమారు 120 strokes ఉన్న ప్రాంతంలో GPU time 77–80 msకు పెరిగింది.
Scratch మరియు composite passల full-screen rectangle area ఆధారంగా అంచనా వేస్తే, ప్రతి frameకు theoretical పని పరిధి సుమారు 783 million pixels నుంచి 1.34 billion pixelsకు పెరిగింది. ఈ సంఖ్య rectangle areas మొత్తం మాత్రమే; ఇది fragment invocation counts, video-memory read/write bytes లేదా GPU hardware countersకు సమానం కాదు. Metal యొక్క fast clear, attachment load/store, pass switchingలు GPU మరియు driver నియంత్రణలోనే ఉంటాయి.
ఖాళీ ప్రాంతాలు ఎందుకు సాఫీగా ఉన్నాయో కూడా ఇది వివరిస్తుంది. Visibility culling viewport వెలుపల ఉన్న strokesను దాటవేస్తుంది; ఖాళీ ప్రాంతంలో
0.85 ms వద్ద వచ్చిన తప్పు సమాధానం
Appలో ఇప్పటికే ప్రతి pointకు 2 pixels వద్ద bake చేసిన full-page ink texture ఉంది. Writing, panning, zooming సమయంలో ఈ textureను నేరుగా చూపిస్తూ, current Apple Pencil strokeను మాత్రమే live vectorగా ఉంచి చూశాం; interaction ముగిసిన తర్వాత high-resolution vector resultను మళ్లీ render చేయడానికి ఒక అదనపు frame ఉపయోగించాం.
ఈ విధానం చాలా బాగా పనిచేసింది. 300% zoomలో అదే దట్టమైన ప్రాంతంలో GPU time 0.84–0.85 msకు పడిపోయింది; ఉన్న chalk strokes సంఖ్యతో అది ఇక పెరగలేదు.
కానీ నిజమైన deviceపై సమస్య కూడా అంతే స్పష్టంగా కనిపించింది. 300% zoomలో pointకు సుమారు ఆరు screen pixels అవసరమయ్యాయి, కానీ cache 2 మాత్రమే ఇచ్చింది. Apple Pencil తాకిన క్షణంలో ఉన్న strokes అన్నీ మృదువైన low-resolution imageగా మారాయి; Pencilను ఎత్తిన తర్వాతే అవి పూర్తి స్పష్టతకు తిరిగి వచ్చాయి.
Tester ఇలా అన్నాడు: “రాస్తున్నప్పుడు మొత్తం canvas మసకబారుతోంది. వదిలిన వెంటనే అది మళ్లీ స్పష్టమవుతోంది.”
ఆ optimizationను తొలగించాం. 0.85 ms కొలిచిన అతి తక్కువ ఫలితం, కానీ అది ఆమోదయోగ్యమైన చాక్ టూల్ కాదు. ఉన్న strokes రాస్తున్నప్పుడు కనిపించే feedbackలో భాగం; pen-down సమయంలో వాటి స్పష్టత మారకూడదు.
ప్రతి చాక్ strokeను దాని స్వంత rectangleకు పరిమితం చేయడం
చివరి సవరణ per-stroke scratch, per-stroke compositingను ఉంచి, వాటి pixel పని పరిధిని మాత్రమే తగ్గించింది. ప్రతి strokeకు అన్ని stamp radii union నుంచి వచ్చిన canvas bounding box ముందే ఉంది. Renderer ఆ bounding boxను ప్రస్తుత viewport drawable coordinatesలోకి మార్చి, antialiasing margin కోసం 2 pixelsను జోడిస్తుంది:
అదే scissor rectangleను మూడు పనులకు ఉపయోగిస్తారు: scratchను clear చేయడం, strokeను draw చేయడం, ఫలితాన్ని main surfaceకు తిరిగి composite చేయడం.
let rect = displayScissorRect(for: stroke.bounds, viewport: viewport)
scratchEncoder.setScissorRect(rect)
clearScratchExplicitly()
drawStrokeIntoScratch(stroke)
mainEncoder.setScissorRect(rect)
compositeChalkFromScratch(stroke)
mainEncoder.setScissorRect(fullDrawable)
ఇదే logic 4096² ink textureపై baking మరియు partial replayకు కూడా ఉపయోగపడుతుంది. అందువల్ల high-zoom display, settled ink layer రెండూ చాక్ను రెండు వేర్వేరు విధాలుగా ప్రవర్తింపజేయవు.
ఇక్కడ సులభంగా మిస్ అయ్యే రెండు వివరాలు ఉన్నాయి.
మొదటిది, render pass యొక్క loadAction = .clear attachment load stageలో జరుగుతుంది; rasterization scissor దాన్ని పరిమితం చేయదు. దాన్ని కొనసాగిస్తే scratch texture మొత్తాన్ని ఇంకా clear చేస్తుంది. సరిచేసిన pass .dontCareను ఉపయోగించి, scissorలోపల clear_fragmentను draw చేస్తుంది. ఈ rectangleను తర్వాత పూర్తిగా write చేస్తారు; composite కూడా అదే rectangleను మాత్రమే read చేస్తుంది; కాబట్టి పాత attachment contentsను load చేయాల్సిన అవసరం లేదు.
రెండవది, ప్రతి chalk stroke composite పూర్తయిన తర్వాత outer scissorను restore చేయాలి. ఈ state restoration lineను వదిలేస్తే, తర్వాతి pens, images లేదా selections మునుపటి chalk stroke boundsతో clip అవుతూనే ఉంటాయి; దాంతో strokes లేదా images కనిపించకుండా పోతాయి.
Chalk grain ఇప్పటికీ rectangleలోని local UVల నుంచి కాకుండా absolute canvas coordinates నుంచి sample అవుతుంది. Scissorను కదపడం GPU process చేసే pixelsను మాత్రమే మార్చుతుంది; ప్రతి pixel చదివే grain texture locationను మార్చదు. అందువల్ల పక్కపక్కన ఉన్న rectangles మధ్య texture seams రావు, canvasను drag చేయడం వల్ల grain drift కూడా జరగదు.
Pixel workloadను మాత్రమే పరిగణిస్తే కొత్త పని పరిధి దాదాపు
కు సమీపంగా ఉంటుంది; ఇందులో
ఒకే రంగు చాక్ strokesను batch ఎందుకు చేయలేదు
పేజీలోని ఎక్కువ chalk strokes ఒకే color, densityను పంచుకుంటాయి. కాబట్టి డజన్ల కొద్దీ strokesను scratchలో ఒకేసారి draw చేసి, ఒక్కసారి మాత్రమే composite చేయాలని అనిపిస్తుంది. ఇది render passesను మరింత తగ్గిస్తుంది, కానీ overlap అయ్యే ప్రాంతాల్లో color మరియు grain semanticsను మార్చుతుంది.
ఒక ఉద్దేశపూర్వకంగా సరళీకరించిన ఉదాహరణను తీసుకుందాం: ఒక pixel వద్ద రెండు strokesకు ఒకే grain-gate value
కానీ bodiesను ముందుగా merge చేసి, ఒకే gateను apply చేస్తే వస్తుంది
రెండింటి మధ్య తేడా
ఖచ్చితమైన batchingకు stroke pixels పరస్పరం overlap కావని నిరూపించాలి, లేదా ప్రతి strokeకు ప్రత్యేక atlas regionను allocate చేసి original orderలో composite చేయాలి. చివరి device పరీక్షలో scissor విధానాన్నే ఉంచాం; కాబట్టి ఈ దశలో atlas లేదా దాని నిర్వహణ సంక్లిష్టతను జోడించలేదు.
ఒక బిలియన్ pixels నుంచి కొన్ని మిలియన్లకు
చివరి device పరీక్ష కొలతలు:
| పరిస్థితి | సవరణకు ముందు | ఖచ్చితమైన scissor |
|---|---|---|
| 70 visible chalk strokes, 300% zoom, writing | GPU 52–60 ms | ≈ 9–10 ms |
| ≈ 121 visible chalk strokes, 300% zoom | GPU 77–80 ms | 13.7–15.6 ms |
| ప్రతి frameకు theoretical rectangle scope (scratch + composite) | 783 M–1.34 B pixels | ≈ 1.7 M–3 M pixels |
ఒక device documentలోని 3,452 strokes మరియు 202,710 sample pointsతో clipping boundsను కూడా నిర్ధారించాం. 0.5×, 1×, 2×, 3×, 5×, 8× zoomల వద్ద 186,408 viewport cases రూపొందించాం; non-zero coverage సృష్టించగల ప్రతి point sprite లెక్కించిన scissorలోనే ఉంది. ఈ తనిఖీ canvas edges, viewport edges, వివిధ offset combinationsను కవర్ చేసింది.
పరీక్షలో ఉన్న code interaction state ఆధారంగా low-resolution LODకు మారదు. Low zoom levelsలో full-page ink textureనే చూపిస్తుంది; high zoom levelsలో కనిపించే strokesను vectorsగా redraw చేస్తుంది. Thresholdకు ఒకే వైపున ఉన్నప్పుడు pen-down, panning ఉన్న strokesను వేరే clarity levelతో భర్తీ చేయవు. High-zoom vector redraw సమయంలో ప్రతి chalk stroke యొక్క scratch clear, composite దాని స్వంత screen bounding boxను మాత్రమే కవర్ చేస్తాయి.
GPU time స్పష్టతను పట్టుకోలేదు
GPU performance సమస్యలు data structureలో అత్యంత కనిపించే సంఖ్యతో తప్పనిసరిగా scale కావు. ఈ సందర్భంలో 3,571 input points సులభంగా అనుమానించదగినవి; కానీ frame timeను నిర్ణయించింది 70 chalk strokesలో ప్రతి ఒక్కటి trigger చేసిన full-screen పని, వాటితో వచ్చిన render-pass switchingతో కలిసి.
కనిపించే ఫలితాల అర్థం కూడా ఉపయోగించగల optimizationsను పరిమితం చేసింది. Per-stroke scratch, original compositing order, absolute canvas grain coordinatesను ఇష్టానుసారం తొలగించలేం. Same color, same density అంటే parameters సరిపోతున్నాయని మాత్రమే; overlap అయ్యే ఫలితాలను merge చేయవచ్చని అది నిరూపించదు.
నిజమైన device feedback GPU time అతి తక్కువగా ఉన్న versionను తిరస్కరించింది. “మొత్తం canvas మసకబారుతోంది” అన్న మాట measurement ఒక్కటే చెప్పలేని product constraintను చూపించింది: Apple Pencil తాకినప్పుడు users ఉన్న strokesను కూడా గమనిస్తున్నారు.
పరీక్షలో ఆమోదించిన implementation కొత్త interaction cache layerను జోడించదు, clarityను తగ్గించదు. High-zoom vector redraw ప్రతి chalk stroke పని దాని స్వంత screen bounding boxకు మాత్రమే పరిమితం చేస్తుంది. పరీక్ష buildను మళ్లీ LucasPadలో లోడ్ చేసిన తర్వాత feedback ఇలా వచ్చింది: “చాలా బాగుంది.”