এমস্ক্রিপ্টেনে ক্যানভাসে আঁকা

Emscripten এর সাথে WebAssembly থেকে ওয়েবে 2D গ্রাফিক্স রেন্ডার করতে শিখুন।

বিভিন্ন অপারেটিং সিস্টেমে গ্রাফিক্স আঁকার জন্য আলাদা API রয়েছে। একটি ক্রস-প্ল্যাটফর্ম কোড লেখার সময়, বা WebAssembly-এ নেটিভ কোড পোর্ট করার সময় সহ একটি সিস্টেম থেকে অন্য সিস্টেমে গ্রাফিক্স পোর্ট করার সময় পার্থক্যগুলি আরও বিভ্রান্তিকর হয়ে ওঠে।

এই পোস্টে আপনি Emscripten-এর সাথে সংকলিত C বা C++ কোড থেকে ওয়েবে ক্যানভাস উপাদানে 2D গ্রাফিক্স আঁকার কয়েকটি পদ্ধতি শিখবেন।

এম্বিন্ডের মাধ্যমে ক্যানভাস

আপনি যদি বিদ্যমান একটি পোর্ট করার চেষ্টা না করে একটি নতুন প্রকল্প শুরু করেন, তাহলে Emscripten এর বাইন্ডিং সিস্টেম Embind এর মাধ্যমে HTML ক্যানভাস API ব্যবহার করা সবচেয়ে সহজ হতে পারে। এম্বিন্ড আপনাকে নির্বিচারে জাভাস্ক্রিপ্ট মানগুলিতে সরাসরি কাজ করার অনুমতি দেয়।

কীভাবে এম্বিন্ড ব্যবহার করবেন তা বোঝার জন্য, প্রথমে MDN থেকে নিম্নলিখিত উদাহরণটি দেখুন যা একটি <canvas> উপাদান খুঁজে পায় এবং এর উপর কিছু আকার আঁকে

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

ctx.fillStyle = 'green';
ctx.fillRect(10, 10, 150, 100);

এম্বিন্ডের সাহায্যে এটি কীভাবে C++ তে প্রতিলিপি করা যেতে পারে তা এখানে:

#include <emscripten/val.h>

using emscripten::val;

// Use thread_local when you want to retrieve & cache a global JS variable once per thread.
thread_local const val document = val::global("document");

// …

int main() {
  val canvas = document.call<val>("getElementById", "canvas");
  val ctx = canvas.call<val>("getContext", "2d");
  ctx.set("fillStyle", "green");
  ctx.call<void>("fillRect", 10, 10, 150, 100);
}

এই কোডটি লিঙ্ক করার সময়, এম্বিন্ড সক্ষম করতে --bind পাস করতে ভুলবেন না:

emcc --bind example.cpp -o example.html

তারপর আপনি একটি স্ট্যাটিক সার্ভারের সাথে কম্পাইল করা সম্পদ পরিবেশন করতে পারেন এবং একটি ব্রাউজারে উদাহরণ লোড করতে পারেন:

এমস্ক্রিপ্টেন-উত্পন্ন HTML পৃষ্ঠাটি একটি কালো ক্যানভাসে একটি সবুজ আয়তক্ষেত্র দেখাচ্ছে।

ক্যানভাস উপাদান নির্বাচন করা হচ্ছে

পূর্ববর্তী শেল কমান্ডের সাথে Emscripten-উত্পন্ন HTML শেল ব্যবহার করার সময়, ক্যানভাস অন্তর্ভুক্ত করা হয় এবং আপনার জন্য সেট আপ করা হয়। এটি সাধারণ ডেমো এবং উদাহরণগুলি তৈরি করা সহজ করে তোলে, তবে বড় অ্যাপ্লিকেশনগুলিতে আপনি আপনার নিজের ডিজাইনের একটি HTML পৃষ্ঠায় Emscripten-উত্পন্ন জাভাস্ক্রিপ্ট এবং WebAssembly অন্তর্ভুক্ত করতে চান৷

জেনারেট করা জাভাস্ক্রিপ্ট কোড Module.canvas প্রপার্টিতে সংরক্ষিত ক্যানভাস উপাদান খুঁজে পাওয়ার আশা করে। অন্যান্য মডিউল বৈশিষ্ট্যের মতো, এটি আরম্ভ করার সময় সেট করা যেতে পারে।

আপনি যদি ES6 মোড ব্যবহার করেন (একটি এক্সটেনশন .mjs সহ একটি পাথে আউটপুট সেট করা বা -s EXPORT_ES6 সেটিংস ব্যবহার করে), আপনি এইভাবে ক্যানভাসটি পাস করতে পারেন:

import initModule from './emscripten-generated.mjs';

const Module = await initModule({
  canvas: document.getElementById('my-canvas')
});

আপনি যদি নিয়মিত স্ক্রিপ্ট আউটপুট ব্যবহার করেন, তাহলে Emscripten-উত্পন্ন জাভাস্ক্রিপ্ট ফাইল লোড করার আগে আপনাকে Module অবজেক্ট ঘোষণা করতে হবে:

<script>
var Module = {
  canvas: document.getElementById('my-canvas')
};
</script>
<script src="emscripten-generated.js"></script>

OpenGL এবং SDL2

OpenGL কম্পিউটার গ্রাফিক্সের জন্য একটি জনপ্রিয় ক্রস-প্ল্যাটফর্ম API। Emscripten-এ ব্যবহার করা হলে, এটি OpenGL অপারেশনগুলির সমর্থিত উপসেটকে WebGL- এ রূপান্তর করার যত্ন নেবে। যদি আপনার অ্যাপ্লিকেশনটি OpenGL ES 2.0 বা 3.0-এ সমর্থিত বৈশিষ্ট্যগুলির উপর নির্ভর করে, কিন্তু WebGL-এ নয়, Emscripten সেগুলিকেও অনুকরণ করার যত্ন নিতে পারে, তবে আপনাকে সংশ্লিষ্ট সেটিংসের মাধ্যমে অপ্ট-ইন করতে হবে৷

আপনি সরাসরি বা উচ্চ-স্তরের 2D এবং 3D গ্রাফিক্স লাইব্রেরির মাধ্যমে OpenGL ব্যবহার করতে পারেন। এর মধ্যে কয়েকটি Emscripten দিয়ে ওয়েবে পোর্ট করা হয়েছে। এই পোস্টে, আমি 2D গ্রাফিক্সের উপর ফোকাস করছি, এবং সেই জন্য SDL2 বর্তমানে পছন্দের লাইব্রেরি কারণ এটি ভালভাবে পরীক্ষা করা হয়েছে এবং আনুষ্ঠানিকভাবে আপস্ট্রিমে Emscripten ব্যাকএন্ড সমর্থন করে।

একটি আয়তক্ষেত্র অঙ্কন

অফিসিয়াল ওয়েবসাইটে "SDL সম্পর্কে" বিভাগটি বলে:

সিম্পল ডাইরেক্টমিডিয়া লেয়ার হল একটি ক্রস-প্ল্যাটফর্ম ডেভেলপমেন্ট লাইব্রেরি যা OpenGL এবং Direct3D এর মাধ্যমে অডিও, কীবোর্ড, মাউস, জয়স্টিক এবং গ্রাফিক্স হার্ডওয়্যারে নিম্ন স্তরের অ্যাক্সেস প্রদান করার জন্য ডিজাইন করা হয়েছে।

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

সরলতার জন্য, এই পোস্টে আমি একটি একক-ফাইল ক্ষেত্রে ফোকাস করব এবং আগের আয়তক্ষেত্রের উদাহরণটিকে SDL2 তে অনুবাদ করব:

#include <SDL2/SDL.h>

int main() {
  // Initialize SDL graphics subsystem.
  SDL_Init(SDL_INIT_VIDEO);

  // Initialize a 300x300 window and a renderer.
  SDL_Window *window;
  SDL_Renderer *renderer;
  SDL_CreateWindowAndRenderer(300, 300, 0, &window, &renderer);

  // Set a color for drawing matching the earlier `ctx.fillStyle = "green"`.
  SDL_SetRenderDrawColor(renderer, /* RGBA: green */ 0x00, 0x80, 0x00, 0xFF);
  // Create and draw a rectangle like in the earlier `ctx.fillRect()`.
  SDL_Rect rect = {.x = 10, .y = 10, .w = 150, .h = 100};
  SDL_RenderFillRect(renderer, &rect);

  // Render everything from a buffer to the actual screen.
  SDL_RenderPresent(renderer);

  // TODO: cleanup
}

Emscripten এর সাথে লিঙ্ক করার সময়, আপনাকে -s USE_SDL=2 ব্যবহার করতে হবে। এটি এমস্ক্রিপ্টেনকে SDL2 লাইব্রেরি আনতে বলবে, যা আগে থেকেই WebAssembly-এ প্রি-কম্পাইল করা হয়েছে এবং এটিকে আপনার প্রধান অ্যাপ্লিকেশনের সাথে লিঙ্ক করুন।

emcc example.cpp -o example.html -s USE_SDL=2

ব্রাউজারে উদাহরণ লোড করা হলে, আপনি পরিচিত সবুজ আয়তক্ষেত্র দেখতে পাবেন:

এমস্ক্রিপ্টেন-উত্পন্ন এইচটিএমএল পৃষ্ঠাটি একটি কালো বর্গাকার ক্যানভাসে একটি সবুজ আয়তক্ষেত্র দেখাচ্ছে।

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

clang example.cpp -o example -lSDL2

এবং সঞ্চালিত হলে, তৈরি করা উইন্ডোটি শুধুমাত্র সংক্ষিপ্তভাবে জ্বলে উঠবে এবং প্রস্থান করার সাথে সাথেই বন্ধ হয়ে যাবে, তাই ব্যবহারকারীর ছবিটি দেখার সুযোগ নেই।

একটি ইভেন্ট লুপ একীভূত করা

আরও সম্পূর্ণ এবং বাজে উদাহরণের জন্য একটি ইভেন্ট লুপে অপেক্ষা করতে হবে যতক্ষণ না ব্যবহারকারী অ্যাপ্লিকেশনটি ছেড়ে দিতে চান:

#include <SDL2/SDL.h>

int main() {
  SDL_Init(SDL_INIT_VIDEO);

  SDL_Window *window;
  SDL_Renderer *renderer;
  SDL_CreateWindowAndRenderer(300, 300, 0, &window, &renderer);

  SDL_SetRenderDrawColor(renderer, /* RGBA: green */ 0x00, 0x80, 0x00, 0xFF);
  SDL_Rect rect = {.x = 10, .y = 10, .w = 150, .h = 100};
  SDL_RenderFillRect(renderer, &rect);
  SDL_RenderPresent(renderer);

  while (1) {
    SDL_Event event;
    SDL_PollEvent(&event);
    if (event.type == SDL_QUIT) {
      break;
    }
  }

  SDL_DestroyRenderer(renderer);
  SDL_DestroyWindow(window);

  SDL_Quit();
}

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

এখন লিনাক্সে এই উদাহরণটি কম্পাইল করা প্রত্যাশিতভাবে কাজ করে এবং একটি সবুজ আয়তক্ষেত্র সহ একটি 300 বাই 300 উইন্ডো দেখায়:

কালো ব্যাকগ্রাউন্ড এবং একটি সবুজ আয়তক্ষেত্র সহ একটি বর্গাকার লিনাক্স উইন্ডো।

যাইহোক, উদাহরণটি আর ওয়েবে কাজ করে না। Emscripten-উত্পন্ন পৃষ্ঠাটি লোডের সময় অবিলম্বে নিথর হয়ে যায় এবং রেন্ডার করা চিত্রটি কখনই দেখায় না:

এমস্ক্রিপ্টেন-উত্পাদিত HTML পৃষ্ঠা একটি 'পৃষ্ঠার অপ্রতিক্রিয়াশীল' ত্রুটি সংলাপের সাথে ওভারলেড যা হয় পৃষ্ঠাটি দায়ী হওয়ার জন্য অপেক্ষা করতে বা পৃষ্ঠা থেকে প্রস্থান করার পরামর্শ দেয়

কি হয়েছে? আমি "WebAssembly থেকে অ্যাসিঙ্ক্রোনাস ওয়েব API ব্যবহার করে" নিবন্ধ থেকে উত্তরটি উদ্ধৃত করব :

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

এই প্রক্রিয়া সম্পর্কে মনে রাখা গুরুত্বপূর্ণ বিষয় হল যে, যখন আপনার কাস্টম জাভাস্ক্রিপ্ট (বা WebAssembly) কোড কার্যকর হয়, ইভেন্ট লুপটি ব্লক করা হয় […]

পূর্বের উদাহরণটি একটি অসীম ইভেন্ট লুপ চালায়, যখন কোডটি নিজেই ব্রাউজার দ্বারা প্রদত্ত অন্য অসীম ইভেন্ট লুপের ভিতরে চলে। অভ্যন্তরীণ লুপ কখনই বাইরের দিকে নিয়ন্ত্রণ ত্যাগ করে না, তাই ব্রাউজারটি বাহ্যিক ইভেন্টগুলি প্রক্রিয়া করার বা পৃষ্ঠায় জিনিসগুলি আঁকার সুযোগ পায় না।

এই সমস্যা ঠিক করার দুটি উপায় আছে।

Asyncify দিয়ে ইভেন্ট লুপ আনব্লক করা হচ্ছে

প্রথমে, লিঙ্ক করা নিবন্ধে বর্ণিত হিসাবে, আপনি Asyncify ব্যবহার করতে পারেন। এটি একটি এমস্ক্রিপ্টেন বৈশিষ্ট্য যা C বা C++ প্রোগ্রামটিকে "পজ" করতে দেয়, ইভেন্ট লুপে আবার নিয়ন্ত্রণ দেয় এবং কিছু অ্যাসিঙ্ক্রোনাস অপারেশন শেষ হয়ে গেলে প্রোগ্রামটিকে জাগিয়ে তোলে।

এই ধরনের অ্যাসিঙ্ক্রোনাস অপারেশন এমনকি "ন্যূনতম সম্ভাব্য সময়ের জন্য ঘুম" হতে পারে, emscripten_sleep(0) API এর মাধ্যমে প্রকাশ করা হয়। লুপের মাঝখানে এটি এম্বেড করার মাধ্যমে, আমি নিশ্চিত করতে পারি যে নিয়ন্ত্রণটি প্রতিটি পুনরাবৃত্তিতে ব্রাউজারের ইভেন্ট লুপে ফিরে এসেছে এবং পৃষ্ঠাটি প্রতিক্রিয়াশীল থাকবে এবং যেকোনো ইভেন্ট পরিচালনা করতে পারে:

#include <SDL2/SDL.h>
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif

int main() {
  SDL_Init(SDL_INIT_VIDEO);

  SDL_Window *window;
  SDL_Renderer *renderer;
  SDL_CreateWindowAndRenderer(300, 300, 0, &window, &renderer);

  SDL_SetRenderDrawColor(renderer, /* RGBA: green */ 0x00, 0x80, 0x00, 0xFF);
  SDL_Rect rect = {.x = 10, .y = 10, .w = 150, .h = 100};
  SDL_RenderFillRect(renderer, &rect);
  SDL_RenderPresent(renderer);

  while (1) {
    SDL_Event event;
    SDL_PollEvent(&event);
    if (event.type == SDL_QUIT) {
      break;
    }
#ifdef __EMSCRIPTEN__
    emscripten_sleep(0);
#endif
  }

  SDL_DestroyRenderer(renderer);
  SDL_DestroyWindow(window);

  SDL_Quit();
}

এই কোডটি এখন Asyncify সক্ষম করে কম্পাইল করা দরকার:

emcc example.cpp -o example.html -s USE_SDL=2 -s ASYNCIFY

এবং অ্যাপ্লিকেশনটি আবার ওয়েবে প্রত্যাশিত হিসাবে কাজ করে:

এমস্ক্রিপ্টেন-উত্পন্ন এইচটিএমএল পৃষ্ঠাটি একটি কালো বর্গাকার ক্যানভাসে একটি সবুজ আয়তক্ষেত্র দেখাচ্ছে।

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

"প্রধান লুপ" API এর সাথে ইভেন্ট লুপ আনব্লক করা হচ্ছে

emscripten_set_main_loop কল স্ট্যাক আনওয়াইন্ডিং এবং রিওয়াইন্ড করার জন্য কোনো কম্পাইলার ট্রান্সফর্মেশনের প্রয়োজন হয় না এবং এইভাবে কোড সাইজ ওভারহেড এড়িয়ে যায়। যাইহোক, বিনিময়ে, এর জন্য কোডে অনেক বেশি ম্যানুয়াল পরিবর্তন প্রয়োজন।

প্রথমত, ইভেন্ট লুপের বডি একটি আলাদা ফাংশনে বের করা দরকার। তারপর, emscripten_set_main_loop প্রথম আর্গুমেন্টে কলব্যাক হিসাবে সেই ফাংশনটির সাথে কল করতে হবে, দ্বিতীয় আর্গুমেন্টে একটি FPS ( নেটিভ রিফ্রেশ ব্যবধানের জন্য 0 ), এবং তৃতীয়টিতে অসীম লুপ ( true ) অনুকরণ করতে হবে কিনা তা নির্দেশ করে একটি বুলিয়ান:

emscripten_set_main_loop(callback, 0, true);

নতুন তৈরি কলব্যাকের main ফাংশনে স্ট্যাক ভেরিয়েবলে কোনো অ্যাক্সেস থাকবে না, তাই window এবং renderer মতো ভেরিয়েবলগুলিকে হয় একটি হিপ-অ্যালোকেটেড স্ট্রাকটে বের করতে হবে এবং এর পয়েন্টারকে API-এর emscripten_set_main_loop_arg ভেরিয়েন্টের মাধ্যমে পাস করতে হবে, অথবা এক্সট্রাক্ট করা হবে। গ্লোবাল static ভেরিয়েবল (আমি সরলতার জন্য পরেরটির সাথে গিয়েছিলাম)। ফলাফলটি অনুসরণ করা কিছুটা কঠিন, তবে এটি শেষ উদাহরণের মতো একই আয়তক্ষেত্র আঁকে:

#include <SDL2/SDL.h>
#include <stdio.h>
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif

SDL_Window *window;
SDL_Renderer *renderer;

bool handle_events() {
  SDL_Event event;
  SDL_PollEvent(&event);
  if (event.type == SDL_QUIT) {
    return false;
  }
  return true;
}

void run_main_loop() {
#ifdef __EMSCRIPTEN__
  emscripten_set_main_loop([]() { handle_events(); }, 0, true);
#else
  while (handle_events())
    ;
#endif
}

int main() {
  SDL_Init(SDL_INIT_VIDEO);

  SDL_CreateWindowAndRenderer(300, 300, 0, &window, &renderer);

  SDL_SetRenderDrawColor(renderer, /* RGBA: green */ 0x00, 0x80, 0x00, 0xFF);
  SDL_Rect rect = {.x = 10, .y = 10, .w = 150, .h = 100};
  SDL_RenderFillRect(renderer, &rect);
  SDL_RenderPresent(renderer);

  run_main_loop();

  SDL_DestroyRenderer(renderer);
  SDL_DestroyWindow(window);

  SDL_Quit();
}

যেহেতু সমস্ত নিয়ন্ত্রণ প্রবাহ পরিবর্তনগুলি ম্যানুয়াল এবং উত্স কোডে প্রতিফলিত হয়, তাই এটি আবার Asyncify বৈশিষ্ট্য ছাড়াই কম্পাইল করা যেতে পারে:

emcc example.cpp -o example.html -s USE_SDL=2

এই উদাহরণটি অকেজো বলে মনে হতে পারে, কারণ এটি প্রথম সংস্করণ থেকে আলাদাভাবে কাজ করে না, যেখানে কোডটি অনেক সহজ হওয়া সত্ত্বেও আয়তক্ষেত্রটি সফলভাবে ক্যানভাসে আঁকা হয়েছিল, এবং SDL_QUIT ইভেন্টটি- যা handle_events ফাংশনে পরিচালিত হয়-কে উপেক্ষা করা হয় যাইহোক ওয়েব।

যাইহোক, সঠিক ইভেন্ট লুপ ইন্টিগ্রেশন - হয় Asyncify এর মাধ্যমে বা emscripten_set_main_loop এর মাধ্যমে - আপনি যদি কোনো ধরনের অ্যানিমেশন বা ইন্টারঅ্যাক্টিভিটি যোগ করার সিদ্ধান্ত নেন তাহলে তা পরিশোধ করে।

ব্যবহারকারীর মিথস্ক্রিয়া পরিচালনা করা

উদাহরণস্বরূপ, শেষ উদাহরণে কিছু পরিবর্তন করে আপনি কীবোর্ড ইভেন্টের প্রতিক্রিয়া হিসাবে আয়তক্ষেত্রটি সরাতে পারেন:

#include <SDL2/SDL.h>
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif

SDL_Window *window;
SDL_Renderer *renderer;

SDL_Rect rect = {.x = 10, .y = 10, .w = 150, .h = 100};

void redraw() {
  SDL_SetRenderDrawColor(renderer, /* RGBA: black */ 0x00, 0x00, 0x00, 0xFF);
  SDL_RenderClear(renderer);
  SDL_SetRenderDrawColor(renderer, /* RGBA: green */ 0x00, 0x80, 0x00, 0xFF);
  SDL_RenderFillRect(renderer, &rect);
  SDL_RenderPresent(renderer);
}

uint32_t ticksForNextKeyDown = 0;

bool handle_events() {
  SDL_Event event;
  SDL_PollEvent(&event);
  if (event.type == SDL_QUIT) {
    return false;
  }
  if (event.type == SDL_KEYDOWN) {
    uint32_t ticksNow = SDL_GetTicks();
    if (SDL_TICKS_PASSED(ticksNow, ticksForNextKeyDown)) {
      // Throttle keydown events for 10ms.
      ticksForNextKeyDown = ticksNow + 10;
      switch (event.key.keysym.sym) {
        case SDLK_UP:
          rect.y -= 1;
          break;
        case SDLK_DOWN:
          rect.y += 1;
          break;
        case SDLK_RIGHT:
          rect.x += 1;
          break;
        case SDLK_LEFT:
          rect.x -= 1;
          break;
      }
      redraw();
    }
  }
  return true;
}

void run_main_loop() {
#ifdef __EMSCRIPTEN__
  emscripten_set_main_loop([]() { handle_events(); }, 0, true);
#else
  while (handle_events())
    ;
#endif
}

int main() {
  SDL_Init(SDL_INIT_VIDEO);

  SDL_CreateWindowAndRenderer(300, 300, 0, &window, &renderer);

  redraw();
  run_main_loop();

  SDL_DestroyRenderer(renderer);
  SDL_DestroyWindow(window);

  SDL_Quit();
}

SDL2_gfx দিয়ে অন্যান্য আকার আঁকা

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

SDL2_gfx হল একটি পৃথক লাইব্রেরি যা সেই ফাঁক পূরণ করে। উদাহরণস্বরূপ, এটি একটি বৃত্ত দিয়ে উপরের উদাহরণে একটি আয়তক্ষেত্র প্রতিস্থাপন করতে ব্যবহার করা যেতে পারে:

#include <SDL2/SDL.h>
#include <SDL2/SDL2_gfxPrimitives.h>
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif

SDL_Window *window;
SDL_Renderer *renderer;

SDL_Point center = {.x = 100, .y = 100};
const int radius = 100;

void redraw() {
  SDL_SetRenderDrawColor(renderer, /* RGBA: black */ 0x00, 0x00, 0x00, 0xFF);
  SDL_RenderClear(renderer);
  filledCircleRGBA(renderer, center.x, center.y, radius,
                   /* RGBA: green */ 0x00, 0x80, 0x00, 0xFF);
  SDL_RenderPresent(renderer);
}

uint32_t ticksForNextKeyDown = 0;

bool handle_events() {
  SDL_Event event;
  SDL_PollEvent(&event);
  if (event.type == SDL_QUIT) {
    return false;
  }
  if (event.type == SDL_KEYDOWN) {
    uint32_t ticksNow = SDL_GetTicks();
    if (SDL_TICKS_PASSED(ticksNow, ticksForNextKeyDown)) {
      // Throttle keydown events for 10ms.
      ticksForNextKeyDown = ticksNow + 10;
      switch (event.key.keysym.sym) {
        case SDLK_UP:
          center.y -= 1;
          break;
        case SDLK_DOWN:
          center.y += 1;
          break;
        case SDLK_RIGHT:
          center.x += 1;
          break;
        case SDLK_LEFT:
          center.x -= 1;
          break;
      }
      redraw();
    }
  }
  return true;
}

void run_main_loop() {
#ifdef __EMSCRIPTEN__
  emscripten_set_main_loop([]() { handle_events(); }, 0, true);
#else
  while (handle_events())
    ;
#endif
}

int main() {
  SDL_Init(SDL_INIT_VIDEO);

  SDL_CreateWindowAndRenderer(300, 300, 0, &window, &renderer);

  redraw();
  run_main_loop();

  SDL_DestroyRenderer(renderer);
  SDL_DestroyWindow(window);

  SDL_Quit();
}

এখন SDL2_gfx লাইব্রেরিটিকেও অ্যাপ্লিকেশনের সাথে সংযুক্ত করতে হবে। এটি SDL2 এর অনুরূপভাবে করা হয়েছে:

# Native version
$ clang example.cpp -o example -lSDL2 -lSDL2_gfx
# Web version
$ emcc --bind foo.cpp -o foo.html -s USE_SDL=2 -s USE_SDL_GFX=2

এবং এখানে লিনাক্সে চলমান ফলাফল রয়েছে:

কালো ব্যাকগ্রাউন্ড এবং একটি সবুজ বৃত্ত সহ একটি বর্গাকার লিনাক্স উইন্ডো।

এবং ওয়েবে:

এমস্ক্রিপ্টেন-উত্পন্ন HTML পৃষ্ঠাটি একটি কালো বর্গাকার ক্যানভাসে একটি সবুজ বৃত্ত দেখাচ্ছে।

আরও গ্রাফিক্স আদিম জন্য, স্বয়ংক্রিয়-উত্পন্ন ডক্স দেখুন।