ดูวิธีแสดงผลกราฟิก 2 มิติบนเว็บจาก WebAssembly ด้วย Emscripten
ระบบปฏิบัติการแต่ละระบบมี API ในการวาดกราฟิกแตกต่างกัน ความแตกต่างจะยิ่งสับสนมากขึ้นเมื่อเขียนโค้ดข้ามแพลตฟอร์มหรือพอร์ตกราฟิกจากระบบหนึ่งไปยังอีกระบบหนึ่ง รวมถึงเมื่อพอร์ตโค้ดเนทีฟไปยัง WebAssembly
ในโพสต์นี้ คุณจะได้เรียนรู้ 2 วิธีในการวาดกราฟิก 2 มิติไปยังองค์ประกอบ Canvas ในเว็บจากโค้ด C หรือ C++ ที่คอมไพล์ด้วย Emscripten
Canvas ผ่าน Embind
หากคุณกำลังเริ่มต้นโปรเจ็กต์ใหม่แทนที่จะพอร์ตโปรเจ็กต์ที่มีอยู่ วิธีที่ง่ายที่สุดอาจเป็นการใช้ Canvas API ของ HTML ผ่านระบบการเชื่อมโยง Embind ของ Emscripten Embind ช่วยให้คุณดําเนินการกับค่า JavaScript ที่กำหนดเองได้โดยตรง
หากต้องการทําความเข้าใจวิธีใช้ Embind ก่อนอื่นให้ดูตัวอย่างจาก MDN ต่อไปนี้ ซึ่งจะค้นหาองค์ประกอบ <canvas> และวาดรูปร่างบางอย่างบนองค์ประกอบนั้น
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'green';
ctx.fillRect(10, 10, 150, 100);
วิธีถอดเสียงเป็น C++ ด้วย Embind มีดังนี้
#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
เพื่อเปิดใช้ Embind
emcc --bind example.cpp -o example.html
จากนั้นคุณสามารถแสดงชิ้นงานที่คอมไพล์แล้วด้วยเซิร์ฟเวอร์แบบคงที่และโหลดตัวอย่างในเบราว์เซอร์ได้ ดังนี้
การเลือกองค์ประกอบ Canvas
เมื่อใช้เชลล์ HTML ที่ Emscripten สร้างขึ้นกับคำสั่งเชลล์ก่อนหน้า ระบบจะรวมและตั้งค่า Canvas ให้คุณ ซึ่งช่วยให้สร้างเดโมและตัวอย่างง่ายๆ ได้ง่ายขึ้น แต่ในแอปพลิเคชันขนาดใหญ่ คุณควรรวม JavaScript และ WebAssembly ที่ Emscripten สร้างขึ้นไว้ในหน้า HTML ที่คุณออกแบบเอง
โค้ด JavaScript ที่สร้างขึ้นจะคาดหวังว่าจะพบองค์ประกอบ Canvas ที่เก็บไว้ในพร็อพเพอร์ตี้ Module.canvas
คุณสามารถตั้งค่าได้ในระหว่างการเริ่มต้นเช่นเดียวกับคุณสมบัติอื่นๆ ของโมดูล
หากใช้โหมด ES6 (การตั้งค่าเอาต์พุตเป็นเส้นทางที่มีนามสกุล .mjs
หรือใช้การตั้งค่า -s EXPORT_ES6
) คุณสามารถส่ง Canvas ดังนี้
import initModule from './emscripten-generated.mjs';
const Module = await initModule({
canvas: document.getElementById('my-canvas')
});
หากใช้เอาต์พุตสคริปต์ปกติ คุณต้องประกาศออบเจ็กต์ Module
ก่อนโหลดไฟล์ JavaScript ที่ Emscripten สร้างขึ้น โดยทำดังนี้
<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 ก็สามารถจำลองฟีเจอร์เหล่านั้นได้เช่นกัน แต่คุณต้องเลือกใช้ผ่านการตั้งค่าที่เกี่ยวข้อง
คุณสามารถใช้ OpenGL โดยตรงหรือผ่านไลบรารีกราฟิก 2 มิติและ 3 มิติในระดับที่สูงขึ้นก็ได้ เกม 2 เกมในจำนวนดังกล่าวได้รับการพอร์ตไปยังเว็บด้วย Emscripten ในโพสต์นี้ เราจะมุ่งเน้นที่กราฟิก 2 มิติ และปัจจุบัน SDL2 เป็นไลบรารีที่แนะนำสำหรับงานดังกล่าว เนื่องจากผ่านการทดสอบมาเป็นอย่างดีและรองรับแบ็กเอนด์ Emscripten อย่างเป็นทางการ
การวาดสี่เหลี่ยมผืนผ้า
ส่วน "เกี่ยวกับ SDL" ในเว็บไซต์อย่างเป็นทางการระบุว่า
Simple DirectMedia Layer เป็นไลบรารีการพัฒนาข้ามแพลตฟอร์มที่ออกแบบมาเพื่อมอบสิทธิ์เข้าถึงระดับต่ำสำหรับเสียง แป้นพิมพ์ เมาส์ จอยสติ๊ก และฮาร์ดแวร์กราฟิกผ่าน 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
ซึ่งจะบอก Emscripten ให้ดึงข้อมูลไลบรารี SDL2 ที่คอมไพล์เป็น WebAssembly ไว้ล่วงหน้าแล้ว และลิงก์กับแอปพลิเคชันหลัก
emcc example.cpp -o example.html -s USE_SDL=2
เมื่อโหลดตัวอย่างในเบราว์เซอร์ คุณจะเห็นสี่เหลี่ยมผืนผ้าสีเขียวที่คุ้นเคยดังต่อไปนี้
แต่โค้ดนี้มีปัญหาอยู่ 2 อย่าง ประการแรก ไม่มีการล้างทรัพยากรที่จัดสรรอย่างเหมาะสม ประการที่ 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 ที่มีสี่เหลี่ยมผืนผ้าสีเขียว
แต่ตัวอย่างดังกล่าวจะใช้ไม่ได้บนเว็บอีกต่อไป หน้าเว็บที่ Emscripten สร้างขึ้นค้างทันทีระหว่างการโหลดและไม่แสดงภาพที่ได้รับการแสดงผล
เกิดอะไรขึ้น เราจะยกคำตอบจากบทความ"การใช้ Web API แบบไม่สอดคล้องจาก WebAssembly" ดังนี้
พูดสั้นๆ ก็คือเบราว์เซอร์จะเรียกใช้โค้ดทั้งหมดในลักษณะของลูปที่ไม่มีที่สิ้นสุด โดยดึงโค้ดจากคิวทีละรายการ เมื่อมีการทริกเกอร์เหตุการณ์บางอย่าง เบราว์เซอร์จะจัดคิวตัวแฮนเดิลที่เกี่ยวข้อง และในการวนซ้ำของลูปถัดไป ระบบจะนำตัวแฮนเดิลออกจากคิวและดำเนินการ กลไกนี้ช่วยให้จําลองการทํางานพร้อมกันและเรียกใช้การดําเนินการแบบขนานจํานวนมากได้โดยใช้เพียงเธรดเดียว
สิ่งที่ควรทราบเกี่ยวกับกลไกนี้คือ ขณะที่โค้ด JavaScript (หรือ WebAssembly) ที่กำหนดเองทำงานอยู่ ระบบจะบล็อกลูปเหตุการณ์ […]
ตัวอย่างก่อนหน้านี้จะเรียกใช้ลูปเหตุการณ์แบบไม่สิ้นสุด ขณะที่โค้ดเองจะทำงานภายในลูปเหตุการณ์แบบไม่สิ้นสุดอีกรายการหนึ่งซึ่งเบราว์เซอร์ให้ไว้โดยนัย ลูปด้านในจะไม่ส่งการควบคุมไปยังลูปด้านนอก ดังนั้นเบราว์เซอร์จึงไม่มีเวลาประมวลผลเหตุการณ์ภายนอกหรือวาดสิ่งต่างๆ ลงในหน้าเว็บ
การแก้ปัญหานี้ทำได้ 2 วิธี
การปลดบล็อกลูปเหตุการณ์ด้วย Asyncify
ก่อนอื่น คุณสามารถใช้ Asyncify ตามที่อธิบายไว้ในบทความที่ลิงก์ ซึ่งเป็นฟีเจอร์ของ Emscripten ที่ช่วยให้ "หยุดชั่วคราว" โปรแกรม 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 ในอาร์กิวเมนต์ที่ 2 (0
สำหรับช่วงเวลาการรีเฟรชแบบเนทีฟ) และบูลีนซึ่งระบุว่าจะจำลองลูปอินฟินิตี้ (true
) ในอาร์กิวเมนต์ที่ 3 ดังนี้
emscripten_set_main_loop(callback, 0, true);
ฟังก์ชันการเรียกกลับที่สร้างขึ้นใหม่จะไม่มีสิทธิ์เข้าถึงตัวแปรสแต็กในฟังก์ชัน main
ดังนั้นตัวแปรอย่าง window
และ renderer
จึงต้องดึงออกมาเป็นโครงสร้างที่จัดสรรหน่วยความจำกองและส่งผ่านพอยน์เตอร์ผ่านตัวแปร emscripten_set_main_loop_arg
ของ API หรือดึงออกมาเป็นตัวแปร 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
ผลลัพธ์ที่ทำงานบน Linux มีดังนี้
และบนเว็บ
ดูรายการกราฟิกพื้นฐานเพิ่มเติมได้ในเอกสารที่สร้างขึ้นโดยอัตโนมัติ