Liquid Glass Easy

المحركات والعارض

يقرأ Impeller الخلفية، ويحتاج Skia إلى التقاط. والعناصر نفسها.

يحتاج الانكسار إلى البكسلات التي خلف الزجاج. يستطيع Impeller أن يناول الشيدر الخلفيةَ الحية، ولا يستطيع Skia ذلك. هذا الفارق وحده هو القصة كلها، وLiquidGlassView هو الجواب عنه.

الإعدادما تفعله العدسة
Impeller — الافتراضي على iOS و Android وسطح المكتبتكسر الخلفية الحية: كل ما رسمه تطبيقك خلفها. بلا عارض ولا عنصر خلفية ولا إعداد.
Skia داخل LiquidGlassViewتكسر backgroundWidget الملتقَط للعارض، أينما جلست العدسة داخل child الخاص به.
Skia بلا عارضتتراجع إلى سطح مصنفر — ضبابية ولون وحافة — وتسجّل تنبيه تنقيح واحداً.

الأداء حسب المحرك والمنصة#

Impeller هو أفضل محرك للزجاج — أداءً ومظهراً. تقرأ العدسة الخلفية الموجودة أصلاً لدى وحدة الرسوميات، فيكلّف الإطار تمريرة شيدر واحدة لكل عدسة (واحدة لكل دفعة مع LiquidGlassBatch) ولا يُرسم شيء مرتين. أما Skia فعليه أن ينتج الخلفية كنسيج أولاً، وهذا الالتقاط هو كامل كلفته. ومقدار الضرر يعتمد على المكان الذي يعمل فيه Skia.

أينالأداءلماذا
Impeller — iOS و Android وسطح المكتبالأفضلخلفية حية بلا التقاط. وتبقى العدسات الكثيرة رخيصة داخل دفعة.
Skia — سطح المكتبأقوى Skiaوحدة رسوميات سريعة وذاكرة وفيرة تمتصّان الالتقاط؛ والزجاج الكامل بـpixelRatio: 1.0 مع التقاط لحظي مريح.
Skia — الهاتفجيدالالتقاط في المتناول لعدسات قليلة. وخفض pixelRatio مع realTimeCapture: false للخلفيات الساكنة يبقيه سلساً على الهواتف الأقدم.
Skia — الويبالأضعفيعبر الالتقاط مسار WebGL في المتصفح كل إطار، فهو أبطأ مكان للزجاج الكامل. أبقِ عدد العدسات قليلاً، وخفّض pixelRatio، وفضّل الزجاج الخفيف لكل ما هو كثيف.

تضبيب ثم شيدر، أم شيدر ثم تضبيب#

يبدو المحركان متطابقين تقريباً، والفارق الوحيد الذي تراه يأتي من ترتيب تشغيل التضبيب وشيدر الانكسار. على Impeller تُضبَّب الخلفية أولاً ثم يعمل الشيدر على البكسلات المضبَّبة: فالانكسار والتكبير والحافة كلها تثني صورة ناعمة أصلاً، فتكون حافة الزجاج بنعومة داخله وتُقرأ القطعة كلها كمادة واحدة.

على Skia الترتيب معكوس: يعمل الشيدر أولاً على الالتقاط الحاد، ثم يُطبَّق التضبيب بعده — وعلى الداخل فقط، فلا تُضبَّب حافة الزجاج. يحتفظ الشريط المنكسر على طول الحافة بتفاصيله، فتبدو حافة Skia أحدّ وأوضح قليلاً من الأسلوب نفسه على Impeller. وهو فارق صغير، وLiquidGlassStyle نفسه يعطي الشكل واللون والانكسار ذاته على الاثنين؛ ما يتغيّر هو نعومة الحافة فحسب. والاستثناء الوحيد على Skia هو حين تستخدم المُدمِج: فشيدر LiquidGlassBlender يضبّب العدسات بنفسه، فتُضبَّب حافتها أيضاً وتبدو الحافة كما على Impeller.

LiquidGlassView#

العارض هو خط الالتقاط: يحوّل backgroundWidget إلى نسيج ثم يسلّم هذا النسيج لكل عدسة داخل child. قائمة معاملاته الكاملة مُولَّدة من الشيفرة في صفحة الواجهة، وهذه هي التي ستمدّ يدك إليها فعلاً.

المعاملالافتراضيما يتحكم به
backgroundWidget—الطبقة التي تُلتقط. العدسات تكسر هذه، لا child.
childnullواجهتك. أي شيء هنا يمكن أن يحمل عدسة، على أي عمق.
pixelRatio1.0دقة الالتقاط. ما دون 1 أرخص وأنعم، والزجاج يخفي قدراً مدهشاً من ذلك.
realTimeCapturetrueإعادة الالتقاط كل إطار. أطفئه لخلفية لا تتحرك.
useSynctrueيُبقي الالتقاط متزامناً مع الإطار قيد البناء. إطفاؤه أرخص وقد يتأخر إطاراً واحداً.
refreshRatedeviceRefreshRateيحدّ من تواتر الالتقاط، بمعزل عن معدّل الشاشة.
useImpellerBackdropnullيتجاوز الاكتشاف التلقائي للمحرك. اتركه فارغاً إلا إن كنت تختبر المسار الآخر عن قصد.
controllernullLiquidGlassViewController، من أجل captureOnce() — لقطة واحدة عند إشارتك — ولقيادة التكيّف يدوياً.
adaptiveSamplingnullعلى المحركين. يشغّل التقاط الإضاءة الصغير الذي يقرأه LiquidGlassAdaptivity. والفارغ يعني ألا معاينة إطلاقاً، فترتدّ الأسطح المتكيّفة إلى تخمين. والتقاط واحد يخدم كل سطح متكيّف في العارض، وهو مقيّد بالخمول: شاشة ساكنة لا تعاين شيئاً.
batchtrueعلى Impeller فقط. يضع عدسات child على قراءة واحدة مشتركة للخلفية، وعدسات backgroundWidget على أخرى. وهو خامل على مسار الالتقاط الذي يقرأ أصلاً مرة واحدة لكل عارض. انظر الحزمة.
regionCapturefalseيلتقط مستطيل كل عدسة بدل الخلفية كلها — مكسبٌ حين تجلس عدسة صغيرة على خلفية كبيرة، وخسارةٌ حين تنتشر عدسات كثيرة عليها. على مسار useSync في Skia فقط.

التقاط واحد أم كل إطار#

يرسم realTimeCapture: true الخلفيةَ في كل إطار. وهذا صحيح لقائمة تنساب أو فيديو أو تدرّج متحرك — أي شيء يتحرك حقاً. وهو هدر خالص لخلفية لا تتغيّر أبداً.

حين تكون الخلفية ساكنة، خذ لقطة واحدة واكسرها إلى الأبد. تحريك عدسة فوق صورة ثابتة لا يكلّف شيئاً إضافياً: يُعاد تشغيل الشيدر، ولا يُعاد رسم أي شيء.

Dart
final controller = LiquidGlassViewController();

LiquidGlassView(
  controller: controller,
  backgroundWidget: const MyBackground(),
  realTimeCapture: false,
  child: const MyGlassUI(),
);

// Re-snapshot by hand after the background actually changes:
await controller.captureOnce();

يشغّل عرض هذه الصفحة خلفيةً لا تكفّ عن الانزلاق. اضبط الالتقاط على مجمَّد فيظل الزجاج يكسر أشرطةً تجاوزها الزمن بينما تمضي الأشرطة حوله — والفارق بينهما هو مقدار قِدَم اللقطة، وcapture once يغلقه.

متى يريد Impeller عارضاً رغم ذلك#

على Impeller لا تحتاج العدسة إلى شيء: تقرأ الخلفية الحية أينما جلست. فيبدو العارض إجراءً شكلياً يخصّ Skia — وهو كذلك بالنسبة للانكسار. لكنه ليس كذلك لبقية ما يوفّره، واثنان منها يستحقان أن تمدّ يدك إليهما على Impeller أيضاً.

  • `adaptiveSampling` — وهو غير اختياري. يحكم LiquidGlassAdaptivity على الخلفية بقراءة بكسلاتها، وقراءة البكسلات تعني التقاطاً. ولا التقاط لدى Impeller: فهو لا يأخذ واحداً أبداً. فـLiquidGlassView مضبوطاً عليه adaptiveSampling هو الطريق ليجد الزجاج المتكيّف ما يحكم عليه على كلا المحركين — وبدونه يرتدّ كل سطح متكيّف إلى التخمين من سِمة التطبيق. وهو التقاط واحد ضئيل (بنسبة بكسل 0.05، ثماني مرات في الثانية) تتشاركه كل الأسطح المتكيّفة في العارض، ويتوقف تماماً ما دامت الشاشة ساكنة.
  • `batch` — خاص بـImpeller بطبعه. يحزم العارض عدساته افتراضياً، وهو توفير خالص على Impeller: فمسار الالتقاط يقرأ أصلاً مرة واحدة لكل عارض، بينما على Impeller تنسخ كل عدسة الخلفية لنفسها ما لم تقل الحزمة غير ذلك.
  • شجرة واحدة، محركان. لا يكلّف العارض على Impeller شيئاً وراء العنصر نفسه، فكتابة الصفحة داخله تعني أن الشيفرة ذاتها تعمل على الهاتف وعلى الويب بلا تفرّع.

ترجم الشيدرات قبل الإطار الأول#

هذا ليس عن الالتقاط، ولا عن المحرك أيضاً — بل ينطبق على الاثنين. فبرامج الزجاج تُترجَم مرة واحدة في كل تشغيل للتطبيق، على يد أول عدسة تُركَّب، والعدسة التي لم يصل برنامجها بعد ترسم بديلها المصنفر حتى يصل. وLiquidGlassShaders.ensureLoaded() في main() ينقل تلك الترجمة إلى ما قبل الإطار الأول.

Dart
Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  // Every program at once: the lens's, and the blender's.
  await LiquidGlassShaders.ensureLoaded();
  runApp(const MyApp());
}
  • للحالة العامة: useSync: true و pixelRatio: 0.8–1.0.
  • عندما يسبق الأداء: useSync: false و pixelRatio: 0.5–0.7.
  • خلفية ساكنة: realTimeCapture: false يتفوّق على كل المقابض الأخرى مجتمعة.

التعليقات

التعليقات هي مناقشات GitHub — يمكنك الرد من أيٍّ من المكانين.