luminoengine / lumino Goto Github PK
View Code? Open in Web Editor NEWLumino is a framework for building real-time graphics applications.
Home Page: https://luminoengine.github.io
License: MIT License
Lumino is a framework for building real-time graphics applications.
Home Page: https://luminoengine.github.io
License: MIT License
MeshResource は内部に合計 5 つの VertexBuffer を持っているが、フォーマットが MMD モデルに寄っているため、汎用的にしたい。
enum VertexBufferGroup
{
VBG_Basic = 0,
VBG_BlendWeights = 1,
VBG_AdditionalUVs = 2,
VBG_SdefInfo = 3,
VBG_MmdExtra = 4,
};
また、VBG_Basic の頂点フォーマットは次のようになっている。
struct Vertex
{
Vector3 position;
Vector3 normal;
Vector2 uv;
Color color;
};
汎用化を目指すなら Unity や Three.js のように、各要素で別々の配列 (頂点バッファ) を指定できるようにした方が良いのでは?
var geometry = new BufferGeometry();
geometry.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
geometry.addAttribute( 'color', new Float32BufferAttribute( colors, 3 ) );
VertexLayout 付きの頂点データを外部から指定できるようにする、という方針となる。
内部データは VertexBuffer が分かれても、ひとつにまとめても構わない。ただ、内部フォーマットへの変換処理を実装することになる。
編集が簡単になるが、描画時のキャッシュ効率が落ちる。
編集と言っても、そもそも一般的に描画用メッシュクラスは編集用に作られてはおらず、編集したければ Half-Edge Mesh などを使うべき。
頂点位置の微調整や法線計算、箱や球といった他のメッシュを(描画効率向上のため)マージするのはサポートしてもいいともうけど…。
例えば、Ruby は構造体という仕組みが無いので、VertexBuffer にバイナリデータを直書きするのか、アクセサを提供するのか。
バイナリデータを直接扱うのは言語によってかなり得手不得手あるので、基本はアクセサの提供にしたい。
ちなみに UE4 は、各要素の配列を受け取って Mesh を作るユーティリティがある。
https://docs.unrealengine.com/en-US/API/Plugins/ProceduralMeshComponent/UProceduralMeshComponent/CreateMeshSectio-/index.html
StaticMesh の頂点バッファは次のようになっている。
struct FStaticMeshVertexBuffers
{
FStaticMeshVertexBuffer StaticMeshVertexBuffer; // Normal, Tangent, Binormal, UV
FPositionVertexBuffer PositionVertexBuffer; // Pos のみ
FColorVertexBuffer ColorVertexBuffer; // Color のみ
}
やはりある程度の要素はひとつの VertexBuffer にまとめることで、キャッシュの効率化を狙っている?
Unity などの PhysX 系と Bullet 系ではこの辺の誰がどんな情報を持つのかが結構違うので注意。
ざっくり以下のような感じ。
内部的には Bullet だけど、API は Unity 的に持っていく。
なぜかというと、コンポーネント指向でオブジェクトを構築する場合 Bullet そのままよりも都合がいいから。
特に、Collider にローカルオフセットをプロパティとして持たせることでエディタ操作やシリアライズが楽になる。
(Bullet のままだと追加される側、つまり Body 側の各子Shapeに付属されるプロパティとなってしまう。面倒)
あと、CollisionShape という名前を Collider にする。
はじめまして、ギバロウと申します。
しがないHSP3ユーザーです。
最近になってLuminoを知り、HSP3でこんなにも高度な3D表現が可能なことに感動しました。
自分でも使わせていただきたいと思い、リファレンスを見ながら試行錯誤したのですが、自作のモデルすら表示させることが出来ませんでした。
もし可能でしたら、自作モデルの読み込みから表示、また、アニメーション付きモデルの読み込み表示や、モーションの変化等、ご教授いただけたらと思い、こちらに書き込みさせていただきました。
よろしくお願いします。
法線マップテクスチャは Material に設定して使う。
material->setNormalMap(Texture2D::load(u"normal.png"));
接線ベクトルは標準頂点データとして追加する。
struct Vertex
{
Vector3 position;
Vector3 normal;
Vector2 uv;
Color color;
+ Vector4 tangent;
};
VertexTangents
は削除。
シェーダについては、法線マップ処理の有無をスイッチできるようにする。
@module
techniques:
Forward_Geometry_UnLighting:
passes:
- Pass0:
+ defines: USE_NORMALMAP
vertexShader: VS_ClusteredForward_Geometry
pixelShader: PS_Main
@end
※define よりは TechniqueClass に新しいグループを作ったほうがいいかも。
Unity や three.js と同じにしてみる。
従法線ベクトルは頂点シェーダで計算する。現状頂点セマンティクスとして Binormal はサポートしているが、Tangent だけにしてみる。
three.js の実装メモは後述。
そろそろ無視できなくなってきそう。
これまでの想定としては従法線も CPU 側で計算する予定だったから VertexTangents を作って、標準頂点とは別ストリームの頂点バッファとして作る予定だった。
VertexTangents はこんな感じ。
struct VertexTangents
{
Vector3 tangent;
Vector3 binormal;
};
結果的に Vector4 だけで済んだので別頂点バッファにするのは大げさかな…ということで標準頂点の方に統合してみる。
実際に Lumino を使ったシーンでどれだけ法線マップが使われるかは何とも言えないけど…。
これ以上別なデータが増えそうならまた分離を考えよう。
ちなみに統合しない場合、MeshGenerator で作っている球とかの標準形状で複数の頂点バッファが必要となり、かなり大掛かりな変更が必要になる。
法線テクスチャを使わないときは、青で塗りつぶしたデフォルトのテクスチャを使用すればスイッチの必要はないが、
シェーダだけでなく CPU 側でも ModelView の逆行列を計算したりと、それなりの負荷の処理を行うため切り替えられるようにしておく。
defaultnormal_vertex.glsl.js で、やっぱりView空間へ変換しているようだ。
vec3 transformedTangent = ( modelViewMatrix * vec4( objectTangent, 0.0 ) ).xyz;
次に normal_vert.glsl.js で
vTangent = normalize( transformedTangent );
vBitangent = normalize( cross( vNormal, vTangent ) * tangent.w );
tangent は vec4。.w は求めた 従法線 を反転するかどうかを示す、おそらく 1 or -1 の値が入っている。
その他
three.js と同じ実装のようだ。
ui-layout-2 ブランチで yoga を使った flex レイアウトをデフォルトにしてみたが、どうにも flex レイアウトはゲームでは使いづらい。
画面の上下左右にスナップしたコンテナをまずルート要素近くに用意し、flex はその中で使うことが多いだろう。(それでもほとんど StackPanel で足りるが)
grid layout は欲しい。この grid も、WPF のような高さを指定できるものと Flutter のような列数だけ指定してあとは Flow にするもの、など欲しい。
そうするとやっぱり UIElement ごとにレイアウト方法を変えたくなる。
HTML/CSS のように Style でやるならこんな感じ。
auto i = UIElement::With()
.layoutDisplay(UILayoutDisplay::Grid)
.size(100, 100)
.build();
これまでのように派生クラスを用意するならこんな感じ。
auto i = UIGridLayout::With()
.size(100, 100)
.build();
後者の方が読みやすいだろう。
WPF, UE, Godot, Kivy, Flutter, Android あたりは後者。Unity の UIElements は前者。ゲームエンジンでは前者は珍しいかも。
これまで同様でよいだろう。
ただ、UIVAlignment, UIHAlignment をやめたい。この指定がどうしても冗長になってしまう。
次のようにできないだろうか?
Litho は positionDip
と positionPercent
といった具合で別関数がある。というかコアになってる yoga そのまま。
https://fblitho.com/javadoc/com/facebook/litho/Component.Builder.html
ComponentKit は オブジェクトを渡す。
https://componentkit.org/docs/avoid-width-100-percent/
Flutter, Kivy, WPF はそもそも無いみたい。親の Grid など側で割合を指定できる。
https://stackoverflow.com/questions/43122113/sizing-elements-to-percentage-of-screen-width-height
Grid があればパーセントの指定は要らないかなという気がする。そうすると nan を無効値として扱うだけでよいため API はかなりシンプルにできる。
An extra null pointer check is not needed in functions like the following.
現状は StaticLib としての配布のみであるが、サイズが非常に大きくなっている。
対策として DLL 配布できないか検討したい。
外部に公開するクラスや関数を __declspec(dllexport) 等で修飾する必要がある。
コンパイラの差を吸収するため LN_API
マクロが定義されているので、これを使う。
これは StaticLib と同じ運用となる。
エクスポートするクラスは、ベースクラスやメンバ変数に STL のクラスを持ってはならない。
自分でコンテナクラスを作成したとき、そのクラスが std::vector を継承していたり、メンバ変数に内包していたりするとアウト。(VisualC++ で警告が出てくる)
あるいは PImpl の実装にする必要がある。
上記の通り STL を使っている個所を何とかしないと進めることはできない。
ひとまず見送り。
First,
WorldObject を作るときは次のようにできるようにする。
auto model = MeshModel::load(u"model.gltf");
auto obj1 = StaticMesh::create(model);
auto obj2 = SkinnedMesh::create(model);
現行↓
auto model1 = StaticMeshModel::load(u"model.gltf");
auto model2 = SkinnedMeshModel::load(u"model.gltf");
auto obj1 = StaticMesh::create(model1);
auto obj2 = SkinnedMesh::create(model2);
WorldObject(Component) からアクセスする。
auto obj2 = SkinnedMesh::create(model);
// フォーマルな方法
SkinnedMeshBone* bone = obj2->skinnedMeshComponent()->findBone(u"腕");
bone->setRotation(...);
// ユーティリティ
SkinnedMeshBone* bone = obj2->findBone(u"腕");
bone->setRotation(...);
SkinnedMeshBone は Transform の派生とし、Editor 上からアクセスできるようにする。 (Unity と同じ仕組み)
MeshModel(StaticMeshModel と SkinnedMeshModel) は静的なデータと動的なデータが統合されていて、リソースの共有が難しい。
これを分離したい。
静的なデータは次の通り。
動的なデータは次の通り。
SkinnedMeshModel は StaticMeshModel の派生クラスとなっているが、
例えば .gltf をインポートしただけではどちらのインスタンスを作るべきか判断できないことがある。
そのため MeshModel クラスに統合し、Bone の有無などはコンポーネント志向の考え方で持たせたい。
Model:Instance の対応関係は次のようなイメージ
*: ユーザープログラムに積極的に公開することになるクラス。
しない。
以下、検討結果。
Lumino は次の2つのエントリーポイントの作成方法がある。
Application の実装では仮想関数などの知識が必要となるため、メインループと比べてやや前提知識のレベルが上がる。
簡単なサンプルではメインループを使って実装した方がわかりやすいのでは?
特に C と HSP3 へ対応するにあたって、継承といった概念の無い言語向けに作るサンプルが少し難しくならないか心配。
メインループの隠蔽が必要なケースに対応するため。
Web や Android 環境で期待通りのメインループを実装することは困難で、どうしても使いたいときはサブスレッドを使う必要がある。
ただサブスレッドでは UI 関係の API に制約がかかることが一般的で、問題の原因となりやすい。
また update のタイミングをエンジン側で完全に制御できるようになるので、
一時停止、ヒットストップ(これは Application でやるべきではないかもしれないが) といった時間制御がやり易くなったり、
メッセージキューが空なら更新しないことで負荷を落とすといった制御をすることが可能になる。
オブジェクト指向の知識を前提とする懸念はあるが、アプリの規模が大きくなりシーン遷移が必要になってくると、
どのみち同じように継承&仮想関数(or コールバック登録)の実装が必要になったり、この辺りの知識が必須になってくる。
Moduler Engine として、他のフレームワークに組み込めるようにするため。
ただこの場合、FPS制御などは他フレームワーク側に任せることになるため Engine::update はやめて、FrameWindow の描画関数や更新関数を直接呼び出すようにした方がいいかもしれない。
逆にこのFPS制御やシステムのメッセージキューの処理を誰に任せるかはっきりさせない限り、メインループサポートは待った方がよさそう。
(例えば HSP3 で await は誰が呼び出せばいいの?とか)
ざっと集めてみたゲームエンジン・ライブラリでは、Application クラス相当のエントリーポイントとなっているものが圧倒的に多かった。
メインループをサポートしているのは、低レイヤーの描画ライブラリであることが多い感じ。
Unity など大きなゲームエンジンや、Web系などプラットフォームの制約がかかるものは全てこちら。
例えば↓のようにしているとき、ln_View について VS_ClusteredForward_Geometry では使わず、VSI_Main で使っているとき、ln_View がall 0 になってしまう。
technique Forward_Geometry
{
pass Pass1
{
VertexShader = VS_ClusteredForward_Geometry;
PixelShader = PS_Main;
}
}
technique Forward_Geometry_UnLighting_Instancing
{
pass Pass1
{
VertexShader = VSI_Main;
PixelShader = PSI_Main;
}
}
near プロパティの役割が異なるため。
PerspectiveCamera の near としては、0.0 は無効。ViewFrustum を作成できない。(一部の面情報が nan になる)
OrthographicCamera の near としては、0.0 は有効。Sprite2D のデフォルト Z は 0.0 であり、これより大きい場合デフォルトで表示されなくなってしまう。一般的にも期待される初期値だろう。
また、PerspectiveCamera は fovY、アスペクト比が必須。一方、OrthographicCamera はビューポートに投影するワールドの範囲を示す width, height が必須。
これらでコンストラクタを作ると、似たような型が並んでしまうのであいまいさのリスクが出てくる。
インストール面倒。
XAudio2 の DLL が原因。
I'd like to use Lumino unfortunately the GitHub documentation is not in English and didn't succeed to understand.
Do you plan an English translation of your Github repository.
thanks
まずは C++ コードから C# 用のラッパーを作れるようにする。
#30 のつづき
Lumino の FileSystem の目的の半分は、アプリ開発でよく使用するユーティリティの提供。
エンコーディング指定のテキストファイル読み書きや、パターンマッチは std::filesystem には無いユーティリティである。
単純なファイルのコピーや削除であれば std::filesystem と機能的な差は無い。
ただし、短いパスの場合は Path 用の SSO が効くので、ln::String, ln::Path と併用する場合、全体的には std::filesystem より高速に動作する。
Lumino の多くの API は発生しうるエラーをロジックエラー(ユーザーではなくプログラマが悪い)と考え、assertion または例外(選択可能) を投げて通知する。
基本方針は「プログラムを止める」である。
しかしファイル操作(に限らずIO関係はそうだが)はどうしてもプログラムの外部の要素が絡むので、ユーザーエラーは避けられない。
他のラフに使える API と同じような感覚で使うべきではないだろう。
現状、システムに近いネイティブアプリでの導入がある。
ここではスクリプト言語のような感覚で使えてしまう API はかえって危険だろう。
Instancing 有効、NormalMap 有効、など、構成の組み合わせがかなり増えてきているので、自動的に作りたい。
開発中のタイトル HC4 では背景モデルに対してシェーダを適用したいが、対象は NormalMap 有無など複数の構成を書かなければならず、シェーダ開発が非効率になっている。
現状の構成は次の通り。
PS の出力方法 (Phase)
VS の種類
Phase が Default のときの PS 種類 (ShadingModel)
PS のオプション
方針:VSMain, PSMain は直接書くことにして、オプションの変更はマクロなどで対応する。
Unity のサーフェスシェーダみたいなのを作ることになるとシェーダコンパイラに手を入れる必要があり、かなり重い作業になるため。
struct MyVSOutput
{
LN_VS_OUTPUT_DATA; // float3 Pos : SV_POSITION; など
float4 MyData : TEXCOORD0;
};
struct MyPSInput
{
LN_PS_INPUT_DATA;
float4 MyData : TEXCOORD0;
};
MyVSOutput VSMain(LN_VSInput input)
{
MyVSOutput output;
LN_ProcessVertex(input, output); // マクロ。output.Pos などへ代入。スキニングやモーフィングもここで計算する。
output.MyData = /* ユーザー定義データの構築 */;
return output;
}
float4 PSMain(MyPSInput input) : SV_TARGET0
{
//LN_Surface surface = LN_InitSurface(); // 初期化するだけ
LN_Surface surface = LN_ProcessSurface(input); // input.TexUV 等を使って、Albedo など各要素をデフォルト構築する。
surface.Albedo = /* ユーザー定義で模様を付けたりする。デフォルトシェーダでは何もしない。 */;
// surface は Unity の SurfaceShader の Output 相当。
// LN_ProcessPixel は UE4 の Output ノード相当。
// LN_ProcessPixel の中で ShadingModel に応じて PBR の計算をしたり、環境光, Fog の適用などが行われる。
return LN_ProcessPixel(surface);
}
technique Default
{
pass Pass1
{
VertexShader = VSMain; // VS はほどんと定型になるので、LN_VSDefault とかを用意してもいいかも。
PixelShader = PSMain;
}
}
4パターンくらいならまだ手打ちでもなんとかなるので、まずは上記サンプルの形に、ビルトインシェーダを全部直す。
ムーブコンストラクタの初期化漏れが原因。
m_string を nullptr で初期化する。
旧シェーダを使用しているのでこれをやめる。
https://github.com/hyperrealm/libconfig
こんなのとかあるけど、JSON ファイルがいいな。
サンプルのような小さいプログラムを書くときはシンプルにできるが、実際にアプリを組むときは邪魔。
// x 座標を直接設定する
sprite->setPositionX(1.0f);
// x 座標を平行移動する
sprite->translateX(1.0f);
// x 軸回転を直接設定する
sprite->setRotation(1.0f);
// x 軸回転を加算する
sprite->rotateX(1.0f);
// x 軸拡大率を直接設定する
sprite->setScale(1.0f);
// x 軸拡大率を加算する
sprite->scaleX(1.0f);
現状、例えばX方向に移動するのに↓のように書く必要があるのがめんどい。
sprite->setPosition(Vector3(sprite->position().x + 1.0f, sprite->position().y, sprite->position().z));
WorldTransform の遅延構築について・・・
現在 WorldTransform は、setPosition時に dirty フラグを立てておき、WorldTransform が欲しいときに計算している。
Binding の Wrapper 側で変数を使う場合、この値をどこかで Native 側に渡さなければならない。
しかし上記の例の場合は WorldTransform が position や rotation に依存しているため、WorldTransform get 時に Wrapper 側にコールバックして値をもらう必要がある。
(onPreUpdate などで定期的に Wrapper から Native へ送り付けるのもアリだが、WorldTransform の場合はタイミング的に onPreUpdate などではカバーできない。setPosition() の直後に getWroldTransform() したりもできる)
そのため、公開変数を用意することはできない。
代替として、setPositionX() による直接設定や、translateX() による相対移動、rotateX() による相対回転などを実装する。
Object3D.position などは公開変数である。(getter, setter などは無い)
そのため、obj.position.x += 1.0 のように、要素単位で演算ができる。
ドキュメント上は「変数」となっているが、ReferenceCode を見るとプロパティとなっている。
キャラクターあるいはカメラを起点とした処理を考えやすいため。
特にピクセルシェーダで ViewSpace 上の計算を行うときに Z+ が "進行方向" と一致するためイメージしやすかった。
ClipSpace に至っては OpenGL でも DirectX でも左手座標系であり、低レイヤーに近づくほど左手座標系の方が扱いやすい。
また Lumino の開発初期にリサーチできたゲームエンジンでは、左手座標系が採用されているものが多かった。(Unity, UE4 も左手)
これまではレンダリングエンジンの実装に力を入れていたため左手座標系の方が都合がよかったのだが、より高レベルの機能をサポートし始めたことで次のような問題が出てきた。
様々な DCC ツールで作成したアセットとの相性が悪いこと。
特に3Dモデルは頂点データの他、ボーンやアニメーション等非常に多くのデータ構造を扱う必要がある。
DCC ツールのほとんどは右手座標系であるが、こういったアセットを正しくインポートするには常に座標系の変換に気を配る必要があり負担が大きい。
またこの変換のためのオーバーヘッドがどうしても乗ってくる。
Lumino が 3D をサポートし始めたころに対応した PMD,PMX は左手座標系であったため問題は少なかったが、その後 glTF を標準的に使用していくことに決めた。Lumino は独自形式を持たないためランタイムでは常にこの変換のオーバーヘッドが発生してしまうことになる。
シェーダを除いてほとんど無い。そのシェーダについても、この Issue を上げた時点の rh-proto ブランチでは既にすべてのユニットテストをパスしている。
X軸の意味が変わるが、実際にゲームでキャラクターの動きを作る際には Z(進行方向) と Y(上方向) をイメージしながら作ることが多く、X軸についてはそれらよりも明確な意図をもって使う機会は少ない。ZY の意味が変わらなければ、既に作られている Lumino のユーティリティへの被害も少ない。
CCW を正面とする。("右手" に統一する)
これは glTF・Vulkan の標準的な仕様なので、これに合わせてみる。
他のライブラリだと imgui や nanovg がこれにあたる。
https://note.com/it_ks/n/nb13311509f0a
imgui
VK_FRONT_FACE_COUNTER_CLOCKWISE
vulkan
VK_FRONT_FACE_COUNTER_CLOCKWISE
Three.js
CW?
KhronosGroup/glTF-Blender-IO#551
glTF
CCW
godot
PipelineRasterizationState の初期値を見ると CW
一部の機能を呼び出すと、リンクエラーとなる。
LNK2019 未解決の外部シンボル __imp__PathMatchSpecExW@12 が関数 "public: static bool __cdecl ln::PlatformFileSystem::matchPath(wchar_t const *,wchar_t const *)" (?matchPath@PlatformFileSystem@ln@@SA_NPB_W0@Z) で参照されました。
プロジェクトに直接 "Shlwapi.lib" をリンクするように設定を加えれば回避可能。
Lumino.Core.targets に
<Link>
<AdditionalLibraryDirectories>$(MSBuildThisFileDirectory)../../build/native/lib/;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <AdditionalDependencies>Shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
とかするとよさそう。
ヘッダで #pragma comment(lib, "Shlwapi.lib") でもいいかも。
Lumino はメインループを内部に持つスタンドアロンのアプリケーションフレームワークのほか、別の GUI フレームワークにレンダリングライブラリとして組み込む機能も持っている。
しかし最近は対応する言語や環境が増えてきたことで、暫定的に用意したグルーコードがかなり複雑になってきている。
エントリーポイントのシンプルさはそのままサンプルコードのとっつきやすさにつながってくるため、仕様を整理する。
これまで ln::Engine::initalize()
で MainWindow を自動生成していたが、それをやめる。
v0.10.0 以降、 Lumino 単体で使う際は Application クラスを使う方法が基本となったため、自分で初期化 & メインループを各機会は外部アプリに組み込む時だけになっている。
サッと Lumino を使いたい人にとって ln::Engine::initalize()
を使った初期化周りは隠蔽されることになるので、このあたりの仕様は外部アプリに組み込みたいケース向けに調整していく。
いくつかの GUI フレームワークでは Application が先にあって、MainWindow は後から作られる。
しかし Lumino は MainWindow が先となるので注意。
他の GUI アプリに組み込む場合、 Application はオプションとなる。
対して MainWindow は描画先サーフェスを表すものであり、単なるレンダリングライブラリとして使う場合でも必須である。
(Window と Surface を分離するのもありだが、いずれにしても Application よりも優先されるインスタンスとなる)
class MyApp : public ln::Application {
MyApp() {
}
void onInit() {
// この時点で内部・外部問わず作成された MainWindow を ln::Engine::mainWindow() で取得できる。
}
}
LN_APP(MyApp, MyMainWindow)
// Launcher例 (Libになっているので、基本はユーザーが書くことは無い)
LuminoConfigureApp();
ln::Engine::initialize();
ln::Engine::setupMainWindow(LuminoCreateMainWindow());
ln::Engine::setupApplication(LuminoCreateApp());
ln::Engine::run();
Launcher 未対応の環境で使う。あまり機会は無いかも。
class MyApp : public ln::Application {
MyApp() {
}
void onInit() {
}
}
ln::Engine::initialize();
auto app = makeObject<MyApp>(); // Application のコンストラクタで、MainWindow 未設定なら ln::Engine::setupMainWindow() 実行、でいいかも。
app->run();
ln::Engine::terminate();
// init
LNHandle mainWindow, app;
LNEngine_Initialize();
LNMainWindow_Create(&mainWindow);
LNMainWindow_SetPrototype_...(mainWindow, ...);
LNEngine_SetupMainWindow(mainWindow);
LNApplication_Create(&app);
LNApplication_SetPrototype_...(app, ...);
LNEngine_SetupApplication(app);
// run
LNEngine_Run();
require "lumino"
Engine.initialize # require の中で initialize してしまうと EngineSettings による設定ができないので、明示的な初期化が要る。(あるいは App のコンストラクタで呼んでもいいが)
app = new App()
app.run()
require "lumino"
Engine.initialize
Engine.setupMainWindow
app = new App();
app.run()
Engine.terminate
ExternalMainLoop 必須。
// init
ln::Engine::initialize();
ln::Engine::setupMainWindow(makeObject<MainWindow>(hWnd)); // ユーザー定義の MainWindow を登録。この仕組みは App とは直接関係は無い。
ln::Engine::setupApplication(makeObject<App>());
// update
ln::Engine::update();
// render
ln::Engine::render(ln::Engine::mainWindow());
swap(hWnd);
// terminate
ln::Engine::terminate();
// init
ln::EngineSettings::setGraphicsBackend(GraphicsBackend::None);
ln::Engine::initialize();
auto renderer = ln::VulkanIntegration::createContext(device);
ln::Engine::setupGraphics(renderer);
ln::Engine::setupMainWindow(makeObject<MainWindow>(hWnd));
ln::Engine::setupApplication(makeObject<App>());
// update
ln::Engine::update();
// render
renderer->resetCommandList(...);
ln::Engine::render(renderer, ln::Engine::mainWindow());
swap(hWnd);
// terminate
ln::Engine::terminate();
# init
require "lumino" # この中で initialize() したいところだが、 require の前で EngineSettings を呼ぶことができないため断念。
Engine.setupMainWindow(new MainWindow(window_id))
Engine.setupApplication(new App)
# update
Engine.update
# render
Engine.render(ln::Engine::mainWindow());
swap(window_id)
// init
const renderer = WebGpuIntegration.getContext(device);
const mainWindow = new MainWindow(canvas);
Engine.setupMainWindow(mainWindow);
Engine.setupApplication(new App());
// update
Engine.update();
// render
renderer.resetCommandList(webgpuCommandList);
Engine.render(renderer, mainWindow, w, h);
例えば、drawText のシグネチャは次のようになっている。
void drawText(const StringRef& text, const Color& color, Font* font);
これを、次のようにしたい。
void setTextColor(const Color& color);
void setFont(Font* font);
void drawText(const StringRef& text);
drawText を UIElement のカスタム実装で使おうとしたときに、既定の設定を使うためには color と font は finalStyle() から取り出す必要がある。
この処理のため定型的なコードが増えてしまい煩わしい。
ステートレス方式はいわゆる数学的に単純であるが、パラメータが多くなるためほとんどのケースでは冗長なコードが増え、メンテナンスが難しくなる。
ステートフル方式はパラメータが少なく簡単に使えるが、プログラマが現在の状態を正しく認識することが難しく、不具合が発生しやすくなる。
ステートフル方式の問題の軽減策として、ステートの Reset と Push/Pop による復元をサポートする。
実際のところステートは Font や Color だけではなく、RenderTarget や BlendState など多岐にわたる。
もし完全ステートレスを目指すなら DX12 や Vulkan の Pipeline 相当のデータ構造が必要になるが、パフォーマンスを徹底的に追求するならまだしも、
フレームワークというものが目指すものである「制約を設けて開発速度を上げる」からは逆の考えになってきそうなので思い直した方がよさそう。
たくさんの場所でコードのコメントアウトがありますが、gitを使っているんですからそっちに任せませんか?
OpenGL 系と DirectX 系で異なる。
UNITY_NEAR_CLIP_VALUE のようなマクロで、シェーダ内で取得できるようにする。
https://answers.unity.com/questions/1443941/shaders-what-is-clip-space.html
cmakeをインストールしてbuild_msvcを実行したのですが
C:_prog\Lumino\build_vs2015_u>cmake ".." -G "Visual Studio 14 2015" -DLN_USE_UNICODE_CHAR_SET=ON
CMake Error at CMakeLists.txt:749 (add_subdirectory):
The source directory
C:/_prog/Lumino/external/Lumino.Core
does not contain a CMakeLists.txt file.
CMake Error at CMakeLists.txt:750 (add_subdirectory):
The source directory
C:/_prog/Lumino/external/Lumino.Math
does not contain a CMakeLists.txt file.
CMake Error at CMakeLists.txt:753 (ln_make_postfix):
Unknown CMake command "ln_make_postfix".
-- Configuring incomplete, errors occurred!
See also "C:/_prog/Lumino/build_vs2015_u/CMakeFiles/CMakeOutput.log".
C:_prog\Lumino\build_vs2015_u>MSBuild ".\Lumino.sln" /t:Rebuild /p:Configuration="Release" /p:Platf
orm="Win32" /m
Microsoft (R) Build Engine バージョン 4.6.1055.0
[Microsoft .NET Framework、バージョン 4.0.30319.42000]
Copyright (C) Microsoft Corporation. All rights reserved.
MSBUILD : error MSB1009: プロジェクト ファイルが存在しません。
スイッチ:.\Lumino.sln
と出てしまいビルドができません。
ソート可能なノードグループを示すフェンスを設ける。
Builder パターンの導入を始めたので、そっちで対応してみる。C++ から使うときはほとんどこのケースで足りる。
auto button = UIButton::Builder()
.onClicked(handler)
.build();
新しめの UI フレームワークは Builder パターンないしそれに近い、副作用の少ない方法で UI要素を構築するケースが多い。これで十分な感じ。
connectXX は主に Binding を作るとき、「これはイベントハンドラの登録関数です」を強調するのに使うことにしてみる。
ネイティブに近いフレームワークは過去形が多い傾向。
ただ過去形に統一しすぎると MouseDowned とかなじみ無い名前になる。
タイミングを区別するなら WPF の "Preview" Prefix のようなものを付ける、でいいかも。
connectOnXXXX 及び onXXXX のインターフェイスを変更する。
例えば UIButton の Clicked イベントであれば、
Ref<EventConnection> connectOnClicked(Ref<UIEventHandler> handler);
virtual void onClicked(UIEventArgs* e) override;
↓
UIButton* onClicked(Ref<UIEventHandler> handler, Ref<EventConnection>* outConnection = nullptr);
virtual void onClickedEvent(UIEventArgs* e) override;
ただし、on~ は他のモジュールでも使用している仮想関数実装の命名規則でもあるため、上記そのまま適用は難しいかもしれない。
候補としては、
現在、ユーザープログラムから、イベントをハンドリングする方法は次の2通り。
ユーザープログラムから見ると、このうち A は非常によく使い、B はあまり使わない。
(A はコントロールを利用するための API であるが、B はコントロールをカスタマイズするための API)
そのため以下のような不満が出てきた。
on~ を採用しているものはそれなりにある。ただ、signal ではなく普通のプロパティとして単一のハンドラをセットするケースが多い印象。
C# の Event や、signal-slot の仕組みで作られているものは on~ ではないことが多いみたい。
onClick の呼び出し元は handleClick.
ただ仮想関数という考え方ではない。
一番根っこは div の onClick にローカル定義した関数をセットしているだけ。
<div ... onClick={handleClick} ...>
これらはすべて C# の event として実装されている。
仮想関数は次のような命名になっている。
イベントと仮想関数の命名規則に区別はない。
イベントと仮想関数の命名規則に区別はない。
let button = Button::with_label("+1").dom().with_id("button")
.with_callback(On::MouseUp, Callback(update_counter));
button.connect_clicked(move |_| { ... });
Button::new(&mut self.reset_button, Text::new("reset"))
.on_press(Message::Reset),
self.on_mouse_up(move |p| { ... });
現時点で Event<> な変数は次の通り。
canUndoChanged;
canRedoChanged;
m_onCollisionEnter;
m_onCollisionLeave;
m_onCollisionStay;
m_onUIEvent;
m_onChecked;
m_onUnchecked;
m_onActivated;
m_onDeactivated;
m_onSubmit;
DragStarted;
DragDelta;
DragCompleted;
DragCanceled;
m_onItemSubmitted;
m_onGenerateTreeItem;
m_onCanExecuteChanged;
m_onCanExecuteEvent;
m_onExecuteEvent;
m_onClosed;
m_onImGuiLayer;
m_onClick;
m_onItemClick;
m_onSelectionChanged;
m_onSelectedTabChanged;
m_onRender
onGenerateTreeItem, onRender あたりは他の名前にし辛いかも。
onUIEvent は動詞でもないケースなので、Ruby とかだと普通の変数と区別ができなくなる。
スクリプト系だとこのあたりの視認性が悪くなるのはマイナス。
そろそろやばいです
スクリプト設計用。まだ公開予定なし。
復元、と言ったほうがいいかもしれないけれど。
動作最優先で作っていたのでごちゃごちゃしてます。
きれいに整えていきます。
Logger に Mutex がかかってないので、Audio thread からの書き込みと衝突してるみたい。
こんなお品書きで。
UIElement::OnUpdateFrame() とか。
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.