「error LNK2001: 外部シンボル "_fltused" は未解決です」を解決する

小さいexeを作っているときに _fltused が未解決だからどーのというエラーが出てしまってビルドできなくなってしまいました。。

ググると↓のページを見つけて

コスミー報告書[社外秘] - CSTLを使ってDLLを小さくするっていう話

EXTERN_C int _fltused = 0x9875; を適当な位置に挿入すると動くようになった

windowsのchromeでglslのコンパイルが遅すぎるのでANGLEを切る

最近shadertoyでシェーダーを書いてるんですが

かなり肥大化してきて、コンパイルに20秒ぐらいかかる & たまにブラウザがクラッシュするという問題が発生していました

windowschromeOpenGL ESがANGLE経由で動いているのでglslコードがhlslコードに変換されたのちにコンパイルされているから遅いんだろうなぁという何となくな予想はしていたんですが、特に解決策も調べずに諦めていました

今日会社でなんとなく呟いてみると同僚の方にGPUのドライバが対応していればchromeの起動オプションでANGLEを切れると教えてもらったので試してみました

そのオプションは --use-angle=gl です

設定した状態で起動してシェーダーをコンパイルしてみると爆速になっていました!うれしい

f:id:kaneta1011:20180705231931p:plain

ただ少し気になることがあって、ANGLEを切った後と切る前でシェーダーの挙動が異なっていました。ANGLEの状態が想定していた挙動でした。

原因は僕が clamp の第一引数と第三引数が入れ替わった状態で実装していだからだったんですが、なんで今までちゃんと動いてたんだろう...

ちなみにANGLE経由の方がシェーダーの実行速度は早かったです

crinklerとvisual studio 2017で400byteのexeを作る

最近4kbデモを作りたくてグラフィック面でいろいろ勉強していたんですが、今週は小さい実行ファイルを生成する手法を学びました

開発環境

空のプロジェクトを作成する

最近めっきりvisual studioを触る機会がなかったので知らなかったのですが、win32アプリケーションプロジェクトの作成の仕方が変わっていてびっくりしました

プロジェクト新規作成から【Visial C++>Windowsデスクトップ>Windows デスクトップ ウィザード】でプロジェクト名を設定してOK f:id:kaneta1011:20180701030008p:plain

次のダイアログで

  • Windowsアプリケーションを選択
  • 空のプロジェクトをチェック
  • セキュリティ開発ライフサイクルのチェックを外してOK f:id:kaneta1011:20180701030107p:plain

crinklerを入手する

以下のURLよりcrinklerを入手します

解凍して得た crinkler.exelink.exe にリネームして、上記で作成した空プロジェクトのソリューションディレクトリに移動します

こちらはデモシーナー界隈でよく使用されているらしいリンカーで、既存のリンカーと差し替えるだけでファイルサイズを圧縮してくれる優れものだそうです

http://www.crinkler.net/

プロジェクトの設定をする

以下の設定をRelease構成で行います

  • 【全般>プログラム全体の最適化】を プログラム全体の最適化なし に変更
  • VC++ディレクトリ>実行可能ファイルディレクトリ】の先頭$(SolutionDir) を追加
    • 先頭に追加しないと link.exe を検索する優先度が既存の物に負けてしまってcrinklerが利用されないので気を付けてください(2時間ぐらい潰されました)
  • 【リンカー>入力>すべての既定のライブラリを無視】を はい に変更
  • 【リンカー>マニフェストファイル>マニフェストの生成】を いいえ に変更
  • 【リンカー>コマンドライン>追加のオプション】に /CRINKLER を追加

main.cppを作成してビルド!

main.cppを作成して以下のコードを入力したらリリースビルドしましょう

int WinMainCRTStartup()
{
    return 0;
}

424byteの実行ファイルが作成できました! ちなみにcrinklerなしでビルドすると1,536byteなので1/4弱削減できているみたいです。すごいですね f:id:kaneta1011:20180701033429p:plain

ここからは映像を表示したり、音楽を再生したりする機能を追加してくことになります

いろいろ見ていると機能追加でだいたい1.5kbぐらいになるみたいですね、使える容量は2.5kbしかないのか...

モザイクフィルタを作ってみたところ不可解な境界線が現れてしまいました

画像をモザイクにしようと思ってこんなシェーダーを作成していました

uniform sampler2D tex;
void main() {
    vec2 uv = floor(texCoord * 30.0) / 30.0;
    gl_FragColor = texture2D(tex, uv);
}

実際に使ってみると↓のような見た目になり、なにやらモザイクのセル間に謎の境界線が...

f:id:kaneta1011:20180622164101p:plain

UVを表示してペイントのスポイトで問題の箇所のUVがおかしくなっていないか確認したが問題は見つかりませんでした

実装はどう考えても問題なかったので、それ以外の要因があると2時間ほど頭を抱えていたところ、原因はミップマップがonになっていたからでした

モザイク処理したことによってセル間で急激にUVが切り替わり、境界線でミップマップレベルが大きくなっていたようです

ミップマップ off後

f:id:kaneta1011:20180622165427p:plain

ミップマップには気を付けましょう

シェーダーで条件分岐は遅いらしいのでベクトル演算に置き換えようとしてできなかった話

一般的にシェーダーでの条件分岐は遅いという話がある

今回作ったシェーダーではブレンドモードや使用するマスクのチャンネルをアプリから送信してシェーダー内で利用するということをした

これを条件分岐を使って素直に実装するとこんな感じになる(空で書いたので動くか不明)

fixed3 blendColor(fixed3 src, fixed4 dest, float blend, int mode) {
    float alpha = dest.a * blend;
    fixed3 blended_dest = dest.rgb * alpha;
    if (mode == 0) {
        return lerp(src, dest, alpha);
    } else if (mode == 1) {
        return src + blended_dest;
    } else if (mode == 2) {
        return src - blended_dest;
    } else if (mode == 3) {
        return src * blended_dest;
    }
    return src;
}

fixed useMask(fixed4 mask, int use_mask_number) {
    if (use_mask_number == 0) {
        return 1.0 - mask.r;
    } else if (use_mask_number == 1) {
        return 1.0 - mask.g;
    } else if (use_mask_number == 2) {
        return 1.0 - mask.b;
    } else if (use_mask_number == 3) {
        return 1.0 - mask.a;
    }
    return 0.0;
}

これの modeuse_mask_number を使用したいチャンネルが1.0のベクトルで渡すことで、if文を行列とベクトル演算に置き換えることができる

fixed3 blendColor(fixed3 src, fixed4 dest, float blend, float4 mode) {
    float alpha = dest.a * blend;
    fixed3 blended_dest = dest.rgb * alpha;
    // GLESでは非正方行列が使用できないので使わないところは0で埋める
    return mul(mode, fixed4x4(lerp(src, dest, alpha), 0,
                                src + blended_dest, 0,
                                src - blended_dest, 0,
                                src * blended_dest, 0)).rgb;
}

fixed useMask(fixed4 mask, float4 use_mask_vector) {
    return 1.0 - dot(mask, use_mask_vector);
}

これで高速化できただろう!と思ってましたが、結局最初に書いたifのパターンの方がパフォーマンスが出てました。

こちらのリンク先のとおりであれば、実行中に実際に分岐されない場合ではifの方がパフォーマンスがでるんじゃないかと思いました。(未検証)

But on modern GPUs, they don't cause a performance issue unless they actually do diverge at runtime.

www.khronos.org

またピクセル毎に分岐するような場合はDivergent分岐と呼ばれるらしくて、こういう時にifのパフォーマンスが悪くなるらしい(uvで偶数奇数を分岐するなど)

ただし、この場合でも算術演算によるブランチングがifと比べて速くなるかは検証していないので不明...

(想像の話しかしてないのでアセンブリ読んでみたいけどどこから参照すればいいのかわからない...)

Unityのマテリアルエディタ拡張でテクスチャのプロパティを表示する

最近Unityを触っていてマテリアルエディタを拡張したくなったのでテクスチャのプロパティを表示するところから始めてみた

最初は自前でUIを組み立ててどうやらするのかと思ってその方面で調べていたけど、標準のテクスチャプロパティのスタイルであれば TextureProperty 関数を呼ぶだけで表示できることが分かったのでそれを使用した

using UnityEngine;
using System.Collections;
using UnityEditor;

public class CardShaderInspector : MaterialEditor
{

    public override void OnInspectorGUI()
    {
        if (!isVisible) { return; }
        MaterialProperty mask1 = GetMaterialProperty(targets, "_Mask1Tex");
        // 第三引数をtrueにするとタイリングとオフセットのUIも表示される
        TextureProperty(mask1, "Mask1", false);
    }
}

ここに行き着くのに二時間もかかってしまった・・

windowsにgccをインストールするメモ

go-glというgolangopenglラッパーをgo getしようとしたところ、cgoを利用しているようでgccが必要という旨が表示されたのでインストールした

MSYS2/MinGW-w64 (64bit/32bit) インストール手順 メモ · GitHub こちらをみて64bitMsys2インストール

pacman -Syuu
pacman -S base-devel
pacman -S mingw-w64-x86_64-toolchain

このコマンドで開発環境インストール 途中のインストールを指定するところはALLにした

C:\msys64\mingw64\bin

上記gcc等開発ツール一式がインストールされるので、こちらを環境変数のPATHに加えた

そのあとgo get -uすれば問題無くインストールできた