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

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

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

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

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

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

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

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

इस कोड को लिंक करते समय, पक्का करें कि एंबाइंड को चालू करने के लिए, --bind को पास किया गया हो:

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

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

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

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

पिछले शेल कमांड के साथ 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

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

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

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

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

इस कोड को अब Asyncify चालू के साथ कंपाइल करना ज़रूरी है:

emcc example.cpp -o example.html -s USE_SDL=2 -s 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 जैसे वैरिएबल को हीप-असाइन किए गए स्ट्रक्चर में एक्सट्रैक्ट किया जाना चाहिए और इसके पॉइंटर को एपीआई के 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();
}

चूंकि सभी नियंत्रण प्रवाह बदलाव मैन्युअल होते हैं और स्रोत कोड में दिखाई देते हैं, इसलिए इन्हें एसिंक्रोनस सुविधा के बिना फिर से कंपाइल किया जा सकता है:

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 विंडो.

और वेब पर:

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

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