在瀏覽開啟 html 檔, 裡面除了可以用 javascript 語言來運行openGL ES, 也能用 wasm 語言來運作 , 透過 emcc 編譯器可以將 c 語言翻譯成 wasm, 安裝方式詳如 Emscripten 官網:
https://emscripten.org/docs/getting_started/downloads.html
在 linux 系統上, 我將 emsdk 安裝到 ~/project/emsdk 目錄內, 底下是簡單的 Makefile 用來將檔案輸出到 ramdisk (/dev/shm)內, 只要執行 make 就能產生包含用 g++ 編譯的可執行檔 /dev/shm/main.out, 加上可以讓瀏覽器開啟的網頁(/dev/shm/main.htm) 和 javascript 執行檔 (/dev/shm/main_wasm.js)
#Makefile
# := 變數指定一次
# = 變數可以重複指定
# 所有來源 : $^
# 第1來源 : $<
# 目標主名+副名: $@
# 目標主名 : $*
c_SRC := gltest.cpp
js_HTML := main.htm
js_WASM := main_wasm.js
path_DST:= /dev/shm
path_SRC:= $(shell pwd)
cc_LIBs := -I include -l GL -l glut
em_LIBs := -I include -s WASM=1 -s LEGACY_GL_EMULATION=1 -s USE_WEBGL2=1 -s SINGLE_FILE -s USE_FREETYPE=1
rd_HTML := $(path_DST)/$(js_HTML)
rd_WASM := $(path_DST)/$(js_WASM)
rd_EXE := $(path_DST)/main.out
define html_content
<!DOCTYPE html>
<html><head><meta charset="utf-8"><meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head><body>
<center>
<canvas id="canvas" oncontextmenu="event.preventDefault()"></canvas>
<script type="text/javascript">var id_from_canvas = document.getElementById("canvas");var Module = {canvas: id_from_canvas};</script>
<script src="$(js_WASM)"></script>
</center>
</body></html>
endef
all: $(rd_HTML) $(rd_WASM) $(rd_EXE)
@echo open $< to run $(rd_WASM) in browser
run: $(rd_EXE)
$(rd_EXE)
$(rd_WASM): $(c_SRC)
cd ~/project/emsdk && . emsdk_env.sh && cd $(path_SRC) && emcc $^ $(em_LIBs) -o $@ && echo " "
$(rd_HTML):
$(file > $@, $(html_content))
$(rd_EXE):$(c_SRC)
@g++ $^ $(cc_LIBs) -lfreetype -o $@
clean:
rm -f $(rd_WASM) $(rd_HTML) $(rd_EXE)
簡單用 c++ 寫一個繪圖程式 :
// gltest.cpp:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <unistd.h>
#include <GL/glut.h>
struct ColorRGB { float r,g,b; };
const ColorRGB colors[] = {
{.r=1, .g=1, .b=1},
{.r=1, .g=1, .b=0},
{.r=1, .g=0, .b=1},
{.r=1, .g=0, .b=0},
{.r=0, .g=1, .b=1},
{.r=0, .g=1, .b=0},
{.r=0, .g=0, .b=1},
{.r=0.5, .g=0.5, .b=0.5}
};
int size_n = sizeof(colors)/sizeof(colors[0]);
void draw_circle (float cx, float cy, float radius, ColorRGB c = colors[0], int max_segments = 32) {
const double d_theta = M_PI * 2 / max_segments;
double theta = 0;// initial θ
int segments = max_segments;// lines to draw
glColor3f(c.r, c.g, c.b);
glBegin(GL_LINE_LOOP);// GL_TRIANGLE_FAN or GL_LINE_LOOP, to close loop
while (segments -- > 0) {
glVertex2f(cx + radius * cos(theta), cy + radius * sin(theta));
theta += d_theta;
}
glEnd();
}
void draw_line(float x0, float y0, float x1, float y1, ColorRGB c = colors[0]){
glColor3f(c.r, c.g, c.b);
glBegin(GL_LINES) ; // to draw one line
glVertex2f(x0, y0); // first point
glVertex2f(x1, y1); // second point
glEnd(); // end drawing
}
void update_loop(int parameter){
printf("parameter = %8d\n", parameter);
glutPostRedisplay();// send event to redraw
glutTimerFunc(1000, update_loop, parameter + 1);// continue to run update_loop again after 1 second
}
int main(int argc, char** argv) {
glutInit(&argc, argv); // Initialize GLUT
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB); // Set display mode
glutInitWindowSize(800, 600); // Set window size
glutCreateWindow("GLUT Example"); // Create window
atexit([]() {
printf("size_n = %-8d, bye~bye.\n", size_n);
});
glutDisplayFunc([]() {// when redraw event happens
static int current = 0;
current = (current + rand()) % size_n;
glClear(GL_COLOR_BUFFER_BIT);
glColor3f(0, 0, 0);
draw_circle( 0, 0, 0.5, colors[current]);
draw_line (-1, 1, 1, -1, colors[(current + 1) % size_n]);
draw_line (-1, -1, 1, 1, colors[(current + 2) % size_n]);
glFlush(); // Flush drawing command buffer. If using double buffering (GLUT_DOUBLE), use glutSwapBuffers();
}); // Register display callback
update_loop(0); // begin to send redraw event
glutMainLoop(); // Enter GLUT event processing loop
return 0;
}
後記: 如果 make 運行時出現錯誤, 有可能是 Makefile 內執行命令前面的縮排字元(\t: Tab 按鍵)被空白字元(' ': Space 按鍵)取代了, 只要用編輯器將它修正回縮排字元就能正常運作了.