Adopt GPU retained renderer

This commit is contained in:
gamer147
2026-07-22 15:37:42 -04:00
parent 4ea352e45d
commit 197adf2e1a
7 changed files with 626 additions and 19 deletions

View 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();
}
}

View File

@@ -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;

View File

@@ -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;
}
}