গেম লুপে রেন্ডারিং সম্পর্কে জানুন

একটি গেম লুপ বাস্তবায়ন করার একটি খুব জনপ্রিয় উপায় এই মত দেখায়:

while (playing) {
    advance state by one frame
    render the new frame
    sleep until it’s time to do the next frame
}

এর সাথে কয়েকটি সমস্যা রয়েছে, সবচেয়ে মৌলিক হচ্ছে এই ধারণা যে গেমটি "ফ্রেম" কী তা নির্ধারণ করতে পারে। বিভিন্ন ডিসপ্লে বিভিন্ন হারে রিফ্রেশ হবে এবং সেই হার সময়ের সাথে পরিবর্তিত হতে পারে। আপনি যদি ডিসপ্লে দেখানোর চেয়ে দ্রুত ফ্রেম তৈরি করেন তবে আপনাকে মাঝে মাঝে একটি ড্রপ করতে হবে। আপনি যদি সেগুলি খুব ধীরে ধীরে তৈরি করেন, SurfaceFlinger পর্যায়ক্রমে অর্জনের জন্য একটি নতুন বাফার খুঁজে পেতে ব্যর্থ হবে এবং পূর্ববর্তী ফ্রেমটি পুনরায় দেখাবে৷ এই উভয় পরিস্থিতিতে দৃশ্যমান ত্রুটি হতে পারে।

আপনাকে যা করতে হবে তা হল ডিসপ্লের ফ্রেমের হারের সাথে মেলে এবং আগের ফ্রেমের থেকে কতটা সময় অতিবাহিত হয়েছে সেই অনুযায়ী গেমের অবস্থা অগ্রিম করা। এই সম্পর্কে যেতে বিভিন্ন উপায় আছে:

  • অ্যান্ড্রয়েড ফ্রেম পেসিং লাইব্রেরি ব্যবহার করুন (প্রস্তাবিত)
  • BufferQueue পূর্ণ করুন এবং "swap buffers" ব্যাক-চাপের উপর নির্ভর করুন
  • কোরিওগ্রাফার ব্যবহার করুন (API 16+)

অ্যান্ড্রয়েড ফ্রেম পেসিং লাইব্রেরি

এই লাইব্রেরি ব্যবহার করার তথ্যের জন্য সঠিক ফ্রেম পেসিং অর্জন দেখুন।

সারি স্টাফিং

এটি বাস্তবায়ন করা খুব সহজ: যত দ্রুত আপনি পারেন বাফার অদলবদল করুন। অ্যান্ড্রয়েডের প্রারম্ভিক সংস্করণে এটি আসলে একটি পেনাল্টি হতে পারে যেখানে SurfaceView#lockCanvas() আপনাকে 100ms ঘুমাতে দেবে। এখন এটি BufferQueue দ্বারা গতিশীল, এবং সারফেসফ্লিংগার যত তাড়াতাড়ি সক্ষম হবে তত দ্রুত বাফার কিউ খালি করা হবে।

এই পদ্ধতির একটি উদাহরণ Android Breakout এ দেখা যায়। এটি GLSurfaceView ব্যবহার করে, যা একটি লুপে চলে যা অ্যাপ্লিকেশনটির onDrawFrame() কলব্যাককে কল করে এবং তারপরে বাফারটি অদলবদল করে। BufferQueue পূর্ণ হলে, eglSwapBuffers() কলটি একটি বাফার উপলব্ধ না হওয়া পর্যন্ত অপেক্ষা করবে। SurfaceFlinger এগুলি প্রকাশ করলে বাফারগুলি উপলব্ধ হয়, যা এটি প্রদর্শনের জন্য একটি নতুন অর্জন করার পরে করে। কারণ এটি VSYNC-তে ঘটে, আপনার ড্র লুপের সময় রিফ্রেশ হারের সাথে মিলবে। অধিকাংশ ক্ষেত্রে।

এই পদ্ধতির সাথে কয়েকটি সমস্যা রয়েছে। প্রথমত, অ্যাপটি SurfaceFlinger কার্যকলাপের সাথে আবদ্ধ, যা কতটা কাজ করতে হবে এবং এটি অন্যান্য প্রক্রিয়াগুলির সাথে CPU সময়ের জন্য লড়াই করছে কিনা তার উপর নির্ভর করে বিভিন্ন পরিমাণে সময় নিতে চলেছে। যেহেতু আপনার গেমের অবস্থা বাফার অদলবদলের মধ্যে সময় অনুযায়ী অগ্রসর হয়, তাই আপনার অ্যানিমেশন একটি সামঞ্জস্যপূর্ণ হারে আপডেট হবে না। 60fps-এ চলার সময় অসঙ্গতিগুলি সময়ের সাথে গড় হয়ে গেছে, যদিও, আপনি সম্ভবত বাধাগুলি লক্ষ্য করবেন না।

দ্বিতীয়ত, বাফার সোয়াপগুলির প্রথম দম্পতি খুব দ্রুত ঘটতে চলেছে কারণ বাফারকিউ এখনও পূর্ণ হয়নি। ফ্রেমের মধ্যে গণনা করা সময় শূন্যের কাছাকাছি হবে, তাই গেমটি কয়েকটি ফ্রেম তৈরি করবে যেখানে কিছুই হবে না। ব্রেকআউটের মতো একটি গেমে, যা প্রতিটি রিফ্রেশে স্ক্রীন আপডেট করে, একটি গেম প্রথম শুরু হলে (বা বিরতিমুক্ত) ব্যতীত সারি সর্বদা পূর্ণ থাকে, তাই প্রভাবটি লক্ষণীয় নয়৷ একটি গেম যা মাঝে মাঝে অ্যানিমেশনকে বিরতি দেয় এবং তারপরে যত দ্রুত-সম্ভব মোডে ফিরে আসে তা অদ্ভুত হেঁচকি দেখতে পারে।

কোরিওগ্রাফার

কোরিওগ্রাফার আপনাকে একটি কলব্যাক সেট করতে দেয় যা পরবর্তী VSYNC-তে চালু হয়। প্রকৃত VSYNC সময় একটি যুক্তি হিসাবে পাস করা হয়। তাই আপনার অ্যাপটি এখনই না জেগে উঠলেও, ডিসপ্লে রিফ্রেশ পিরিয়ড কখন শুরু হয়েছে তার একটি সঠিক ছবি আপনার কাছে আছে। এই মান ব্যবহার করে, বর্তমান সময়ের পরিবর্তে, আপনার গেম স্টেট আপডেট লজিকের জন্য একটি সামঞ্জস্যপূর্ণ সময় উৎস পাওয়া যায়।

দুর্ভাগ্যবশত, প্রতিটি VSYNC-এর পরে আপনি যে একটি কলব্যাক পান তা এই গ্যারান্টি দেয় না যে আপনার কলব্যাক একটি সময়োপযোগী পদ্ধতিতে সম্পাদিত হবে বা আপনি এটিকে যথেষ্ট দ্রুততার সাথে কাজ করতে সক্ষম হবেন। আপনার অ্যাপটিকে এমন পরিস্থিতিতে সনাক্ত করতে হবে যেখানে এটি পিছিয়ে পড়ছে এবং ম্যানুয়ালি ফ্রেমগুলি ড্রপ করবে৷

গ্রাফিকাতে "রেকর্ড জিএল অ্যাপ" কার্যকলাপ এটির একটি উদাহরণ প্রদান করে। কিছু ডিভাইসে (যেমন Nexus 4 এবং Nexus 5), আপনি যদি বসে বসে দেখেন তবে কার্যকলাপটি ফ্রেম ড্রপ করা শুরু করবে। GL রেন্ডারিং তুচ্ছ, কিন্তু মাঝে মাঝে ভিউ উপাদানগুলি পুনরায় আঁকা হয়, এবং ডিভাইসটি কম-পাওয়ার মোডে চলে গেলে পরিমাপ/লেআউট পাসটি খুব দীর্ঘ সময় নিতে পারে। (সিস্ট্রেস অনুসারে, অ্যান্ড্রয়েড 4.4 এ ঘড়ির গতি ধীর হয়ে যাওয়ার পরে এটি 6ms এর পরিবর্তে 28ms লাগে। আপনি যদি আপনার আঙুলটি স্ক্রিনের চারপাশে টেনে আনেন, তাহলে এটি মনে করে যে আপনি কার্যকলাপের সাথে ইন্টারঅ্যাক্ট করছেন, তাই ঘড়ির গতি বেশি থাকে এবং আপনি কখনই নামবেন না একটি ফ্রেম।)

VSYNC সময়ের পরে বর্তমান সময় N মিলিসেকেন্ডের বেশি হলে কোরিওগ্রাফার কলব্যাকে একটি ফ্রেম ড্রপ করা ছিল সহজ সমাধান। আদর্শভাবে N এর মান পূর্বে পর্যবেক্ষণ করা VSYNC ব্যবধানের উপর ভিত্তি করে নির্ধারিত হয়। উদাহরণস্বরূপ, যদি রিফ্রেশ সময়কাল 16.7ms (60fps) হয়, আপনি যদি 15ms এর বেশি দেরিতে চালান তবে আপনি একটি ফ্রেম ড্রপ করতে পারেন।

আপনি যদি "রেকর্ড জিএল অ্যাপ" চালাতে দেখেন, আপনি ড্রপ-ফ্রেম কাউন্টার বৃদ্ধি দেখতে পাবেন, এমনকি ফ্রেম ড্রপ হয়ে গেলে বর্ডারে লাল ফ্ল্যাশ দেখতে পাবেন। আপনার চোখ খুব ভালো না হলে, আপনি অ্যানিমেশন তোতলা দেখতে পাবেন না। 60fps-এ, অ্যানিমেশনটি ধ্রুবক হারে অগ্রসর হওয়া পর্যন্ত কেউ খেয়াল না করেই অ্যাপটি মাঝে মাঝে ফ্রেমটি ফেলে দিতে পারে। আপনি কী আঁকছেন, ডিসপ্লের বৈশিষ্ট্য এবং অ্যাপ ব্যবহারকারী ব্যক্তি জ্যাঙ্ক শনাক্ত করার ক্ষেত্রে কতটা ভালো তার উপর কিছুটা নির্ভর করে আপনি কতটা থেকে দূরে থাকতে পারবেন।

থ্রেড ব্যবস্থাপনা

সাধারণভাবে বলতে গেলে, আপনি যদি একটি SurfaceView, GLSurfaceView, বা TextureView-এ রেন্ডার করছেন, আপনি একটি ডেডিকেটেড থ্রেডে সেই রেন্ডারিং করতে চান। UI থ্রেডে অনির্দিষ্ট সময় লাগে এমন কোনও "ভারী উত্তোলন" বা এমন কিছু করবেন না। পরিবর্তে, গেমটির জন্য দুটি থ্রেড তৈরি করুন: একটি গেম থ্রেড এবং একটি রেন্ডার থ্রেড৷ আরও তথ্যের জন্য আপনার গেমের কর্মক্ষমতা উন্নত দেখুন।

ব্রেকআউট এবং "রেকর্ড জিএল অ্যাপ" ডেডিকেটেড রেন্ডারার থ্রেড ব্যবহার করে এবং তারা সেই থ্রেডে অ্যানিমেশন অবস্থাও আপডেট করে। এটি একটি যুক্তিসঙ্গত পন্থা যতক্ষণ গেমের অবস্থা দ্রুত আপডেট করা যায়।

অন্যান্য গেম গেমের যুক্তি এবং রেন্ডারিংকে সম্পূর্ণ আলাদা করে। যদি আপনার কাছে এমন একটি সাধারণ গেম থাকে যা প্রতি 100 মিলিমিটারে একটি ব্লক সরানো ছাড়া কিছুই করে না, তাহলে আপনার কাছে একটি ডেডিকেটেড থ্রেড থাকতে পারে যা এইমাত্র করেছে:

run() {
    Thread.sleep(100);
    synchronized (mLock) {
        moveBlock();
    }
}

(আপনি ড্রিফ্ট রোধ করার জন্য একটি নির্দিষ্ট ঘড়ি থেকে ঘুমের সময় বেস করতে চাইতে পারেন -- sleep() পুরোপুরি সামঞ্জস্যপূর্ণ নয়, এবং moveBlock() একটি শূন্য পরিমাণ সময় নেয় -- কিন্তু আপনি ধারণা পেয়েছেন।)

ড্র কোড জেগে উঠলে, এটি শুধু লকটি ধরে, ব্লকের বর্তমান অবস্থান পায়, লকটি প্রকাশ করে এবং আঁকে। আন্তঃ-ফ্রেম ডেল্টা সময়ের উপর ভিত্তি করে ভগ্নাংশীয় আন্দোলন করার পরিবর্তে, আপনার কাছে শুধু একটি থ্রেড আছে যা জিনিসগুলিকে বরাবর নিয়ে যায় এবং আরেকটি থ্রেড যা অঙ্কন শুরু করার সময় জিনিসগুলি যেখানেই হোক না কেন তা আঁকে৷

যেকোন জটিলতা সহ একটি দৃশ্যের জন্য আপনি জেগে ওঠার সময় অনুসারে সাজানো আসন্ন ইভেন্টগুলির একটি তালিকা তৈরি করতে চান এবং পরবর্তী ইভেন্টটি শেষ না হওয়া পর্যন্ত ঘুমাতে চান, তবে এটি একই ধারণা।