Rust(wasm)とJavaScript(WebGL)のデータ受け渡し Rust+WebGLでポリゴン描画 (2/2)

投稿日:

前回準備した機能を使って、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ファイル名を書き換えればビルド&実行できます。

実行
□rust-webgl-polygon

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)

コメントを残す

メールアドレスが公開されることはありません。

認証:数字を入力してください(必須) * Time limit is exhausted. Please reload CAPTCHA.