// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
using SixLabors.ImageSharp.Formats.Heif.Av1;
using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Heif;
///
/// Owns a benchmark-only libaom encoder through a C adapter compiled against the reference's actual headers.
///
internal sealed unsafe partial class LibaomBenchmarkEncoder : SafeHandleZeroOrMinusOneIsInvalid
{
///
/// The benchmark adapter's platform-independent library name.
///
private const string LibraryName = "imagesharp_aom_benchmark";
///
/// Initializes an empty handle for the native create call's generated marshaller.
///
public LibaomBenchmarkEncoder()
: base(ownsHandle: true)
{
}
///
/// Opens one native sequence with a single coding thread, no lookahead, and the requested quality and speed.
///
public static LibaomBenchmarkEncoder Open(int width, int height, int quality, int speed)
{
CheckStatus(Create((uint)width, (uint)height, (uint)quality, speed, out LibaomBenchmarkEncoder encoder));
return encoder;
}
///
/// Encodes the converted source planes and consumes every output packet before those bytes can be invalidated.
///
public void Encode(Av1EncoderFrame frame, long frameIndex, Stream output)
{
Buffer2DRegion y = frame.View.GetPlane(Av1Plane.Y);
Buffer2DRegion u = frame.View.GetPlane(Av1Plane.U);
Buffer2DRegion v = frame.View.GetPlane(Av1Plane.V);
fixed (byte* yPointer = y.DangerousGetRowSpan(0), uPointer = u.DangerousGetRowSpan(0), vPointer = v.DangerousGetRowSpan(0))
{
// The native call is synchronous. Its input descriptors borrow these pinned rows only until
// EncodeFrame returns; the encoder owns any retained reference and lookahead storage itself.
CheckStatus(EncodeFrame(this, yPointer, uPointer, vPointer, y.Stride, u.Stride, frameIndex));
}
this.WritePackets(output);
}
///
/// Finishes the sequence and writes any remaining coded packets.
///
public void Finish(Stream output)
{
do
{
CheckStatus(Flush(this));
}
while (this.WritePackets(output));
}
///
protected override bool ReleaseHandle() => Destroy(this.handle) == 0;
///
/// Writes borrowed packet memory before the next call into the native codec invalidates it.
///
private bool WritePackets(Stream output)
{
bool wrotePacket = false;
while (NextPacket(this, out byte* data, out nuint length) != 0)
{
// Packet lengths are bounded by the benchmark's image dimensions. Stream.Write consumes the
// borrowed bytes synchronously, without retaining a native pointer or creating a managed array.
output.Write(new ReadOnlySpan(data, (int)length));
wrotePacket = true;
}
return wrotePacket;
}
///
/// Converts a native codec error into a managed benchmark failure instead of accepting invalid timing data.
///
private static void CheckStatus(int status)
{
if (status != 0)
{
throw new InvalidOperationException(Marshal.PtrToStringUTF8(ErrorString(status)));
}
}
///
/// Creates an owned opaque context; no libaom structure layout crosses the managed boundary.
///
[LibraryImport(LibraryName, EntryPoint = "benchmark_create")]
private static partial int Create(uint width, uint height, uint quality, int speed, out LibaomBenchmarkEncoder encoder);
///
/// Borrows three pinned planes for one synchronous native encode call.
///
[LibraryImport(LibraryName, EntryPoint = "benchmark_encode")]
private static partial int EncodeFrame(LibaomBenchmarkEncoder encoder, byte* y, byte* u, byte* v, int yStride, int uvStride, long frameIndex);
///
/// Signals the end of the native sequence.
///
[LibraryImport(LibraryName, EntryPoint = "benchmark_flush")]
private static partial int Flush(LibaomBenchmarkEncoder encoder);
///
/// Returns borrowed native packet storage and its pointer-sized length.
///
[LibraryImport(LibraryName, EntryPoint = "benchmark_next_packet")]
private static partial int NextPacket(LibaomBenchmarkEncoder encoder, out byte* data, out nuint length);
///
/// Destroys the context through the same native library that allocated it.
///
[LibraryImport(LibraryName, EntryPoint = "benchmark_destroy")]
private static partial int Destroy(nint encoder);
///
/// Returns a static UTF-8 error message owned by libaom.
///
[LibraryImport(LibraryName, EntryPoint = "benchmark_error_string")]
private static partial nint ErrorString(int status);
}