فهرست مستندات
زمان اجرای رندرینگ#
Environment یک سند مرورگرِ خنثی نسبت به موتور است، نه مترادف R3F، Three.js، WebGPU یا حتی سهبعدی. میتواند DOM، Canvas 2D، WebGL، WebGPU، WASM یا هر موتور قابلاجرای دیگری در مرورگر، مانند Three/R3F، Babylon، PlayCanvas، Phaser، Pixi، Unity Web یا Godot Web را ترکیب کند. همهٔ این انتخابها پشت همان قراردادهای میزبان، چرخهٔ عمر، هویت و امنیت Environment باقی میمانند.
نمایهٔ رسمی R3F الامر (al-amr.r3f-webgpu@1) که در
ADR-0048 ثبت
شده، یک نمایهٔ اختیاری نویسندگی و رندرینگ است. این نمایه از Three.js r185 با
WebGPURenderer ساختهشده فقط داخل @al-amr/r3f و مواد سفارشی نوشتهشده با
TSL استفاده میکند. نمایه WebGPU-first است، نه WebGPU-only: وقتی WebGPU در
دسترس نیست، همان renderer روی backend داخلی و واقعی WebGL2 خود اجرا میشود و
یک دستگاه فقط برای این نمایه و زمانی پشتیبانینشده است که هر دو backend شکست
بخورند.
عضویت در نمایه، metadata محلی ابزارهاست. یک package در workspace وقتی عضو است
که نام خودش @al-amr/r3f یا @react-three/fiber باشد، یا یکی از این markerها
را در هر بخش dependency اعلام کند. Registry و schema عمومی Environment از این
انتخاب موتور را استنتاج نمیکنند و هیچ فیلد engine، renderer یا
threeVersion در manifest ندارند.
مرز conformance نیز همین تفکیک را دارد. runEnvironmentConformance عمومی
قرارداد خنثی نسبت به موتور Environment را اعتبارسنجی میکند. یک Environment
که این نمایه را bind میکند، علاوه بر آن runR3fEnvironmentConformance را به
کار میبرد؛ این runner بررسیهای عمومی را با قواعد ترتیب gate در R3F و نبود
canvas پیش از gate ترکیب میکند. انتخاب این نمایه بررسیهای نویسندگی را اضافه
میکند؛ تعریف Environment را تغییر نمیدهد.
اثبات کراندار PAP 0.1 مجموعهٔ عمومی را روی یک Environment فقط-DOM و بستهبندیشده اجرا میکند و سپس همان digest دقیق artifact را از مسیر میزبان product-runtime و web adapter ساختهشدهٔ Desktop باز میکند. وقتی Electron WebContents در دسترس نیست، اجرای system Chrome میتواند seam چرخهٔ عمر مرورگر را اثبات کند؛ بااینحال receipt باید این محدودیت را نام ببرد و نمیتواند جای conformance ویژهٔ Electron یا نمایهٔ R3F را بگیرد.
نسخههای cohort دقیقاً پین شدهاند — three/@types/three نسخهٔ 0.185.1،
@react-three/fiber نسخهٔ 9.6.1، @react-three/drei نسخهٔ 10.7.7 و
react/react-dom نسخهٔ 19.2.7 — و پکیجهای عمومی محدودههای peer کراندار
اعلام میکنند. هر importer عضو نمایه در lockfile باید دقیقاً نسخهٔ Three.js
همان نمایه را resolve کند. quality/rendering-profile.json و یک اسکریپت
guardrail سیاست، cohort، قاعدهٔ ساخت renderer فقط از factory، بررسی Canvas خام
و ممنوعیت GLSL را به همین اعضای نمایه محدود میکنند و Environmentهای عمومی را
scan نمیکنند.
حالتهای renderer#
رندرینگ در این نمایه از یک factory مشترک در @al-amr/r3f عبور میکند که
three/webgpu را بهصورت تنبل بارگذاری میکند و پیش از برگرداندن handle،
renderer.init() را await میکند. دو حالت دائمی وجود دارد:
auto(پیشفرض) WebGPU را ترجیح میدهد و backend داخلی WebGL2 همان renderer را تنها fallback عملیاتی میداند. WebGL1 پشتیبانی نمیشود وWebGLRendererکلاسیک دیگر در کد زمان اجرای جهان وجود ندارد.force-webgl2backend WebGL2 را (forceWebGL: true) برای fallback، آزمایش و بازیابی کاربر پین میکند.
نادیدهگرفتن حالت توسط کاربر پیش از هر بوت resolve میشود: پارامتر query
al-amr-rendering (مانند ?al-amr-rendering=force-webgl2) بر مقدار پایدار
al-amr:rendering-mode در localStorage — که پس از یک بازیابی موفق در حالت
سازگار اجباری نوشته میشود — اولویت دارد. هر مقدار ناشناخته نادیده گرفته
میشود، پس پیشفرض WebGPU-first همیشه پابرجاست.
قرارداد دروازه و بوم#
هیچ عضو نمایهای new WebGPURenderer() نمیسازد و گزینهٔ خصوصی
<Canvas gl={...}> پاس نمیدهد. هاب، گالری، سالن کنفرانس و قالب شروع CLI همگی
همان دو مؤلفه را از @al-amr/r3f ترکیب میکنند:
EnvironmentRenderingGateیک probe قابلیت سریع و غیرمرجع — secure context، ساخت context WebGL2 و یک درخواست محدود adapter مربوط به WebGPU — پیش از فرزندانش و در نتیجه پیش از mountشدن حضور، پلاگینها یا کد صحنه اجرا میکند. درخواست محدود adapter نخستین تلاش WebGPU همان بوت است. اگر این درخواست نتواند adapter بدهد ولی WebGL2 در دسترس باشد، حالت درخواستیautoباقی میماند، اما نخستین تلاش Canvas باforceWebGL: trueآغاز میشود وfallbackUsed: trueگزارش میکند. به این ترتیب همان درخواست بدون محدودیت زمانی داخل Three تکرار نمیشود. حکم نهایی همچنان از مقداردهی واقعی backend انتخابشده به دست میآید.EnvironmentCanvasتنها نقطهای است که renderer جهان را میسازد. انتخاب backend، نخستین فریم، گمشدن دستگاه و شکست را از طریق context مشترک رندرینگ به دروازه گزارش میکند.
R3F 9.6 میتواند تا وقتی configure() پیشین منتظر renderer ناهمگام است و
state.gl هنوز خالی است، factory مربوط به gl را دوباره فراخوانی کند. Canvas
مشترک همهٔ فراخوانیهای یک attempt، عنصر canvas و پیکربندی renderer یکسان را
در یک Promise و یک نسل renderer ادغام میکند. پذیرش R3F از مسیر onCreated
نقطهٔ commit برای callbackهای انتخاب backend و آمادهشدن renderer است. cleanup
نیز با نسل attempt کلیدگذاری میشود تا cleanup دیرهنگام Canvas قدیمی نتواند
نسل جایگزین را abort یا dispose کند. وقتی پیکربندی واقعاً تغییر میکند، نسل
جایگزین پیش از شروع منتظر پایان task مقداردهی و dispose نسل قبلی میماند؛
callbackهای device-loss و نخستین فریم نیز به نسل دقیق renderer قفل میشوند.
مقداردهی دقیقاً یک تلاش مجدد محدود دارد: وقتی probe محدود WebGPU یک adapter
میدهد، شکست واقعی بعدی در حالت auto باعث disposeشدن تلاش و تکرار یکباره
روی بومی تازه با forceWebGL: true میشود. اگر probe نتواند adapter بدهد، همان
بوت مستقیماً با تلاش WebGL2 آغاز میشود. وقتی هر دو backend شکست بخورند، دروازه
صفحهٔ پشتیبانینشده را رندر میکند.
دستگاه GPU گمشده یک دنبالهٔ بازیابی محدود را طی میکند — یک تلاش مجدد در حالت
فعلی و سپس در حالت auto یک تلاش اجباری WebGL2 — پیش از آنکه دستگاه
device_lost_unrecovered اعلام شود. هر تلاش قابل لغو است: تلاشهای
جایگزینشده یا unmountشده یک abort signal میگیرند که حول بارگذاری تنبل ماژول و
مقداردهی بررسی میشود و هر renderer دقیقاً یک بار dispose میشود.
بوم یک سطح بیرونیِ مالکِ layout دارد و اندازهٔ منطقی و DPR آن را پیش از
WebGPURenderer.init() اعمال میکند؛ همان زمانی که Three پیوست عمق پیشفرض را
میسازد. R3F اندازهٔ اندازهگیریشدهٔ Canvas را تا resolveشدن factory ناهمگام
gl اعمال نمیکند، بنابراین مقداردهی با اندازهٔ HTML پیشفرضِ canvas داخلی
میتواند texture عمق کهنه را با نخستین پیوست رنگ واقعی ترکیب کند. سطح پس از
مقداردهی دوباره اندازهگیری میشود و اگر ابعاد فیزیکی هنگام شروع تغییر کرده
باشد، آن نسل renderer در یک حلقهٔ تثبیت محدود dispose و از نو ساخته میشود.
هر تلاش بوم یک دوربین perspective واقعی و memoشده را به تمام ادامههای بازگشتیِ
configure در R3F میدهد؛ ارسال گزینههای دوربین میتواند به یک ادامهٔ ناهمگام
کهنه اجازه دهد دوربین اندازهگذاریشده را با دوربینی تازه و aspect صفر جایگزین
کند. تلاش بازیابی نسل تازهای از دوربین دریافت میکند. پذیرش R3F و نخستین فریم
post-render اندازهٔ نهایی backing store، aspect
دوربین و متناهیبودن ماتریس projection را دوباره میسنجند و پیش از readiness
یک فریم اصلاحشدهٔ بعدی را لازم میدانند. همان probe پس از post-render در تمام
عمر نسل renderer فعال میماند و تغییر دیرهنگام اندازه، backing store، DPR یا
projection دوربین را با بودجهای محدود اصلاح میکند. اگر یک اندازهگیری ثابت
همگرا نشود، بهجای بازنویسی backing store و سیاهکردن هر فریم کنار گذاشته
میشود؛ یک اندازهگیری تازه بودجهای تازه میگیرد. بوم مشترک frameloop در R3F
را عمداً روی "always" ثابت نگه میدارد و prop عمومی frameloop ندارد؛ کار
on-demand باید invalidate() یا advance() را از داخل درخت canvas فراخوانی
کند، جایی که store زنده است نه snapshot کهنهٔ پیکربندی ناهمگام.
بالای همین پین، راهِ مجازِ کاهش هزینهٔ رندر برای دنیاهایی که تمام روز باز
میمانند قرار دارد: دندهٔ سکونِ رندر، بهصورت opt-in. یک Environment یک
IdleCadenceController را بیرون از canvas میسازد و از راه prop به نام
cadence به آن میدهد؛ پس از نخستین فریمِ نشسته بهعلاوهٔ یک مهلت، canvas
ارسالِ رندر را بهدست میگیرد و تا وقتی بازدیدکننده بیکار است بیشتر tickها را
رد میکند، و با ورودی، حرکت دوربین، تغییر سطح، فراخوانی wake() از سوی دامنه،
یا یک اجارهٔ پیوستهٔ نگهداشتهشده بیدرنگ بیدار میشود. خودِ حلقه همچنان tick
میخورد — کادنس ارسالِ رندر را گیت میکند، نه frameloop را؛ پس تصمیم تاریخیِ
کنارگذاشتن رندرِ تقاضامحور سر جایش میماند و کادنس نقضش نمیکند. مالکِ
RenderPipeline روی بومِ کادنسدار بهجای useFrame با اولویت مثبت از راه
useCadenceRenderDelegate() ثبت میشود، و probeهای سطح کیفیت و زمانسنجی GPU
فقط فریمهای رندرشدهٔ فازِ کامل را اندازه میگیرند تا فاصلهگذاریِ سکون
تصمیمهای سطح را منحرف نکند. بدون این prop هیچچیز mount نمیشود و رفتار ارسال
دستنخورده میماند.
منابع تولیدشدهٔ Three که با memoization در React ساخته میشوند از
useDisposableRenderResource استفاده میکنند. این hook منابع مشترک را
reference-count میکند و dispose نهایی را از cleanup آزمایشی StrictMode عبور
میدهد. منبعی که کاملاً داخل یک effect ساخته میشود میتواند cleanup همان
effect را نگه دارد. dispose مستقیم geometry یا material memoشده در cleanup
عادی ممنوع است، چون StrictMode ممکن است همان شیء جاوااسکریپت را پس از نابودی
GPU buffer آن دوباره مصرف کند.
آمادگی صادقانه است. محیط فقط پس از پذیرش renderer و تکمیل نخستین فریم در فاز
post-render مربوط به R3F، یعنی پس از gl.render() و نه از subscriber پیشرندر
useFrame، ready گزارش میکند و overlay بوت هرگز صفحهٔ پشتیبانینشده را
نمیپوشاند. هاب پیش از اعلام آمادگی بوت محیط، این فریم معتبر canvas را با
سیگنال post-render مربوط به Active Zone جاری ترکیب میکند. تلاش مجدد یا mount
دوبارهٔ renderer هرگز نشستهای شبکه را از بین نمیبرد: Multiplayer، SDK، حضور
و وضعیت نشست بیرون از مرز Canvas زندگی میکنند و از mount مجدد renderer جان
سالم به در میبرند. مجوز حساب به Shell دسکتاپ و بیرون از نمای Environment تعلق
دارد. یک bundle هرگز callback یا tokenهای OAuth را مدیریت نمیکند. وقتی رندرینگ
پشتیبانینشده از آب درمیآید، gate providerهای متعلق به Environment را unmount
میکند، در حالی که نشست کنترلشده توسط میزبان در Shell بیرون از این lifecycle
میماند.
پشتیبانی و صفحهٔ پشتیبانینشده#
پشتیبانی این نمایه مبتنی بر قابلیت است و هرگز فهرست مجاز user-agent نیست. یک
دستگاه وقتی پشتیبانیشده است که محیط عملیاتی در secure context اجرا شود،
WebGPURenderer واقعاً روی WebGPU یا WebGL2 مقداردهی شود و نخستین فریم بدون
خطای اعتبارسنجی رندر شود. کندی هرگز پشتیبانینشدن نیست؛ دستگاههای کند در عوض
به سطح کیفیت پایینتر میافتند. موتورهای دیگر مالک سیاست پشتیبانی و fallback
خودشاناند.
وقتی هیچ backend واقعی نتواند آغاز شود، gate نمایه یک صفحهٔ پشتیبانینشدهٔ فقط-DOM و
سازگار با RTL با راهنمای فارسی و انگلیسی، کنشهای تلاش دوباره و سازگاری اجباری
و یک کد دلیل قابلکپی نمایش میدهد. هیچ Canvas، پلاگین یا حضوری روی آن صفحه
mount نمیشود. کدهای دلیل پایدار و ماشینخوانا که در
packages/r3f/src/rendering-types.ts تعریف شدهاند عبارتاند از:
| کد | معنی |
|---|---|
insecure_context | صفحه در context امن (HTTPS) اجرا نمیشود. |
webgpu_init_failed | مقداردهی WebGPU روی این دستگاه شکست خورد. |
webgl2_unavailable | هیچ context WebGL2ای ساخته نشد. |
webgl2_init_failed | تلاش اجباری سازگاری WebGL2 نیز شکست خورد. |
hardware_acceleration_unavailable | هیچکدام از backendها در دسترس نیست؛ شتابدهندهٔ سختافزاری غیرفعال یا غایب است. |
device_lost_unrecovered | دستگاه GPU گم شد و بازیابی محدود موفقیتآمیز نبود. |
renderer_initialization_failed | تلاش خودکار و تلاش اجباری هر دو در مقداردهی renderer شکست خوردند. |
renderer_unknown | شکست قابل دستهبندی نبود. |
سیاست پشتیبانی روبهروی کاربر، ماتریس مرورگرهای آزمایششده و راهنمای بازیابی در پشتیبانی از رندرینگ آمده است.
سطحهای کیفیت#
backend (webgpu یا webgl2)، سطح کیفیت (compatible، standard یا
enhanced) و در دسترسبودن قابلیتها محورهایی مستقل از هماند. انتخاب سطح با
زمان فریم پایدار همراه با یک warm-up کوتاه و hysteresis انجام میشود — هرگز با
مدل GPU یا VRAM، که شرط قطعی نیستند. پس از warm-up، کنترلکنندهٔ کیفیت
میانگین پیراستهٔ زمان فریم هر پنجره را ارزیابی میکند — پنجره مرتب میشود،
۵٪ سریعترین و ۵٪ کندترین کنار گذاشته میشوند و از بقیه میانگین گرفته میشود —
و standard را فقط پس از پنجرههای متوالیِ عبورکرده از آستانه به compatible
تنزل میدهد. این سنجه عمداً صدک نیست: نمایشگر قفلشده به vsync فقط بازههای
کامل نوسازی را گزارش میکند، پس روی ۶۰ هرتز هر نمونه یا حدود ۱۶.۷ms است یا
حدود ۳۳.۳ms و نرخ فریم در نسبت میان این دو زندگی میکند — چیزی که صدک نمیتواند
بخواند و میانگین میتواند. هر آستانهای که اینجا تنظیم شود باید از کف ۱۶.۶۷
میلیثانیهایِ vsync هم فاصله بگیرد، وگرنه سختگیر نیست بلکه دستنیافتنی است.
دستگاهِ تنزلیافته پس از یک دورهٔ پایدارِ پنجرههای راحت دوباره به standard
ارتقا مییابد. چون ارتقا دقیقاً همان باری را روشن میکند که سطح سنگینتر را
تعریف میکند، سه سازوکار جلوی نوسان را میگیرند: یک cooldown پس از هر تغییر
خودکار، یک backoff دوبرابرشونده برای هر ارتقایی که دوام نیاورد، و درز
notifySceneChange که محیط هنگام تعویض Zone صدا میزند تا هزینهٔ ساختِ صحنه —
کامپایل شیدر، آپلود بافت، تولید زمین — هرگز بهجای هزینهٔ اجرای آن سنجیده نشود.
محیطی که تنظیم کیفیت ارائه میدهد آن را از مسیر setQualityPreference میفرستد،
نه setQuality: یک سطح مشخص کنترلکننده را پین میکند و هر دو لِین خودکار را
میخواباند، و "auto" اختیارشان را بازمیگرداند. نوشتن یک سطح در حالی که
لِینها زندهاند دعوا است نه ترجیح — لِین تنزل میدهد، همگامسازیِ تنظیماتِ محیط
سطح را دوباره مینویسد، و جهان هر cooldown یکبار پلک میزند.
مسیر بازیابی سازگاری اجباری مستقیماً در compatible آغاز
میشود. سطح enhanced فقط plumbing است: هرگز بهصورت خودکار انتخاب نمیشود و
تا رسیدن قابلیتهای WebGPU-nativeاش بهعنوان پشتیبانی عملیاتی معرفی نمیشود.
تنزل سطح هرگز DPR بوم را وسط سشن تغییر نمیدهد: backing store یکبار و بر
اساس سطح اولیه هنگام آغاز سشن renderer resolve میشود، بنابراین drawing
buffer بهخاطر تغییر سطح بازتخصیص (و برای لحظهای خالی) نمیشود. سطحهای
پایینتر هزینه را از مسیر پیچیدگی صحنه — tessellation، instancing، سایهها و
post-processing — کم میکنند، نه از مسیر وضوح تصویر.
تشخیص و تلهمتری#
readRendererStats و detectRendererBackend تشخیص بکاند-خنثی روی هر دو
backend فراهم میکنند (شمارش draw call، مثلث، geometry و texture و backend
فعال)، و پلهای end-to-end هاب و سالن کنفرانس backend فعال را با همین واژگان
گزارش میکنند. حلقهٔ WebGPU شمارندههای هر فریم را پیش از useFrame در R3F
صفر میکند؛ بنابراین تشخیص از useAfterRenderFrame و پس از gl.render()
نمونه میگیرد. خواندن info.render.calls بهعنوان مقدار هر فریم نیز ممنوع
است، چون در WebGPU تجمعی است و readRendererStats مقدار drawCalls را انتخاب
میکند. رخدادهای تلهمتری امن برای حریم خصوصی و محدود به یک فهرست مجاز
بستهاند: نوع رخداد (backend-selected، first-frame، init-failed،
unsupported، device-lost، recovered)، backend، استفاده از fallback، سطح
کیفیت، سطل مدت مقداردهی، کد دلیل و نتیجهٔ بازیابی. فروشنده و مدل GPU، رشتههای
maskنشدهٔ renderer، VRAM، اطلاعات خام adapter، خطاهای خام درایور و سورسهای
shader هرگز جمعآوری یا نمایش داده نمیشوند؛ صفحهٔ پشتیبانینشده فقط کدهای دلیل
پالایششده را نشان میدهد.
زمانسنجی تجمیعی GPU#
محیطهایی که به مدت GPU مربوط به renderer نیاز دارند با گزینهٔ
gpuTiming: "aggregate" روی EnvironmentCanvas فعالش میکنند؛ factory مشترک
این گزینه را به ردیابی timestamp در renderer نگاشت میکند. پیشفرض off است
که در آن هیچ پرسوجوی زمانیای هرگز
تخصیص نمییابد. پس از مقداردهی، خودِ backend ردیابی را وقتی دستگاه فاقد قابلیت
timestamp است غیرفعال میکند، پس فعالکردن این گزینه روی دستگاه پشتیبانینشده
امن است و صرفاً هیچ مقداری تولید نمیکند.
نمونهها بدون اینکه حلقهٔ رندر هرگز متوقف شود resolve میشوند: هر ۴۵ فریم
رندرشده (حدود ۷۵۰ میلیثانیه در ۶۰ فریم بر ثانیه) زمان اجرای بوم یک resolve
ناهمگام timestamp برای صفهای
render و compute آغاز میکند و نتیجه را هر زمان که readback برسد تحویل میدهد.
هر نمونهٔ resolveشده مدتهای کلِ renderer را گزارش میکند —
{ sampledFrameRenderMs, sampledFrameComputeMs, framesSinceLastSample }
— یعنی میلیثانیههای GPU یک فریم، آخرین فریم دستهای که three رزالو کرده،
بدون هیچ مقیاسگذاری. framesSinceLastSample تراکم نمونهبرداری است و هرگز
مخرج نیست؛ تقسیم مدتها بر آن، زمان GPU را به اندازهٔ کل آهنگ نمونهبرداری
کمتر از واقع گزارش میکند. اینها زمانهای کل renderer هستند، هرگز زمان
اختصاصی یک Plugin یا Avatar نیستند. روی backend داخلی WebGL2 وقتی extension
زمانسنج آن موجود باشد مقادیر جریان مییابند؛ EXT_disjoint_timer_query
همزمان فقط یک پرسوجوی فعال میپذیرد، پس انتظار میرود render context های
تودرتو آنجا پرسوجوی مستقل نگیرند — محدودیتی که استدلال کردهایم ولی
اندازه نگرفتهایم. صفی که هیچ کاری ثبت نکرده (compute در صحنهای
فقط-رندر) صفر گزارش میکند؛ resolveی که وسط بازیابی شکست بخورد نمونهاش را
حذف میکند تا عدد ساختگی گزارش نشود.
مصرفکنندهها آخرین نمونه را از طریق callback مربوط به
onGpuTimingWindow در بوم یا hook عمومی useRendererGpuTiming() میخوانند
که تا وقتی گزینه خاموش است، قابلیت موجود نیست یا هنوز هیچ پنجرهای resolve
نشده، null برمیگرداند؛ دروازه مقدار را هنگام گمشدن دستگاه،
پشتیبانینشدن و تلاش دوباره نیز صفر میکند تا مدتهای کهنه از یک نسل
renderer به نسل بعدی سرایت نکند. resolver همراه با نسل renderer خود dispose
میشود، پس پس از unmount یا گمشدن دستگاه هیچ readback معلقی باقی نمیماند.
زمانسنجی GPU یک تشخیص محلی است، نه تلهمتری: مقادیر فقط مدت به میلیثانیهاند،
تا وقتی محیط میزبان خودش آنها را ارسال نکند از پروسه خارج نمیشوند و هیچ
اطلاعاتی از فروشنده، مدل، VRAM یا adapter مربوط به GPU در آنها نیست. چون
هیچ رخداد تلهمتریای منتشر نمیشود، telemetryAllowlist در
quality/rendering-profile.json آگاهانه بدون تغییر باقی میماند.
مواد TSL-first#
WebGPURenderer اجرا نمیکند: ShaderMaterial، RawShaderMaterial، وصلههای
onBeforeCompile و EffectComposer قدیمی را — حتی روی backend داخلی WebGL2
خود. بنابراین مواد سفارشی تازه فقط با TSL/NodeMaterial (three/tsl) نوشته
میشوند. مواد عملیاتی نخستین خارج از Park — ذرات مسیر قدسی هاب، پردهٔ دروازهٔ
درخشان هاب و آسمان گرادیانی — با برابری بصری در فرمولها، رنگها، blending،
شفافیت، depthWrite و depthTest تبدیل شدهاند. یک بررسی ایستای
سیاست، نقاط ورود GLSL تازه را در کد زمان اجرای جهانِ عضو نمایه رد میکند. بازطراحی
WebGPU-native — compute shader، storage buffer، GPU culling، indirect draw و
post-processing با TSL — آگاهانه به فاز بعدی موکول شده است.
داراییهای باینری glTF#
جهانهای first-party رویهایاند و مخزن هیچ دارایی سهبعدی باینری منتشر نمیکند.
این یک انتخاب است، نه محدودیت: برای محصولی که از ایران سرو میشود، وابستگی به
CDN، دانلود transcoder از میزبانی که ممکن است در دسترس نباشد و ایست بوت روی
کش سرد را حذف میکند. با این حال محیطها و پلاگینهای شخص ثالث به یک مسیر بارگذاری دارند، بنابراین
ADR-0079 دقیقاً
یک مسیر اضافه میکند: useGltfPack از @al-amr/r3f، که فقط داخل درخت
EnvironmentCanvas قابل استفاده است. GLTFLoader پیکربندیشده export
نمیشود.
هیچ چیز از یک شبکهٔ توزیع محتوا گرفته نمیشود. KTX2Loader و DRACOLoader به
ASSET_DECODER_BASE_PATH (/al-amr/decoders/) روی مبدأ خودِ محیط اشاره
میکنند، که از باینریهای vendorشده در پکیج سرو میشود و اسکریپتی که پکیج
ارائه میدهد آنها را در public/ کپی میکند. هیچ مسیر دیکودری هرگز از
import.meta.url مشتق نمیشود، چون آن مسیر زیر باندل تولیدی حل میشود و زیر
سرور توسعه نه. useGLTF از @react-three/drei اینجا قابل استفاده نیست: در
نسخهٔ pinشدهٔ cohort، Draco را از gstatic و transcoder مربوط به Basis را از
jsDelivr میگیرد.
GLTFLoader، KTX2Loader، DRACOLoader و MeshoptDecoder تنها در نخستین
بارگذاری و با import پویا وارد میشوند — دقیقاً همانطور که factory رندرر
three/webgpu را میآورد — پس هیچکدام به گراف اولیهٔ جاوااسکریپت نمیرسند. هر
بارگذاری کران زمانی دارد (پیشفرض ۲۰ ثانیه) و AbortSignal را رعایت میکند،
چون WorkerPool در درخت افزونههای Three هیچ listener خطایی ثبت نمیکند و هیچ
rejectی نگه نمیدارد: workerای که میمیرد، promise خود را تا ابد بینتیجه رها
میکند. بارگذاری دارایی مجاز است شکست بخورد؛ مجاز نیست بوت را معلق کند.
هر لودر به یک رندرر تعلق دارد. detectSupport فقط روی رندرر مقداردهیشده
معتبر است، WebGPU فرمتهایش را از hasFeature گزارش میکند، و دو backend به
فرمتهای GPU متفاوتی transcode میکنند — پس هویت خودِ شیء رندرر، نسل کش است.
این هویت دقیقاً هنگام رویدادهایی عوض میشود که یک تکسچر رمزگشاییشده را باطل
میکنند، از جمله رویدادهایی که هیچ شمارندهٔ دروازهای ردشان را نمیگیرد: تلاش
مجدد کراندار روی WebGL2 اجباری، حلقهٔ تثبیت سطح، و بازیابی از دست رفتن دستگاه.
با تغییر آن، لودر و worker poolش dispose میشوند، ورودیهای کش آن رندرر آزاد
میشوند و لودر از نو ساخته میشود. پکها با useDisposableRenderResource
شمارش مرجع میشوند، پس دو زون که یک URL را میخواهند یکبار دانلود و transcode
میکنند و منابع با رفتن آخرین مصرفکننده آزاد میشود.
بارگذاری ناموفق یک خطای برنامه است. AssetLoadError یک reason پایدار حمل
میکند — network، decode، timeout، aborted، renderer_unavailable — و
به RenderingErrorBoundary میرسد؛ نیامدن یک مدل هرگز به معنی دستگاه
پشتیبانینشده نیست، پس کدهای دلیل بالا تغییر نکردهاند و صفحهٔ پشتیبانینشده
هرگز برای آن نمایش داده نمیشود. متن خطا از همان پاکسازی خطاهای رندرر عبور
میکند و هیچ مسیر داخلی یا جزئیات دستگاهی حمل نمیکند.
scripts/check-performance-budget.mjs کنار بودجهٔ جاوااسکریپت، یک لِین دارایی
سهبعدی برای هر محیط اندازه میگیرد: مجموع بایت glTF، مجموع بایت تکسچر، و رد
کردن هر تکسچری در یک بستهٔ دارایی سهبعدی که نه KTX2 است و نه WebP. دیکودرهای
vendorشده بیرون هر دو لِیناند — بایت پلتفرماند و هرگز در گراف اولیه نیستند.
استثناها و موارد مستثنا#
-
boot loader یک سطح بوت است، نه renderer جهان، و اصلاً هیچچیزی از پشتهٔ گرافیکی نمیخواهد. مارکآپ و CSS و یک اسکریپت درونخطی بود که
alAmrEnvironmentHandshake()آن را در سند هر محیط مینوشت، پس در اولین فریم سند نقاشی میشد نه بعد از رسیدن یک باندل. ADR-0094 حذفش کرد: کلاینت دسکتاپ تنها میزبان است و خودش بوت را در یک view بالای سندِ جهان میکشد. خاصیتش عوض نشده — چیزی که اول نقاشی میشود هنوز از پشتهٔ گرافیکی هیچ نمیخواهد.این جایگزین دیورامایی از جنس WebGPU شد که در یک Worker روی OffscreenCanvas رندر میکرد و دستگاهش را فقط پس از پخش کاملِ کوریوگرافی پس میداد — یعنی روی هر رفرشِ گرم دو دستگاه حدود ۵٫۵ ثانیه زنده بودند و هرکدام دیگری را کند میکرد، دقیقاً روی سختافزاری که کمترین توان پرداخت هیچکدام را داشت. آن استثنای renderer که ADR-0048 برای آن موتور ثبت کرده بود با خودش برداشته میشود: حالا در تمام طول یک بوت دقیقاً یک دستگاه گرافیکی روی صفحه است، و
docs/design/boot-experience.md(قاعدهٔ R9) همانجا نگهش میدارد. -
قابلیتهای WebGPU-native و هر قرارداد سازگاری موتور در سطح Registry، درست همانطور که در ADR-0048 ثبت شده، به تعویق ماندهاند.