前回準備した機能を使って、RustからWebGLのAPIを呼び出して、ポリゴン描画行います。ちょっとした(無駄な)こだわりですがOpenGLに似た記述と仕様を再現しています。それを実現することでRustについての理解が深まったと思います。GL部分だけを見ると、C++のプログラミングをしていると錯覚するかも。今回は、ソースコード2つ(lib.rsとindex.js)と簡単な説明のみです。
OpenGL(C/C++)の命名規則
Rustが推奨する命名規則は、gl_create_buffer()のスネークケースですが、それを無視してglCreateBuffer()のキャメルケースにしました。推奨外の命名規則を使用するとRustコンパイラがwaringを出すため
#![allow(non_snake_case)]
をソースの先頭に追加してwarning無効にしてます。
OpenGLと同じリソース管理仕様
WebGLはバッファやシェーダーなどを作成した際、GLuintではなく対応したオブジェクト(WebGLBufferやWebGLShaderなど)を返すため、Rustからはハンドル(整数値)による間接的なオブジェクト操作になります。結果、OpenGLに同じ仕様となります。C/C++と同じように削除(deleteBufferなど)後のハンドル(ダングリングポインタ)の扱いに注意が必要です。その危険性を許容してハンドルを直接使うか、管理する構造体を定義するか、使用者が選択できるようにしておく。
WebGL仕様を採用 文字列、バイナリー関連
文字列に関係するところはWebGLの仕様に準拠しています。OpenGLでは、ポインタとサイズで文字列を渡したり、コンパイルエラーログの受け取り時は、事前にサイズを取得、メモリ確保、ポインタとサイズを渡すなど、煩雑な手順が必要でした。WebGL仕様に対応するために、Rustのジェネリクス、JavaScriptからRust側のオブジェクト(String)を生成、操作するためにポインタを扱うBoxを利用しました。これでRustのジェネリクス、ポインタやメモリ管理についての理解が深まったと思います。ソースコード最後のほうに関連する処理をまとめています。
.rs glShaderSourceに文字列(str& String &String)を//文字列
pub trait GetStrPtr {
fn get_ptr(&self) -> usize;
fn get_size(&self) -> usize;
}
// str&
impl GetStrPtr for &str {
fn get_ptr(&self) -> usize{ self.as_ptr() as usize }
fn get_size(&self) -> usize{ self.len() as usize }
}
impl GetStrPtr for &String {略}// &String
impl GetStrPtr for String {略}// String
fn glShaderSource<T : GetStrPtr>( shader: GLHandle, src: T ) {
unsafe {
webgl_shader_source(shader, src.get_ptr(), src.get_size());
}
}
fn glGetShaderInfoLog(shader: GLHandle) -> String {
unsafe {
string_from_raw( webgl_get_shader_info_log(shader))
}
}
//バイナリーデータ
pub trait GetBinPtr {
fn get_ptr(&self) -> usize;
fn get_size(&self) -> usize;
}
impl<T> GetBinPtr for &Vec<T> {略}// &Vec
impl<T> GetBinPtr for Vec<T> {略}// Vec
impl<T> GetBinPtr for &[T] {略}// slice
fn glBufferData<T : GetBinPtr>(target : GLenum, buff : T, usage : GLenum) ;
ソースコード
Rust環境構築の記事のlib.rsとindex.jsを置き換えてwasmファイル名を書き換えればビルド&実行できます。
lib.rs#![allow(non_snake_case)]
#![allow(unused)]
use std::mem;
// シェーダーソースコード
const VSHADER_SRC : &str = "
attribute vec3 position;
attribute vec4 color;
varying vec4 v_color;
void main(void) {
v_color = color;
gl_Position = vec4(position, 1.0);
}
";
const FSHADER_SRC : &str = "
precision mediump float;
varying vec4 v_color;
void main(void) {
gl_FragColor = v_color;
}
";
#[no_mangle]
fn rust_main() {
let vs = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vs, VSHADER_SRC);
glCompileShader(vs);
if GL_FALSE == glGetShaderParameter(vs, GL_COMPILE_STATUS) {
console_log( glGetShaderInfoLog(vs) );
return;
}
let fs = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fs, FSHADER_SRC);
glCompileShader(fs);
if GL_FALSE == glGetShaderParameter(fs, GL_COMPILE_STATUS) {
console_log( glGetShaderInfoLog(fs) );
return;
}
let prog = glCreateProgram();
glAttachShader(prog, vs);
glAttachShader(prog, fs);
glLinkProgram(prog);
if GL_FALSE == glGetProgramParameter(prog, GL_LINK_STATUS) {
console_log( glGetProgramInfoLog(prog) );
return;
}
glDetachShader(prog, vs);
glDetachShader(prog, fs);
glDeleteShader(vs);
glDeleteShader(fs);
let pos_attr = glGetAttribLocation(prog, "position");
let col_attr = glGetAttribLocation(prog, "color");
//console_log( format!("position = {}",pos_attr));
//console_log( format!("color = {}",col_attr));
let vertex : [GLfloat;21] = [
// x,y,z r,g,b,a
0.0, 0.8, 0.0, 0.0, 1.0, 1.0, 1.0,
0.8, -0.8, 0.0, 1.0, 0.0, 1.0, 1.0,
-0.8, -0.8, 0.0, 1.0, 1.0, 0.0, 1.0 ];
let index : [u16;3] = [0, 2, 1];
let vb = glCreateBuffer();
glBindBuffer(GL_ARRAY_BUFFER, vb);
glBufferData(GL_ARRAY_BUFFER, &vertex[..], GL_STATIC_DRAW);
let ib = glCreateBuffer();
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ib);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, &index[..], GL_STATIC_DRAW);
{
glClearColor(0.6, 0.8, 0.9, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(prog);
glBindBuffer(GL_ARRAY_BUFFER, vb);
glEnableVertexAttribArray(pos_attr);
glVertexAttribPointer(pos_attr, 3, GL_FLOAT, GL_FALSE, 28, 0);
glEnableVertexAttribArray(col_attr);
glVertexAttribPointer( col_attr, 4, GL_FLOAT, GL_FALSE, 28, 12);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ib);
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, 0);
}
glDisableVertexAttribArray(pos_attr);
glDisableVertexAttribArray(col_attr);
glDeleteBuffer(ib);
glDeleteBuffer(vb);
glDeleteProgram(prog);
}
// js console.log(str);
pub fn console_log<T : GetStrPtr>(log_str: T) {
unsafe {
js_console_log(log_str.get_ptr(), log_str.get_size());
}
}
//OpenGL関数
// とりあえずwebglコンテキストを複数扱うことはなさそうなので
// C/C++と同じ書式を再現
pub type GLenum = u32;
pub type GLuint = u32;
pub type GLint = i32;
pub type GLsizei = u32;
pub type GLintptr = u32;
pub type GLfloat = f32;
pub type GLclampf = f32;
pub type GLbitfield = u32;
pub type GLboolean = u32;
pub type GLHandle = GLuint;
//必要な分だけ
pub const GL_FALSE : GLboolean = 0;
pub const GL_TRUE : GLboolean = 1;
pub const GL_COLOR_BUFFER_BIT : GLenum = 0x00004000;
pub const GL_FRAGMENT_SHADER : GLenum = 0x8B30;
pub const GL_VERTEX_SHADER : GLenum = 0x8B31;
pub const GL_COMPILE_STATUS : GLenum = 0x8B81;
pub const GL_LINK_STATUS : GLenum = 0x8B82;
pub const GL_ARRAY_BUFFER : GLenum = 0x8892;
pub const GL_ELEMENT_ARRAY_BUFFER : GLenum = 0x8893;
pub const GL_STATIC_DRAW : GLenum = 0x88E4;
pub const GL_FLOAT : GLenum = 0x1406;
pub const GL_UNSIGNED_SHORT : GLenum = 0x1403;
pub const GL_TRIANGLES : GLenum = 0x0004;
fn glClearColor(r: GLclampf, g: GLclampf, b: GLclampf, a: GLclampf) {
unsafe { webgl_clear_color(r,g,b,a); }
}
fn glClear( mask : GLbitfield ) {
unsafe { webgl_clear(mask); }
}
fn glCreateShader(shader_type: GLenum) -> GLHandle {
unsafe { webgl_create_shader(shader_type) }
}
fn glShaderSource<T : GetStrPtr>( shader: GLHandle, src: T ) {
unsafe {
webgl_shader_source(shader, src.get_ptr(), src.get_size());
}
}
fn glCompileShader(shader: GLHandle) {
unsafe { webgl_compile_shader(shader); }
}
fn glGetShaderParameter(shader: GLHandle, pname : GLenum) -> GLenum {
unsafe { webgl_get_shader_parameter(shader, pname) }
}
fn glGetShaderInfoLog(shader: GLHandle) -> String {
unsafe {
string_from_raw( webgl_get_shader_info_log(shader))
}
}
fn glDeleteShader(shader: GLHandle) {
unsafe { webgl_delete_shader(shader); }
}
fn glCreateProgram() -> GLHandle {
unsafe { webgl_create_program() }
}
fn glDeleteProgram(prog: GLHandle) {
unsafe {webgl_delete_program(prog); }
}
fn glAttachShader(prog: GLHandle, shader: GLHandle) {
unsafe { webgl_attach_shader(prog, shader); }
}
fn glDetachShader(prog: GLHandle, shader: GLHandle) {
unsafe { webgl_detach_shader(prog, shader); }
}
fn glLinkProgram(prog: GLHandle) {
unsafe { webgl_link_program(prog); }
}
fn glGetProgramParameter(prog: GLHandle, pname : GLenum) -> GLenum {
unsafe { webgl_get_program_parameter(prog, pname) }
}
fn glGetProgramInfoLog(prog: GLHandle) -> String {
unsafe {
string_from_raw( webgl_get_program_info_log(prog))
}
}
fn glUseProgram(prog_hdl: GLHandle) {
unsafe { webgl_use_program(prog_hdl); }
}
fn glGetAttribLocation<T : GetStrPtr>(prog_hdl: GLHandle, name : T ) -> GLint {
unsafe{
webgl_get_attrib_location(prog_hdl, name.get_ptr(), name.get_size())
}
}
fn glCreateBuffer() -> GLHandle {
unsafe { webgl_create_buffer() }
}
fn glDeleteBuffer(prog_hdl: GLHandle) {
unsafe { webgl_delete_buffer(prog_hdl) }
}
fn glBindBuffer(target : GLenum, buff_hdl: GLHandle) {
unsafe{ webgl_bind_buffer(target, buff_hdl); }
}
fn glBufferData<T : GetBinPtr>(target : GLenum, buff : T, usage : GLenum) {
unsafe {
webgl_buffer_data(target, buff.get_ptr(), buff.get_size(), usage );
}
}
fn glEnableVertexAttribArray(index : GLint) {
unsafe {
webgl_enable_vertex_attrib_array(index);
}
}
fn glDisableVertexAttribArray(index : GLint) {
unsafe {
webgl_disable_vertex_attrib_array(index);
}
}
fn glVertexAttribPointer( index: GLint, size: GLuint, typo: GLenum, normalized: GLboolean, stride: GLuint, offset: GLuint) {
unsafe {
webgl_vertex_attrib_pointer(index, size, typo, normalized, stride, offset);
}
}
fn glDrawArrays( mode: GLenum, first: GLint, count: GLuint ) {
unsafe { webgl_draw_arrays(mode, first, count); }
}
fn glDrawElements(mode: GLenum, count: GLsizei, index_type: GLenum, offset: GLintptr) {
unsafe { webgl_draw_elements(mode, count, index_type, offset); }
}
extern "C" {
fn js_console_log(ptr: usize, byte_size: usize);
fn webgl_clear_color(r: GLclampf, g: GLclampf, b: GLclampf, a: GLclampf);
fn webgl_clear(mask : GLbitfield);
fn webgl_create_shader(shader_type: GLenum) -> GLHandle;
fn webgl_shader_source( shader_hdl: GLHandle, str_ptr: usize, str_byte_len: usize );
fn webgl_compile_shader(shader_hdl: GLHandle );
fn webgl_get_shader_parameter(shader_hdl: GLHandle, pname : GLenum) -> GLenum;
fn webgl_get_shader_info_log(shader_hdl: GLHandle) -> usize;
fn webgl_delete_shader(shader_hdl: GLHandle);
fn webgl_create_program() -> GLHandle;
fn webgl_delete_program(prog_hdl: GLHandle);
fn webgl_attach_shader(prog_hdl: GLHandle, shader_hdl: GLHandle);
fn webgl_detach_shader(prog_hdl: GLHandle, shader_hdl: GLHandle);
fn webgl_link_program(prog_hdl: GLHandle);
fn webgl_get_program_parameter(shader_hdl: GLHandle, pname : GLenum) -> GLenum;
fn webgl_get_program_info_log(prog_hdl: GLHandle) -> usize;
fn webgl_use_program(prog_hdl: GLHandle);
fn webgl_get_attrib_location(prog_hdl: GLHandle, str_ptr: usize, str_byte_len: usize) -> GLint;
fn webgl_create_buffer() -> GLHandle;
fn webgl_delete_buffer(buff_hdl: GLHandle);
fn webgl_bind_buffer(target : GLenum, buff_hdl: GLHandle);
fn webgl_buffer_data(target : GLenum, buff_ptr : usize, byte_size : usize, usage : GLenum);
fn webgl_enable_vertex_attrib_array(index : GLint);
fn webgl_disable_vertex_attrib_array(index : GLint);
fn webgl_vertex_attrib_pointer(index: GLint, size: GLuint, typo: GLenum, normalized: GLboolean, stride: GLuint, offset: GLuint);
fn webgl_draw_arrays( mode: GLenum, first: GLint, count: GLuint );
fn webgl_draw_elements(mode: GLenum, count: GLsizei, index_type: GLenum, offset: GLintptr);
}
// JSへ渡すためのポインタとサイズ取得
// バイナリー
pub trait GetBinPtr {
fn get_ptr(&self) -> usize;
fn get_size(&self) -> usize;
}
// 文字列
pub trait GetStrPtr {
fn get_ptr(&self) -> usize;
fn get_size(&self) -> usize;
}
// trait GetPtrSize
// &str
impl GetStrPtr for &str {
fn get_ptr(&self) -> usize{ self.as_ptr() as usize }
fn get_size(&self) -> usize{ self.len() as usize }
}
// &String
impl GetStrPtr for &String {
fn get_ptr(&self) -> usize{ self.as_ptr() as usize }
fn get_size(&self) -> usize{ self.len() as usize }
}
// &String
impl GetStrPtr for String {
fn get_ptr(&self) -> usize{ self.as_ptr() as usize }
fn get_size(&self) -> usize{ self.len() as usize }
}
// Vec
impl<T> GetBinPtr for &Vec<T> {
fn get_ptr(&self) -> usize{ self.as_ptr() as usize }
fn get_size(&self) -> usize{ (self.len()*mem::size_of::<T>()) as usize }
}
impl<T> GetBinPtr for Vec<T> {
fn get_ptr(&self) -> usize{ self.as_ptr() as usize }
fn get_size(&self) -> usize{ (self.len()*mem::size_of::<T>()) as usize }
}
// slice
impl<T> GetBinPtr for &[T] {
fn get_ptr(&self) -> usize{ self.as_ptr() as usize }
fn get_size(&self) -> usize{ (self.len()*mem::size_of::<T>()) as usize }
}
// Stringラップ構造体 from_raw忘れ検出とか(予定)
struct RustJSString{
s : String,
}
impl RustJSString {
fn new() -> RustJSString {
RustJSString{ s : String::new(), }
}
}
// JSから受け取った生ポインタからString
fn string_from_raw(ptr : usize) -> String {
unsafe {
if( ptr == 0){ String::new() }
else { (*Box::from_raw(ptr as *mut RustJSString)).s }
}
}
// Stringをヒープに配置&生ポインタ(所有権放棄)
#[no_mangle]
fn rust_js_string_new() -> usize {
let stack = Box::new( RustJSString::new() );
unsafe { Box::into_raw(stack) as usize }
}
// 文字列領域を確保
#[no_mangle]
fn rust_js_string_reserve(str_ptr : usize, add_size : usize) -> usize {
if str_ptr == 0 { return 0; }//nullpo
unsafe {
let s = str_ptr as *mut RustJSString;
(*s).s.reserve(add_size);// try_reserveは1.37stableでは使えない
(*s).s.as_mut_ptr() as usize
}
}
// 文字列領域を確定
#[no_mangle]
fn rust_js_string_set_len(str_ptr : usize, len : usize) {
if str_ptr == 0 { return; }//nullpo
unsafe {
let s = str_ptr as *mut RustJSString;
(*s).s.as_mut_vec().set_len(len);
}
}
index.js// rust(wasm)から呼ばれる関数
// WebAssembly.instantiate()へ渡す
const imports = {
env: {
js_console_log : console_log,
webgl_clear_color : webgl_clear_color,
webgl_clear : webgl_clear,
webgl_create_shader : webgl_create_shader,
webgl_shader_source : webgl_shader_source,
webgl_compile_shader : webgl_compile_shader,
webgl_get_shader_parameter : webgl_get_shader_parameter,
webgl_get_shader_info_log : webgl_get_shader_info_log,
webgl_delete_shader : webgl_delete_shader,
webgl_create_program : webgl_create_program,
webgl_delete_program : webgl_delete_program,
webgl_attach_shader : webgl_attach_shader,
webgl_detach_shader : webgl_detach_shader,
webgl_link_program : webgl_link_program,
webgl_get_program_parameter : webgl_get_program_parameter,
webgl_get_program_info_log : webgl_get_program_info_log,
webgl_use_program : webgl_use_program,
webgl_get_attrib_location : webgl_get_attrib_location,
webgl_create_buffer : webgl_create_buffer,
webgl_delete_buffer : webgl_delete_buffer,
webgl_bind_buffer : webgl_bind_buffer,
webgl_buffer_data : webgl_buffer_data,
webgl_enable_vertex_attrib_array : webgl_enable_vertex_attrib_array,
webgl_disable_vertex_attrib_array : webgl_disable_vertex_attrib_array,
webgl_vertex_attrib_pointer : webgl_vertex_attrib_pointer,
webgl_draw_arrays : webgl_draw_arrays,
webgl_draw_elements : webgl_draw_elements,
},
}
// wasm読み込み
fetch('./rust.wasm')
.then(response => response.arrayBuffer())
.then(bytes => WebAssembly.instantiate(bytes, imports))
.then(results=> { main(results.instance); })
.catch(err=>console.log(err));
var wasm_inst; // WebAssembly.instantiate(..).instance
var wasm_buff_u8; // Uint8Array
var wasm_buff_u16; // Uint16Array
var glctx; // WebGLRenderingContext
function main(instance)
{
// wasmとwebglの準備
wasm_inst = instance;
//毎回newすると効率が悪いので、事前に作成しておく
//ただしmemory.grow()でメモリが拡張された場合、作り直し
wasm_buff_u8 = new Uint8Array(wasm_inst.exports.memory.buffer);
wasm_buff_u16 = new Uint8Array(wasm_inst.exports.memory.buffer);
var canvas = document.getElementById('canvas');
glctx = canvas.getContext('webgl');
// 終了処理 16回リロードで発生するwarning対策 for Firefox
window.addEventListener('beforeunload', function(e){
glctx.getExtension('WEBGL_lose_context').loseContext();
});
wasm_inst.exports.rust_main();
}
// WebGL API
// とりあえず必要な分だけ定義
const GL_FALSE = 0;
const GL_TRUE = 1;
function webgl_clear_color(r, g, b, a) {
glctx.clearColor(r, g, b,a);
}
function webgl_clear(mask) {
glctx.clear(mask);
}
function webgl_create_shader(shader_type) {
return create_obj_handle( glctx.createShader(shader_type) );
}
function webgl_shader_source( shader_hdl, str_ptr, str_byte_len ) {
var s = string_from_wasm_utf8(str_ptr, str_byte_len);
glctx.shaderSource( get_obj(shader_hdl), s);
}
function webgl_compile_shader( shader_hdl ) {
glctx.compileShader( get_obj(shader_hdl) );
}
function webgl_get_shader_parameter(shader_hdl, pname) {
var r = glctx.getShaderParameter( get_obj(shader_hdl), pname);
if(r === Boolean){
return r? GL_TRUE : GL_FALSE;
}
return r;
}
function webgl_get_shader_info_log( shader_hdl, str_ptr, str_buff_len) {
let log = glctx.getShaderInfoLog( get_obj(shader_hdl) );
return rust_string_from_js(log);
}
function webgl_delete_shader( shader_hdl ) {
glctx.deleteShader( release_obj_handle(shader_hdl) );
}
// Program
function webgl_create_program() {
return create_obj_handle( glctx.createProgram() );
}
function webgl_delete_program( prog_hdl ) {
glctx.deleteProgram( release_obj_handle( prog_hdl ) );
}
function webgl_attach_shader( prog_hdl, shader_hdl ) {
glctx.attachShader( get_obj( prog_hdl ), get_obj( shader_hdl ));
}
function webgl_detach_shader( prog_hdl, shader_hdl ) {
glctx.detachShader( get_obj( prog_hdl ), get_obj( shader_hdl ));
}
function webgl_link_program( prog_hdl ) {
glctx.linkProgram( get_obj( prog_hdl ));
}
function webgl_get_program_parameter( prog_hdl, pname) {
var r = glctx.getProgramParameter( get_obj(prog_hdl), pname);
if(r === Boolean){
return r? GL_TRUE : GL_FALSE;
}
return r;
}
function webgl_get_program_info_log( prog_hdl, str_ptr, str_buff_len) {
let log = glctx.getProgramInfoLog( get_obj(prog_hdl) );
return rust_string_from_js(log);
}
function webgl_use_program( prog_hdl ) {
glctx.useProgram( get_obj( prog_hdl ));
}
function webgl_get_attrib_location( prog_hdl, str_ptr, str_byte) {
var s = string_from_wasm_utf8(str_ptr, str_byte);
return glctx.getAttribLocation( get_obj(prog_hdl), s);
}
function webgl_create_buffer() {
return create_obj_handle( glctx.createBuffer());
}
function webgl_delete_buffer( buffer_hdl ) {
glctx.deleteBuffer( release_obj_handle(buffer_hdl));
}
function webgl_bind_buffer( target, buffer_hdl ) {
glctx.bindBuffer( target, get_obj(buffer_hdl) );
}
function webgl_buffer_data( target, ptr, byte_size, usage ) {
var data = get_subbuff_u8( ptr, byte_size );
glctx.bufferData(target, data, usage);
}
function webgl_enable_vertex_attrib_array( index ) {
if(index >= 0)glctx.enableVertexAttribArray(index);
}
function webgl_disable_vertex_attrib_array( index ) {
if(index >= 0)glctx.disableVertexAttribArray(index);
}
function webgl_vertex_attrib_pointer( index, size, type, normalized, stride, offset) {
normalized = (normalized == 0)? false : true;
glctx.vertexAttribPointer(index, size, type, normalized, stride, offset);
}
function webgl_draw_arrays(mode , first, count ) {
glctx.drawArrays(mode, first, count );
}
function webgl_draw_elements(mode, count, type, offset ) {
glctx.drawElements( mode, count, type, offset );
}
// コンソール出力
function console_log(ptr, byte_size) {
console.log( string_from_wasm_utf8(ptr, byte_size) );
}
// wasmバッファー操作
// バッファ更新
// Rust(wasm)のメモリをgrow()で拡張した場合
// wasm_inst.exports.memory.bufferが変わるため再度作成
function update_wasm_buffer() {
if(wasm_inst.exports.memory.buffer !== wasm_buff_u8.buffer){
wasm_buff_u8 = new Uint8Array(wasm_inst.exports.memory.buffer);
wasm_buff_u16 = new Uint16Array(wasm_inst.exports.memory.buffer);
}
}
// バッファスライス
function get_subbuff_u8( byte_offset, byte_size) {
update_wasm_buffer();
return wasm_buff_u8.subarray(byte_offset, byte_offset + byte_size);
}
function get_subbuff_u16( byte_offset, byte_size) {
update_wasm_buffer();
return wasm_buff_u16.subarray(byte_offset/2, byte_offset/2 + byte_size/2);
}
// UTF8からString
function string_from_wasm_utf8(str_ptr, str_byte_len) {
return utf8_to_string( get_subbuff_u8(str_ptr, str_byte_len) );
}
// オブジェクトヒープ
// RustからJSオブジェクトを間接アクセスするためのハンドル生成
const OBJ_HANLDE_MAX = 64;
var obj_heap = new Array(OBJ_HANLDE_MAX);
obj_heap[0] = "nullpo";//handle=0はnullハンドル
function get_obj(hdl) {
if(hdl == 0)return null;
return obj_heap[hdl];
}
function get_empty_handle() {
// 数が少ないので 増えたら考える
for(var i = 1;i < obj_heap.length; ++i){
if( obj_heap[i] === undefined ) return i;
}
return 0;
}
function create_obj_handle(obj) {
var hdl = get_empty_handle();
if( hdl == 0)return hdl;
obj_heap[hdl] = obj;
//console.log("hdl=" + hdl + obj);
return hdl;
}
function release_obj_handle(hdl) {
if(hdl == 0)return null;
var obj = obj_heap[hdl];
//console.log("rel=" + hdl + obj_heap[hdl]);
obj_heap[hdl] = undefined;
return obj;
}
// String(UTF16) ⇒ Uint8Array(UTF8)
function string_to_utf8(str) {
var u8_ary = new Uint8Array(str.length*4);//最長*4
var ptr8 = 0;
for(var i = 0;i < str.length; ++i){
// utf16 > utf32
var utf32 = 0;
var utf16_0 = str.charCodeAt(i);
if( utf16_0 >= 0xd800 && utf16_0 < 0xdc00){//high
if(i+1>str.length)return;// 不正な文字列
var utf16_1 = str.charCodeAt(++i);
if( utf16_1 >= 0xdc00 && utf16_1 < 0xe000){//low
utf32 = ((utf16_0 & 0x03c0)<<10) + 0x10000;
utf32 += (utf16_0 & 0x003f)<<10;
utf32 += (utf16_1 & 0x03ff);
//000u uuuu xxxx xxxx xxxx xxxx
//1101 10ww wwxx xxxx 1101 11xx xxxx xxxx ( wwww = uuuuu-1
}else{
return;// 不正な文字列
}
}else if(utf16_0 >= 0xdc00 && utf16_0 < 0xe000){
return; // 不正な文字列
}else{
utf32 = utf16_0;
}
if(ptr8+4 > u8_ary.length)return;//不正な文字列
// utf32 > utf8
if(utf32 < 0x80){
u8_ary[ptr8++] = utf32;
}else if(utf32 < 0x800){
u8_ary[ptr8++] = 0xc0 + (utf32 >> 6);
u8_ary[ptr8++] = 0x80 + (utf32 & 0x3f);
}else if(utf32 < 0x10000){
u8_ary[ptr8++] = 0xe0 + (utf32 >> 12);
u8_ary[ptr8++] = 0x80 + ((utf32 >> 6) & 0x3f);
u8_ary[ptr8++] = 0x80 + (utf32 & 0x3f);
}else if(utf32 < 0x110000){
u8_ary[ptr8++] = 0xf0 + (utf32 >> 18);
u8_ary[ptr8++] = 0x80 + ((utf32 >> 12) & 0x3f);
u8_ary[ptr8++] = 0x80 + ((utf32 >> 6) & 0x3f);
u8_ary[ptr8++] = 0x80 + (utf32 & 0x3f);
}
}
return Uint8Array.from(u8_ary.subarray(0,ptr8));
}
// Uint8Array(UTF8) ⇒ String(UTF16)
function utf8_to_string(u8_ary)
{
var str = "";
var ptr8 = 0;
var len8 = u8_ary.length;
// Stringの1文字追加効率悪い? とりあえずキャッシュ
var str_cache = new Uint16Array(256);
var cptr = 0;
do {
var utf32 = 0;
do {//utf8 > utf32
// 0xxx xxxx
var c0 = u8_ary[ptr8++];
if( c0 < 0x80 ){utf32 = c0; break;}
// 110yyyyx
if( (c0 & 0xe0) == 0xc0 ){ //len = 2
if( ptr8+1 > len8)break;
if( (c0 & 0x1e) == 0 )break;// yyyyどれか必ず1
var c1 = u8_ary[ptr8++];
if( (c1 & 0xc0) != 0x80 )break;//10ではない
utf32 = ((c0 & 0x1f)<<6) | (c1 & 0x3f);
break;
}
// 1110yyyy 10yxxxxx 10xxxxxxx
// yyyy yxxx xxxx xxxx
if( (c0 & 0xf0) == 0xe0 ){ //len = 3
if( ptr8+2 > len8)break;
var v = (c0 & 0x0f) << 12;
var c1 = u8_ary[ptr8++];
if( (c1 & 0xc0) != 0x80 )break;//10ではない
v |= (c1 & 0x3f) << 6;
var c2 = u8_ary[ptr8++];
if( (c2 & 0xc0) != 0x80 )break;//10ではない
v |= (c2 & 0x3f);
if( (v & 0xf800) == 0 )break;//yyyyyどれか必ず1
utf32 = v;
break;
}
// 1111 0yyy 10yy xxxx 10xxx xxx 10xx xxxx
// y yyyy xxxx xxxx xxxx xxxx
if ( (c0 & 0xf8) == 0xf0 ) { //len = 4
if( ptr8+3 > len8)break;
var v = (c0 & 0x07) << 18;
var c1 = u8_ary[ptr8++];
if( (c1 & 0xc0) != 0x80 )break;//10ではない
v |= (c1 & 0x3f) << 12;
var c2 = u8_ary[ptr8++];
if( (c2 & 0xc0) != 0x80 )break;//10ではない
v |= (c2 & 0x3f) << 6;
var c3 = u8_ary[ptr8++];
if( (c3 & 0xc0) != 0x80 )break;//10ではない
v |= (c3 & 0x3f);
if( (v & 0x1f0000) == 0 )break;//yyyyyどれか必ず1
utf32 = v;
break;
}
}while(0);
// 不正なコード
if(utf32 <= 0 || utf32 > 0x10ffff)return;
// キャッシュ書き込み
if(cptr+2 > str_cache.length){
str += String.fromCharCode.apply(null,str_cache.slice(0,cptr));
cptr = 0;
}
if(utf32 < 0x10000) {
str_cache[cptr++] = utf32;
}else{
//000u uuuu xxxx xxxx xxxx xxxx
//1101 10ww wwxx xxxx 1101 11xx xxxx xxxx ( wwww = uuuuu-1
var u1 = (utf32 & 0x01f0000) - 0x10000;
var u2 = (utf32 & 0x000fc00);
var u3 = (utf32 & 0x00003ff);
str_cache[cptr++] = 0xd800 + (u1>>10) + (u2>>10);
str_cache[cptr++] = 0xdc00 + u3;
}
}while(ptr8 < len8);
str += String.fromCharCode.apply(null,str_cache.slice(0,cptr));
// apply 配列の各要素を引数に分解 スタックに積まれるので数に制限あり
return str;
}
// Rust Stringを作成
function rust_string_from_js(js_str) {
var utf8 = string_to_utf8(js_str);
// String::new()
var str_ptr = wasm_inst.exports.rust_js_string_new();
// String::reserve
var buff_ptr = wasm_inst.exports.rust_js_string_reserve(str_ptr, utf8.length);
if( buff_ptr == 0 )return 0;//メモリ確保失敗
// 書き込み
var u8_array = new Uint8Array(wasm_inst.exports.memory.buffer);
var u8_buff = u8_array.subarray(buff_ptr, buff_ptr+utf8.length);
u8_buff.set(utf8);
wasm_inst.exports.rust_js_string_set_len(str_ptr, utf8.length);
return str_ptr;
}
まとめ
個人的なこだわりと趣味に走ったプログラムになりましたが、Rustの勉強になりました。あとはこの仕様で機能を追加していきます。テクスチャ、ゲームループ、行列計算、ファイル読み込みなど。コードが増えてきたのでモジュール化や外部クレート化にも取り掛かります。
>> Rust(wasm)⇔JavaScript ファイル読み込みとアニメーション(ゲームループ)
<< Rust(wasm)とJavaScript(WebGL)のデータ受け渡し Rust+WebGLでポリゴン描画 (1/2)