Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
f308f98
feat: エントリアセンブリ以外のシーンを登録する UseScenesFrom を追加
EbiseLutica Aug 3, 2026
c036192
docs: シーンの登録範囲と UseScenesFrom の使い方を追記
EbiseLutica Aug 3, 2026
e69eb85
fix(Rendering): トリム座標を左上原点に統一しY反転をGLランナーへ移動
EbiseLutica Jul 23, 2026
e6451f7
feat(Backends): Vulkanバックエンドの骨格を追加 (Phase 1)
EbiseLutica Jul 23, 2026
43b9591
feat(Experimental): Vulkanバックエンド起動確認用プロジェクトを追加
EbiseLutica Jul 23, 2026
f0cc50f
feat(Backends): VulkanバックエンドでSprite/Shape描画に対応 (Phase 2/3)
EbiseLutica Jul 23, 2026
7ab366b
fix(Vulkan): FrameBufferのテクスチャが上下反転する問題を修正
EbiseLutica Jul 23, 2026
0889a4e
feat(Example): --vulkan フラグでVulkanバックエンドを選択できるように
EbiseLutica Jul 23, 2026
9ec9599
feat(Vulkan): カスタムシェーダー・PieSprite・ポストプロセスに対応
EbiseLutica Jul 23, 2026
f948122
feat(Vulkan): ステンシル/アルファマスクに対応
EbiseLutica Jul 23, 2026
dd00076
feat(Vulkan): Pie/Primitiveカスタムマテリアル・Texture2D Uniformに対応
EbiseLutica Jul 23, 2026
bbce355
feat(ImGui): VulkanバックエンドでのImGui描画に対応
EbiseLutica Jul 23, 2026
380770d
chore: バージョンを 2.0.0-beta.8 に更新
EbiseLutica Jul 23, 2026
419acfb
vulkanテスト
EbiseLutica Jul 28, 2026
85d3591
不具合修正など
EbiseLutica Aug 3, 2026
2d412a9
chore: バージョンを 2.0.0-beta.9 に更新
EbiseLutica Aug 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Promete.Docs/src/content/docs/guide/extends/rendercommand.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ queue.Enqueue(new DrawPieTextureCommand
`BeginTrimCommand` / `EndTrimCommand` で描画範囲を矩形に制限します。`Container` ノードの実装で使用されます。

```csharp
// トリム開始(物理ピクセル座標で指定
// トリム開始(物理ピクセル座標・左上原点で指定
queue.Enqueue(new BeginTrimCommand
{
X = physicalX,
Expand Down Expand Up @@ -151,7 +151,7 @@ public override void Collect(RenderCommandQueue queue, RenderContext ctx)
```csharp
public class MyCommandRunner : CommandRunner<MyCommand>
{
protected override void Execute(MyCommand command, RenderContext ctx)
public override void Execute(MyCommand command)
{
// コマンドの実行処理
}
Expand Down
2 changes: 2 additions & 0 deletions Promete.Example/Kernel/DemoKernel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ public static class DemoKernel
public static Folder CurrentFolder { get; set; } = FileSystem.Root;

public static int CurrentIndex { get; set; }

public static bool UseVulkan { get; set; }
}
5 changes: 3 additions & 2 deletions Promete.Example/MainScene.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ public override void OnUpdate()
private void OutputUI()
{
console.Clear();
console.Print("Promete Demo\n");
var rendererName = DemoKernel.UseVulkan ? "Vulkan" : "OpenGL";
console.Print($"Promete Demo ({rendererName})\n");
console.Print($"現在のディレクトリ: /{CurrentFolder.GetFullPath()}\n");
View.Title = $"Promete Demo - {CurrentFolder.GetFullPath()}";
View.Title = $"Promete Demo ({rendererName}) - {CurrentFolder.GetFullPath()}";

for (var i = 0; i < CurrentFolder.Files.Count; i++)
{
Expand Down
39 changes: 27 additions & 12 deletions Promete.Example/Program.cs
Original file line number Diff line number Diff line change
@@ -1,28 +1,43 @@
using Promete;
using Promete.Coroutines;
using Promete.Example;
using Promete.Example.Kernel;
using Promete.GLDesktop;
using Promete.ImGui;
using Promete.Input;
using Promete.Vulkan.Validation;
using Promete.VulkanDesktop;
using Promete.Windowing;

var app = PrometeApp
// --vulkan フラグで実験的な Vulkan バックエンドを使用する
DemoKernel.UseVulkan = args.Contains("--vulkan");

// --validation フラグで Vulkan バリデーションレイヤーを有効化する (要 Vulkan SDK)
var useValidation = args.Contains("--validation");

var builder = PrometeApp
.Create()
.Use<Keyboard>()
.Use<Mouse>()
.Use<Gamepads>()
.Use<ConsoleLayer>()
.Use<CoroutineManager>()
.Use<ImGuiPlugin>()
.BuildWithOpenGLDesktop(
WindowOptions.Default with
{
Title = "Promete Demo",
Mode = WindowMode.Resizable,
TargetFps = 0,
TargetUps = 0,
IsVsyncMode = false,
}
);
.Use<ImGuiPlugin>();

if (DemoKernel.UseVulkan && useValidation)
builder = builder.UseVulkanValidation();

var options = WindowOptions.Default with
{
Title = DemoKernel.UseVulkan ? "Promete Demo (Vulkan)" : "Promete Demo",
Mode = WindowMode.Resizable,
TargetFps = 0,
TargetUps = 0,
IsVsyncMode = false,
};

var app = DemoKernel.UseVulkan
? builder.BuildWithVulkanDesktop(options)
: builder.BuildWithOpenGLDesktop(options);

return app.Run<MainScene>();
1 change: 1 addition & 0 deletions Promete.Example/Promete.Example.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<ItemGroup>
<ProjectReference Include="..\Promete.ImGui\Promete.ImGui.csproj" />
<ProjectReference Include="..\Promete\Promete.csproj" />
<ProjectReference Include="..\Promete.Vulkan.Validation\Promete.Vulkan.Validation.csproj" />
</ItemGroup>

<ItemGroup>
Expand Down
189 changes: 189 additions & 0 deletions Promete.Example/examples/debug/TrimClampDebugScene.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
using System.Drawing;
using Promete.Example.Kernel;
using Promete.Graphics;
using Promete.Input;
using Promete.Nodes;

namespace Promete.Example.examples.debug;

/// <summary>
/// レビュー指摘 #03 の再現シーン。
///
/// 発生源は RenderCommandQueue.PushTrim (RenderCommandQueue.cs:97-100)。
/// トリム矩形の原点が負のとき 0 にクランプするが、クランプした分を size から
/// 減算しないため、可視領域が「はみ出した量」だけ広いまま残る。
/// GL / Vulkan 双方のランナーが同じ矩形を受け取る、両バックエンド共通の不具合。
///
/// 重要: トリムコンテナ単体では、この不具合は目に見えない。
/// クリップ矩形はコンテナ自身の位置とサイズから作られるため、過大になった帯は
/// 必ずそのコンテナの中身より右側、つまり何も描かれていない領域に落ちるためである。
///
/// 可視化にはトリムコンテナの入れ子が要る。PushTrim は親トリムとの積集合を取る
/// (RenderCommandQueue.cs:115-123) ので、外側の過大な幅が内側の積集合に漏れる。
/// 外側を左へはみ出させて幅を過大にし、内側は画面内に置いて中身を持たせると、
/// 内側が本来の右端を越えて描画される。
///
/// 外側 loc=(-200,140) size=400x150 / 内側 loc=画面(50,140) size=300x150 のとき:
/// 正しい内側クリップ = x 50..200 (幅150)
/// 実装の内側クリップ = x 50..350 (幅300) -> 150px ぶん余分に見える
/// </summary>
[Demo("/debug/trim_clamp", "指摘#03: 入れ子トリムで内側のクリップ幅が過大になる")]
public class TrimClampDebugScene(ConsoleLayer console, Keyboard keyboard) : Scene
{
private const int TrimTop = 140;
private const int TrimHeight = 150;

private const int OuterWidth = 400;
private const int InnerWidth = 300;

/// <summary>内側コンテナの画面上の左端。外側の可視領域内に置く。</summary>
private const int InnerScreenX = 50;

/// <summary>
/// 外側コンテナ。自身は中身を持たず、左へはみ出させてクリップ幅を過大にする役に徹する。
/// </summary>
private readonly Container _outer = new Container().Size(OuterWidth, TrimHeight);

/// <summary>内側コンテナ。中身を全域に持ち、外側の過大な幅の影響を受ける。</summary>
private readonly Container _inner = new Container().Size(InnerWidth, TrimHeight);

private Container _guide = null!;
private int _outerX = -200;

public override void OnStart()
{
_outer.IsTrimmable = true;
_inner.IsTrimmable = true;

// 内側の中身。全域に敷き詰めることで、過大な帯に「切り取られるべき中身」を置く
for (var x = 0; x < InnerWidth; x += 20)
{
var isEven = x / 20 % 2 == 0;
_inner.Add(
Shape.CreateRect(
x,
0,
x + 19,
TrimHeight - 1,
isEven ? Color.MediumSeaGreen : Color.SeaGreen
)
);
}

// 外側自身は中身を持たない。トリム枠としてのみ機能させる
_outer.Add(_inner);
Root.Add(_outer);

_guide = new Container();
Root.Add(_guide);

SetOuterX(-200);

console.Print("指摘#03 再現シーン (入れ子トリム)");
console.Print("左右キー: 外側コンテナを移動 / R: -200 へ戻す / 0: 0 へ");
}

public override void OnUpdate()
{
if (keyboard.Left)
SetOuterX(_outerX - 4);

if (keyboard.Right)
SetOuterX(_outerX + 4);

if (keyboard.R.IsKeyDown)
SetOuterX(-200);

if (keyboard.Number0.IsKeyDown)
SetOuterX(0);

if (keyboard.Escape.IsKeyUp)
App.LoadScene<MainScene>();
}

/// <summary>
/// 外側の位置を更新する。内側は画面上で固定に見えるよう、外側ローカル座標を補正する。
/// </summary>
private void SetOuterX(int outerX)
{
_outerX = Math.Clamp(outerX, -OuterWidth, 0);

_outer.Location = (_outerX, TrimTop);

// 内側は常に画面 x = InnerScreenX に来るようにする
_inner.Location = (InnerScreenX - _outerX, 0);

UpdateGuide();
PrintState();
}

/// <summary>
/// 内側の期待クリップ右端 (赤) と、実装が使う右端 (黄) を引く。
/// </summary>
private void UpdateGuide()
{
var (expectedRight, actualRight) = ComputeInnerRight();

var top = TrimTop - 20;
var bottom = TrimTop + TrimHeight + 20;

_guide.Clear();
_guide.Add(Shape.CreateLine(expectedRight, top, expectedRight, bottom, Color.Red));

if (actualRight != expectedRight)
_guide.Add(Shape.CreateLine(actualRight, top, actualRight, bottom, Color.Yellow));
}

/// <summary>
/// 内側トリムの右端を、期待値と実装値の双方について求める。
/// PushTrim と同じく、親トリムとの積集合を左右両端について取る。
/// </summary>
private (int Expected, int Actual) ComputeInnerRight()
{
// 外側: 原点を 0 にクランプする際、実装は幅を縮めない
var outerClampedX = Math.Max(0, _outerX);
var outerExpectedWidth = _outerX < 0 ? OuterWidth + _outerX : OuterWidth;

var actual = IntersectRight(outerClampedX, outerClampedX + OuterWidth);
var expected = IntersectRight(outerClampedX, outerClampedX + outerExpectedWidth);

return (expected, actual);
}

/// <summary>
/// 内側の矩形を親トリム [parentLeft, parentRight] と積集合し、その右端を返す。
/// 幅が負にならないよう左端でクランプする。
/// </summary>
private static int IntersectRight(int parentLeft, int parentRight)
{
var left = Math.Max(InnerScreenX, parentLeft);
var right = Math.Min(InnerScreenX + InnerWidth, parentRight);
return Math.Max(left, right);
}

private void PrintState()
{
var (expectedRight, actualRight) = ComputeInnerRight();
var over = actualRight - expectedRight;

console.Clear();
console.Print("指摘#03 再現シーン (入れ子トリム)");
console.Print("左右キー: 外側を移動 / R: -200 / 0: 0");
console.Print("赤線 = 内側の期待クリップ右端 / 黄線 = 実装が使う右端");
console.Print("");
console.Print($"外側 loc.X = {_outerX} (size {OuterWidth}x{TrimHeight})");
console.Print($"内側 画面X = {InnerScreenX} (size {InnerWidth}x{TrimHeight})");
console.Print($"内側クリップ右端 期待 = {expectedRight} / 実装 = {actualRight}");
console.Print(
over > 0
? $"緑の帯が赤線を {over}px 越えていれば再現"
: "過大なし (外側がはみ出していないため一致)"
);
}

public override void OnDestroy()
{
_guide.Destroy();
_outer.Destroy();
}
}
81 changes: 81 additions & 0 deletions Promete.Example/examples/debug/VulkanFrameArenaGrowthDebugScene.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using System.Drawing;
using Promete.Example.Kernel;
using Promete.Input;
using Promete.Nodes;

namespace Promete.Example.examples.debug;

/// <summary>
/// レビュー指摘 #02 の再現シーン。
/// VulkanFrameArena は全バッファを永続マップするが、容量超過で退役したバッファを
/// Reset()/Dispose() で破棄する際に UnmapMemory を呼ばない。
/// マップ済みメモリの解放は未定義動作であり、バリデーションレイヤ有効時に検出される。
///
/// 1 フレームあたりのプッシュ量が初期容量 256 KB を超えるとアリーナが再確保され、
/// 旧バッファが退役リストへ入る。矩形 1 個あたり頂点 32 B + インデックス 24 B、
/// 16 B アライン込みで概ね 64 B 消費するため、4,096 個前後が境界となる。
/// </summary>
[Demo("/debug/vulkan_frame_arena_growth", "指摘#02: アリーナ再確保でマップ済みメモリを解放")]
public class VulkanFrameArenaGrowthDebugScene(ConsoleLayer console, Keyboard keyboard) : Scene
{
/// <summary>初期容量 256 KB を超えない個数。</summary>
private const int BelowThreshold = 2000;

/// <summary>初期容量を明確に超え、毎フレーム退役バッファを生む個数。</summary>
private const int AboveThreshold = 8000;

private readonly Container _container = new();

private int _shapeCount = BelowThreshold;

public override void OnStart()
{
Root.Add(_container);
Rebuild();

console.Print("指摘#02 再現シーン");
console.Print("バリデーションレイヤを有効にして実行すること");
console.Print("SPACE: 図形数を 2000 <-> 8000 で切り替え");
console.Print("8000 側でアリーナが 256KB を超えて再確保され、退役バッファが発生する");
}

public override void OnUpdate()
{
if (keyboard.Space.IsKeyDown)
{
_shapeCount = _shapeCount == BelowThreshold ? AboveThreshold : BelowThreshold;
Rebuild();
}

if (keyboard.Escape.IsKeyUp)
App.LoadScene<MainScene>();
}

/// <summary>
/// 指定個数の矩形を敷き詰め、1 フレームのプッシュ量を変化させる。
/// </summary>
private void Rebuild()
{
_container.Clear();

const int columns = 100;
for (var i = 0; i < _shapeCount; i++)
{
var x = i % columns * 6;
var y = i / columns * 6;
var color = i % 2 == 0 ? Color.SteelBlue : Color.LightSalmon;
_container.Add(Shape.CreateRect(x, y, x + 4, y + 4, color));
}

var estimatedBytes = _shapeCount * 64;
console.Print(
$"図形数={_shapeCount} 推定プッシュ量={estimatedBytes / 1024}KB "
+ (estimatedBytes > 256 * 1024 ? "(初期容量超過 → 再確保あり)" : "(容量内)")
);
}

public override void OnDestroy()
{
_container.Destroy();
}
}
Loading
Loading