engine: materialize live ADV glyphs
This commit is contained in:
@@ -68,6 +68,7 @@ public sealed class GodotAdvHost : IHost
|
||||
private readonly Dictionary<int, List<SurfaceTextDraw>> _surfaceText = new();
|
||||
private readonly CachedGlyphMaskRasterizer? _surfaceTextMaskCache;
|
||||
private readonly ImmediateSurfaceTextRenderer? _surfaceTextPixelRenderer;
|
||||
private readonly RetainedGlyphLayoutEngine? _retainedGlyphLayoutEngine;
|
||||
private readonly GlyphRasterizerBackendInfo? _surfaceTextBackendInfo;
|
||||
private string _surfaceTextFallbackReason;
|
||||
private bool _surfaceTextFallbackWarningReported;
|
||||
@@ -77,9 +78,15 @@ public sealed class GodotAdvHost : IHost
|
||||
public required AdvLiveTextRun Run;
|
||||
public required long StartedMs;
|
||||
public required int GlyphDelayMilliseconds;
|
||||
public bool RetainedPixels;
|
||||
public int FirstGlyphIndex;
|
||||
public int GlyphCount;
|
||||
}
|
||||
private readonly List<LiveTextState> _liveText = new();
|
||||
private readonly Dictionary<int, RetainedAdvTextLayoutPresentation>
|
||||
_retainedTextLayouts = new();
|
||||
private LiveTextState? _activeLiveText;
|
||||
private int _suspendedRetainedTextLayoutSlot;
|
||||
private string _advText = "";
|
||||
private int _advTextX = 100, _advTextY = 47;
|
||||
private int _currentAdvLayout = 1; // SYSTEM4's ordinary SC0000 ADV layout
|
||||
@@ -143,6 +150,8 @@ public sealed class GodotAdvHost : IHost
|
||||
surfaceTextRasterizer, capacity: 2048);
|
||||
_surfaceTextPixelRenderer =
|
||||
new ImmediateSurfaceTextRenderer(_surfaceTextMaskCache);
|
||||
_retainedGlyphLayoutEngine =
|
||||
new RetainedGlyphLayoutEngine(_surfaceTextMaskCache);
|
||||
}
|
||||
_surfaceTextFallbackReason = surfaceTextFallbackReason
|
||||
?? (surfaceTextRasterizer == null
|
||||
@@ -330,6 +339,205 @@ public sealed class GodotAdvHost : IHost
|
||||
_timeline?.State("running", new() { ["text_reveal_complete"] = true });
|
||||
}
|
||||
|
||||
public AdvRetainedTextRunResult? ShowText(
|
||||
GfxState gfx,
|
||||
AdvTextLayoutPresentationBinding binding,
|
||||
AdvLiveTextRun run,
|
||||
int glyphDelayMilliseconds)
|
||||
{
|
||||
if (_retainedGlyphLayoutEngine == null
|
||||
|| binding.LayoutSlot != run.Layout.Slot
|
||||
|| binding.FirstObjectHandle < 0
|
||||
|| binding.ObjectCapacity <= 0
|
||||
|| !TryPrepareRetainedTextRun(
|
||||
gfx, binding, run,
|
||||
out RetainedAdvTextLayoutPresentation? presentation,
|
||||
out AdvRetainedTextRunResult result))
|
||||
{
|
||||
ShowText(run, glyphDelayMilliseconds);
|
||||
return null;
|
||||
}
|
||||
|
||||
Captured.Add((run.SourceOffset, run.Text));
|
||||
_locator.Text(run.SourceOffset, run.Text);
|
||||
int delay = System.Math.Max(0, glyphDelayMilliseconds);
|
||||
int revealGlyphCount = System.Math.Max(
|
||||
0, System.Math.Min(
|
||||
result.GlyphCount,
|
||||
presentation.PublishableGlyphCount - result.FirstGlyphIndex));
|
||||
var state = new LiveTextState
|
||||
{
|
||||
Run = run,
|
||||
StartedMs = _clock.NowMs,
|
||||
GlyphDelayMilliseconds = delay,
|
||||
RetainedPixels = true,
|
||||
FirstGlyphIndex = result.FirstGlyphIndex,
|
||||
GlyphCount = revealGlyphCount,
|
||||
};
|
||||
lock (_textLock)
|
||||
{
|
||||
_liveText.Add(state);
|
||||
_activeLiveText = state;
|
||||
_advText = run.Text;
|
||||
_advTextX = run.Layout.CursorX;
|
||||
_advTextY = run.Layout.CursorY;
|
||||
_currentAdvLayout = run.Layout.Slot;
|
||||
_advTextStartedMs = state.StartedMs;
|
||||
_activeGlyphDelayMilliseconds = delay;
|
||||
_advTextForceComplete = _messageSkipActive;
|
||||
IsTextRevealing =
|
||||
revealGlyphCount > 0 && delay > 0 && !_messageSkipActive;
|
||||
}
|
||||
_timeline?.State("text-reveal", new()
|
||||
{
|
||||
["offset"] = $"0x{run.SourceOffset:x}",
|
||||
["layout"] = run.Layout.Slot,
|
||||
["x"] = run.Layout.OriginX + run.Layout.CursorX,
|
||||
["y"] = run.Layout.OriginY + run.Layout.CursorY,
|
||||
["glyphs"] = revealGlyphCount,
|
||||
["records"] = result.GlyphCount,
|
||||
["delay_ms"] = delay,
|
||||
["presentation"] = "retained-glyphs",
|
||||
});
|
||||
|
||||
int initiallyVisible = IsTextRevealing ? 1 : revealGlyphCount;
|
||||
presentation.PublishThrough(
|
||||
gfx, checked(result.FirstGlyphIndex + initiallyVisible));
|
||||
Interlocked.Exchange(ref _presentRequested, 1);
|
||||
if (!IsTextRevealing)
|
||||
{
|
||||
_timeline?.State("running", new() { ["text_reveal_complete"] = true });
|
||||
return result;
|
||||
}
|
||||
|
||||
bool scriptSuspended = SuspendScriptForPresentation();
|
||||
try
|
||||
{
|
||||
RequestSynchronizedPresentation();
|
||||
while (IsTextRevealing && !_stopping)
|
||||
{
|
||||
int visible;
|
||||
lock (_textLock)
|
||||
{
|
||||
visible = _advTextForceComplete
|
||||
? revealGlyphCount
|
||||
: (int)System.Math.Clamp(
|
||||
(_clock.NowMs - state.StartedMs) / delay + 1,
|
||||
0, revealGlyphCount);
|
||||
if (visible >= revealGlyphCount) IsTextRevealing = false;
|
||||
}
|
||||
int before = presentation.PublishedGlyphCount;
|
||||
presentation.PublishThrough(
|
||||
gfx, checked(result.FirstGlyphIndex + visible));
|
||||
if (presentation.PublishedGlyphCount != before)
|
||||
{
|
||||
Interlocked.Exchange(ref _presentRequested, 1);
|
||||
RequestSynchronizedPresentation();
|
||||
}
|
||||
if (IsTextRevealing) _frameSignal.WaitOne(50);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
ResumeScriptAfterPresentation(scriptSuspended);
|
||||
}
|
||||
_timeline?.State("running", new() { ["text_reveal_complete"] = true });
|
||||
return result;
|
||||
}
|
||||
|
||||
private bool TryPrepareRetainedTextRun(
|
||||
GfxState gfx,
|
||||
AdvTextLayoutPresentationBinding binding,
|
||||
AdvLiveTextRun run,
|
||||
out RetainedAdvTextLayoutPresentation presentation,
|
||||
out AdvRetainedTextRunResult result)
|
||||
{
|
||||
presentation = null!;
|
||||
result = default;
|
||||
if (run.Layout.Width <= 0 || run.Layout.Height <= 0)
|
||||
{
|
||||
ReportSurfaceTextFallback(
|
||||
$"ADV layout {run.Layout.Slot} has invalid dimensions " +
|
||||
$"{run.Layout.Width}x{run.Layout.Height}.");
|
||||
return false;
|
||||
}
|
||||
|
||||
RgbaImage destination = ResolveSurfacePixels(binding.SourceSurfaceSlot)
|
||||
?? new RgbaImage(
|
||||
run.Layout.Width,
|
||||
run.Layout.Height,
|
||||
new byte[checked(run.Layout.Width * run.Layout.Height * 4)]);
|
||||
if (destination.Width != run.Layout.Width
|
||||
|| destination.Height != run.Layout.Height)
|
||||
{
|
||||
ReportSurfaceTextFallback(
|
||||
$"ADV layout {run.Layout.Slot} surface {binding.SourceSurfaceSlot} is " +
|
||||
$"{destination.Width}x{destination.Height}; expected " +
|
||||
$"{run.Layout.Width}x{run.Layout.Height}.");
|
||||
return false;
|
||||
}
|
||||
|
||||
var updated = new RgbaImage(
|
||||
destination.Width, destination.Height, (byte[])destination.Pixels.Clone());
|
||||
GlyphTextLayoutResult rendered;
|
||||
try
|
||||
{
|
||||
IReadOnlyList<GlyphRasterRequest> requests =
|
||||
ImmediateSurfaceTextRenderer.CreateRequests(run.Text, run.Style);
|
||||
rendered = _retainedGlyphLayoutEngine!.Render(
|
||||
updated,
|
||||
new GlyphTextLayoutOptions(
|
||||
run.Layout.CursorX,
|
||||
run.Layout.CursorY,
|
||||
binding.ResetCursorX,
|
||||
run.Layout.Right,
|
||||
run.Layout.Bottom,
|
||||
WrapHorizontally: true,
|
||||
run.Style),
|
||||
requests);
|
||||
}
|
||||
catch (Exception error) when (
|
||||
error is ArgumentException
|
||||
or InvalidOperationException
|
||||
or PlatformNotSupportedException
|
||||
or System.ComponentModel.Win32Exception)
|
||||
{
|
||||
ReportSurfaceTextFallback(
|
||||
$"{error.GetType().Name}: {error.Message}");
|
||||
return false;
|
||||
}
|
||||
|
||||
lock (_textLock)
|
||||
{
|
||||
if (!_retainedTextLayouts.TryGetValue(
|
||||
binding.LayoutSlot, out presentation!))
|
||||
{
|
||||
presentation = new RetainedAdvTextLayoutPresentation(binding);
|
||||
_retainedTextLayouts.Add(binding.LayoutSlot, presentation);
|
||||
}
|
||||
else if (presentation.Binding != binding)
|
||||
{
|
||||
ReportSurfaceTextFallback(
|
||||
$"ADV layout {binding.LayoutSlot} changed its retained binding without reset.");
|
||||
return false;
|
||||
}
|
||||
int first = presentation.Append(
|
||||
rendered.Records, run.Layout.OriginX, run.Layout.OriginY);
|
||||
result = new AdvRetainedTextRunResult(
|
||||
binding.LayoutSlot,
|
||||
first,
|
||||
rendered.ConsumedGlyphs,
|
||||
rendered.CursorX,
|
||||
rendered.CursorY);
|
||||
}
|
||||
|
||||
lock (_imageLock)
|
||||
_surfaceImages[binding.SourceSurfaceSlot] = updated;
|
||||
_slotDims[binding.SourceSurfaceSlot] = (updated.Width, updated.Height);
|
||||
gfx.CreateSurface(binding.SourceSurfaceSlot);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SetAdvTextCursor(int layoutSlot, int x, int y)
|
||||
{
|
||||
lock (_textLock)
|
||||
@@ -450,17 +658,17 @@ public sealed class GodotAdvHost : IHost
|
||||
{
|
||||
lock (_textLock)
|
||||
{
|
||||
var snapshot = new LiveAdvTextSnapshot[_liveText.Count];
|
||||
for (int i = 0; i < _liveText.Count; i++)
|
||||
var snapshot = new List<LiveAdvTextSnapshot>(_liveText.Count);
|
||||
foreach (LiveTextState state in _liveText)
|
||||
{
|
||||
var state = _liveText[i];
|
||||
if (state.RetainedPixels) continue;
|
||||
bool revealing = ReferenceEquals(state, _activeLiveText) && IsTextRevealing;
|
||||
int visible = !revealing || _advTextForceComplete || state.GlyphDelayMilliseconds == 0
|
||||
? state.Run.Text.Length
|
||||
: (int)System.Math.Clamp(
|
||||
(_clock.NowMs - state.StartedMs) / state.GlyphDelayMilliseconds + 1,
|
||||
0, state.Run.Text.Length);
|
||||
snapshot[i] = new LiveAdvTextSnapshot(state.Run, visible, revealing);
|
||||
snapshot.Add(new LiveAdvTextSnapshot(state.Run, visible, revealing));
|
||||
}
|
||||
return snapshot;
|
||||
}
|
||||
@@ -502,6 +710,7 @@ public sealed class GodotAdvHost : IHost
|
||||
{
|
||||
int slot = layoutSlot == 0 ? _currentAdvLayout : layoutSlot;
|
||||
_historyText.Remove(slot);
|
||||
if (_retainedTextLayouts.ContainsKey(slot)) return;
|
||||
_liveText.RemoveAll(state => state.Run.Layout.Slot == slot);
|
||||
if (_activeLiveText?.Run.Layout.Slot == slot)
|
||||
{
|
||||
@@ -513,6 +722,47 @@ public sealed class GodotAdvHost : IHost
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetRenderedAdvTextLayout(
|
||||
GfxState gfx, AdvTextLayoutPresentationBinding binding)
|
||||
{
|
||||
lock (_textLock)
|
||||
{
|
||||
_historyText.Remove(binding.LayoutSlot);
|
||||
_retainedTextLayouts.Remove(binding.LayoutSlot);
|
||||
_liveText.RemoveAll(
|
||||
state => state.Run.Layout.Slot == binding.LayoutSlot);
|
||||
if (_activeLiveText?.Run.Layout.Slot == binding.LayoutSlot)
|
||||
{
|
||||
_activeLiveText = null;
|
||||
_advText = "";
|
||||
_advTextForceComplete = false;
|
||||
IsTextRevealing = false;
|
||||
}
|
||||
if (_suspendedRetainedTextLayoutSlot == binding.LayoutSlot)
|
||||
_suspendedRetainedTextLayoutSlot = 0;
|
||||
}
|
||||
|
||||
if (_slotDims.TryGetValue(
|
||||
binding.SourceSurfaceSlot, out var dimensions)
|
||||
&& dimensions.W > 0
|
||||
&& dimensions.H > 0)
|
||||
{
|
||||
lock (_imageLock)
|
||||
_surfaceImages[binding.SourceSurfaceSlot] = new RgbaImage(
|
||||
dimensions.W,
|
||||
dimensions.H,
|
||||
new byte[checked(dimensions.W * dimensions.H * 4)]);
|
||||
}
|
||||
Interlocked.Exchange(ref _presentRequested, 1);
|
||||
_timeline?.Event("adv-text-layout-reset", new()
|
||||
{
|
||||
["layout"] = binding.LayoutSlot,
|
||||
["surface"] = binding.SourceSurfaceSlot,
|
||||
["first_handle"] = binding.FirstObjectHandle,
|
||||
["capacity"] = binding.ObjectCapacity,
|
||||
});
|
||||
}
|
||||
|
||||
public void RenderTextHistory(AdvTextHistoryRenderBatch batch)
|
||||
{
|
||||
lock (_textLock) _historyText[batch.LayoutSlot] = batch;
|
||||
@@ -666,6 +916,17 @@ public sealed class GodotAdvHost : IHost
|
||||
_timeline?.Event("adv-text-layout-publish", new() { ["layout"] = layoutSlot });
|
||||
}
|
||||
|
||||
public void PublishAdvTextLayout(
|
||||
GfxState gfx, AdvTextLayoutPresentationBinding binding)
|
||||
{
|
||||
lock (_textLock)
|
||||
if (_retainedTextLayouts.TryGetValue(
|
||||
binding.LayoutSlot, out RetainedAdvTextLayoutPresentation? presentation)
|
||||
&& presentation.Binding == binding)
|
||||
presentation.Republish(gfx);
|
||||
PublishAdvTextLayout(binding.LayoutSlot);
|
||||
}
|
||||
|
||||
public AdvWaitIndicatorSnapshot? SnapshotAdvWaitIndicator()
|
||||
{
|
||||
if (!IsWaiting || _advPagePresentationSuspended) return null;
|
||||
@@ -894,6 +1155,35 @@ public sealed class GodotAdvHost : IHost
|
||||
_timeline?.State(suspended ? "adv-page-suspended" : "adv-page-restored", new());
|
||||
}
|
||||
|
||||
public void SetAdvPagePresentationSuspended(GfxState gfx, bool suspended)
|
||||
{
|
||||
lock (_textLock)
|
||||
{
|
||||
if (suspended)
|
||||
{
|
||||
int slot = IsWaiting && _activeWaitLayout != 0
|
||||
? _activeWaitLayout
|
||||
: _currentAdvLayout;
|
||||
if (_retainedTextLayouts.TryGetValue(
|
||||
slot, out RetainedAdvTextLayoutPresentation? presentation))
|
||||
{
|
||||
presentation.ErasePublished(gfx);
|
||||
_suspendedRetainedTextLayoutSlot = slot;
|
||||
}
|
||||
}
|
||||
else if (_suspendedRetainedTextLayoutSlot != 0)
|
||||
{
|
||||
if (_retainedTextLayouts.TryGetValue(
|
||||
_suspendedRetainedTextLayoutSlot,
|
||||
out RetainedAdvTextLayoutPresentation? presentation))
|
||||
presentation.Republish(gfx);
|
||||
_suspendedRetainedTextLayoutSlot = 0;
|
||||
}
|
||||
}
|
||||
Interlocked.Exchange(ref _presentRequested, 1);
|
||||
SetAdvPagePresentationSuspended(suspended);
|
||||
}
|
||||
|
||||
public void InputCallbackCompleted(GfxState gfx)
|
||||
{
|
||||
// Callback completion itself is not native graphics dirtiness. Any retained writes made by the
|
||||
@@ -1200,7 +1490,9 @@ public sealed class GodotAdvHost : IHost
|
||||
_surfaceResources.Clear();
|
||||
_historyText.Clear();
|
||||
_liveText.Clear();
|
||||
_retainedTextLayouts.Clear();
|
||||
_activeLiveText = null;
|
||||
_suspendedRetainedTextLayoutSlot = 0;
|
||||
_advText = "";
|
||||
_advTextX = 100;
|
||||
_advTextY = 47;
|
||||
@@ -1839,6 +2131,13 @@ public sealed class GodotAdvHost : IHost
|
||||
_surfaceText.Remove(slot);
|
||||
_surfaceResources.Remove(slot);
|
||||
}
|
||||
foreach (int layoutSlot in _retainedTextLayouts
|
||||
.Where(pair =>
|
||||
pair.Value.Binding.SourceSurfaceSlot >= firstSlot
|
||||
&& pair.Value.Binding.SourceSurfaceSlot < end)
|
||||
.Select(pair => pair.Key)
|
||||
.ToArray())
|
||||
_retainedTextLayouts.Remove(layoutSlot);
|
||||
}
|
||||
foreach (MovieSurfaceBinding binding in stoppedMovies)
|
||||
{
|
||||
|
||||
102
godot/Main.cs
102
godot/Main.cs
@@ -2541,6 +2541,75 @@ public partial class Main : Godot.Control
|
||||
}
|
||||
_host.ReleaseSurface(996);
|
||||
_host.ReleaseSurface(997);
|
||||
bool liveRetainedTextOk;
|
||||
string liveRetainedTextMode;
|
||||
AdvTextLayoutPresentationBinding liveBinding =
|
||||
_vm.TextHistory.GetPresentationBinding(1);
|
||||
if (_host.UsesSurfaceTextPixels)
|
||||
{
|
||||
static bool InLiveRange(
|
||||
RenderObject item, AdvTextLayoutPresentationBinding binding)
|
||||
=> item.Handle >= binding.FirstObjectHandle
|
||||
&& item.Handle - binding.FirstObjectHandle < binding.ObjectCapacity;
|
||||
IReadOnlyList<RenderObject> liveObjects = _vm.Gfx.SnapshotVisibleObjects()
|
||||
.Where(item => InLiveRange(item, liveBinding))
|
||||
.ToArray();
|
||||
RgbaImage? liveSurface =
|
||||
_host.CaptureSurfacePixels(liveBinding.SourceSurfaceSlot);
|
||||
bool builtCompleteLine =
|
||||
liveObjects.Count == "HelloWorldSub".Length
|
||||
&& liveObjects.Select(item => item.Handle)
|
||||
.SequenceEqual(Enumerable.Range(0, liveObjects.Count)
|
||||
.Select(index => liveBinding.FirstObjectHandle + index))
|
||||
&& liveSurface != null
|
||||
&& liveSurface.Pixels.Where((_, index) => index % 4 == 3)
|
||||
.Any(alpha => alpha != 0)
|
||||
&& _host.SnapshotLiveAdvText().Count == 0
|
||||
&& _vm.TextHistory.GetLayoutSnapshot(1).CursorX
|
||||
> liveBinding.ResetCursorX;
|
||||
|
||||
_vm.Gfx.EraseRange(liveBinding.FirstObjectHandle + 1, 1);
|
||||
_host.PublishAdvTextLayout(_vm.Gfx, liveBinding);
|
||||
bool republishedPartialErase =
|
||||
_vm.Gfx.SnapshotVisibleObjects()
|
||||
.Count(item => InLiveRange(item, liveBinding))
|
||||
== liveObjects.Count;
|
||||
|
||||
_host.SetAdvPagePresentationSuspended(_vm.Gfx, true);
|
||||
bool suspended =
|
||||
!_vm.Gfx.SnapshotVisibleObjects()
|
||||
.Any(item => InLiveRange(item, liveBinding));
|
||||
_host.SetAdvPagePresentationSuspended(_vm.Gfx, false);
|
||||
bool restored =
|
||||
_vm.Gfx.SnapshotVisibleObjects()
|
||||
.Count(item => InLiveRange(item, liveBinding))
|
||||
== liveObjects.Count;
|
||||
|
||||
_vm.Gfx.EraseRange(
|
||||
liveBinding.FirstObjectHandle, liveBinding.ObjectCapacity);
|
||||
_host.ResetRenderedAdvTextLayout(_vm.Gfx, liveBinding);
|
||||
RgbaImage? resetSurface =
|
||||
_host.CaptureSurfacePixels(liveBinding.SourceSurfaceSlot);
|
||||
bool reset =
|
||||
resetSurface != null
|
||||
&& resetSurface.Pixels.All(value => value == 0)
|
||||
&& !_vm.Gfx.SnapshotVisibleObjects()
|
||||
.Any(item => InLiveRange(item, liveBinding));
|
||||
liveRetainedTextOk =
|
||||
builtCompleteLine
|
||||
&& republishedPartialErase
|
||||
&& suspended
|
||||
&& restored
|
||||
&& reset;
|
||||
liveRetainedTextMode = "retained-glyphs";
|
||||
}
|
||||
else
|
||||
{
|
||||
liveRetainedTextOk =
|
||||
_host.SnapshotLiveAdvText().Count == 3
|
||||
&& !string.IsNullOrWhiteSpace(_host.SurfaceTextFallbackReason);
|
||||
liveRetainedTextMode = "label-fallback";
|
||||
}
|
||||
Window rootWindow = GetTree().Root;
|
||||
bool logicalCanvasOk = _host.LogicalCanvas == new Sys4LogicalCanvas(_screenWidth, _screenHeight)
|
||||
&& _screen.GetWidth() == _screenWidth
|
||||
@@ -2580,6 +2649,7 @@ public partial class Main : Godot.Control
|
||||
&& bgmReplacementCancelsFade && bgmOneShotModeOk && bgmLoopModeOk
|
||||
&& bgmStopReleaseOk && textEffectModesOk && fontCalibrationOk
|
||||
&& immediateSurfaceTextOk
|
||||
&& liveRetainedTextOk
|
||||
&& logicalCanvasOk && backbufferPreservationOk;
|
||||
if (ok) GD.Print($"SELFTEST OK: threaded host matches headless ({actual.Count} lines, full handling); " +
|
||||
$"debug launcher catalog/UI smoke ({debugEntries.Count} packed scripts); " +
|
||||
@@ -2588,6 +2658,7 @@ public partial class Main : Godot.Control
|
||||
$"bgm-fade-replacement=ok; bgm-start-modes-stop=ok; " +
|
||||
$"text-effect-modes=ok; font-calibration=ok; " +
|
||||
$"immediate-surface-text={immediateSurfaceTextMode}; " +
|
||||
$"live-adv-text={liveRetainedTextMode}; " +
|
||||
$"backbuffer-preservation=ok; " +
|
||||
$"logical-canvas={_screenWidth}x{_screenHeight}; " +
|
||||
$"window-request={_windowOptions.Width}x{_windowOptions.Height}");
|
||||
@@ -2600,6 +2671,7 @@ public partial class Main : Godot.Control
|
||||
$"bgm-stop-release={bgmStopReleaseOk}; " +
|
||||
$"text-effect-modes={textEffectModesOk}; font-calibration={fontCalibrationOk}; " +
|
||||
$"immediate-surface-text={immediateSurfaceTextOk}({immediateSurfaceTextMode}); " +
|
||||
$"live-adv-text={liveRetainedTextOk}({liveRetainedTextMode}); " +
|
||||
$"backbuffer-preservation={backbufferPreservationOk}; " +
|
||||
$"logical-canvas={logicalCanvasOk}({_screenWidth}x{_screenHeight}); " +
|
||||
$"window-request={_windowOptions.Width}x{_windowOptions.Height}");
|
||||
@@ -2610,7 +2682,7 @@ public partial class Main : Godot.Control
|
||||
// nested call-script into a synthetic subroutine (exercises call-script handling), shared globals.
|
||||
private static (Script, IScriptProvider) BuildSelfTestScene(OpcodeTable table)
|
||||
{
|
||||
(int, Operand[]) ShowText(int s) => (0x6e, new[] { new Operand(2, s), new Operand(0, 0) });
|
||||
(int, Operand[]) ShowText(int s) => (0x6e, new[] { new Operand(0, 0), new Operand(2, s) });
|
||||
(int, Operand[]) Wait() => (0x72, new[] { new Operand(0, 0) });
|
||||
(int, Operand[]) CallScript(long id) => (0x3, new[] { new Operand(0, id) });
|
||||
(int, Operand[]) MovGG(int d, int s) => (0x55, new[] { new Operand(3, d), new Operand(3, s) });
|
||||
@@ -2620,7 +2692,33 @@ public partial class Main : Godot.Control
|
||||
var callee = ScriptAssembler.Assemble(table, "SUBSCENE",
|
||||
new List<(int, Operand[])> { ShowText(0), MovGI(0x31, 42), Exit() }, new[] { "Sub" });
|
||||
var caller = ScriptAssembler.Assemble(table, "SELFTEST",
|
||||
new List<(int, Operand[])> { ShowText(0), Wait(), ShowText(1), CallScript(5), MovGG(0x30, 0x31), Exit() },
|
||||
new List<(int, Operand[])>
|
||||
{
|
||||
(0x70, new[]
|
||||
{
|
||||
new Operand(0, 1), new Operand(0, 512), new Operand(0, 64),
|
||||
new Operand(0, 0), new Operand(0, 0),
|
||||
}),
|
||||
(0x79, new[]
|
||||
{
|
||||
new Operand(0, 1), new Operand(0, 1), new Operand(0, 1),
|
||||
}),
|
||||
(0x71, new[] { new Operand(0, 1) }),
|
||||
(0x1c1, new[]
|
||||
{
|
||||
new Operand(0, 1), new Operand(0, 511), new Operand(0, 63),
|
||||
}),
|
||||
(0x213, new[]
|
||||
{
|
||||
new Operand(0, 1), new Operand(0, 700000), new Operand(0, 64),
|
||||
}),
|
||||
ShowText(0),
|
||||
Wait(),
|
||||
ShowText(1),
|
||||
CallScript(5),
|
||||
MovGG(0x30, 0x31),
|
||||
Exit(),
|
||||
},
|
||||
new[] { "Hello", "World" });
|
||||
return (caller, new SelfTestProvider(callee));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user