Adopt GPU retained renderer
This commit is contained in:
251
godot/GpuRetainedRenderer.cs
Normal file
251
godot/GpuRetainedRenderer.cs
Normal file
@@ -0,0 +1,251 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Godot;
|
||||
using Age.Engine.Model;
|
||||
using Age.Engine.Sys4;
|
||||
|
||||
/// <summary>
|
||||
/// Godot-native presentation of AGE's sampled retained objects. Static decoded/color-key variants are
|
||||
/// uploaded once; pooled Sprite2D canvas items retain their GPU resources between presentation boundaries.
|
||||
/// GfxState remains the backend-neutral source of truth and the software compositor remains the oracle.
|
||||
/// </summary>
|
||||
internal sealed class GpuRetainedRenderer : IDisposable
|
||||
{
|
||||
internal readonly record struct FrameStats(int DrawItems, int TextureUploads, long TextureUploadTicks);
|
||||
|
||||
private sealed class CachedTexture
|
||||
{
|
||||
public required ImageTexture Texture;
|
||||
public required int Width;
|
||||
public required int Height;
|
||||
public byte[]? LastPixels;
|
||||
}
|
||||
|
||||
private readonly Node2D _stage;
|
||||
private readonly List<Sprite2D> _items = new(1024);
|
||||
private readonly Dictionary<(int AssetId, long ColorKey, long DynamicKey), CachedTexture> _textures = new();
|
||||
private readonly Dictionary<(long Tint, int Strength, BlendKind Blend), ShaderMaterial> _lerpMaterials = new();
|
||||
private readonly CanvasItemMaterial _alphaMaterial = new() { BlendMode = CanvasItemMaterial.BlendModeEnum.Mix };
|
||||
private readonly CanvasItemMaterial _additiveMaterial = new() { BlendMode = CanvasItemMaterial.BlendModeEnum.Add };
|
||||
private readonly ImageTexture _whiteTexture;
|
||||
private readonly Shader _lerpShader;
|
||||
private int _used;
|
||||
private int _textureUploads;
|
||||
private long _textureUploadTicks;
|
||||
|
||||
public bool Visible
|
||||
{
|
||||
get => _stage.Visible;
|
||||
set => _stage.Visible = value;
|
||||
}
|
||||
|
||||
public GpuRetainedRenderer(Node parent)
|
||||
{
|
||||
_stage = new Node2D { Name = "GpuRetainedStage", Visible = false };
|
||||
parent.AddChild(_stage);
|
||||
|
||||
var white = Image.CreateEmpty(1, 1, false, Image.Format.Rgba8);
|
||||
white.SetData(1, 1, false, Image.Format.Rgba8, new byte[] { 255, 255, 255, 255 });
|
||||
_whiteTexture = ImageTexture.CreateFromImage(white);
|
||||
|
||||
_lerpShader = new Shader
|
||||
{
|
||||
Code = """
|
||||
shader_type canvas_item;
|
||||
render_mode blend_mix, unshaded;
|
||||
uniform vec3 age_tint = vec3(1.0);
|
||||
uniform float age_strength = 0.0;
|
||||
void fragment() {
|
||||
vec4 source = texture(TEXTURE, UV);
|
||||
source.rgb = mix(source.rgb, age_tint, age_strength);
|
||||
COLOR = source * COLOR;
|
||||
}
|
||||
"""
|
||||
};
|
||||
}
|
||||
|
||||
public void BeginFrame()
|
||||
{
|
||||
_used = 0;
|
||||
_textureUploads = 0;
|
||||
_textureUploadTicks = 0;
|
||||
}
|
||||
|
||||
public bool DrawTexture(RgbaImage source, int assetId, long colorKey,
|
||||
int srcX, int srcY, int width, int height,
|
||||
Affine2D localToDest, long tint, int tintStrength,
|
||||
float opacity, bool multiplyTint, bool dynamic, long dynamicKey, BlendKind blend)
|
||||
{
|
||||
if (width <= 0 || height <= 0 || opacity <= 0) return false;
|
||||
int clippedWidth = Math.Min(width, source.Width - srcX);
|
||||
int clippedHeight = Math.Min(height, source.Height - srcY);
|
||||
if (srcX < 0 || srcY < 0 || clippedWidth <= 0 || clippedHeight <= 0) return false;
|
||||
|
||||
var texture = ResolveTexture(source, assetId, colorKey, dynamic, dynamicKey);
|
||||
var item = NextItem();
|
||||
item.Texture = texture;
|
||||
item.RegionEnabled = true;
|
||||
item.RegionRect = new Rect2(srcX, srcY, clippedWidth, clippedHeight);
|
||||
item.Transform = ToGodot(localToDest);
|
||||
item.Modulate = Modulation(tint, opacity, multiplyTint);
|
||||
item.Material = ResolveMaterial(tint, tintStrength, multiplyTint, blend);
|
||||
item.Visible = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool DrawFill(int width, int height, Affine2D localToDest, long tint, float opacity)
|
||||
{
|
||||
if (width <= 0 || height <= 0 || opacity <= 0) return false;
|
||||
var item = NextItem();
|
||||
item.Texture = _whiteTexture;
|
||||
item.RegionEnabled = false;
|
||||
item.Transform = ToGodot(new Affine2D(
|
||||
localToDest.XX * width, localToDest.XY * width,
|
||||
localToDest.YX * height, localToDest.YY * height,
|
||||
localToDest.TX, localToDest.TY));
|
||||
item.Modulate = Modulation(tint, opacity, multiplyTint: true);
|
||||
item.Material = _alphaMaterial;
|
||||
item.Visible = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
public FrameStats EndFrame()
|
||||
{
|
||||
for (int i = _used; i < _items.Count; i++) _items[i].Visible = false;
|
||||
return new FrameStats(_used, _textureUploads, _textureUploadTicks);
|
||||
}
|
||||
|
||||
private Sprite2D NextItem()
|
||||
{
|
||||
if (_used == _items.Count)
|
||||
{
|
||||
var item = new Sprite2D
|
||||
{
|
||||
Centered = false,
|
||||
RegionFilterClipEnabled = true,
|
||||
TextureFilter = CanvasItem.TextureFilterEnum.Nearest,
|
||||
Visible = false,
|
||||
};
|
||||
_stage.AddChild(item);
|
||||
_items.Add(item);
|
||||
}
|
||||
var result = _items[_used];
|
||||
// Children are created in retained handle order, which is enough to preserve AGE z-order. Keep
|
||||
// their absolute Godot Z at the stage level so Main's later dialogue/wait controls remain above
|
||||
// the complete AGE canvas instead of being covered by a high-numbered full-screen sprite.
|
||||
result.ZIndex = 0;
|
||||
_used++;
|
||||
return result;
|
||||
}
|
||||
|
||||
private Texture2D ResolveTexture(RgbaImage source, int assetId, long colorKey, bool dynamic, long dynamicKey)
|
||||
{
|
||||
// One movie resource may play concurrently on multiple surfaces. Static assets share one upload;
|
||||
// dynamic surfaces use their surface/playback identity so one frame cannot overwrite another.
|
||||
var key = (assetId, colorKey, dynamic ? dynamicKey : 0);
|
||||
if (!_textures.TryGetValue(key, out var cached))
|
||||
{
|
||||
byte[] pixels = PreparePixels(source.Pixels, colorKey);
|
||||
var image = Image.CreateFromData(source.Width, source.Height, false, Image.Format.Rgba8, pixels);
|
||||
long started = System.Diagnostics.Stopwatch.GetTimestamp();
|
||||
var texture = ImageTexture.CreateFromImage(image);
|
||||
_textureUploadTicks += System.Diagnostics.Stopwatch.GetTimestamp() - started;
|
||||
_textureUploads++;
|
||||
cached = new CachedTexture
|
||||
{
|
||||
Texture = texture,
|
||||
Width = source.Width,
|
||||
Height = source.Height,
|
||||
LastPixels = dynamic ? source.Pixels : null,
|
||||
};
|
||||
_textures.Add(key, cached);
|
||||
}
|
||||
else if (dynamic && (!ReferenceEquals(cached.LastPixels, source.Pixels) ||
|
||||
cached.Width != source.Width || cached.Height != source.Height))
|
||||
{
|
||||
byte[] pixels = PreparePixels(source.Pixels, colorKey);
|
||||
var image = Image.CreateFromData(source.Width, source.Height, false, Image.Format.Rgba8, pixels);
|
||||
long started = System.Diagnostics.Stopwatch.GetTimestamp();
|
||||
if (cached.Width == source.Width && cached.Height == source.Height)
|
||||
cached.Texture.Update(image);
|
||||
else
|
||||
{
|
||||
cached.Texture = ImageTexture.CreateFromImage(image);
|
||||
cached.Width = source.Width;
|
||||
cached.Height = source.Height;
|
||||
}
|
||||
_textureUploadTicks += System.Diagnostics.Stopwatch.GetTimestamp() - started;
|
||||
_textureUploads++;
|
||||
cached.LastPixels = source.Pixels;
|
||||
}
|
||||
return cached.Texture;
|
||||
}
|
||||
|
||||
private static byte[] PreparePixels(byte[] source, long colorKey)
|
||||
{
|
||||
if (!BlendMath.HasColorKey(colorKey)) return source;
|
||||
byte[] pixels = (byte[])source.Clone();
|
||||
for (int i = 0; i < pixels.Length; i += 4)
|
||||
if (BlendMath.ColorKeyMatches(pixels[i], pixels[i + 1], pixels[i + 2], colorKey))
|
||||
pixels[i + 3] = 0;
|
||||
return pixels;
|
||||
}
|
||||
|
||||
private Material ResolveMaterial(long tint, int tintStrength, bool multiplyTint, BlendKind blend)
|
||||
{
|
||||
if (!multiplyTint && tintStrength > 0)
|
||||
{
|
||||
var key = (tint & 0x00ff_ffff, tintStrength, blend);
|
||||
if (!_lerpMaterials.TryGetValue(key, out var material))
|
||||
{
|
||||
material = new ShaderMaterial { Shader = _lerpShader };
|
||||
material.SetShaderParameter("age_tint", new Vector3(
|
||||
((tint >> 16) & 0xff) / 255f,
|
||||
((tint >> 8) & 0xff) / 255f,
|
||||
(tint & 0xff) / 255f));
|
||||
material.SetShaderParameter("age_strength", tintStrength / 255f);
|
||||
// LERP-tint objects observed so far use source-over. If an additive LERP mode appears,
|
||||
// keep the frame on the software oracle until it has a dedicated shader blend variant.
|
||||
_lerpMaterials.Add(key, material);
|
||||
}
|
||||
return material;
|
||||
}
|
||||
return blend == BlendKind.Additive ? _additiveMaterial : _alphaMaterial;
|
||||
}
|
||||
|
||||
private static Color Modulation(long tint, float opacity, bool multiplyTint)
|
||||
{
|
||||
float r = 1, g = 1, b = 1;
|
||||
if (multiplyTint)
|
||||
{
|
||||
r = ((tint >> 16) & 0xff) / 255f;
|
||||
g = ((tint >> 8) & 0xff) / 255f;
|
||||
b = (tint & 0xff) / 255f;
|
||||
}
|
||||
return new Color(r, g, b, Math.Clamp(opacity, 0, 1));
|
||||
}
|
||||
|
||||
private static Godot.Transform2D ToGodot(Affine2D value) => new(
|
||||
new Vector2((float)value.XX, (float)value.XY),
|
||||
new Vector2((float)value.YX, (float)value.YY),
|
||||
new Vector2((float)value.TX, (float)value.TY));
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_stage.Visible = false;
|
||||
foreach (var item in _items)
|
||||
{
|
||||
item.Texture = null;
|
||||
item.Material = null;
|
||||
}
|
||||
_whiteTexture.Dispose();
|
||||
_alphaMaterial.Dispose();
|
||||
_additiveMaterial.Dispose();
|
||||
foreach (var material in _lerpMaterials.Values) material.Dispose();
|
||||
foreach (var texture in _textures.Values) texture.Texture.Dispose();
|
||||
_lerpShader.Dispose();
|
||||
_items.Clear();
|
||||
_textures.Clear();
|
||||
_lerpMaterials.Clear();
|
||||
}
|
||||
}
|
||||
214
godot/Main.cs
214
godot/Main.cs
@@ -20,6 +20,8 @@ public partial class Main : Godot.Control
|
||||
private TextureRect _screenView = null!; // shows the composited screen backbuffer
|
||||
private Image _screen = null!; // 800x600 immediate-mode canvas
|
||||
private ImageTexture _screenTex = null!;
|
||||
private GpuRetainedRenderer _gpuRenderer = null!;
|
||||
private bool _useGpuBackend = true;
|
||||
private ImageTexture? _ageCursorTexture;
|
||||
private TextureRect _waitIndicator = null!;
|
||||
private ImageTexture? _waitIndicatorSheet;
|
||||
@@ -103,6 +105,7 @@ public partial class Main : Godot.Control
|
||||
};
|
||||
AddChild(_screenView); // added first -> draws behind the text/status labels
|
||||
_screenView.SetAnchorsAndOffsetsPreset(LayoutPreset.FullRect);
|
||||
_gpuRenderer = new GpuRetainedRenderer(this);
|
||||
|
||||
// Native ADV wait marker: a tiny independently animated atlas region. Keeping it separate from the
|
||||
// 800x600 software backbuffer avoids recompositing the entire retained scene throughout static waits.
|
||||
@@ -187,6 +190,14 @@ public partial class Main : Godot.Control
|
||||
if (userArgs[i] == "--gfx-log" && i + 1 < userArgs.Length) _gfxLogPath = userArgs[i + 1];
|
||||
if (userArgs[i] == "--timeline-log" && i + 1 < userArgs.Length) _timelineLogPath = userArgs[i + 1];
|
||||
if (userArgs[i] == "--perf-log" && i + 1 < userArgs.Length) _perfLogPath = userArgs[i + 1];
|
||||
if (userArgs[i] == "--render-backend" && i + 1 < userArgs.Length)
|
||||
{
|
||||
if (userArgs[i + 1].Equals("gpu", System.StringComparison.OrdinalIgnoreCase))
|
||||
_useGpuBackend = true;
|
||||
else if (userArgs[i + 1].Equals("software", System.StringComparison.OrdinalIgnoreCase))
|
||||
_useGpuBackend = false;
|
||||
else GD.PushWarning($"unknown --render-backend '{userArgs[i + 1]}'; using gpu");
|
||||
}
|
||||
if (userArgs[i] == "--frames" && i + 1 < userArgs.Length) int.TryParse(userArgs[i + 1], out _seqFrames);
|
||||
if (userArgs[i] == "--sleep-scale" && i + 1 < userArgs.Length) double.TryParse(userArgs[i + 1], out sleepScale);
|
||||
if (userArgs[i] == "--speed" && i + 1 < userArgs.Length) double.TryParse(userArgs[i + 1], out speed);
|
||||
@@ -208,6 +219,7 @@ public partial class Main : Godot.Control
|
||||
|
||||
if (!double.IsFinite(speed) || speed <= 0) speed = 1.0;
|
||||
_clock.Speed = System.Math.Clamp(speed, 0.05, 8.0);
|
||||
GD.Print($"[renderer] retained backend={(_useGpuBackend ? "gpu" : "software")}");
|
||||
|
||||
var table = OpcodeTableJson.Load(Paths.OpcodesJson);
|
||||
// Full op handling everywhere: the provider lets call-script load & run subroutines. Selftest
|
||||
@@ -721,6 +733,7 @@ public partial class Main : Godot.Control
|
||||
public override void _ExitTree()
|
||||
{
|
||||
DumpHistogram(); _host?.Stop(); _timeline?.Dispose(); _locator?.Dispose();
|
||||
_gpuRenderer?.Dispose();
|
||||
if (_perf != null)
|
||||
{
|
||||
_perf.Dispose();
|
||||
@@ -761,6 +774,202 @@ public partial class Main : Godot.Control
|
||||
private readonly System.Collections.Generic.List<SurfaceTextDraw> _surfaceTextSnapshot = new();
|
||||
|
||||
private void Recomposite()
|
||||
{
|
||||
bool gpuSnapshotCaptured = false;
|
||||
if (_useGpuBackend && TryRecompositeGpu(out gpuSnapshotCaptured)) return;
|
||||
_gpuRenderer.Visible = false;
|
||||
_screenView.Visible = true;
|
||||
RecompositeSoftware(gpuSnapshotCaptured ? _visibleSnapshot : null);
|
||||
}
|
||||
|
||||
private bool TryRecompositeGpu(out bool snapshotCaptured)
|
||||
{
|
||||
snapshotCaptured = false;
|
||||
// Preserve the existing high-volume object/timeline diagnostics exactly. They are debugging tools,
|
||||
// not performance workloads, and their software decision strings remain the canonical evidence.
|
||||
if (_gfxLogPath != null || _timeline != null) return false;
|
||||
|
||||
long phase = _perf != null ? PerformanceFrameLog.Timestamp() : 0;
|
||||
long allocationPhase = _perf != null ? PerformanceFrameLog.AllocatedBytes() : 0;
|
||||
if (_host.TrySnapshotScreenTransition(out _)) return false; // P4: whole-screen offscreen targets
|
||||
_vm.Gfx.SnapshotVisibleObjects(_clock.NowMs, _visibleSnapshot);
|
||||
snapshotCaptured = true;
|
||||
_perf?.RecordSnapshotAllocation(PerformanceFrameLog.AllocatedBytes() - allocationPhase);
|
||||
_perf?.RecordSnapshot(PerformanceFrameLog.Timestamp() - phase);
|
||||
|
||||
// Additive LERP-tint has not appeared in the target workloads and needs a dedicated additive shader
|
||||
// variant before leaving the software oracle.
|
||||
if (_visibleSnapshot.Any(v =>
|
||||
v.Blend == BlendKind.Additive && !v.MultiplyTint && v.TintStrength > 0))
|
||||
return false;
|
||||
|
||||
if (_perf != null)
|
||||
{
|
||||
var presentStep = _trace.LatestStep;
|
||||
_perf.RecordPresentationCoordinate(presentStep?.Script ?? "<startup>",
|
||||
presentStep?.Offset ?? -1, presentStep?.Opcode ?? -1);
|
||||
}
|
||||
_perf?.BeginRecomposite(screenTransition: false);
|
||||
phase = _perf != null ? PerformanceFrameLog.Timestamp() : 0;
|
||||
foreach (var label in _surfaceTextLabels) label.Visible = false;
|
||||
_perf?.RecordClear(PerformanceFrameLog.Timestamp() - phase);
|
||||
|
||||
int surfaceTextLabelIndex = 0;
|
||||
_gpuRenderer.BeginFrame();
|
||||
foreach (var v in _visibleSnapshot)
|
||||
{
|
||||
_perf?.RecordObject(v.TimeVarying);
|
||||
var affine = Transform2DMath.Build(v.Transform, v.Rotation).FromLocalOrigin(v.DstX, v.DstY);
|
||||
if (v.RangeTransform is { } rangeTransform) affine = affine.Then(rangeTransform);
|
||||
float opacity = v.Alpha / 255f;
|
||||
var rawObject = _vm.Gfx.TryGet(v.Handle);
|
||||
long resolveStarted = _perf != null ? PerformanceFrameLog.Timestamp() : 0;
|
||||
var texture = rawObject != null
|
||||
? _host.ResolveSurfaceTexture(rawObject.SourceSlot, v.SurfaceResId)
|
||||
: null;
|
||||
_perf?.RecordResolve(PerformanceFrameLog.Timestamp() - resolveStarted);
|
||||
bool movieSurfaceBound = rawObject != null && _host.IsMovieSurfaceBound(rawObject.SourceSlot);
|
||||
|
||||
if (v.SurfaceTransition is { } transition)
|
||||
{
|
||||
_perf?.RecordTransitionLayer();
|
||||
DrawTransitionRangeGpu(_visibleSnapshot, transition);
|
||||
}
|
||||
else if (v.SurfaceResId == 0 && texture == null)
|
||||
{
|
||||
if (v.Blend != BlendKind.Opaque)
|
||||
{
|
||||
int width = v.W > 0 ? v.W : ScreenWidth;
|
||||
int height = v.H > 0 ? v.H : ScreenHeight;
|
||||
float fillOpacity = v.MultiplyTint
|
||||
? opacity
|
||||
: opacity * v.TintStrength / 255f;
|
||||
_perf?.RecordFillLayer();
|
||||
if (_gpuRenderer.DrawFill(width, height, affine, v.Tint, fillOpacity))
|
||||
_perf?.RecordGpuLayer(width, height, affine, ScreenWidth, ScreenHeight,
|
||||
dynamic: false, BlendKind.Alpha);
|
||||
}
|
||||
else _perf?.RecordSkippedLayer();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (texture == null && !movieSurfaceBound)
|
||||
{
|
||||
resolveStarted = _perf != null ? PerformanceFrameLog.Timestamp() : 0;
|
||||
texture = _host.ResolveResIdTexture(v.SurfaceResId);
|
||||
_perf?.RecordResolve(PerformanceFrameLog.Timestamp() - resolveStarted);
|
||||
}
|
||||
if (texture == null) _perf?.RecordSkippedLayer();
|
||||
else
|
||||
{
|
||||
var resolved = texture.Value;
|
||||
bool drawn = _gpuRenderer.DrawTexture(resolved.Image, resolved.AssetId, v.ColorKey,
|
||||
v.SrcX, v.SrcY, v.W, v.H, affine, v.Tint, v.TintStrength,
|
||||
opacity, v.MultiplyTint, resolved.IsDynamic,
|
||||
rawObject?.SourceSlot ?? v.Handle, v.Blend);
|
||||
if (drawn)
|
||||
_perf?.RecordGpuLayer(v.W, v.H, affine, ScreenWidth, ScreenHeight,
|
||||
resolved.IsDynamic, v.Blend);
|
||||
}
|
||||
}
|
||||
|
||||
if (rawObject != null)
|
||||
{
|
||||
_host.SnapshotSurfaceText(rawObject.SourceSlot, _surfaceTextSnapshot);
|
||||
foreach (var surfaceText in _surfaceTextSnapshot)
|
||||
{
|
||||
if (surfaceText.X < v.SrcX || surfaceText.X >= v.SrcX + v.W ||
|
||||
surfaceText.Y < v.SrcY || surfaceText.Y >= v.SrcY + v.H) continue;
|
||||
var textPos = affine.Apply(surfaceText.X - v.SrcX, surfaceText.Y - v.SrcY);
|
||||
var label = GetSurfaceTextLabel(surfaceTextLabelIndex++);
|
||||
label.Position = new Vector2((float)textPos.X, (float)textPos.Y);
|
||||
label.Size = new Vector2(System.Math.Max(1, v.W - (surfaceText.X - v.SrcX)),
|
||||
System.Math.Max(1, v.H - (surfaceText.Y - v.SrcY)));
|
||||
label.Text = surfaceText.Text;
|
||||
ApplyAdvTextStyle(label, surfaceText.Style);
|
||||
label.Visible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var stats = _gpuRenderer.EndFrame();
|
||||
_perf?.RecordGpu(stats.DrawItems, stats.TextureUploads, stats.TextureUploadTicks);
|
||||
_screenView.Visible = false;
|
||||
_gpuRenderer.Visible = true;
|
||||
_perf?.EndRecomposite();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Native type-0 retained range transition: range A has already passed through ordinary z-order;
|
||||
// republish range B at the transition placeholder with progress-scaled source opacity. This mirrors
|
||||
// DrawTransitionRange's software-oracle order without allocating an offscreen CPU surface.
|
||||
private int DrawTransitionRangeGpu(IReadOnlyList<RenderObject> visible, SurfaceTransitionState transition)
|
||||
{
|
||||
int drawn = 0;
|
||||
long end = transition.RangeBStart + transition.RangeBCount;
|
||||
foreach (var source in visible)
|
||||
{
|
||||
if (source.Handle < transition.RangeBStart || source.Handle >= end || source.SurfaceTransition != null)
|
||||
continue;
|
||||
_perf?.RecordObject(source.TimeVarying);
|
||||
var affine = Transform2DMath.Build(source.Transform, source.Rotation)
|
||||
.FromLocalOrigin(source.DstX, source.DstY);
|
||||
if (source.RangeTransform is { } rangeTransform) affine = affine.Then(rangeTransform);
|
||||
float opacity = source.Alpha / 255f * (float)transition.Progress;
|
||||
var rawObject = _vm.Gfx.TryGet(source.Handle);
|
||||
long resolveStarted = _perf != null ? PerformanceFrameLog.Timestamp() : 0;
|
||||
var texture = rawObject != null
|
||||
? _host.ResolveSurfaceTexture(rawObject.SourceSlot, source.SurfaceResId)
|
||||
: null;
|
||||
_perf?.RecordResolve(PerformanceFrameLog.Timestamp() - resolveStarted);
|
||||
bool movieSurfaceBound = rawObject != null && _host.IsMovieSurfaceBound(rawObject.SourceSlot);
|
||||
|
||||
if (source.SurfaceResId == 0 && texture == null)
|
||||
{
|
||||
if (source.Blend == BlendKind.Opaque)
|
||||
{
|
||||
_perf?.RecordSkippedLayer();
|
||||
continue;
|
||||
}
|
||||
int width = source.W > 0 ? source.W : ScreenWidth;
|
||||
int height = source.H > 0 ? source.H : ScreenHeight;
|
||||
_perf?.RecordFillLayer();
|
||||
if (_gpuRenderer.DrawFill(width, height, affine, source.Tint,
|
||||
opacity * source.TintStrength / 255f))
|
||||
{
|
||||
_perf?.RecordGpuLayer(width, height, affine, ScreenWidth, ScreenHeight,
|
||||
dynamic: false, BlendKind.Alpha);
|
||||
drawn++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!movieSurfaceBound && texture == null)
|
||||
{
|
||||
resolveStarted = _perf != null ? PerformanceFrameLog.Timestamp() : 0;
|
||||
texture = _host.ResolveResIdTexture(source.SurfaceResId);
|
||||
_perf?.RecordResolve(PerformanceFrameLog.Timestamp() - resolveStarted);
|
||||
}
|
||||
if (texture == null)
|
||||
{
|
||||
_perf?.RecordSkippedLayer();
|
||||
continue;
|
||||
}
|
||||
var resolved = texture.Value;
|
||||
if (_gpuRenderer.DrawTexture(resolved.Image, resolved.AssetId, source.ColorKey,
|
||||
source.SrcX, source.SrcY, source.W, source.H, affine, source.Tint, source.TintStrength,
|
||||
opacity, source.MultiplyTint, resolved.IsDynamic,
|
||||
rawObject?.SourceSlot ?? source.Handle, source.Blend))
|
||||
{
|
||||
_perf?.RecordGpuLayer(source.W, source.H, affine, ScreenWidth, ScreenHeight,
|
||||
resolved.IsDynamic, source.Blend);
|
||||
drawn++;
|
||||
}
|
||||
}
|
||||
return drawn;
|
||||
}
|
||||
|
||||
private void RecompositeSoftware(IReadOnlyList<RenderObject>? sampledVisible = null)
|
||||
{
|
||||
if (_perf != null)
|
||||
{
|
||||
@@ -796,11 +1005,12 @@ public partial class Main : Godot.Control
|
||||
{
|
||||
phase = _perf != null ? PerformanceFrameLog.Timestamp() : 0;
|
||||
allocationPhase = _perf != null ? PerformanceFrameLog.AllocatedBytes() : 0;
|
||||
_vm.Gfx.SnapshotVisibleObjects(_clock.NowMs, _visibleSnapshot); // synchronized objects + ranges
|
||||
if (sampledVisible == null)
|
||||
_vm.Gfx.SnapshotVisibleObjects(_clock.NowMs, _visibleSnapshot); // synchronized objects + ranges
|
||||
_perf?.RecordSnapshotAllocation(PerformanceFrameLog.AllocatedBytes() - allocationPhase);
|
||||
_perf?.RecordSnapshot(PerformanceFrameLog.Timestamp() - phase);
|
||||
allocationPhase = _perf != null ? PerformanceFrameLog.AllocatedBytes() : 0;
|
||||
CompositeVisibleObjects(_visibleSnapshot, 1f, ref surfaceTextLabelIndex, decisions, true);
|
||||
CompositeVisibleObjects(sampledVisible ?? _visibleSnapshot, 1f, ref surfaceTextLabelIndex, decisions, true);
|
||||
_perf?.RecordCompositeAllocation(PerformanceFrameLog.AllocatedBytes() - allocationPhase);
|
||||
}
|
||||
phase = _perf != null ? PerformanceFrameLog.Timestamp() : 0;
|
||||
|
||||
@@ -36,7 +36,8 @@ public sealed class PerformanceFrameLog : IDisposable
|
||||
"clear_ms,snapshot_ms,resolve_ms,source_prep_ms,raster_ms,set_data_ms,texture_update_ms,ui_ms," +
|
||||
"allocated_bytes,recompose_allocated_bytes,snapshot_allocated_bytes," +
|
||||
"composite_allocated_bytes,source_prep_allocated_bytes,set_data_allocated_bytes," +
|
||||
"ui_allocated_bytes,gen0,gen1,gen2,recomposited,screen_transition," +
|
||||
"ui_allocated_bytes,gen0,gen1,gen2,recomposited,render_backend,screen_transition," +
|
||||
"gpu_draw_items,gpu_texture_uploads,gpu_texture_upload_ms," +
|
||||
"present_host_request,present_screen_transition,present_retained_mutation," +
|
||||
"present_continuous_channel,present_discrete_cell,object_visits," +
|
||||
"time_varying_objects,draw_layers,fill_layers,transition_layers,skipped_layers," +
|
||||
@@ -95,6 +96,13 @@ public sealed class PerformanceFrameLog : IDisposable
|
||||
public void RecordUiAllocation(long bytes) => _current.UiAllocatedBytes += Math.Max(0, bytes);
|
||||
public void RecordSetData(long ticks) => _current.SetDataTicks += ticks;
|
||||
public void RecordTextureUpdate(long ticks) => _current.TextureUpdateTicks += ticks;
|
||||
public void RecordGpu(int drawItems, int textureUploads, long textureUploadTicks)
|
||||
{
|
||||
_current.GpuBackend = true;
|
||||
_current.GpuDrawItems += drawItems;
|
||||
_current.GpuTextureUploads += textureUploads;
|
||||
_current.GpuTextureUploadTicks += textureUploadTicks;
|
||||
}
|
||||
|
||||
public void BeginRecomposite(bool screenTransition)
|
||||
{
|
||||
@@ -131,8 +139,22 @@ public sealed class PerformanceFrameLog : IDisposable
|
||||
int destinationWidth, int destinationHeight, bool dynamic,
|
||||
BlendKind blend, long ticks)
|
||||
{
|
||||
_current.DrawLayers++;
|
||||
_current.RasterTicks += ticks;
|
||||
RecordLayer(sourceWidth, sourceHeight, localToDest, destinationWidth, destinationHeight,
|
||||
dynamic, blend);
|
||||
}
|
||||
|
||||
public void RecordGpuLayer(int sourceWidth, int sourceHeight, Affine2D localToDest,
|
||||
int destinationWidth, int destinationHeight, bool dynamic,
|
||||
BlendKind blend)
|
||||
=> RecordLayer(sourceWidth, sourceHeight, localToDest, destinationWidth, destinationHeight,
|
||||
dynamic, blend);
|
||||
|
||||
private void RecordLayer(int sourceWidth, int sourceHeight, Affine2D localToDest,
|
||||
int destinationWidth, int destinationHeight, bool dynamic,
|
||||
BlendKind blend)
|
||||
{
|
||||
_current.DrawLayers++;
|
||||
_current.SourcePixels += Math.Max(0L, (long)sourceWidth * sourceHeight);
|
||||
long candidates = EstimateCandidatePixels(localToDest, sourceWidth, sourceHeight,
|
||||
destinationWidth, destinationHeight);
|
||||
@@ -228,7 +250,9 @@ public sealed class PerformanceFrameLog : IDisposable
|
||||
Append(b, f.SnapshotAllocatedBytes); Append(b, f.CompositeAllocatedBytes);
|
||||
Append(b, f.SourcePrepAllocatedBytes); Append(b, f.SetDataAllocatedBytes);
|
||||
Append(b, f.UiAllocatedBytes); Append(b, f.Gen0); Append(b, f.Gen1); Append(b, f.Gen2);
|
||||
Append(b, f.Recomposited ? 1 : 0); Append(b, f.ScreenTransition ? 1 : 0);
|
||||
Append(b, f.Recomposited ? 1 : 0); Append(b, f.GpuBackend ? 1 : 0);
|
||||
Append(b, f.ScreenTransition ? 1 : 0);
|
||||
Append(b, f.GpuDrawItems); Append(b, f.GpuTextureUploads); AppendTicks(b, f.GpuTextureUploadTicks);
|
||||
Append(b, f.PresentHostRequest ? 1 : 0); Append(b, f.PresentScreenTransition ? 1 : 0);
|
||||
Append(b, f.PresentRetainedMutation ? 1 : 0); Append(b, f.PresentContinuousChannel ? 1 : 0);
|
||||
Append(b, f.PresentDiscreteCell ? 1 : 0);
|
||||
@@ -286,12 +310,13 @@ public sealed class PerformanceFrameLog : IDisposable
|
||||
public string Script = "<unknown>";
|
||||
public string PresentScript = "<none>";
|
||||
public int PresentOffset = -1, PresentOpcode = -1;
|
||||
public bool Recomposited, ScreenTransition;
|
||||
public bool Recomposited, GpuBackend, ScreenTransition;
|
||||
public bool PresentHostRequest, PresentScreenTransition, PresentRetainedMutation;
|
||||
public bool PresentContinuousChannel, PresentDiscreteCell;
|
||||
public long ObjectVisits, TimeVaryingObjects, DrawLayers, FillLayers, TransitionLayers, SkippedLayers;
|
||||
public long IntegerLayers, FractionalTranslationLayers, AxisAlignedScaleLayers;
|
||||
public long GeneralAffineLayers, AffineLayers, SingularLayers, DynamicLayers;
|
||||
public long OpaqueLayers, AlphaLayers, AdditiveLayers, SourcePixels, CandidatePixels, FullScreenLayers;
|
||||
public long GpuDrawItems, GpuTextureUploads, GpuTextureUploadTicks;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user