Rust(wasm)⇒WebGL テクスチャファイル読み込み

投稿日:

Rust(wasm)で画像ファイルを読み込み、WebGLに転送、テクスチャポリゴンを表示します。JavaScriptの画像処理は使わずに、Rustで画像のバイナリーデータを解析、必要があればデコードしてWebGLのtexImage2Dへ。GPUが直接扱えるDDS(無圧縮)とATF(DXT+ETC1+PVRTC)、デコードが簡単なTGAの3種類の画像に対応します。

 

WebGL(GPU)対応フォーマット

今回対応したフォーマットです。

フォーマット ピクセル 対応画像
GL_ALPHA 8bit DDS(A8)
GL_RGB RGB8  24bit DDS(BGR8) TGA(RGB)
GL_RGBA RGBA8  32bit DDS(ABGR8) TGA(RGBA)
GL_LUMINANCE 8bit DDS(R8) TGA(GrayScale)
圧縮フォーマット
   
S3TC_DXT
RGB RGBA DDS(DXT)  ATF(DXT)
ETC1
RGB ATF(ETC1)
PVRTC_4BPPV1
RGB RGBA ATF(PVRTC) ※未確認
DDS(無圧縮)

DirectDraw Surface file format 、対応フォーマットが多いのが特徴です。WebGLで直接扱えるフォーマットのみに対応しました。RGBA8やRGB8だと色の並びが逆になるため、BGR8とABGR8にする必要があります。グレースケールはR8、アルファ値のみはA8。ミップマップに対応しています。

WebGL PACK_ALIGNMENT = 4(deafult)

ミップマップありDDS(R8/A8/BGR8)でWebGLのテクスチャを作成すると

> Error: WebGL warning: texImage2D:
Desired upload requires more data than is available:
(1 rows plus 2 pixels needed, 1 rows plus 1 pixels available)

こんなエラーがでてテクスチャ作成に失敗します。これは1行(row)のデータサイズがPACK_ALIGNMENTの倍数になっていないため発生します。横幅がPACK_ALIGNMENT未満、1ピクセルのサイズがPACK_ALIGNMENTの倍数ではないテクスチャで起こります。対象のミップマップ画像データのアライメントが必要になります。

2×2、PACK_ALIGNMENT=4、GL_RGBの場合(メモリの並びBGRかも)

4byte 4byte
R G B R G B
R G B R G B

gl.pixelStorei(gl.PACK_ALIGNMENT, n)で変更できますが、1や2にするとWebGLのピクセル処理の効率が悪くなります(たぶん、未確認)。

TGA

データ構造が単純でアルファチャンネル対応。とりあえず対応したけどDDS(無圧縮)で十分かもしれない。色の並び変えが必要。

png  jpeg

一般的な画像。ファイルサイズが小さいけれど、WebGLで扱うためにはRGBにデコードする必要があるためメモリ(GPU)の節約にはならない。デコードも複雑なので非対応に。

圧縮テクスチャ(DXT/ETC1/PVRTC)

GPUが直接扱えるフォーマット。画像サイズ1/4~1/8の非可逆圧縮。WebGLの拡張機能として利用できます。ほとんどの環境でどれか1つに対応しているはずです。WindowsはDXT、iOSはPVRTC、AndroidはETC1。

ATF(Adobe Texture Format)

Adobeが提供しているテクスチャフォーマットで、いろいろな種類のフォーマットに対応しています。その中のDXT、ETC1、PVRTCを1つのファイルにまとめたフォーマットを使用します。

参考にしたページ
3Dコンテンツの最適化に。圧縮テクスチャーをWebGLで扱う方法と利点
ATF SDKを使った圧縮テクスチャの使い方入門

ATF変換ツール
ATF SDKが見つからない。AIR SDKにATF関連のツールが含まれているようなので、Downloadして必要なものだけ抜き出しました。
Download Adobe AIR SDK

> png2atf -c d,e,p  -i texture.png -o texture.atf

これでDXT、ETC1、PVRTCがまとめられたATFファイルを作成しました。

 

テクスチャポリゴンの表示

画像からテクスチャを作成できるようになったので、テクスチャを貼ったポリゴンを表示してみます。あと、ATFから対応している圧縮テクスチャを選択します。PVRTCは確認する環境がないので正常に表示されるか未確認です。

実行

□rust-webgl-texture

Rustプロジェクトとソースコードです。Windowsコマンドスクリプトでのビルドになります。

“Rust(wasm)⇒WebGL テクスチャファイル読み込み” をダウンロード rust_webgl_texture-1.zip – 16 回のダウンロード – 72 KB

ソースコード

画像ファイルを解析し、WebGLのtexImage2Dでテクスチャを作成するためのコードです。TGA以外はファイル内容を加工なしで直接texImage2Dに渡せるよう工夫しています。あとヒープメモリをできるだけ利用しないように。

画像データへのアクセス

JavaScriptのfetchでは、ファイルの一括読み込みしかできないため、画像ファイルをメモリにロードし、texImage2Dへ渡すメモリをファイル先頭からのオフセットとサイズで指定するようにしました。

teximg/mod.rspub mod tga;
pub mod dds;
pub mod atf;

pub struct Error {
    s : String,
}
impl Error {
    pub fn to_string(&self) -> String { self.s.clone() }
    //pub fn as_str(&self) -> &str { self.s.as_str() }
}

/// 圧縮フォーマット
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum CompType {
    Dxt = 0,
    Pvrtc = 1,
    Etc1 = 2,
    Etc2 = 3,

    Not = 99,
}

/// 画像ファイルの内容を直接参照 GPUが直接扱える画像フォーマット
pub struct Slice{
    ofs : usize,
    end : usize,
}

/// 画像データの指定方法
pub enum DataType{
    None,
    /// 未加工データ ファイル先頭からのオフセット
    Raw(Slice),
    /// webgl pitch aligned 4x4未満のmipmap画像など Vecだと効率が悪い小サイズ画像
    SmallAligned([u8;ALIGNED_IMG_MAX]),
    /// 加工済みデータ GPUが直接扱える画像フォーマットに変換
    Decode(Vec<u8>),
}

impl DataType{
    // 画像データのスライス(アドレスとサイズ)
    pub fn get<'a>(&'a self, file : &'a[u8]) -> Option<&'a[u8]> {
        match self {
            DataType::SmallAligned(d) => Some(&d[..]),
            DataType::Raw(o) => {
                if o.ofs < file.len() && o.end <= file.len()
                    { Some(&file[o.ofs..o.end])}
                else{ None } },
            DataType::Decode(v) => Some(&v),
            _ => None,
        }      
    }
}

impl Slice{
    fn new(ofs :usize, size:usize) -> Slice {
        Slice{ofs:ofs, end:ofs+size,}
    }
}

const ALIGNED_IMG_MAX : usize = 16;

pub trait ErrorFrom{
    fn error(&self) -> Error;
}
impl ErrorFrom for &str{
    fn error(&self) -> Error{
        Error{ s : String::from(*self),}
    }
}
impl ErrorFrom for &String{
    fn error(&self) -> Error{
        Error{ s : (*self).clone(),}
    }
}
impl ErrorFrom for String{
    fn error(&self) -> Error{
        Error{ s : self.clone(),}
    }
}
impl Error {
    fn new<T : ErrorFrom >(s : T) -> Error { s.error() }
}
pub fn error<T : ErrorFrom >(s : T) -> Error { Error::new(s) }
ATF解析
teximg/atf.rs#![allow(unused)] 
use super::Error;
use super::error;
use super::DataType;
use super::Slice;
use super::CompType;


#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Format {
    RawCompressed = 3,
    RawCompressedAlpha = 5,
}

pub const MIPMAP_MAX_NUM : usize = 12;

/// ATF画像情報
pub struct Info {
    pub width : u32,
    pub height : u32,
    pub count : u32,
    pub format : Format,
    pub comp : CompType,
    images : [DataType;MIPMAP_MAX_NUM],//[mipmap]
    alphas : [DataType;MIPMAP_MAX_NUM],//Etc1 && RawCompressedAlpha
}

impl Info {
    pub fn get_images(&self) -> &[DataType] { &self.images[0..self.count as usize] }
    pub fn get_alphas(&self) -> &[DataType] { &self.alphas[0..self.count as usize] }
    
    /// ATF画像情報作成 compで指定した圧縮フォーマットのみ抽出
    pub fn from_u8(atf : &[u8], comp : CompType) -> Result<Info,Error>{
        let unsuppo = Err(error("atf : unsupported format"));
        let errfile = Err(error("atf : file error"));
        
        // "ATF"
        if atf[0]!=0x41 || atf[1]!=0x54 || atf[2]!=0x46 {
            return errfile;
        }
        if atf[7] != 3 {return Err(error("atf  : version!=3"));}

        // data size (BE)
        let length = ((atf[8] as u32) << 24) +  ((atf[9] as u32) << 16)
                   + ((atf[10] as u32) << 8) + atf[11] as u32;
        if length+12 > atf.len() as u32 {
            return errfile;//ファイルサイズが足りない
        }
        if (atf[12] & 0x80) != 0 {
            return unsuppo;//cube map
        }
        let atf_format = (atf[12] & 0x7f) as u32;

        let width = 1 << atf[13];
        let height = 1 << atf[14];
        let count = atf[15] as u32;
        let mut ptr = 16;
        if count as usize  > MIPMAP_MAX_NUM {
            return Err(error(format!("atf : mipmap {} > {}",count, MIPMAP_MAX_NUM)));
        }

        let format = match atf_format {
            3 => Format::RawCompressed,
            5 => Format::RawCompressedAlpha,
            _ => { return Err(error("atf : only RAW Compressed (Alpha)")); },
        };

        let mut images = make_empty_data_type_array();
        let mut alphas = make_empty_data_type_array();
        let comps = [CompType::Dxt,CompType::Pvrtc, CompType::Etc1, CompType::Etc2];
        for mi in 0..count as usize {
            for i in &comps[..] {
                let size = ((atf[ptr] as u32) << 24) +  ((atf[ptr+1] as u32) << 16)
                          + ((atf[ptr+2] as u32) << 8) + atf[ptr+3] as u32;               
                let size = size as usize;
                ptr += 4;
                if (size + ptr) > atf.len() {return errfile;}
                if *i == comp  && size > 0 {
                    if atf_format == (Format::RawCompressedAlpha as u32)
                    && comp == CompType::Etc1 {
                        // ETC1 + alpha
                        images[mi] = DataType::Raw(Slice::new(ptr,size/2));
                        alphas[mi] = DataType::Raw(Slice::new(ptr+size/2,size/2));
                    }else{
                        images[mi] = DataType::Raw(Slice::new(ptr,size));
                    }
                }
                ptr += size;
            }
        }

        Ok(Info{
            width : width,
            height : height,
            count : count,
            format : format,
            comp : comp,
            images : images,
            alphas : alphas,
        })
    }
}

fn make_empty_data_type_array() -> [DataType;MIPMAP_MAX_NUM] {
    [DataType::None, DataType::None, DataType::None, DataType::None,
     DataType::None, DataType::None, DataType::None, DataType::None,
     DataType::None, DataType::None, DataType::None, DataType::None,
    ]
}
DDS解析
teximg/dds.rsuse std::mem;
use std::cmp;

use super::Error;
use super::error;
use super::DataType;
use super::Slice;
use super::ALIGNED_IMG_MAX;

pub const MIPMAP_MAX_NUM : usize = 15;

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Format{
    R8Unorm,       // GL_LUMINANCE 
    A8Unorm,       // GL_ALPHA
    BC1Unorm,      // DXT1
    BC2Unorm,      // DXT3
    BC3Unorm,      // DXT5

    //DXGI未対応フォーマット webGLで直接扱える画像
    ABGR8Unorm, // GL_RGBA 32bit
    BGR8Unorm,  // GL_RGB 24bit
}

/// DDSファイル情報 2Dテクスチャのみ 
/// 画像データを保持しない、ファイル先頭からのオフセット値で指定
/// WebGL/無圧縮で使用する場合、GL_RGBA=>ABGR8 GL_RGB=>BGR8
pub struct Info {
    pub width : u32,
    pub height : u32,
    pub bpp : u32,
    pub format : Format,
    pub mipmap_count : u32,
    pub file_size : usize,
    pub images_size : usize,
    pub pitch_align : usize,//4 : webgl default
    images : [DataType;MIPMAP_MAX_NUM],//[mipmap]
}

impl Info {
    pub fn get_images<'a>(&'a self) -> &'a [DataType] {
        &self.images[0..self.mipmap_count as usize]
    }
}

impl Info {
    /// DDSファイル情報生成
    pub fn from_u8(dds : &[u8], align : usize) -> Result<Info,Error> {
      unsafe{
        let dds_ptr = dds.as_ptr() as usize;
        let unsuppo = Err(error("dds unsupported format"));

        // "DDS "
        let dds_magic = *(dds_ptr as *const u32);
        if dds_magic != DDS_MAGIC_NUMBER {
            return Err(error("dds file error"));
        }

       //ヘッダー解析
        let header = &*((dds_ptr+4) as *const DDSHeader);
        if header.size as usize != mem::size_of::<DDSHeader>() {
             return Err(error("dds file error"));
        }
        if (header.flags & DDS_FOURCC) != 0 {
            if header.ddspf.four_cc == DDS_DX10 {//"DX10"
                return unsuppo; //DX10未対応
            }
        }
        if header.ddspf.size as usize != mem::size_of::<DDSPixelFormat>() {
            return Err(error("dds file error"));
        }

        let width = header.width;
        let height = header.height;
        //let depth = 1;//header.depth;
        let mipmap_count = if 0==header.mipmap_count {1}else{header.mipmap_count };

        // 画像データの開始位置とサイズ
        let offset = 4 + header.size as usize;
        //let data_ptr = dds_ptr + offset;
        let images_size = dds.len() - offset;
        let format = match get_dxgi_format(&header.ddspf) { 
            Some(f) => f,
            None => { return unsuppo;}, };
        let bpp = bit_per_pixel(format);

        // 未対応
        if 0 != (header.caps2 & DDS_CUBEMAP) { return unsuppo; }

        // 非対応
        if 0 == bpp { return unsuppo; }
        if 0 != (header.flags & DDS_HEADER_FLAGS_VOLUME) { return unsuppo; }
        if mipmap_count > MIPMAP_MAX_LEVEL { return unsuppo; }
        if width > TEXTURE_MAX_WIDTH { return unsuppo; }
        if height > TEXTURE_MAX_WIDTH { return unsuppo; }

        let (mut mip_w, mut mip_h) = (width, height);
        
        let mut images = make_empty_data_type_array();

        let mut img_ofs = offset;
        for mi in 0..mipmap_count as usize{
            let (size, block, _) = get_image_size(mip_w, mip_h, format);

            // webgl対応 小サイズ画像のpitch alignment
            // 1x1 2x2 8bit 24bitなどALIGNED_IMG_MAX以内に収まる画像限定
            let mut data = DataType::Raw(Slice::new(img_ofs,size as usize));
            let pitch = (mip_w*bpp/8) as usize;
            let a_pitch = align*((pitch + align - 1)/align);

            if pitch != a_pitch && !block{ //DXTは不要
                //縦長すぎ // 24bit 1xNとか
                // 2x2 24bit まで対応
                let aligned_size = a_pitch*mip_h as usize;
                if aligned_size > ALIGNED_IMG_MAX {
                    // 未対応
                    let _data = DataType::Decode(Vec::new());
                    return unsuppo;
                }
                if bpp == 8 || bpp == 16 || bpp == 24 {
                    let mut img_tmp = [0u8;ALIGNED_IMG_MAX];
                    let mut gptr = 0;
                    let mut pptr = 0;
                    for _ in 0..mip_h {
                        for _ in 0..mip_w {
                            for _ in 0..bpp/8 {
                                img_tmp[pptr] = dds[img_ofs + gptr];
                                pptr += 1;
                                gptr += 1;
                            }
                        }
                        pptr = align*( (pptr + align-1) / align);
                        //if pptr >= ALIGNED_IMG_MAX { break; }
                    }
                    data = DataType::SmallAligned(img_tmp);
                }
            }
            images[mi] = data;
            /*images.push(Image {
                data : data,
            } );*/
            mip_w /= 2;
            mip_h /= 2;
            img_ofs = img_ofs + size as usize;
        }
        Ok( Info {
            width : width,
            height : height,
            bpp : bpp,
            format : format,
            mipmap_count : mipmap_count,
            pitch_align : align ,
            file_size : dds.len(),
            images_size : images_size, 
            images : images,
        })

      }      
    }
}


// pointer cast 
struct DDSPixelFormat {
    size : u32,
    flags : u32,
    four_cc : u32,
    rgb_bitcount : u32,
    rbit_mask : u32,
    gbit_mask : u32,
    bbit_mask : u32,
    abit_mask : u32,
}

// pointer cast 
struct DDSHeader
{
    size : u32,
    flags : u32,
    height : u32,
    width : u32,
    _pitch_or_linear_size : u32,
    _depth : u32,
    mipmap_count : u32,
    _reserved1 : [u32;11],
    ddspf : DDSPixelFormat,
    _caps : u32,
    caps2 : u32,
    _caps3 : u32,
    _caps4 : u32,
    _reserved2 : u32,
}

/* 非対応
struct DDSHeaderDXT10 {
    dxgi_format : u32,
    resource_dimension : u32,
    misc_flag : u32,
    array_size : u32,
    misc_flags2 : u32,
}*/

const DDS_MAGIC_NUMBER : u32 = 0x20534444;//"DDS "
const DDS_DXT1 : u32 = 0x31545844;//"DXT1"
const DDS_DXT3 : u32 = 0x33545844;//"DXT3"
const DDS_DXT5 : u32 = 0x35545844;//"DXT5"
const DDS_DX10 : u32 = 0x30315844;//"DX10"
const DDS_FOURCC : u32 = 0x00000004;  // DDPF_FOURCC
const DDS_RGB : u32 = 0x00000040;  // DDPF_RGB
const DDS_LUMINANCE : u32 = 0x00020000;  // DDPF_LUMINANCE
const DDS_ALPHA : u32 = 0x00000002;  // DDPF_ALPHA
const DDS_HEADER_FLAGS_VOLUME : u32 = 0x00800000;
const DDS_CUBEMAP : u32 = 0x00000200;
const MIPMAP_MAX_LEVEL : u32 = 15;
const TEXTURE_MAX_WIDTH : u32 = 16384;

fn get_dxgi_format(ddpf : &DDSPixelFormat) -> Option<Format> {
    let is_bitmask = |r,g,b,a| {
        ddpf.rbit_mask == r && ddpf.gbit_mask == g
        && ddpf.bbit_mask == b && ddpf.abit_mask == a };
    if (ddpf.flags & DDS_RGB) != 0 {
        match ddpf.rgb_bitcount {
            32 => if is_bitmask(0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000) {
                return Some(Format::ABGR8Unorm); },
            24 => if is_bitmask(0x000000ff, 0x0000ff00, 0x00ff0000, 0x00000000) {
                return Some(Format::BGR8Unorm) },
             _ => {},
         }
    }else if (ddpf.flags & DDS_LUMINANCE) != 0 {
        if ddpf.rgb_bitcount == 8 {
            return Some(Format::R8Unorm);
        }
    }else if (ddpf.flags & DDS_ALPHA) != 0 {
        if ddpf.rgb_bitcount == 8 {
            return Some(Format::A8Unorm);
        }
    }else if (ddpf.flags & DDS_FOURCC) != 0 {
        match ddpf.four_cc {
            DDS_DXT1 => { return Some(Format::BC1Unorm); },
            DDS_DXT3 => { return Some(Format::BC2Unorm); },
            DDS_DXT5 => { return Some(Format::BC3Unorm); },
            _ => {},
        }
    };

    None
}

fn bit_per_pixel(format : Format) -> u32{
    match format {
        Format::ABGR8Unorm => 32,
        Format::BGR8Unorm => 24,
        Format::R8Unorm => 8,
        Format::A8Unorm => 8, 
        Format::BC1Unorm => 4,      // DXT1
        Format::BC2Unorm => 8,      // DXT3
        Format::BC3Unorm => 8,      // DXT5
        //_ => 0,
    }
}

fn get_image_size(w : u32, h : u32, format : Format) -> (u32, bool, u32) {
    let (block, bsize) = match format {
        Format::BC1Unorm => (true,8),
        Format::BC2Unorm |
        Format::BC3Unorm => (true,16),
        _ => (false, 0),
    };

    if block {
        let num_bwide = if w > 0 { cmp::max(1, (w + 3) / 4) }
                        else { 0 };

        let num_bhigh = if h > 0 { cmp::max(1, (h + 3) / 4) }
                        else { 0 };
       
        (num_bwide * bsize * num_bhigh, block, bsize)
    }else{
        let bpp = bit_per_pixel(format);
        let row_byte = (w * bpp + 7) / 8;
        (row_byte * h, block, bsize)
    }
}

// なんか
fn make_empty_data_type_array() -> [DataType;MIPMAP_MAX_NUM] {
    [DataType::None, DataType::None, DataType::None, DataType::None,
     DataType::None, DataType::None, DataType::None, DataType::None,
     DataType::None, DataType::None, DataType::None, DataType::None,
     DataType::None, DataType::None, DataType::None,
    ]
}
TGA解析
teximg/tga.rsuse super::Error;
use super::error;
use super::DataType;

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Format {
    GrayScale,
    Rgb,
    Rgba,
}

/// TGA画像 webglで扱えるフォーマットに変換
pub struct Image {
    pub width : u32,
    pub height : u32,
    pub bpp : u32,
    pub format : Format,
    pub data : DataType,
}

impl Image {
    pub fn from_u8(tga : &[u8]) -> Result<Image,Error> {
        match tga[2] {
            2 | 3 => {},//gray or full color
            _ => { return Err(error("tga : unsupported format"));} ,
        };
        let width = (tga[12] as u32) + ((tga[13] as u32)<<8);
        let height = (tga[14] as u32) + ((tga[15] as u32)<<8);
        let bpp = tga[16] as u32;

        // left->right only
        if (tga[17] & 0x10) != 0 {return Err(error("tga : unsupported format"));}
        let rev_h = (tga[17] & 0x20) == 0;//上下反転

        let format = match bpp {
            8 => Format::GrayScale,
            24 => Format::Rgb,
            32 => Format::Rgba,
            _ => { return Err(error("tga : unsupported format"));} ,
        };
        let ofs : usize = 18;
        let size : usize = (width*height*bpp/8) as usize;
        if size + ofs > tga.len() { return Err(error("tga : file size error"));}
        let mut dec = Vec::<u8>::new();
        dec.reserve(size);
        let img = &tga[ofs..ofs + size];
        let d = bpp/8;
        match format {
            Format::Rgb => {
                for h in 0..height {
                    for w in 0..width {
                        let ptr = if rev_h {(((height - h-1)*width + w)*d)}
                                    else{((h*width + w)*d)} as usize;
                        dec.push(img[ptr+2]);
                        dec.push(img[ptr+1]);
                        dec.push(img[ptr+0]);
                    }
                }
            },
            Format::Rgba => {
                for h in 0..height {
                    for w in 0..width {
                        let ptr = if rev_h {(((height - h-1)*width + w)*d)}
                                    else{((h*width + w)*d)} as usize;
                        dec.push(img[ptr+2]);
                        dec.push(img[ptr+1]);
                        dec.push(img[ptr+0]);
                        dec.push(img[ptr+3]);
                    }
                }
            },
            Format::GrayScale => {
                for h in 0..height {
                    for w in 0..width {
                        let ptr = if rev_h {(((height - h-1)*width + w)*d)}
                                    else{((h*width + w)*d)} as usize;
                        dec.push(img[ptr+0]);
                    }
                }
            },
        };
        Ok( Image {
            width : width,
            height : height,
            bpp : bpp,
            format : format,
            data : DataType::Decode(dec),
        })
    }
}
WebGLテクスチャ作成
ren.rsuse super::srwgl::prelude::*;
use super::teximg;
use super::teximg::Error;
use super::teximg::error;

/// DDSからテクスチャ作成
pub fn create_texture_dds(dds_file : &[u8]) -> Result<GLHandle,Error> {
    let dds = match teximg::dds::Info::from_u8(&dds_file, 4){ Ok(d)=>d,
        Err(e) => { return Err(e); }, };

    let format = match dds.format {
        teximg::dds::Format::ABGR8Unorm => GL_RGBA,
        teximg::dds::Format::BGR8Unorm => GL_RGB,
        teximg::dds::Format::R8Unorm => GL_LUMINANCE,
        teximg::dds::Format::A8Unorm => GL_ALPHA,
        teximg::dds::Format::BC1Unorm => GL_COMPRESSED_RGBA_S3TC_DXT1_EXT,
        teximg::dds::Format::BC2Unorm => GL_COMPRESSED_RGBA_S3TC_DXT3_EXT,
        teximg::dds::Format::BC3Unorm => GL_COMPRESSED_RGBA_S3TC_DXT5_EXT,
        //_ => {return Err(error("tex dds format error"));} 
    };

    let mut tex = Texture::new();//glCreateTexture
    glBindTexture(GL_TEXTURE_2D, tex.get());       
    match format {
        // 圧縮テクスチャ
        GL_COMPRESSED_RGBA_S3TC_DXT1_EXT |
        GL_COMPRESSED_RGBA_S3TC_DXT3_EXT |
        GL_COMPRESSED_RGBA_S3TC_DXT5_EXT => {
            let mut level : GLint = 0;
            let mut width = dds.width;
            let mut height = dds.height;
            for i in dds.get_images() {
                if let Some(img) = i.get(dds_file) {
                    glCompressedTexImage2D(
                            GL_TEXTURE_2D, level, format,
                            width, height, 0, img);
                }else{
                    return Err(error("tex gl dds error"));
                }
                width /= 2;
                height /= 2;
                level+=1;
            }
            Ok(tex.take())
        },
        
        GL_RGBA | GL_RGB |
        GL_LUMINANCE |GL_ALPHA => {
            let mut level : GLint = 0;
            let mut width = dds.width;
            let mut height = dds.height;
            for i in dds.get_images() {             
                if let Some(img) = i.get(dds_file) {
                    glTexImage2D(GL_TEXTURE_2D, level, format,
                            width, height, 0, format, GL_UNSIGNED_BYTE,
                            img);
                }else{
                    return Err(error("tex gl dds error"));
                }
                width /= 2;
                height /= 2;
                level+=1; 
            }
            Ok(tex.take())
        },
        _ => Err(error("tex gl format error")),
    }
}

/// TGAからテクスチャ作成
pub fn create_texture_tga(tga_file : &[u8], gen_mipmap : bool ) -> Result<GLHandle,Error> {
    let tga = match teximg::tga::Image::from_u8(&tga_file){ Ok(tga) => tga,
            Err(e) => { return Err(e); }, };

    let format = match tga.format {
        teximg::tga::Format::Rgba => GL_RGBA,
        teximg::tga::Format::Rgb => GL_RGB,
        teximg::tga::Format::GrayScale => GL_LUMINANCE,
        //_ => {return Err(error("tex tga format error"));} 
    };

    if let Some(img) = tga.data.get(tga_file) {
        let mut tex = Texture::new();//glCreateTexture
        glBindTexture(GL_TEXTURE_2D, tex.get());       
        glTexImage2D(GL_TEXTURE_2D, 0, format, tga.width, tga.height,
                    0, format, GL_UNSIGNED_BYTE, img);
        if gen_mipmap {
            glGenerateMipmap(GL_TEXTURE_2D);
        }
        Ok(tex.take())
    }else{ return Err(error("tex gl tga error")) }
}

/// ATFからテクスチャ作成
pub fn create_texture_atf(atf_file : &[u8], comp : teximg::CompType)
-> Result<GLHandle,Error> {
    let atf = match teximg::atf::Info::from_u8(atf_file, comp) {
        Ok(atf) => atf,
        Err(e) => {return Err(e); }, };

    let gl_format = match atf.comp {
        teximg::CompType::Dxt => {
            match atf.format {
              teximg::atf::Format::RawCompressed
                => GL_COMPRESSED_RGB_S3TC_DXT1_EXT,
              teximg::atf::Format::RawCompressedAlpha
                => GL_COMPRESSED_RGBA_S3TC_DXT5_EXT,
            }
        },
        teximg::CompType::Etc1 => {
            GL_COMPRESSED_RGB_ETC1_WEBGL
        },
        teximg::CompType::Pvrtc => {
            match atf.format {
              teximg::atf::Format::RawCompressed
                => GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG,
              teximg::atf::Format::RawCompressedAlpha
                => GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG,
            }
        },
        _ => { return Err(error("tex gl atf error")); },
    };

    let mut tex = Texture::new();//glCreateTexture
    glBindTexture(GL_TEXTURE_2D, tex.get());       
    
    let mut level : GLint = 0;
    let mut width = atf.width;
    let mut height = atf.height;
    for i in atf.get_images() {
        if let Some(img) = i.get(atf_file) {
            glCompressedTexImage2D(
                    GL_TEXTURE_2D, level, gl_format,
                    width, height, 0, img);
        }else{
            return Err(error("tex gl atf error"));
        }
        level+=1;
        width /= 2;
        height /= 2;
    }
    Ok(tex.take())
}

struct Shader {
    h : GLHandle,
}

impl Shader {
    /// *shader_type : GL_VERTEX_SHADER, GL_FRAGMENT_SHADER
    fn new(shader_type : GLenum) -> Shader {
        Shader { h : glCreateShader(shader_type), }
    }
    fn get(&self) -> GLHandle { self.h }
    /*fn take(&mut self) -> GLHandle {
        let ret = self.h;
        self.h = GLHANDLE_NULL;
        return ret;
    }*/
}
impl Drop for Shader {
    fn drop(&mut self) {
        if self.h != GLHANDLE_NULL {
            glDeleteShader(self.h);
        }
    }
}
struct Program {
    h : GLHandle,
}
impl Program {
    fn new() -> Program {
        Program { h : glCreateProgram(), }
    }
    fn get(&self) -> GLHandle { self.h }
    fn take(&mut self) -> GLHandle {
        let ret = self.h;
        self.h = GLHANDLE_NULL;
        return ret;
    }
}
impl Drop for Program {
    fn drop(&mut self) {
        if self.h != GLHANDLE_NULL {
            glDeleteProgram(self.h );
        }
    }
}
struct Texture {
    h : GLHandle,
}
impl Texture {
    fn new() -> Texture {
        Texture { h : glCreateTexture(), }
    }
    fn get(&self) -> GLHandle { self.h }
    fn take(&mut self) -> GLHandle {
        let ret = self.h;
        self.h = GLHANDLE_NULL;
        return ret;
    }
}
impl Drop for Texture {
    fn drop(&mut self) {
        if self.h != GLHANDLE_NULL {
            glDeleteTexture(self.h );
        }
    }
}

まとめ

これでRustでWebGLプログラムミングの基本的な環境が完成?あとは入力インターフェイスがあればゲームが作れそうです。

<< Rust+Wasm+WebGLはじめました 環境構築=>Hello World =>画面クリアまで(Windows10)

 

コメントを残す

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

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