Emscripten में कैनवस की तरफ़ ड्रॉइंग

Emscripten की मदद से WebAssembly से वेब पर 2D ग्राफ़िक रेंडर करने का तरीका जानें.

अलग-अलग ऑपरेटिंग सिस्टम में ग्राफ़िक बनाने के लिए, अलग-अलग एपीआई होते हैं. क्रॉस-प्लैटफ़ॉर्म कोड लिखने या एक सिस्टम से दूसरे सिस्टम में ग्राफ़िक पोर्ट करने पर, अंतर और भी मुश्किल हो जाता है. ऐसा तब भी हो सकता है, जब नेटिव कोड को WebAssembly में पोर्ट करना हो.

इस पोस्ट में, Emscripten के साथ कॉम्पाइल किए गए C या C++ कोड से, वेब पर कैनवस एलिमेंट में 2D ग्राफ़िक्स बनाने के कुछ तरीके जानेंगे.

Embind के ज़रिए कैनवस

अगर आपको किसी मौजूदा प्रोजेक्ट को पोर्ट करने के बजाय नया प्रोजेक्ट शुरू करना है, तो Emscripten के बाइंडिंग सिस्टम Embind की मदद से, एचटीएमएल Canvas API का इस्तेमाल करना आसान हो सकता है. Embind की मदद से, सीधे तौर पर किसी भी JavaScript वैल्यू पर काम किया जा सकता है.

Embind को इस्तेमाल करने का तरीका समझने के लिए, सबसे पहले MDN का यह उदाहरण देखें कि इसमें <कैनवस> एलिमेंट मिलता है और उस पर कुछ आकार बनाए जाते हैं

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

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

Embind की मदद से, इसे 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);
}

इस कोड को लिंक करते समय, Embind को चालू करने के लिए --bind को पास करना न भूलें:

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

इसके बाद, स्टैटिक सर्वर की मदद से कंपाइल की गई ऐसेट को दिखाया जा सकता है और उदाहरण को ब्राउज़र में लोड किया जा सकता है:

Emscripten से जनरेट किया गया एचटीएमएल पेज, जिसमें काले कैनवस पर हरा रेक्टैंगल दिख रहा है.

कैनवस एलिमेंट चुनना

पिछले शेल कमांड के साथ Emscripten से जनरेट किए गए एचटीएमएल शेल का इस्तेमाल करने पर, कैनवस शामिल किया जाता है और उसे आपके लिए सेट अप किया जाता है. इससे आसान डेमो और उदाहरण बनाना आसान हो जाता है, लेकिन बड़े ऐप्लिकेशन में आप पसंद करेंगे कि आप अपने खुद के डिज़ाइन के एचटीएमएल पेज में Emscripten से जनरेट किया गया JavaScript और WebAssembly शामिल करना चाहें.

जनरेट किया गया JavaScript कोड, Module.canvas प्रॉपर्टी में स्टोर किए गए कैनवस एलिमेंट को ढूंढता है. अन्य मॉड्यूल प्रॉपर्टी की तरह, इसे भी शुरू करने के दौरान सेट किया जा सकता है.

अगर ES6 मोड का इस्तेमाल किया जा रहा है (.mjs एक्सटेंशन वाले पाथ पर आउटपुट सेट करना या -s EXPORT_ES6 सेटिंग का इस्तेमाल करना), तो कैनवस को इस तरह पास किया जा सकता है:

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

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

अगर सामान्य स्क्रिप्ट आउटपुट का इस्तेमाल किया जा रहा है, तो Emscripten से जनरेट की गई JavaScript फ़ाइल को लोड करने से पहले, आपको Module ऑब्जेक्ट का एलान करना होगा:

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

OpenGL और SDL2

OpenGL कंप्यूटर ग्राफ़िक के लिए एक लोकप्रिय क्रॉस-प्लैटफ़ॉर्म एपीआई है. Emscripten में इस्तेमाल करने पर, यह OpenGL ऑपरेशन के काम करने वाले सबसेट को WebGL में बदल देगा. यदि आपका ऐप्लिकेशन OpenGL ES 2.0 या 3.0 में समर्थित सुविधाओं पर निर्भर करता है, लेकिन WebGL में नहीं, तो Emscripten उनका अनुकरण करने का काम भी कर सकता है, लेकिन आपको संबंधित सेटिंग के माध्यम से ऑप्ट-इन करना होगा.

OpenGL का इस्तेमाल सीधे तौर पर या उच्च लेवल की 2D और 3D ग्राफ़िक लाइब्रेरी के ज़रिए किया जा सकता है. उनमें से कुछ को Emscripten की मदद से वेब पर पोर्ट किया गया है. इस पोस्ट में, 2D ग्राफ़िक्स पर फ़ोकस किया गया है. इसके लिए, फ़िलहाल SDL2 लाइब्रेरी को प्राथमिकता दी जाएगी, क्योंकि इसकी काफ़ी अच्छी तरह से जांच की गई है और यह आधिकारिक तौर पर Emscripten बैकएंड के साथ काम करता है.

आयत बनाना

आधिकारिक वेबसाइट के "एसडीएल के बारे में" सेक्शन में यह लिखा है:

सिंपल डायरेक्टमीडिया लेयर एक क्रॉस-प्लैटफ़ॉर्म डेवलपमेंट लाइब्रेरी है. इसे OpenGL और Direct3D के ज़रिए ऑडियो, कीबोर्ड, माउस, जॉयस्टिक, और ग्राफ़िक्स हार्डवेयर के लिए, कम लेवल का ऐक्सेस देने के लिए डिज़ाइन किया गया है.

ऑडियो, कीबोर्ड, माउस, और ग्राफ़िक्स को कंट्रोल करने की सभी सुविधाएं, पोर्ट की गई हैं और वेब पर Emscripten के साथ भी काम करती हैं. इससे, SDL2 की मदद से बनाए गए सभी गेम को बिना किसी परेशानी के पोर्ट किया जा सकता है. अगर किसी मौजूदा प्रोजेक्ट को पोर्ट करना है, तो Emscripten Docs का "बिल्ड सिस्टम के साथ इंटिग्रेट करना" सेक्शन देखें.

इसे आसानी से समझने के लिए, इस पोस्ट में हम सिंगल-फ़ाइल केस पर फ़ोकस करेंगे और पिछले रेक्टैंगल के उदाहरण का अनुवाद 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 का इस्तेमाल करना होगा. इससे Emscripten, SDL2 लाइब्रेरी को फ़ेच कर लेगा, जो पहले से WebAssembly में पहले से कंपाइल किया जा चुका है. साथ ही, इसे आपके मुख्य ऐप्लिकेशन से लिंक कर देगा.

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

उदाहरण ब्राउज़र में लोड होने के बाद, आपको हरे रंग का रेक्टैंगल दिखेगा:

Emscripten से जनरेट किया गया एचटीएमएल पेज, जिसमें काले रंग के स्क्वेयर कैनवस पर हरा रेक्टैंगल दिख रहा है.

हालांकि, इस कोड में कुछ समस्याएं हैं. सबसे पहले, इसमें ऐलोकेट किए गए संसाधनों को ठीक से क्लीनअप नहीं किया जाता. दूसरा, वेब पर, किसी ऐप्लिकेशन का एक्ज़ीक्यूशन पूरा होने के बाद, पेज अपने-आप बंद नहीं होते, इसलिए कैनवस पर मौजूद इमेज सुरक्षित रहती है. हालांकि, जब उसी कोड को नेटिव रूप से

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 इवेंट ट्रिगर करेगा, जिसे लूप से बाहर निकलने के लिए बीच में रोका जाएगा. लूप से बाहर निकल जाने के बाद, ऐप्लिकेशन क्लीनअप करके खुद से बाहर निकल जाएगा.

अब Linux पर इस उदाहरण को कंपाइल किया जा सकता है. यह उम्मीद के मुताबिक काम करता है और हरे रंग के रेक्टैंगल के साथ 300 x 300 विंडो दिखाता है:

काले रंग के बैकग्राउंड और हरे रंग के रेक्टैंगल वाली स्क्वेयर Linux विंडो.

हालांकि, यह उदाहरण अब वेब पर काम नहीं करता. Emscripten से जनरेट किया गया पेज, लोड होने के दौरान तुरंत फ़्रीज़ हो जाता है और रेंडर की गई इमेज कभी नहीं दिखाता:

&#39;पेज जवाब न देने वाला&#39; गड़बड़ी डायलॉग के ऊपर एंस्क्रिप्टन से जनरेट किया गया एचटीएमएल पेज, जो पेज के ज़िम्मेदारी लेने तक इंतज़ार करने या पेज से बाहर निकलने का सुझाव देता है

क्या हुआ? हम "WebAssembly से एसिंक्रोनस वेब एपीआई का इस्तेमाल करना" लेख में दिए गए जवाब को कोट करेंगे:

आसान शब्दों में, ब्राउज़र कोड के सभी हिस्सों को एक अनलिमिटेड लूप में चलाता है. इसके लिए, वह उन्हें लाइन में एक-एक करके लेता है. जब कोई इवेंट ट्रिगर होता है, तो ब्राउज़र उससे जुड़े हैंडलर को सूची में जोड़ देता है. इसके बाद, अगले लूप में, उसे सूची से निकालकर लागू कर दिया जाता है. यह तरीका सिर्फ़ एक थ्रेड का इस्तेमाल करते समय, कई पैरलल ऑपरेशन को एक साथ प्रोसेस करने और चलाने में मदद करता है.

इस तरीके के बारे में यह याद रखना ज़रूरी है कि जब आपका कस्टम JavaScript (या WebAssembly) कोड चलता है, तब इवेंट लूप ब्लॉक हो जाता है […]

ऊपर दिया गया उदाहरण, अनलिमिटेड इवेंट लूप को लागू करता है. हालांकि, कोड खुद ही एक दूसरे अनलिमिटेड इवेंट लूप में चलता है, जिसे ब्राउज़र ने इंप्लिसिट तौर पर उपलब्ध कराया है. इनर लूप कभी भी बाहरी इवेंट पर कंट्रोल नहीं छोड़ता, इसलिए ब्राउज़र को बाहरी इवेंट को प्रोसेस करने या पेज पर चीज़ें ड्रॉ करने का मौका नहीं मिलता.

इस समस्या को ठीक करने के दो तरीके हैं.

Asyncify के साथ इवेंट लूप को अनब्लॉक किया जा रहा है

सबसे पहले, लिंक किए गए लेख में बताए गए तरीके से, Asyncify का इस्तेमाल किया जा सकता है. यह एक Emscripten सुविधा है, जो C या C++ प्रोग्राम को "रोकने" देती है, इवेंट लूप पर वापस कंट्रोल देती है, और कुछ एसिंक्रोनस कार्रवाई खत्म होने पर प्रोग्राम को चालू करती है.

इस तरह के एसिंक्रोनस ऑपरेशन को emscripten_sleep(0) एपीआई की मदद से बताया जा सकता है कि आप कम से कम कितने समय तक स्लीप मोड में हैं. इसे लूप के बीच में जोड़कर, यह पक्का किया जा सकता है कि हर बार दोहराए जाने पर, कंट्रोल ब्राउज़र के इवेंट लूप पर वापस आ जाए. साथ ही, पेज रिस्पॉन्सिव बना रहे और किसी भी इवेंट को हैंडल कर सके:

#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();
}

इस कोड को अब एसिंक्रोनसी तौर पर चालू के साथ कंपाइल करना ज़रूरी है:

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

और ऐप्लिकेशन वेब पर फिर से उम्मीद के मुताबिक काम करता है:

एमस्क्रिप्टन से जनरेट किया गया एचटीएमएल पेज, जो काले रंग के स्क्वेयर कैनवस पर हरे रंग का रेक्टैंगल दिखा रहा है.

हालांकि, Asyncify का इस्तेमाल करने पर, कोड का साइज़ ज़्यादा हो सकता है. अगर इसका इस्तेमाल सिर्फ़ ऐप्लिकेशन में टॉप-लेवल इवेंट लूप के लिए किया जाता है, तो emscripten_set_main_loop फ़ंक्शन का इस्तेमाल करना बेहतर विकल्प हो सकता है.

"मुख्य लूप" एपीआई की मदद से इवेंट लूप को अनब्लॉक करना

emscripten_set_main_loop को कॉल स्टैक को खोलने और रिवाइंड करने के लिए किसी कंपाइलर ट्रांसफ़ॉर्मेशन की ज़रूरत नहीं होती है और इस तरह से कोड साइज़ ओवरहेड से बचा जा सकता है. हालांकि, इसके बदले में कोड में बहुत ज़्यादा मैन्युअल बदलाव करने पड़ते हैं.

सबसे पहले, इवेंट लूप के मुख्य हिस्से को एक अलग फ़ंक्शन में एक्सट्रैक्ट करना ज़रूरी है. इसके बाद, emscripten_set_main_loop को पहले आर्ग्युमेंट में कॉलबैक के तौर पर, दूसरे आर्ग्युमेंट में एफ़पीएस (नेटिव रीफ़्रेश इंटरवल के लिए 0) के साथ, और तीसरे आर्ग्युमेंट में एक बूलियन के साथ कॉल किया जाना चाहिए. बूलियन से यह पता चलता है कि अनलिमिटेड लूप (true) को सिम्युलेट करना है या नहीं:

emscripten_set_main_loop(callback, 0, true);

नए कॉलबैक के पास main फ़ंक्शन में स्टैक वैरिएबल का ऐक्सेस नहीं होगा. इसलिए, window और renderer जैसे वैरिएबल को हीप-असाइन किए गए स्ट्रक्चर में एक्सट्रैक्ट किया जाना चाहिए और इसके पॉइंटर को एपीआई के 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

यह उदाहरण काम का नहीं लग सकता, क्योंकि यह पहले वर्शन से अलग नहीं है, जहां कोड के बहुत आसान होने के बावजूद, रेक्टैंगल को कैनवस पर बनाया गया. साथ ही, handle_events फ़ंक्शन में मैनेज किए जाने वाले सिर्फ़ SDL_QUIT इवेंट को वेब पर अनदेखा किया जाता है.

हालांकि, अगर आपको किसी तरह का ऐनिमेशन या इंटरैक्टिविटी जोड़नी है, तो 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, अलग-अलग प्लैटफ़ॉर्म के बीच के अंतर और अलग-अलग तरह के मीडिया डिवाइसों को एक ही एपीआई में अलग रखता है. हालांकि, यह अब भी एक बहुत ही लो-लेवल लाइब्रेरी है. खास तौर पर, ग्राफ़िक के मामले में, जबकि यह ड्रॉइंग पॉइंट, लाइन, और रेक्टैंगल के लिए एपीआई उपलब्ध कराता है. हालांकि, ज़्यादा मुश्किल आकारों और बदलावों को लागू करने का काम सिर्फ़ उपयोगकर्ता पर ही होता है.

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

Linux पर चलने वाले नतीजे यहां दिए गए हैं:

काले रंग के बैकग्राउंड और हरे रंग के सर्कल वाली एक स्क्वेयर Linux विंडो.

और वेब पर:

काले रंग के स्क्वेयर कैनवस पर हरे रंग का गोला दिखाता हुआ एमस्क्रिप्टन से जनरेट किया गया एचटीएमएल पेज.

ज़्यादा ग्राफ़िक्स के लिए, अपने-आप जनरेट होने वाले दस्तावेज़ देखें.