المزامنة بين المضيف والجهاز في Vulkan باستخدام السياج Fence

من ويكي
اذهب إلى: تصفح، ابحث

عندما نرسل الأوامر للمعالجة باستخدام vkQueueSubmit() فيمكننا انتظار انتهاء تنفيذ تلك الأوامر سواءً باستخدام vkQueueWaitIdle() أو vkDeviceWaitIdle()، إلا أن هذه الدوال ستوقف تنفيذ العملية إلى خمول الطابور أو الجهاز وانتهاء تنفيذ كل المهام المرسلة للطابور في حالة vkQueueWaitIdle() أو انتهاء كل المهام المرسلة لك طوابير الجهاز في حالة vkDeviceWaitIdle()، يمكننا أن نستغل وقت الإنتظار هذا في عمل شيء مفيد كالتجهيز للإطار التالي باستخدام كائن السياج VkFence والذي يوفر خيارات مزامنة أكثر من مجرد الانتظار.

السياج VkFence يستخدم للمزامنة بين المضيف والجهاز ويمكن استخدامه عند إرسال دفعة من أوامر التصيير عبر vkQueueSubmit()، السياج يكون دوماً في أحد الحالتين: إما جاهز signaled أو غير جاهز unsignaled، أبسط استخدام للسياج أن نجعله غير جاهز عنما ننشئه في البداية وعندما نمرره للدالة vkQueueSubmit() ستعطي إشارة الجهازية عن طريقه دلالة على أنتهاء تنفيذ الأوامر التي أرسلتها عبر الطابور، انتبه أن الحاجز يجب إعادة ضبطه باستخدام vkResetFences() لتغيير حالته لغير جاهز قبل استخدامه مع vkQueueSubmit().

إنشاء السياج VkFence

في البداية نستدعي الدالة vkCreateFence() لإنشاء كائن السياج VkFence:

VkResult vkCreateFence(
    VkDevice                                    device,
    const VkFenceCreateInfo*                    pCreateInfo,
    const VkAllocationCallbacks*                pAllocator,
    VkFence*                                    pFence);

حيث تأخذ في المعامل الأول كائن الجهاز، ثم البنية VkFenceCreateInfo التي تحتوي معلومات الكائن الذي نريد إنشاءه، يليها مؤشر للمخصص الذاكرة، وفي المعامل الأخير مؤشر لمتغير الذي سيخزن فيه كائن السياج.

البنية VkFenceCreateInfo:

typedef struct VkFenceCreateInfo {
    VkStructureType       sType;
    const void*           pNext;
    VkFenceCreateFlags    flags;
} VkFenceCreateInfo;
  • ضع VK_STRUCTURE_TYPE_FENCE_CREATE_INFO في sType و NULL> في pNext
  • flags راية بتات تأخذ قيمة واحدة وهي VK_FENCE_CREATE_SIGNALED_BIT لإنشاء سياج بحالة الجاهزية، عادة نضع صفر لإنشاء سياج غير جاهز، قد تستخدم VK_FENCE_CREATE_SIGNALED_BIT لو كان تسلسل برنامجك يتطلب هذا لأنه سيبدأ بانتظار جاهزية سياج مثلاً

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

إعادة ضبط السياج

لإعادة ضبط السياج لحالة عدم الجهازية استخدم الدالة vkResetFences():

VkResult vkResetFences(
    VkDevice                                    device,
    uint32_t                                    fenceCount,
    const VkFence*                              pFences);

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

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

انتظار جاهزية السياج

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

VkResult vkWaitForFences(
    VkDevice                                    device,
    uint32_t                                    fenceCount,
    const VkFence*                              pFences,
    VkBool32                                    waitAll,
    uint64_t                                    timeout);

تأخذ الدالة في المعامل الأول كائن الجهاز، ثم عدد السياجات التي تريد انتظارها، يليها مؤشر لمتغير أو مصفوفة سياجات، المعامل waitAll قيمة منطقية إذا وضعت VK_TRUE فإن الدالة ستنتظر جهازية كل السياجات الممررة في pFences، أما لو كانت VK_FALSE فإنها ستخرج من وضع الإنتظار فور جاهزية أي من السياجات، يمكنك استخدام الدالة vkGetFenceStatus() لتحديد أي السياجات تلك أصبحت جاهز، ضع في الإعتبار أن أكثر من سياج قد يصبح جاهز، وفي حال أردت الرجوع للإنتظار أزل تلك السياجات الجاهزة من pFences.

المعامل الأخير timeout يأخذ عدد صحيح 64 بت يمثل الوقت الازم للإنظار بالنانو ثانية بحيث تعيد الدالة VK_TIMEOUT بعد انتهاء هذه المدة وعدم انتهاء جاهزية السياج، انتبه أن العديد من الأجهزة قد لاتدعم مؤقت بدقة نانو ثانية، لذا قد يتم تقريب القيمة لأقرب دقة يدعمها الجهاز والتي قد تكون أعلى من القيمة التي حددتها في timeout، مثلاً لو وضعت 200 نانو ثانية فقد يقربها الجهاز إلى 1000 نانو ثانية لأنه يدعم فقط دقة مايكرو ثانية التي تساوي 1000 نانو ثانية، في حال كانت قيمة timeout تساوي صفر، فإن الدالة لن تنتظر بل تعيد حالة السياجات سواءً VK_SUCCESS دلالة على انتهاء بعض أو كل تلك السياجات حسب قيمة waitAll أو ستعيد الدالة VK_SUCCESS دلالة على انتهاء جميع السياجات.

قد تلاحظ أن قيمة الإنتظار إلزامية، الدالة لايمكن أن تنتظر إلى الأبد، لكن يمكنك استخدام هذه الحيلة لإجبارها على الإنتظار بتمرير أقصى قيمة 64 بت [math]2^{64} - 1[/math] نانو ثانية والتي تكافئ 584 سنة:

  VkResult waitResult;

  do
  {
    waitResult = vkWaitForFences(device, 1, &fence, VK_TRUE, std::numeric_limits<uint64_t>::max());
  } while (waitResult == VK_TIMEOUT);

  assert(waitResult == VK_SUCCESS);

الدالة vkGetFenceStatus() تعيد لك حالة سياج واحد دون الانظار:

VkResult vkGetFenceStatus(
    VkDevice                                    device,
    VkFence                                     fence);

حيث تعيد VK_SUCCESS دلالة على جاهزية السياج، أو VK_NOT_READY في حال عدم جاهزية السياج.

هدم كائن السياج

في النهاية وعند التأكد من أن السياج لم يعد في الاستخدام، يمكنك هدم كائن السياج باستدعاء الدالة vkDestroyFence():

void vkDestroyFence(
    VkDevice                                    device,
    VkFence                                     fence,
    const VkAllocationCallbacks*                pAllocator);