仮眠プログラマーのつぶやき

自分がプログラムやっていて、思いついたことをつぶやいていきます。

自作ゲームやツール、ソースなどを公開しております。
①ポンコツ自動車シュライシュラー
DOWNLOAD
②流体力学ソース付き
汚いほうDOWNLOAD
綺麗なほうDOWNLOAD
③ミニスタヲズ
DOWNLOAD
④地下鉄でGO
DOWNLOAD
⑤ババドン
DOWNLOAD
⑥HLSLサンプル
DOWNLOAD
⑦圧縮拳(ツール)
DOWNLOAD
⑧複写拳
DOWNLOAD
⑨布シミュレーション
DOWNLOAD
⑩hspでgpgpu
DOWNLOAD
⑪Handbrakeドラッグドロップツール
DOWNLOAD
⑫minecraft巨大電卓地形データ
DOWNLOAD
⑬フリュードランダー
デジゲー博頒布α版
DOWNLOAD
⑭パズドラルート解析GPGPU版
DOWNLOAD
⑮ゲーム「流体de月面着陸」
DOWNLOAD

PyCUDA導入メモ

せっかくNVIDIAグラボが入っているPCが手に入ったのでPyCUDAで遊ぼうかと

環境
OS:Windows 10
CPU:core i7 8700k
RAM:32GB
GPU:GTX 1080


導入手順

Visual Studio 2017 community版をインストール


最新のcuda tool kit (cuda_10.0.130_411.31_win10)
をAll Install

最新のAnaconda 5.3(Python 3.7)
をAll Userにインストール
・Add Anaconda to the system PATH environment variable
・Register Anaconda as the system Python 3.7
の両方にチェックが入っていることを確認(多分片方チェックが外れている)

3-1
インストールできたか確認
cmd(コマンドプロンプト)でpythonと打ちpython 3.7 ::Anaconda・・・がでればOK

cmdを管理者権限で実行
pip install pycuda
でpycudaをインストール

4-1
cmdを開いて
pythonと打ちimport pycuda
ができるか確認

4-2
次にpythonでテストプログラム
import pycuda.gpuarray as gpuarray
import pycuda.driver as cuda
import pycuda.autoinit
import numpy
a_gpu = gpuarray.to_gpu(numpy.random.randn(4,4).astype(numpy.float32))
a_doubled = (2*a_gpu).get()
print(a_doubled)
print(a_gpu)
ができるか確認。おそらく今の状態だとcl.exeのパスが通っていなくエラーがでるはず


cl.exeのパスを通す
環境変数のPATHに以下を追加
『C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\bin\Hostx64\x64』

5-1
確認
cmdでclと打ち
Microsoft(R) C/C++ Optimizing Compiler Version・・・とでればパスが通っている

5-2
もう一回pythonでテストプログラム
import pycuda.gpuarray as gpuarray
import pycuda.driver as cuda
import pycuda.autoinit
import numpy
a_gpu = gpuarray.to_gpu(numpy.random.randn(4,4).astype(numpy.float32))
a_doubled = (2*a_gpu).get()
print(a_doubled)
print(a_gpu)
おそらく大量のWarningがでるがErrorはでないはず
→できた!(終了)
→やはりできない。この場合グラボのドライバをアップデートすることで私の場合は解決した。GPU-ZでCUDAにチェックが入っているか再度確認を


Warningの消し方・・・
よくわからんがまぁ動いているからヨシ!(分かったら追記します)

デジゲー博頒布物(フリュードランダー)

2018/11/4 デジゲー博サークル参加しました。
poster

大変好評で遊んでくださった皆様本当にありがとうございました。
また最後のほうCD焼きが間に合わず名刺だけになってしまった方はすみません。
当日配布した無料体験版のweb公開をしております。
https://github.com/toropippi/cfdgame
↑のメッセージボックスやホームページからもダウンロードできます。

冬コミC95もよろしくお願いします!!!

CommandBuffer.DrawProceduralと半透明合成

UnityでGraphics.DrawProcedural使ってたけど半透明処理とかで詰まって、CommandBuffer.DrawProceduralに切り替えてうまくいったよという話。


やりたいこと
全レイヤー

レイヤー1でDrawProceduralを使いつつ、一番手前レイヤーの半透明の矩形をうまく表示したかった。


しかし「Graphics.DrawProcedural」を使っていた当初はdamenahou

こんな感じに本来後ろに描画されてほしい粒子が半透明の矩形より手前に描画されてしまっていた。



どういうことかというと、最初から説明すると
全レイヤー

Zはマイナスのほうが「手前」。カメラの位置はz=-10.0
で、レイヤー0、2、3はそれぞれ2DスプライトをGameObjectとしてUnity画面上に配置している。レイヤーというのは頭の中で考えているだけで、実際の配置はUnity側でGameObjectの座標を0.01とか-0.02とかベタ打ちしている。
レイヤー1の流体の粒子だけは特殊で、65536*16個のドットの描画を必要とする。この大量のドットの描画の仕方としてはここを参考にOnRenderObject()内でGraphics.DrawProceduralを使うことで実現していた。いわゆるGPU Instancing機能だ。スクリプト側で各ドットの座標を記憶するCompute Bufferを生成しておき、Compute shaderで座標を更新して、最後にGraphics.DrawProceduralで描画といった形。
このGraphics.DrawProceduralは「即時実行」の命令だ。
https://docs.unity3d.com/ja/2017.4/ScriptReference/Graphics.DrawProcedural.html
(公式リファレンスより「この呼び出しは Graphics.DrawMeshNow のようにすぐに実行されることに注意してください。」)

さて、先ほどOnRenderObject()内でGraphics.DrawProceduralを実行すると言ったがこれはどのタイミングになるのか

http://staff.live2d.com/archives/55264880.html
このサイトを参考にさせて頂くと
Update()

中略

Camera1のレンダリング

中略

OnRenderObject()

・・・・
このようになる。
ということは全てのGameObjectが「Camera1のレンダリング」フェーズで描画された後に「OnRenderObject()」フェーズでGraphics.DrawProceduralが実行されていたことがわかる。これではGPUインスタンスで描画されるものがすべて手前にきてしまう!
damenahou

もう一度よくさっきの失敗画像を見てみると、確かに緑のステージより手前に半透明の矩形は描画されている。そしてその後実行されるGraphics.DrawProceduralでレイヤー1が矩形の上から描画されてしまっている。
今回半透明の矩形描画はZWrite Offとしていたため、背景のz=0.01の情報が残っており描画されてしまったのだろう。ちなみにZWrite Onにしたら今度は矩形の領域だけ粒子が「抜けて」しまった。
saisyo
予想通り。ZTestで負けた粒子は描画されてない。
しかしこれは解決にはならない。

今回のでGraphics.DrawProceduralのスクリプト上での実行位置が悪いことが分かった。これをUpdateの最後にしたらどうか

これは実際にやってみたがうまくいかなかった。
今度はz=0.01の背景が粒子の上から描画されてしまうようだった。背景もGameObjectなので「Camera1のレンダリング」フェーズで描画されるためだ。

じゃあなんとか「Camera1のレンダリング」フェーズでGraphics.DrawProceduralを実行できないのか!?

いろいろ調べたところ、CommandBufferというのを使えばいいらしいことがわかった。
https://forum.unity.com/threads/commandbuffer-drawprocedural-crashes-editor-when-converted-to-drawproceduralindirect.434113/
using UnityEngine;
using UnityEngine.Rendering;
using System.Collections;
using System.Runtime.InteropServices;
 
// Apply Script to Camera GameObject
public class MyCommandBuffer : MonoBehaviour {
 
    public Shader shader;
 
    ComputeBuffer cbPoints;
 
    void Start () {
        Material mat = new Material(this.shader);
        Camera cam = this.GetComponent();
        CommandBuffer cb = new CommandBuffer();
 
        var verts = new Vector4[6] { new Vector4(-1, -1, 0, 1),
                                     new Vector4(1, 1, 0, 1),
                                     new Vector4(1, -1, 0, 1),
                                     new Vector4(1, 1, 0, 1),
                                     new Vector4(-1, -1, 0, 1),
                                     new Vector4(-1, 1, 0, 1) };
 
        this.cbPoints = new ComputeBuffer(6, Marshal.SizeOf(typeof(Vector4)), ComputeBufferType.Default);
        this.cbPoints.SetData(verts);
        mat.SetBuffer("quadVerts", this.cbPoints);
 
        cb.name = "stencil mask";
        cb.DrawProcedural(cam.cameraToWorldMatrix, mat, 0, MeshTopology.Triangles, 6, 1);
        cam.AddCommandBuffer(CameraEvent.BeforeForwardOpaque, cb);
    }
 
    void OnDestroy () {
        if (this.cbPoints != null) this.cbPoints.Release();
        this.cbPoints = null;
    }
}

このソースを参考にして書き直した。
OnRenderObject()で実行していたGraphics.DrawProceduralは消して、Start()内でCommandBuffer.DrawProceduralでコマンドを登録する。あとは何もしなくとも毎フレーム自動的にDrawProceduralが行われる。

また「CameraEvent.BeforeForwardOpaque」
この部分を変えればさらにいろんなタイミングでDrawProceduralが使えるようだ。今回はBeforeForwardOpaqueのままでうまくいった。→うまくなんかいってませんでした。
今回はAfterForwardOpaqueにすることでだいたいの順番を指定することができた。ShaderのTagのQueueでBackground(1000)、Geometry(2000)はOpaqueに相当するらしく、Transparent(3000)、Overlay(4000)はOpaqueに相当しない。なのでこの間に挟むことで初めて任意のタイミングでの実行が保証される
yaritaikoto

そのあともいろいろ実験したけど、確かに粒子のz座標を変えるだけで手前にきたり奥にいったり。うまくいっているようだ。→ように見えただけ。zバッファ書き込みonにしてたため粒子の描画が先に来てもぱっと見問題ないように見えただけでした・・・
ただ今回すべての粒子をz=0.00としてやっているのでうまくいっているだけなのかもしれない。というわけでshader側で粒子のz座標を-1.0と0.0の2パターンにランダムにばらけさせて実験してみたところ、ちゃんとz==-1.0の粒子だけ灰色の矩形より手前に表示された!
完璧だった

結論
Graphics.DrawProceduralよりCommandBuffer.DrawProceduralを使うべき
それでも完全に任意のタイミングでDrawProceduralを行うには、ほかの全てのSprite等でタイミング指定記述(Queue=???)が必要。さらに2つ以上CommandBuffer.DrawProceduralでAfterForwardOpaqueを指定した場合どちらが先に描画されるかは不明・・・





今回使ったshaderのコード(いろいろ間違ってます、今後修正予定)





レイヤー1 粒子のほう
Shader "MenyBulletsShader" {
	SubShader{
		// アルファを使う
		//ZWrite Off
		Blend One One
		//ZTest Always
		//Blend SrcAlpha OneMinusSrcAlpha
		//Tags{ "Queue" = "Background" "RenderType" = "Transparent" }
		Pass{
		CGPROGRAM

		// シェーダーモデルは5.0を指定
#pragma target 5.0

		// シェーダー関数を設定 
#pragma vertex vert
#pragma fragment frag

#define WX (uint)(192)
#define WY (uint)(144)

#include "UnityCG.cginc"
	//粒子の位置
	StructuredBuffer RYS;
	//粒子の色
	StructuredBuffer RYc;

	// 頂点シェーダからの出力
	struct VSOut {
		float4 pos : SV_POSITION;
		float4 col : COLOR;
	};

	// 頂点シェーダ
	VSOut vert(uint id : SV_VertexID)
	{
		// idを元に、弾の情報を取得
		VSOut output;
		uint di = RYS[id];
		float2 rysxy=RYS[id];
		float xx=rysxy.x;
		float yy=rysxy.y;

		float3 worldPos = float3(10.0f / WY *(xx- 0.5f*WX),10.0f/WY*(0.5f*WY -yy),0.0);//0.078125は10.0/128。RYSを0を中心に+-5以内に収める処理
		output.pos = mul(UNITY_MATRIX_VP, float4(worldPos, 1.0f));
		uint uintcol = RYc[id];
		output.col.r = (float)(uintcol % 256) / 255.0;
		output.col.g = (float)((uintcol / 256) % 256) / 255.0;
		output.col.b = (float)((uintcol / 65536) % 256) / 255.0;
		output.col.a = 1.0f;//加算合成でのレンダリングのせいなのかαは0.0でも1.0として計算される
		return output;
	}
	// ピクセルシェーダー
	float4 frag(VSOut i) : COLOR
	{
		return i.col;
	}

		ENDCG
	}
	}
}







レイヤー3 灰色のほう
Shader "Blendshader" {
	SubShader{
		Pass{
		//Tags{ "RenderType" = "Transparent" "Queue" = "Opaque" }
		ZWrite Off
		Blend SrcAlpha OneMinusSrcAlpha
		
		//ZTest Always

		CGPROGRAM
#pragma vertex vert
#pragma fragment frag

		float4 vert(float4 v:POSITION) : SV_POSITION{
		return UnityObjectToClipPos(v);
	}

		fixed4 frag() : SV_Target{
		return fixed4(0.5, 0.5, 0.5, 0.5);
	}
		ENDCG
	}
	}
}




C#側は全部乗せるとえらく長くなるので重要な部分のみ
using UnityEngine;
using System.Runtime.InteropServices;
using System;
using System.IO;
using UnityEngine.Rendering;

public class MenyBullets : MonoBehaviour
{
    public Shader bulletsShader;///MenyBulletsShader.shader 粒子をレンダリングするシェーダー//
    Material bulletsMaterial;///粒子のマテリアル bulletsShaderと紐づけされる
    public ComputeShader bulletsComputeShader;///NS.compute 流体の更新を行うコンピュートシェーダー 
    CommandBuffer commandb;
//中略
    void Start()
    {
	//中略
        bulletsMaterial = new Material(bulletsShader);
	//中略
        //コマンドバッファ系
        commandb = new CommandBuffer();
        Camera cam = GameObject.Find("Main Camera").GetComponent();//コンポーネント
        bulletsMaterial.SetBuffer("RYS", dtprtcomp.RYS);
        bulletsMaterial.SetBuffer("RYc", dtprtcomp.RYc);
        commandb.name = "gpu instanse";
        commandb.DrawProcedural(cam.cameraToWorldMatrix, bulletsMaterial,
 0, MeshTopology.Points, dtprtcomp.RYS.count, 1);
        cam.AddCommandBuffer(CameraEvent.BeforeForwardOpaque, commandb);
	//中略
    }


(ゲーム完成したらソースも全部公開します)

UnityのResources.Loadでテキスト読み込み

ハマったのでメモ

バージョン:Unity 2018.2.8f1
C#スクリプトで

TextAsset textasset = Resources.Load("hoge");
Debug.Log(textasset.text);


で何も表示されない、といったもの
textasset.textはnullになっているようで出力コンソールには
UnityEngine.Debug:Log(Object)」
と空白が出力されている

hogeファイルは「Resources」フォルダにhoge.txtでおいてあり内容も書いてある。
ただUnityのInspector viewで何も内容が表示されてないのがややおかしいと思っていただ気にしていなかった


いろいろ試して2時間後・・

txtファイルがShift_JIS(ANSI)形式で保存されていたのがいけなかったようでどうもutf-8を使えとのこと。utf-8で保存したら難なく成功した。
リファレンスマニュアルに書いておいてくれよ・・

CPU-GPUアーキテクチャ備忘録

一度覚えたけど忘れてしまったのでメモ
SIMDとかGPGPUとかやる時に地味に必要な知識
適当に調べたので間違ってる可能性あり

CPU
1コアあたり1clockあたりの積和スループット
Intel core(core 2 duo等)128ADD+128MUL
Nehalem(第1世代)128ADD+128MUL
Sandy Bridge(第2世代)256ADD+256MUL
Ivy Bridge(第3世代)256ADD+256MUL
Haswell(第4世代)256FMA+256FMA
Broadwell(第5世代)256FMA+256FMA
Skylake(第6世代)256FMA+256FMA
Kaby Lake(第7世代)256FMA+256FMA(多分)
Coffee Lake(第8世代)256FMA+256FMA(多分)
Skylake-X(第6世代)512FMA+512FMA
Knights Landing(Xeon Phi等)512FMA+512FMA
Zen1128ADD*2+128MUL*2※
Zen2256ADD*2+256MUL*2

※MUL演算器はFMAもできるがその場合ADDが停止する
(2018/11/8追記)Zen1ではSIMDスループット低くどうなんだと思っていたけど、まぁなんだかんだコア数でカバーしてて結果悪くはないと思ってた。Zen2は14nm→7nmになってSIMD幅2倍になり、これが本当ならば凄いと思う



GPU
1SM(1CU)あたりの
CUDAコア数(PE数)
warp実行
cycle
1CUDAコアあたりの
レジスタ(32bit)本数
1SMあたりの
共有メモリ
Tesla84??
Fermi322102448KB
Kepler1921341.3348KB
Maxwell128151264-96KB?
Pascal641102464KB
Volta6411024128KB?
Turing641102496KB?
GCN(Tahiti)644102464KB
NCU(Vega)644102464KB


参考
https://news.mynavi.jp/photo/article/20170814-vega/images/004l.jpg
etc

一般的にGPUプログラミングにおいてレイテンシ隠ぺいのため、warp(≒wavefront)をたくさん立ち上げることが良いとされる。これはglobal memoryのアクセスで一つのwarpが止まっても他のwarpで演算器リソースを使うことで無駄を少なくできるから(うまく説明できないが)。
一方チューニングされたコードは、例えば1SM内で64threadを立ち上げ、その64threadで共有メモリとレジスタを使いきる・・みたいな処理が考えられる。
その際1SM内で使える共有メモリの最大量やレジスタ量が分かっていないと何かと不便なのである。


また「warp実行cycle」についてだが、一応AMDではwarpでなくwavefrontといい、それぞれ1warp=32thread、1wavefront=64threadとなってて並列処理の最小単位みたいなもので、これを何サイクルで実行するのかという項目。
NVIDIAのグラボを使っている人はkepler以降であればあまり気にしなくてもよいのかもしれないが問題はAMD
1CU(SMみないなもん)=64PE(CUDAコアみたいなもん)なのだが、GCNでもNCUでも64PE(processing element)で64threadを1cycleで実行するというものではない
1CUあたりせっかく64PEあるのに、16PEが4cycleで1wavefrontを実行するという形になっている。もちろんwavefrontがたくさんある状況下では64PEが4cycleで4wavefrontを処理するためスループットはちゃんとでるのだが。
もし例えばOpenCLなんかのコードでgroup size(=local work size)が64で、1CU内で64threadが共有メモリとレジスタをほぼ使い切るような処理を書いてしまうと、16PEがその1wavefrontを実行しその間残りの48PEがお留守になってしまうという痛ましいことになる。
なのでgroup sizeを64じゃなく256でやるか、どうしても64のままがいいならその分共有メモリやレジスタの使う量を1/4に減らさないとPEを使い切ることができない。

昔原因がわからなくハマって最近調べてやっと分かったため上記にまとめてメモを残そうと思う
プロフィール

toropippi

記事検索
アクセスカウンター

    QRコード
    QRコード
    • ライブドアブログ