// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Columns; using BenchmarkDotNet.Configs; using BenchmarkDotNet.Jobs; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.FilmGrain; namespace SixLabors.ImageSharp.Benchmarks.Codecs.Heif; /// /// Measures AV1 film-grain application across full-HD-equivalent 4:2:0 component planes. /// [Config(typeof(Configuration))] [MemoryDiagnoser(displayGenColumns: false)] [GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] [CategoriesColumn] public class Av1FilmGrainBenchmarks { /// /// The aligned full-HD luma width. /// private const int Width = 1920; /// /// The aligned full-HD luma height. /// private const int Height = 1088; /// /// The luma width and height of one selected grain block. /// private const int BlockSize = 32; /// /// The 4:2:0 chroma-plane width. /// private const int ChromaWidth = Width / 2; /// /// The 4:2:0 chroma-plane height. /// private const int ChromaHeight = Height / 2; /// /// The 4:2:0 chroma width and height of one selected grain block. /// private const int ChromaBlockSize = BlockSize / 2; /// /// The deterministic eight-bit luma plane. /// private readonly byte[] luma8 = new byte[Width * Height]; /// /// The deterministic eight-bit first chroma plane. /// private readonly byte[] cb8 = new byte[ChromaWidth * ChromaHeight]; /// /// The deterministic eight-bit second chroma plane. /// private readonly byte[] cr8 = new byte[ChromaWidth * ChromaHeight]; /// /// The deterministic twelve-bit luma plane. /// private readonly ushort[] luma12 = new ushort[Width * Height]; /// /// The deterministic twelve-bit first chroma plane. /// private readonly ushort[] cb12 = new ushort[ChromaWidth * ChromaHeight]; /// /// The deterministic twelve-bit second chroma plane. /// private readonly ushort[] cr12 = new ushort[ChromaWidth * ChromaHeight]; /// /// The expanded luma scaling function. /// private readonly int[] scalingY = new int[256]; /// /// The expanded first chroma scaling function. /// private readonly int[] scalingCb = new int[256]; /// /// The expanded second chroma scaling function. /// private readonly int[] scalingCr = new int[256]; /// /// The selected luma grain block. /// private readonly int[] lumaGrain = new int[BlockSize * BlockSize]; /// /// The selected first chroma grain block. /// private readonly int[] cbGrain = new int[ChromaBlockSize * ChromaBlockSize]; /// /// The selected second chroma grain block. /// private readonly int[] crGrain = new int[ChromaBlockSize * ChromaBlockSize]; /// /// The active grain parameters shared by both measured sample precisions. /// private readonly ObuFilmGrainParameters parameters = new() { NumYPoints = 2, ChromaScalingFromLuma = true, GrainScalingMinus8 = 3 }; /// /// Populates deterministic source planes and grain blocks outside the measured traversal. /// [GlobalSetup] public void Setup() { for (int index = 0; index < this.luma8.Length; index++) { int value = ((index * 37) + 113) & byte.MaxValue; this.luma8[index] = (byte)value; this.luma12[index] = (ushort)(value << 4); } for (int index = 0; index < this.cb8.Length; index++) { int cb = ((index * 53) + 97) & byte.MaxValue; int cr = ((index * 71) + 41) & byte.MaxValue; this.cb8[index] = (byte)cb; this.cr8[index] = (byte)cr; this.cb12[index] = (ushort)(cb << 4); this.cr12[index] = (ushort)(cr << 4); } for (int index = 0; index < this.lumaGrain.Length; index++) { this.lumaGrain[index] = ((index * 29) & byte.MaxValue) - 128; } for (int index = 0; index < this.cbGrain.Length; index++) { this.cbGrain[index] = ((index * 43) & byte.MaxValue) - 128; this.crGrain[index] = ((index * 61) & byte.MaxValue) - 128; } // A zero scaling function keeps every invocation's source planes stable. The measured code still performs the // production lookup, interpolation, grain multiplication, clipping, and native sample packing for every lane. this.scalingY.AsSpan().Clear(); this.scalingCb.AsSpan().Clear(); this.scalingCr.AsSpan().Clear(); } /// /// Applies eight-bit grain blocks across full-HD-equivalent 4:2:0 planes. /// /// The final luma sample, keeping the output observable. [Benchmark] [BenchmarkCategory("8Bit")] public byte Apply8Bit() { for (int y = 0; y < Height; y += BlockSize) { for (int x = 0; x < Width; x += BlockSize) { int lumaOffset = (y * Width) + x; int chromaOffset = ((y / 2) * ChromaWidth) + (x / 2); Av1FilmGrainNoise.Apply( this.parameters, this.scalingY, this.scalingCb, this.scalingCr, this.luma8.AsSpan(lumaOffset), this.cb8.AsSpan(chromaOffset), this.cr8.AsSpan(chromaOffset), Width, ChromaWidth, this.lumaGrain, this.cbGrain, this.crGrain, BlockSize, ChromaBlockSize, BlockSize / 2, BlockSize / 2, 8, 1, 1, isMonochrome: false, isIdentityMatrix: false); } } return this.luma8[^1]; } /// /// Applies twelve-bit grain blocks across full-HD-equivalent 4:2:0 planes. /// /// The final luma sample, keeping the output observable. [Benchmark] [BenchmarkCategory("12Bit")] public ushort Apply12Bit() { for (int y = 0; y < Height; y += BlockSize) { for (int x = 0; x < Width; x += BlockSize) { int lumaOffset = (y * Width) + x; int chromaOffset = ((y / 2) * ChromaWidth) + (x / 2); Av1FilmGrainNoise.Apply( this.parameters, this.scalingY, this.scalingCb, this.scalingCr, this.luma12.AsSpan(lumaOffset), this.cb12.AsSpan(chromaOffset), this.cr12.AsSpan(chromaOffset), Width, ChromaWidth, this.lumaGrain, this.cbGrain, this.crGrain, BlockSize, ChromaBlockSize, BlockSize / 2, BlockSize / 2, 12, 1, 1, isMonochrome: false, isIdentityMatrix: false); } } return this.luma12[^1]; } /// /// Configures production-process measurements for hardware, no-AVX, and scalar grain application. /// public sealed class Configuration : ManualConfig { /// /// Initializes a new instance of the class. /// public Configuration() { this.AddJob(Job.ShortRun.WithId("Hardware").AsBaseline()); this.AddJob( Job.ShortRun .WithId("NoAvx") .WithEnvironmentVariable("DOTNET_EnableAVX", "0")); this.AddJob( Job.ShortRun .WithId("Scalar") .WithEnvironmentVariable("DOTNET_EnableHWIntrinsic", "0")); } } }