إدارة بيانات Vertex

إنّ ضغط البيانات وتخطيطها الجيدَين أمران أساسيان في أداء أي تطبيق رسومي، سواء كان التطبيق يتكون من واجهات مستخدم ثنائية الأبعاد أو لعبة عالم مفتوح كبيرة ثلاثية الأبعاد. يشير الاختبار الداخلي الذي تم إجراؤه باستخدام أداة تحليل الإطارات في Android GPU Inspector على العشرات من أفضل ألعاب Android إلى أنّه يمكن إجراء الكثير لتحسين إدارة بيانات الرأس. لاحظنا أنّه من الشائع أن تستخدم بيانات الرأس الدقة الكاملة، والقيم العائمة 32 بت لجميع سمات الرأس، إلى جانب تنسيق المخزن المؤقت للرأس الذي يستخدم مجموعة من الهياكل المنسَّقة بسمات متشابكة بالكامل.

تناقش هذه المقالة كيفية تحسين أداء الرسومات في تطبيق Android باستخدام الأساليب التالية:

  • ضغط Vertex
  • تقسيم تدفق Vertex

يمكن أن يؤدي تنفيذ هذه الأساليب إلى تحسين استخدام النطاق الترددي للذاكرة بنسبة تصل إلى 50%، وتقليل ازدحام ناقل الذاكرة من خلال وحدة المعالجة المركزية، وتقليل فترات الانقطاع في ذاكرة النظام، وتحسين عمر البطارية، وكل ذلك مكاسب للمطورين والمستخدمين النهائيين على حد سواء.

تأتي جميع البيانات المقدَّمة من مثال لمشهد ثابت يحتوي على ما يقارب 19,000,000 رأس يتم تشغيلها على Pixel 4:

مشهد نموذجي يضم 6 حلقات ورؤوس عمودية بطول 19 مترًا

الشكل 1: نموذج لمشهد يتضمّن 6 حلقات ورؤوس رأسية بطول 19 متر

ضغط Vertex

ضغط Vertex هو مصطلح شامل لتقنيات الضغط مع فقدان البيانات التي تستخدم حزمًا فعالة لتقليل حجم بيانات الرأس أثناء وقت التشغيل وأثناء التخزين. تقليل حجم الرؤوس له فوائد عديدة، منها تقليل معدل نقل بيانات الذاكرة على وحدة معالجة الرسومات (من خلال تبادل الحوسبة لمعدل نقل البيانات)، وتحسين استخدام ذاكرة التخزين المؤقت، وربما تقليل خطر تسرب السجلات.

تشمل الأساليب الشائعة لضغط Vertex ما يلي:

  • تقليل الدقة العددية لسمات بيانات الرأس (مثل عدد عائم 32 بت إلى عدد عائم 16 بت)
  • تمثيل السمات بصيغ مختلفة

على سبيل المثال، إذا كان الرأس يستخدم أعداد عشرية كاملة 32 بت للموضع (vec3) وعادي (vec3) وتنسيق بنية (vec2)، سيؤدي استبدال كل هذه القيم بأعداد عشرية 16 بت إلى تقليل حجم الرأس بنسبة 50% (16 بايت في متوسط رأس الرأس 32 بايت).

مواضع Vertex

يمكن ضغط بيانات موضع Vertex من قيم النقاط العائمة بدقة كاملة 32 بت إلى قيم النقطة العائمة بنصف الدقة في الغالبية العظمى من الشبكات، ويتم دعم نصف العوامات في الأجهزة على جميع الأجهزة الجوّالة تقريبًا. وتظهر دالة التحويل - من float32 إلى float16 - على النحو التالي (تعديلها من هذا الدليل):

uint16_t f32_to_f16(float f) {
  uint32_t x = (uint32_t)f;
  uint32_t sign = (unsigned short)(x >> 31);
  uint32_t mantissa;
  uint32_t exp;
  uint16_t hf;

  mantissa = x & ((1 << 23) - 1);
  exp = x & (0xFF << 23);
  if (exp >= 0x47800000) {
    // check if the original number is a NaN
    if (mantissa && (exp == (0xFF << 23))) {
      // single precision NaN
      mantissa = (1 << 23) - 1;
    } else {
      // half-float will be Inf
      mantissa = 0;
    }
    hf = (((uint16_t)sign) << 15) | (uint16_t)((0x1F << 10)) |
         (uint16_t)(mantissa >> 13);
  }
  // check if exponent is <= -15
  else if (exp <= 0x38000000) {
    hf = 0;  // too small to be represented
  } else {
    hf = (((uint16_t)sign) << 15) | (uint16_t)((exp - 0x38000000) >> 13) |
         (uint16_t)(mantissa >> 13);
  }

  return hf;
}

هناك قيود على هذا النهج، إذ تقلّ الدقة كلما ابتعد الرأس عن نقطة الأصل، ما يجعلها أقل ملاءمة للشبكات ذات الحجم المكاني الكبير (الرؤوس التي تضمّ عناصر يزيد طولها عن 1024). يمكنك حل هذه المشكلة عن طريق تقسيم شبكة إلى أجزاء أصغر، مع توسيط كل مقطع حول أصل النموذج، والتحجيم بحيث تتناسب جميع الرؤوس لكل مقطع مع النطاق [-1، 1]، والذي يحتوي على أعلى دقة لقيم النقطة العائمة. ويشبه الرمز الزائف للضغط ما يلي:

for each position p in Mesh:
   p -= center_of_bounding_box // Moves Mesh back to the center of model space
   p /= half_size_bounding_box // Fits the mesh into a [-1, 1] cube
   vec3<float16> result = vec3(f32_to_f16(p.x), f32_to_f16(p.y), f32_to_f16(p.z));

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

vec3 in in_pos;

void main() {
   ...
   // bounding box data packed into uniform buffer
   vec3 decompress_pos = in_pos * half_size_bounding_box + center_of_bounding_box;
   gl_Position = proj * view * model * decompress_pos;
}

وتتضمّن الطريقة الأخرى استخدام الأعداد الصحيحة الصحيحة الموقَّعة (SNORM). تستخدم أنواع بيانات SNORM أعدادًا صحيحة بدلاً من النقطة العائمة لتمثيل القيم بين [-1، 1]. يوفر لك استخدام 16 بت SNORM للمواقع الإلكترونية توفير الوقت نفسه في الذاكرة مثل float16 بدون عيوب التوزيعات غير المنتظمة. تبدو عملية التنفيذ التي ننصح بها لاستخدام SNORM كما يلي:

const int BITS = 16

for each position p in Mesh:
   p -= center_of_bounding_box // Moves Mesh back to the center of model space
   p /= half_size_bounding_box // Fits the mesh into a [-1, 1] cube
   // float to integer value conversion
   p = clamp(p * (2^(BITS - 1) - 1), -2^(BITS - 1), 2^(BITS - 1) - 1) 
التنسيق حجم الملف
قبل vec4<float32> 16 بايت
بعد vec3<float16/SNORM16> 6 بايت

مسافة Vertex العادية ومساحة المماس

تكون القيم العادية الرأسية مطلوبة للإضاءة، ومساحة المماس لأساليب أكثر تعقيدًا مثل التخطيط العادي.

مساحة الظل

مساحة المماس هي نظام إحداثي يتكون فيه كل رأس من الخط المتجه الطبيعي، ظل الزاوية، ومتماسك. ولأن هذه المتجهات الثلاثة عادةً ما تكون متعامدة مع بعضها البعض، نحتاج فقط إلى تخزين اثنين منها ويمكننا حساب الخط الثالث بأخذ ناتج حاصل ضرب الاتجاهين الآخرين في خط الرأس.

يمكن عادةً تمثيل هذه المتجهات باستخدام أعداد عشرية 16 بت دون أي فقد ملموس في الدقة المرئية، لذا يعد هذا مكانًا جيدًا للبدء!

يمكننا الضغط أكثر باستخدام تقنية تُعرف باسم QTangents التي تخزن مساحة المماس بالكامل في ربع واحد. نظرًا لأنه يمكن استخدام الرباعيات لتمثيل الدوران، فمن خلال النظر في متجهات مساحة المماس كمتجهات عمودية لمصفوفة ثلاثية الأبعاد تمثل الدوران (في هذه الحالة من مساحة النموذج إلى مساحة المماس) يمكننا التحويل بين الاثنين! يمكن التعامل مع الخوارزمية الرباعية على أنّها vec4 من حيث البيانات، وتحويل من متجهات مساحة المماس إلى ظل الزاوية استنادًا إلى الورقة المرتبطة أعلاه وتعديلها من التنفيذ هنا على النحو التالي:

const int BITS = 16

quaternion tangent_space_to_quat(vec3 normal, vec3 tangent, vec3 bitangent) {
   mat3 tbn = {normal, tangent, bitangent};
   quaternion qTangent(tbn);
   qTangent.normalize();

   //Make sure QTangent is always positive
   if (qTangent.w < 0)
       qTangent = -qTangent;

   const float bias = 1.0 / (2^(BITS - 1) - 1);

   //Because '-0' sign information is lost when using integers,
   //we need to apply a "bias"; while making sure the Quaternion
   //stays normalized.
   // ** Also our shaders assume qTangent.w is never 0. **
   if (qTangent.w < bias) {
       Real normFactor = Math::Sqrt( 1 - bias * bias );
       qTangent.w = bias;
       qTangent.x *= normFactor;
       qTangent.y *= normFactor;
       qTangent.z *= normFactor;
   }

   //If it's reflected, then make sure .w is negative.
   vec3 naturalBinormal = cross_product(tangent, normal);
   if (dot_product(naturalBinormal, binormal) <= 0)
       qTangent = -qTangent;
   return qTangent;
}

ستتم تسوية حجم الرباعي، وستتمكن من ضغطه باستخدام SNORMs. تتميز SNORMs 16 بت بدقة عالية وتوفير في الذاكرة. يمكن أن توفر SNORMs 8 بت المزيد من التوفير، ولكنها قد تتسبب في حدوث أخطاء في مواد محددة للغاية. يمكنك تجربة الاثنين ومعرفة ما يناسب مواد العرض لديك على أفضل نحو. يبدو ترميز العنصر الرباعي على النحو التالي:

for each vertex v in mesh:
   quaternion res = tangent_space_to_quat(v.normal, v.tangent, v.bitangent);
   // Once we have the quaternion we can compress it
   res = clamp(res * (2^(BITS - 1) - 1), -2^(BITS - 1), 2^(BITS - 1) - 1);

لفك ترميز الرباعي في أداة تظليل الرأس (تعديل من هنا):

vec3 xAxis( vec4 qQuat )
{
  float fTy  = 2.0 * qQuat.y;
  float fTz  = 2.0 * qQuat.z;
  float fTwy = fTy * qQuat.w;
  float fTwz = fTz * qQuat.w;
  float fTxy = fTy * qQuat.x;
  float fTxz = fTz * qQuat.x;
  float fTyy = fTy * qQuat.y;
  float fTzz = fTz * qQuat.z;

  return vec3( 1.0-(fTyy+fTzz), fTxy+fTwz, fTxz-fTwy );
}

vec3 yAxis( vec4 qQuat )
{
  float fTx  = 2.0 * qQuat.x;
  float fTy  = 2.0 * qQuat.y;
  float fTz  = 2.0 * qQuat.z;
  float fTwx = fTx * qQuat.w;
  float fTwz = fTz * qQuat.w;
  float fTxx = fTx * qQuat.x;
  float fTxy = fTy * qQuat.x;
  float fTyz = fTz * qQuat.y;
  float fTzz = fTz * qQuat.z;

  return vec3( fTxy-fTwz, 1.0-(fTxx+fTzz), fTyz+fTwx );
}

void main() {
  vec4 qtangent = normalize(in_qtangent); //Needed because 16-bit quantization
  vec3 normal = xAxis(qtangent);
  vec3 tangent = yAxis(qtangent);
  float biNormalReflection = sign(in_qtangent.w); //ensured qtangent.w != 0
  vec3 binormal = cross(normal, tangent) * biNormalReflection;
  ...
}
التنسيق حجم الملف
قبل vec3<float32> + vec3<float32> + vec3<float32> 36 بايت
بعد vec4<SNORM16> 8 بايت

العادية فقط

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

إسقاط وحدة كرة كروية على شكل ثماني الأوجه وعرض الشكل الثماني الأوجه على مستوى ثنائي الأبعاد

الشكل 2: رسم بياني ثماني الأوجه (المصدر)

const int BITS = 8

// Assumes the vector is unit length
// sign() function should return positive for 0
for each normal n in mesh:
  float invL1Norm = 1.0 / (abs(n.x) + abs(n.y) + abs(n.z));
  vec2 res;
  if (n.z < 0.0) {
    res.x = (1.0 - abs(n.y * invL1Norm)) * sign(n.x);
    res.y = (1.0 - abs(n.x * invL1Norm)) * sign(n.y);
  } else {
    res.x = n.x * invL1Norm;
    res.y = n.y * invL1Norm;
  }
  res = clamp(res * (2^(BITS - 1) - 1), -2^(BITS - 1), 2^(BITS - 1) - 1)

فك الضغط في أداة تظليل الرأس (للتحويل مرة أخرى إلى الإحداثيات الديكارتية) غير مكلف؛ بالنسبة إلى معظم أجهزة الجوال الحديثة، لم نشهد أي تراجع كبير في الأداء عند تطبيق هذه التقنية. فك الضغط في أداة تظليل الرأس:

//Additional Optimization: twitter.com/Stubbesaurus/status/937994790553227264
vec3 oct_to_vec(vec2 e):
  vec3 v = vec3(e.xy, 1.0 - abs(e.x) - abs(e.y));
  float t = max(-v.z, 0.0);
  v.xy += t * -sign(v.xy);
  return v;

يمكن أيضًا استخدام هذا النهج لتخزين مساحة المماس بالكامل، باستخدام هذه التقنية لتخزين الخط المتجه العادي وظل الزاوية باستخدام vec2<SNORM8> ولكنك ستحتاج إلى إيجاد طريقة لتخزين اتجاه ظل التمام (مطلوب للسيناريو الشائع الذي تكون فيه إحداثيات الأشعة فوق البنفسجية عكسية على أحد النماذج). تتمثل إحدى طرق تنفيذ ذلك في تعيين أحد مكونات ترميز الخط المتجه المماس ليكون موجبًا دائمًا، ثم قلب العلامة إذا كنت بحاجة إلى قلب اتجاه التمام والتحقق من ذلك في أداة التظليل الرأسي:

const int BITS = 8
const float bias = 1.0 / (2^(BITS - 1) - 1)

// Compressing
for each normal n in mesh:
  //encode to octahedron, result in range [-1, 1]
  vec2 res = vec_to_oct(n);

  // map y to always be positive
  res.y = res.y * 0.5 + 0.5;

  // add a bias so that y is never 0 (sign in the vertex shader)
  if (res.y < bias)
    res.y = bias;

  // Apply the sign of the binormal to y, which was computed elsewhere
  if (binormal_sign < 0)
    res.y *= -1;

  res = clamp(res * (2^(BITS - 1) - 1), -2^(BITS - 1), 2^(BITS - 1) - 1)
// Vertex shader decompression
vec2 encode = vec2(tangent_encoded.x, abs(tangent_encoded.y) * 2.0 - 1.0));
vec3 tangent_real = oct_to_vec3(encode);
float binormal_sign = sign(tangent_encode.y);
التنسيق حجم الملف
قبل vec3<float32> 12 بايت
بعد vec2<SNORM8> 2 بايت

إحداثيات Vertex للأشعة فوق البنفسجية

عادةً ما يتم تخزين إحداثيات الأشعة فوق البنفسجية التي تُستخدم في تعيين الهيئة (من بين أمور أخرى) باستخدام أعداد عشرية 32 بت. يؤدي ضغطها باستخدام عدد عائم 16 بت إلى حدوث مشاكل دقة في الزخارف الأكبر من 1024×1024؛ وتعني دقة النقطة العائمة بين [0.5، 1.0] أن القيم ستزيد بمقدار أكبر من 1 بكسل!

والطريقة الأفضل هي استخدام الأعداد الصحيحة العادية غير الموقعة (UNORM)، وتحديدًا UNORM16، حيث يقدم ذلك توزيعًا موحدًا عبر نطاق إحداثيات الهيئة بالكامل، مما يدعم الزخارف حتى 65536x65536! وهذا يفترض أن تكون إحداثيات الزخرفة ضمن النطاق [0.0، 1.0] لكل عنصر، وقد لا يكون ذلك هو الحال اعتمادًا على الشبكة المتداخلة (على سبيل المثال، يمكن أن تستخدم الجدران إحداثيات التفاف الهيئة التي تتجاوز 1.0) لذا ضع ذلك في الاعتبار عند النظر إلى هذه التقنية. ستبدو دالة التحويل على النحو التالي:

const int BITS = 16

for each vertex_uv V in mesh:
  V *= clamp(2^BITS - 1, 0, 2^BITS - 1);  // float to integer value conversion
التنسيق حجم الملف
قبل vec2<float32> 8 بايت
بعد vec2<UNORM16> 4 بايت

نتائج ضغط Vertex

أدت تقنيات ضغط الرأس هذه إلى انخفاض بنسبة 66% في مساحة تخزين الذاكرة الرأسية، حيث ارتفعت من 48 بايت إلى 16 بايت. وقد تم توضيح ذلك على النحو التالي:

  • معدّل نقل البيانات في Vertex Memory Read:
    • الربط: 27 غيغابايت/ثانية إلى 9 غيغابايت/ثانية
    • العرض: من 4.5 مليار ثانية إلى 1.5 غيغابايت/ثانية
  • Vertex Fetch Stalls:
    • الربط: من 50% إلى 0%
    • العرض: من 90% إلى 90%
  • متوسط وحدات البايت/Vertex:
    • الربط: 48 مليار إلى 16 مليار
    • العرض: 52B إلى 18B

عرض أداة فحص وحدة معالجة الرسومات من نظام التشغيل Android للرؤوس غير المضغوطة

الشكل 3: طريقة عرض أداة فحص وحدة معالجة الرسومات لنظام التشغيل Android للرؤوس غير المضغوطة

عرض أداة فحص وحدة معالجة الرسومات من Android للرؤوس المضغوطة

الشكل 4: طريقة عرض أداة فحص وحدة معالجة الرسومات لنظام التشغيل Android للرؤوس المضغوطة

تقسيم تدفق Vertex

تعمل ميزة تقسيم البث من Vertex على تحسين تنظيم البيانات في المخزن المؤقت للرأس. هذا تحسين أداء ذاكرة التخزين المؤقت يُحدث فرقًا في وحدات معالجة الرسومات المستنِدة إلى مربّعات والتي يمكن العثور عليها عادةً في أجهزة Android، وخاصةً أثناء خطوة الدمج من عملية العرض.

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

يتضمن تقسيم التدفق إعداد المخزن المؤقت بالرأس مع قسم متجاورة من بيانات موضع الرأس وقسم آخر يحتوي على سمات رأس متشابكة. تقوم معظم التطبيقات عادةً بإعداد الموارد الاحتياطية وتتداخل بشكل كامل مع جميع السمات. يوضّح هذا العنصر المرئي الفرق:

Before:
|Position1/Normal1/Tangent1/UV1/Position2/Normal2/Tangent2/UV2......|

After:
|Position1/Position2...|Normal1/Tangent1/UV1/Normal2/Tangent2/UV2...|

يساعدنا النظر إلى الطريقة التي تجلب بها وحدة معالجة الرسومات لبيانات الرأس في فهم مزايا تقسيم البث. على افتراض أنّه للنقاش:

  • سطور ذاكرة التخزين المؤقت بحجم 32 بايت (حجم شائع جدًا)
  • تنسيق Vertex الذي يتألّف مما يلي:
    • الموضع، vec3<float32> = 12 بايت
    • vec3 عادي<float32> = 12 بايت
    • إحداثيات UV vec2<float32> = 8 بايت
    • إجمالي الحجم = 32 بايت

عندما تجلب وحدة معالجة الرسومات البيانات من الذاكرة للدمج، فإنها تسحب سطر ذاكرة تخزين مؤقت بسعة 32 بايت للعمل عليه. بدون تقسيم ساحة المشاركات الرأسية، ستستخدم فعليًا أول 12 بايت فقط من سطر ذاكرة التخزين المؤقت هذا للتجميع، وتتجاهل 20 بايت أخرى أثناء جلب الرأس التالي. عند تقسيم تدفق الرأس، ستكون مواضع الرأس متجاورة في الذاكرة، لذا عند سحب هذا المقطع الذي يبلغ 32 بايت إلى ذاكرة التخزين المؤقت، سيحتوي فعليًا على موضعي رأس كامل للعمل علىهما قبل الرجوع إلى الذاكرة الرئيسية لاسترجاع المزيد، وهو تحسُّن بمقدار 2x.

والآن، إذا دمجنا تقسيم تدفق الرأس مع ضغط الرأس، سيتم تقليل حجم موضع رأس واحد إلى 6 بايت، بحيث يكون لسطر ذاكرة التخزين المؤقت الفردي 32 بايت الذي يتم سحبه من ذاكرة النظام 5 مواضع رأس كاملة للعمل عليها، وهو ما يحسّن 5 أضعاف.

نتائج تقسيم ساحة مشاركات Vertex

  • معدّل نقل البيانات في Vertex Memory Read:
    • الربط: من 27 غيغابايت/ثانية إلى 6.5 غيغابايت/ثانية
    • العرض: من 4.5 غيغابايت/ثانية إلى 4.5 غيغابايت/ثانية
  • Vertex Fetch Stalls:
    • الربط: من 40% إلى 0%
    • العرض: من 90% إلى 90%
  • متوسط وحدات البايت/Vertex:
    • الربط: 48 مليار إلى 12 مليار
    • العرض: 52B إلى 52B

عرض أداة فحص وحدة معالجة الرسومات في نظام التشغيل Android لتدفقات الرأس غير المقسّمة

الشكل 5: طريقة عرض أداة فحص وحدة معالجة الرسومات في نظام التشغيل Android لتدفقات الرأس غير المقسّمة

عرض أداة فحص وحدة معالجة الرسومات من Android لتدفقات الرأس المقسّمة

الشكل 6: طريقة عرض أداة فحص وحدة معالجة الرسومات لنظام التشغيل Android لتدفقات الرأس المقسّمة

النتائج المركّبة

  • معدّل نقل البيانات في Vertex Memory Read:
    • الربط: من 25 غيغابايت/ثانية إلى 4.5 غيغابايت/ثانية
    • العرض: من 4.5 غيغابايت/ثانية إلى 1.7 غيغابايت/ثانية
  • Vertex Fetch Stalls:
    • الربط: 41% إلى 0%
    • العرض: من 90% إلى 90%
  • متوسط وحدات البايت/Vertex:
    • الربط: 48B إلى 8B
    • العرض: 52B إلى 19B

عرض أداة فحص وحدة معالجة الرسومات في نظام التشغيل Android لتدفقات الرأس غير المقسّمة

الشكل 7: طريقة عرض أداة فحص وحدة معالجة الرسومات في نظام التشغيل Android لمجموعات البث الرأسية غير المضغوطة وغير المضغوطة

عرض أداة فحص وحدة معالجة الرسومات في نظام التشغيل Android لتدفقات الرأس غير المقسّمة

الشكل 8: طريقة عرض أداة فحص وحدة معالجة الرسومات في نظام التشغيل Android لتدفقات الرأس المضغوطة

اعتبارات إضافية

بيانات المخزن المؤقت للفهرس 16 مقابل 32 بت

  • يتم دائمًا تقسيم/تقسيم الشبكات المتداخلة بحيث يمكن وضعها في مخزن مؤقت للفهرس 16 بت (بحد أقصى 65536 رأسًا فريدًا). سيساعد ذلك في العرض المفهرَس على الأجهزة الجوّالة، لأنّه من الأرخص استرجاع بيانات رأس الصفحة وسيستهلك طاقة أقل.

تنسيقات غير متوافقة لسمة المخزن المؤقت Vertex

  • لا يتوفر استخدام تنسيقات الرأس SSCALED على نطاق واسع على الأجهزة الجوّالة، وعند استخدامها يمكن أن يؤدي إلى إجراء مفاضلات مُكلِفة بشأن الأداء في برامج التشغيل التي تحاول محاكاتها إذا لم تكن الأجهزة تدعمها. اختر دائمًا SNORM وادفع تكلفة ALU ضئيلة لفك الضغط.