feat(gfx): surface+object render model (replaces flat layers)
GfxState: SurfaceStore (slot->{resId,colorkey} from create/set-texture) + object
SourceSlot/SrcRect/Visible (from draw-texture bind); SnapshotVisibleObjects returns
visible objects in ascending-handle order (=z-order) with their live surface resolved.
VM set/create/draw-texture wired to it. Oracle dumps visible objects. Erase removes the
object from the registry (faithful). Engine 44 green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,13 @@
|
||||
using System.Linq;
|
||||
|
||||
namespace Age.Engine.Model;
|
||||
|
||||
/// <summary>A retained composite layer captured when the VM executes draw-texture — the host clears and
|
||||
/// re-composites the layer set each frame (replacing the immediate-mode permanent canvas). Keyed by the
|
||||
/// object <see cref="Handle"/> so a re-draw updates in place and an erase (op 0x1f7) removes it.</summary>
|
||||
public readonly record struct DrawLayer(long Handle, int Slot, int SrcX, int SrcY, int W, int H, int DstX, int DstY);
|
||||
/// <summary>A renderable view of one visible gfx object — the host composites these in ascending-handle order
|
||||
/// (= the engine's z-order) each frame. Built by <see cref="GfxState.SnapshotVisibleObjects"/>; the surface
|
||||
/// resId/colorkey are resolved from the object's live source slot at snapshot time (see docs/engine-re.md,
|
||||
/// "The full gfx render model").</summary>
|
||||
public readonly record struct RenderObject(long Handle, long SurfaceResId, long ColorKey,
|
||||
int SrcX, int SrcY, int W, int H, int DstX, int DstY);
|
||||
|
||||
/// <summary>Host-agnostic model of the AGE native gfx command-buffer (reversed in
|
||||
/// docs/engine-re.md, gfx op-contract table). One registry maps an object handle to a GfxObject — the
|
||||
@@ -19,6 +23,10 @@ public sealed class GfxState
|
||||
public (long X, long Y, long Z) V18, V24, V16c;
|
||||
public long Field64, Field68, Field6c;
|
||||
public long Color;
|
||||
// draw-texture bind (gfx_object_bind_draw): the surface to draw + its source rect + the visible flag.
|
||||
public int SourceSlot = -1;
|
||||
public (int X, int Y, int W, int H) SrcRect;
|
||||
public bool Visible;
|
||||
}
|
||||
|
||||
private readonly Dictionary<long, GfxObject> _objects = new();
|
||||
@@ -64,30 +72,51 @@ public sealed class GfxState
|
||||
/// NOT a create — objects are created lazily by the geometry SET ops (gfx_object_get_or_create).</summary>
|
||||
public void EraseRange(long handle, long count)
|
||||
{
|
||||
if (count > 1) for (long i = handle; i < handle + count; i++) { Release(i); RemoveLayers(i); }
|
||||
else { Release(handle); RemoveLayers(handle); }
|
||||
}
|
||||
|
||||
// ---- retained composite layers (mutated on the VM thread; snapshotted on the host render thread) ----
|
||||
private readonly List<DrawLayer> _layers = new();
|
||||
private readonly object _lock = new();
|
||||
|
||||
/// <summary>Record a draw: update the layer with this handle in place (preserving composite order), else
|
||||
/// append. Order = execution order = paint order.</summary>
|
||||
public void AddOrUpdateLayer(DrawLayer l)
|
||||
{
|
||||
// Registry/slot cleanup (native gfx_registry_erase): removes the object from the registry, so it stops
|
||||
// compositing next frame. Faithful to the engine (the render loop iterates the registry).
|
||||
lock (_lock)
|
||||
{
|
||||
for (int i = 0; i < _layers.Count; i++)
|
||||
if (_layers[i].Handle == l.Handle) { _layers[i] = l; return; }
|
||||
_layers.Add(l);
|
||||
if (count > 1) for (long i = handle; i < handle + count; i++) Release(i);
|
||||
else Release(handle);
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveLayers(long handle) { lock (_lock) { _layers.RemoveAll(l => l.Handle == handle); } }
|
||||
private readonly object _lock = new();
|
||||
|
||||
/// <summary>An immutable snapshot of the current layers, in composite order (for the host render loop).</summary>
|
||||
public IReadOnlyList<DrawLayer> SnapshotLayers() { lock (_lock) { return _layers.ToArray(); } }
|
||||
// ---- surfaces (image buffers per slot): ctx+0x52bd4[slot], from create/set-texture ----
|
||||
private readonly Dictionary<int, (long ResId, long ColorKey)> _surfaces = new();
|
||||
public void SetSurface(int slot, long resId, long colorKey) { lock (_lock) { _surfaces[slot] = (resId, colorKey); } }
|
||||
public void ClearSurface(int slot) { lock (_lock) { _surfaces[slot] = (0, 0); } } // create-texture (blank)
|
||||
|
||||
/// <summary>draw-texture bind (gfx_object_bind_draw): object <paramref name="handle"/> draws surface
|
||||
/// <paramref name="slot"/>'s rect at (dstX,dstY) and becomes visible.</summary>
|
||||
public void BindDraw(long handle, int slot, int sx, int sy, int w, int h, int dstX, int dstY)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
var o = GetOrCreate(handle);
|
||||
o.SourceSlot = slot; o.SrcRect = (sx, sy, w, h); o.V24 = (dstX, dstY, 0); o.Visible = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Visible objects in ascending-handle order (= the engine's z-order), each with its source
|
||||
/// surface (resId/colorkey) resolved from its live source slot — for the host per-frame compositor.</summary>
|
||||
public IReadOnlyList<RenderObject> SnapshotVisibleObjects()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
var list = new List<RenderObject>();
|
||||
foreach (var kv in _objects.OrderBy(k => k.Key))
|
||||
{
|
||||
var o = kv.Value;
|
||||
if (!o.Visible) continue;
|
||||
var (resId, ck) = _surfaces.TryGetValue(o.SourceSlot, out var s) ? s : (0L, 0L);
|
||||
list.Add(new RenderObject(kv.Key, resId, ck, o.SrcRect.X, o.SrcRect.Y, o.SrcRect.W, o.SrcRect.H,
|
||||
(int)o.V24.X, (int)o.V24.Y));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Pack (alpha, rgb) → 0xAARRGGBB, matching op 0x202/0x203's handler bit-manipulation for the
|
||||
/// common (non-negative-sentinel) case. The alpha<0 / color<0 native-fetch path is deferred.</summary>
|
||||
|
||||
@@ -211,15 +211,17 @@ public sealed class VirtualMachine
|
||||
case "end-text-line": case "set-font":
|
||||
case "comment": case "display-furigana": case "dev_ukn":
|
||||
return pc + 1;
|
||||
case "create-texture":
|
||||
case "create-texture": // 0x1f8 (slot)(w)(h) — allocate a blank surface at the slot
|
||||
Gfx.ClearSurface((int)Read(a[0]));
|
||||
_host.CreateTexture((int)Read(a[0]), (int)Read(a[1]), (int)Read(a[2])); return pc + 1;
|
||||
case "set-texture":
|
||||
_host.SetTexture(Read(a[0]), (int)Read(a[1])); return pc + 1;
|
||||
case "draw-texture": // (handle, slot, srcX, srcY, w, h, dstX, dstY)
|
||||
Gfx.AddOrUpdateLayer(new DrawLayer(Read(a[0]), (int)Read(a[1]), (int)Read(a[2]), (int)Read(a[3]),
|
||||
(int)Read(a[4]), (int)Read(a[5]), (int)Read(a[6]), (int)Read(a[7])));
|
||||
case "set-texture": // 0x1f9 (resId)(slot)(colorkey) — load a file into the slot's surface
|
||||
Gfx.SetSurface((int)Read(a[1]), Read(a[0]), a.Count > 2 ? Read(a[2]) : 0);
|
||||
_host.SetTexture(Read(a[0]), (int)Read(a[1])); return pc + 1; // host still tracks dims for get-texture-size
|
||||
case "draw-texture": // 0x1fb (handle)(slot)(srcX)(srcY)(w)(h)(dstX)(dstY) — bind object -> surface + rect + pos
|
||||
Gfx.BindDraw(Read(a[0]), (int)Read(a[1]), (int)Read(a[2]), (int)Read(a[3]),
|
||||
(int)Read(a[4]), (int)Read(a[5]), (int)Read(a[6]), (int)Read(a[7]));
|
||||
_host.DrawTexture((int)Read(a[1]), (int)Read(a[2]), (int)Read(a[3]), (int)Read(a[4]),
|
||||
(int)Read(a[5]), (int)Read(a[6]), (int)Read(a[7])); return pc + 1;
|
||||
(int)Read(a[5]), (int)Read(a[6]), (int)Read(a[7])); return pc + 1; // IHost seam (oracle log; Godot no-ops)
|
||||
case "get-texture-size": // 0x208 (slot) (out_w) (out_h)
|
||||
{
|
||||
var (gw, gh) = _host.GetTextureSize((int)Read(a[0]));
|
||||
|
||||
Reference in New Issue
Block a user