Kimi K3 સાથે Lulucat Notes ની રેન્ડરિંગ પાઇપલાઇનનું પુનઃનિર્માણ
અમારી Core Graphics ટાઇલ પાઇપલાઇન છ નાના પગલાંમાં Metalની point-sprite પાઇપલાઇન બની, અને દરેક પગલું વાસ્તવિક iPad પર ચકાસાયું. કોડ Fireworks પર Kimi K3 સાથે જોડી પ્રોગ્રામિંગથી લખાયો.
ગયા અઠવાડિયે, Lulucat Notesમાં લાસો પસંદગી ખેંચતાં દેખાતી લહેર ઊભી થતી: કેટલીક સ્ક્રીન ટાઇલ્સ પસંદગીને તેના નવા સ્થાન પર બતાવતી, જ્યારે બીજી એ જ ફ્રેમમાં જૂની સ્થિતિ બતાવતી રહેતી. અમે આખી રેન્ડરિંગ પાઇપલાઇન — Core Graphics bitmap અને CATiledLayer — ને છ નાના પગલાંમાં Metalથી બદલી; આગળનું શરૂ કરતાં પહેલાં દરેક પગલું વાસ્તવિક iPad પર ચકાસ્યું.
કોડ Fireworks પર ચાલતા Moonshotના ખુલ્લા મોડલ Kimi K3 સાથે જોડી પ્રોગ્રામિંગથી લખાયો. માણસે દિશા આપી, નિર્ણયો લીધા અને પરીક્ષણ કર્યું; મોડેલે લગભગ દરેક લાઇન લખી.
પાઇપલાઇન
જૂની પાઇપલાઇનમાં દોરવાની બે રીતો હતી, જે ધીમે ધીમે અલગ થતી ગઈ: સસ્તા પ્રદર્શન માટે સ્ટ્રોક્સને bitmapમાં સમાવવામાં આવતા, અને ટાઇલ્સને વધુ વિગત જોઈતી ત્યારે vectors તરીકે ફરી દોરવામાં આવતા. નવી પાઇપલાઇનનો એક જ વિચાર છે: શાહી જેવું બધું point sprite છે. પેન સ્ટ્રોક, હાઇલાઇટરનો સ્વીપ અને ઇરેઝરનો ડૅબ એક જ 32-byte vertex છે — સ્થિતિ, વ્યાસ, રંગ — જેને એ જ shader pair સ્ટ્રોકની arc length પર એક pointના અંતરે રહેલા GPU-rasterized circles તરીકે દોરે છે.
![]()
એક pointના અંતરે ગોઠવાયેલી circlesની સાંકળ ગાણિતિક રીતે સંપૂર્ણ capsuleથી લગભગ 0.075 points જેટલી અલગ પડે છે — અમારા canvasની ઘનતામાં pixelનો પાંચમો ભાગ. બદલામાં, ત્રણ સાધનો એક જ code pathમાં સમાઈ જાય છે, અને GPU તે જ કરે છે જેમાં તે શ્રેષ્ઠ છે.
આ વિચારની આસપાસ architecture સરળ છે. Commit થયેલી શાહી એક જ 4096² textureમાં રહે છે. UIScrollView રહે છે, પરંતુ તેને માત્ર gesture engine તરીકે પછાડી દેવામાં આવ્યું છે: તેનું contentOffset અને zoomScale દર ફ્રેમે viewport uniformને feed કરે છે, એટલે panning અને zoomingથી કંઈ લખાતું નથી. દરેક ફ્રેમમાં પાંચ draw operations છે:
flowchart TB
subgraph frame["Every frame: five draws"]
direction TB
paper["1 · paper blit"] --> ink["2 · committed ink"]
ink --> live["3 · live stroke"]
live --> sel["4 · selection"]
sel --> dash["5 · lasso dashes"]
end
commit["stroke commit<br/>append stamps"] --> tex[("ink texture<br/>4096² render target")]
replay["regional replay<br/>erase · delete · move · undo"] --> tex
tex -. "sampled or re-drawn" .-> ink
ફેરફારો textureમાં સીધા લખાય છે. Stroke commit તેના stamps ઉમેરે છે. Erasing, deleting, moving અને undo scissor rectangleની અંદર અસરગ્રસ્ત વિસ્તારને replay કરે છે: વિસ્તાર સાફ કરો, તેને છેદતા strokes ફરી દોરો, કામ પૂરું. Partial erasure પહેલાની પોસ્ટની ownership semantics જાળવે છે — erasure એ તે strokeની છે જેમાંથી તે શાહી કાઢે છે — દરેક erased strokeને scratch textureમાં દોરીને, તેના પોતાના erasure pathsને destination-out blending (
આ બધાની શરૂઆત કરનાર selection હવે point sprites તરીકે પણ દોરાય છે. તેને drag કરવાથી એક uniform offset અપડેટ થાય છે. ટેક્સચરમાં શૂન્ય લખાણ, ટાઇલ્સમાં શૂન્ય invalidation — લહેર માળખાકીય રીતે ગાયબ છે, માત્ર ઓછી કરવામાં આવી નથી.
રક્ષક: પિક્સેલ તફાવત
અમે Core Graphics renderer કાઢી નાખ્યો નથી. તેને ઑફલાઇન reference implementation સુધી મર્યાદિત કર્યો છે, અને દરેક Metal changeએ ઉપકરણ પર capture કરાયેલા વાસ્તવિક stroke data સામે pixel comparison પાસ કરવી પડે છે. સ્વીકાર માપદંડ “સમાન પિક્સેલ્સ” નથી — બે સાચા rasterizers anti-aliased edges પર થોડા gray levels જેટલા તફાવત સાથે યોગ્ય રીતે અલગ હોઈ શકે. માપદંડ માળખાકીય છે: શાહી ગુમ ન હોવી જોઈએ, offset ન હોવો જોઈએ, રંગમાં drift ન હોવો જોઈએ અને શાહીથી દૂર ક્યાંય મોટો difference ન હોવો જોઈએ.
![]()
આ ટેસ્ટ હાર્નેસે ઑફલાઇન renderer બનાવતી વખતે મળેલી પાંચમાંથી ચાર ભૂલો પકડી, અને બધીનું નિદાન outputને pixel level સુધી crop કરીને થયું: Swift/Metal structનો stride mismatch (28 bytes સામે 32, કારણ કે Metal float4ને 16 પર align કરે છે — screen રંગીન blocksથી ભરાઈ ગઈ), [[point_size]]ને fragment shaderમાં varying તરીકે વાંચી ન શકાયું (દરેક stamp square નીકળ્યો), એક command buffer પર બે render encodersનું સહઅસ્તિત્વ (બધું કાળું) અને missing start dab જેના કારણે ઝડપી strokesનો પહેલો millimeter દેખાતો નહોતો.
છ પગલાં, એક પુનર્લેખન નહીં
સ્થળાંતર યોજના છ સ્વતંત્ર રીતે પહોંચાડી શકાય એવા પગલાંની હતી: pixel diff પાસ કરતો ઑફલાઇન renderer; zero visual change ધરાવતું display shell; GPU પર live stroke; GPU પર selection; textureમાં સીધું લખતી mutations; high-zoom vector re-drawing (step twoમાં સમાવી દેવાયું, કારણ કે “zero visual change” માટે તે જરૂરી હતું). દરેક પગલું માણસથી પૂરું થતું — simulatorથી નહીં, screenshot diffથી નહીં — ડેસ્ક પરના iPad Pro પર લખતા, ભૂંસતા, zoom કરતા અને drag કરતા.
ડિવાઇસે ત્રણ ભૂલો પકડી જે દરેક સ્વચાલિત ચકાસણીએ ચૂકી હતી. 100% zoomથી ઉપર strokes બે વાર દોરાતા — નીચે નરમ texture, ઉપર તીક્ષ્ણ sprites — અને તે ઝાંખા blur જેવા લાગતા, જેને માણસે secondsમાં પકડી લીધા. Stroke commit એક ફ્રેમ માટે flicker થતું, કારણ કે જૂનું overlay texture update સાથે અસંકલિત cross-fade કરતું. અને 170% zoomથી ઉપર બધી નોંધો ગાયબ થઈ જતી: visibility-culling rectangle પોતાની scaled coordinate spaceમાં contentOffset વાપરતું, તેથી zoom કરતાં તે strokesથી દૂર સરકતું. ત્રણેય one-line-to-one-function fixes હતાં, અને એમાંથી એક પણ એવા testમાં નહોતું જે આપણે અગાઉથી લખી શકતા, કારણ કે શું શોધવું તે અમને ખબર નહોતી. UI-first consumer app માટે આ જ કારણે માણસ loopમાં રહે છે.
Kimi K3 સાથે કામ કરવું કેવું લાગે છે
સૌ પ્રથમ, ઝડપી. “ચર્ચા કરો, લખો, બનાવો, ઇન્સ્ટોલ કરો, જુઓ”નું ચક્ર મિનિટોમાં ચાલતું, અને ઝડપથી જવાબ આપતું મોડેલ દિવસમાં તમે કેટલાં ચક્ર ચલાવી શકો તે બદલી નાખે છે.
બીજું, તે જરૂર કરતાં વધુ જટિલતા ઉમેરતું નથી. આ કોડબેઝ સ્પષ્ટ આંતરિક નિયમો પર ચાલે છે — લોન્ચ પહેલાં પાછળની સુસંગતતા માટેનું માળખું નહીં, અને ડિવાઇસ તેની જરૂરિયાત સાબિત કરે ત્યારે જ જટિલતા — અને K3ને યાદ અપાવ્યા વિના તે નિયમો અનુસરે છે. તેણે “પછી માટે” spatial indexes ઉમેર્યા નહીં, દરેક callને defensive checksમાં લપેટ્યો નહીં અને અનુમાનાધારિત abstraction બનાવી નહીં. તેને સૂચનાઓ આપવી એવી નિપુણ સહકર્મી સાથે કામ કરવા જેવી લાગે છે જેણે આંતરિક નિયમો વાંચ્યા છે અને ખરેખર તેમાં વિશ્વાસ રાખે છે.
ત્રીજું, તેને tools આપો અને તે ઉત્સાહથી તેમનો ઉપયોગ કરે છે. અમે image utilities — view, pixel region સુધી crop અને resize — જોડ્યાં, અને મોડેલે ઉપરની પાંચ harness ખામીઓનું નિદાન કરવા પોતાના rendererના outputને સક્રિય રીતે crop કરવાનું શરૂ કર્યું. Tool હોવાથી તેને જોવાની યાદ આવી.
બીજી બાજુ: K3એ આ વાર્તાની મોટાભાગની ભૂલો લખી, જેમાં coordinate-spaceની એ ભૂલ પણ છે જેનાથી નોંધો ગાયબ થઈ ગઈ. તેની મર્યાદાઓ વાસ્તવિક છે. કામ સલામત બન્યું તે મોડેલ સાચું હતું એટલા માટે નહીં; rendering drift પકડતું harness અને વપરાશનો અનુભવ પકડતો માણસ તેને સલામત બનાવતા હતા. છતાં, રોજિંદા કામમાં, અમે પણ વાપરતા અગ્રણી બંધ મોડલ્સ — Opus-કક્ષાની સિસ્ટમ્સ —થી હું તેને વિશ્વસનીય રીતે અલગ ઓળખી શકતો નહોતો. કેટલાક માપદંડોમાં તે સ્પષ્ટ રીતે સારું હતું: વધુ ઝડપી અને codebaseને defensive designથી ભરવાની વૃત્તિ ઘણી ઓછી.
હવેથી અમે કેવી રીતે બનાવવું ઇચ્છીએ છીએ
અમે હવે મોટા specification આધારિત agentic developmentથી દૂર છીએ — એવી શૈલી જેમાં મોડલને મોટું specification આપીને જે તૈયાર થાય તે સ્વીકારી લો. નિષ્ફળતા ખરાબ code નથી; તે એવો code છે જે કોઈ સમજી શકતું નથી.
અહીં જે કામ કર્યું અને અમે જાળવીશું: નાના પગલાં, દરેક શરૂ થાય તે પહેલાં ચર્ચાયેલું, બનાવતાં પહેલાં માણસે સમજેલું અને જે ડિવાઇસ પર જીવવાનું છે તેના પર ચકાસેલું. મોડેલનું કામ ઝડપી, ચોક્કસ અને અનિશ્ચિતતા વિશે પ્રામાણિક રહેવાનું છે. માણસનું કામ નિર્ણય, રુચિ અને e2e ચકાસણી છે — ખાસ કરીને UI-first consumer software માટે, જ્યાં specification “સાચું” કેવું લાગે છે તે વર્ણવી શકતું નથી. Fireworks પરનું Kimi K3 આ જ ચક્ર માટે સારી રીતે યોગ્ય નીકળ્યું: ચક્રને ચુસ્ત રાખવા જેટલું ઝડપી, અને પગલાંને નાના અને સ્વચ્છ રાખવા જેટલું સમજદાર.
લહેર ગાયબ છે, પાઇપલાઇન બેની જગ્યાએ એક જ વિચાર છે, અને અમને અહીં સુધી લાવનાર પ્રક્રિયા યથાવત્ રહેશે.