From 7c637eec3e99d179bd7edad8a73ba8f9af42a88a Mon Sep 17 00:00:00 2001 From: Renjaya Raga Zenta Date: Thu, 31 Jul 2025 00:26:58 +0700 Subject: [PATCH] hacks: add NetVips hacks to minimize string allocation --- src/Oh.My.Stitcher/NetVips/GObjectHacks.cs | 38 ++++++++++++++++++++ src/Oh.My.Stitcher/NetVips/OperationHacks.cs | 34 ++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 src/Oh.My.Stitcher/NetVips/GObjectHacks.cs create mode 100644 src/Oh.My.Stitcher/NetVips/OperationHacks.cs diff --git a/src/Oh.My.Stitcher/NetVips/GObjectHacks.cs b/src/Oh.My.Stitcher/NetVips/GObjectHacks.cs new file mode 100644 index 0000000..cb363c9 --- /dev/null +++ b/src/Oh.My.Stitcher/NetVips/GObjectHacks.cs @@ -0,0 +1,38 @@ +using System.Runtime.InteropServices; +using System.Security; +using NetVips.Internal; +using GObjectManaged = NetVips.GObject; + +namespace Oh.My.Stitcher.NetVips; + + +internal static unsafe class GObjectHacks +{ + internal static void SetProperty(GObjectManaged gObject, string name, ReadOnlySpan value) + { + var gvStruct = new GValue.Struct(); + + try + { + GValue.Init(ref gvStruct, 16 << 2); + fixed( byte* pValue = value ) + { + GValueHacks.SetString(ref gvStruct, pValue); + } + + GObject.SetProperty(gObject, name, in gvStruct); + } + finally + { + GValue.Unset(ref gvStruct); + } + } +} + +internal static unsafe class GValueHacks +{ + [SuppressUnmanagedCodeSecurity] + [DllImport("libgobject-2.0.so.0", CallingConvention = CallingConvention.Cdecl, + EntryPoint = "g_value_set_string")] + internal static extern void SetString(ref GValue.Struct value, byte* vString); +} diff --git a/src/Oh.My.Stitcher/NetVips/OperationHacks.cs b/src/Oh.My.Stitcher/NetVips/OperationHacks.cs new file mode 100644 index 0000000..f73894a --- /dev/null +++ b/src/Oh.My.Stitcher/NetVips/OperationHacks.cs @@ -0,0 +1,34 @@ +using NetVips; +using NetVips.Internal; +using VipsObject = NetVips.Internal.VipsObject; + +namespace Oh.My.Stitcher.NetVips; + +public static class OperationHacks +{ + public static object Call(string operationName, ReadOnlySpan arg) + { + var intro = Introspect.Get(operationName); + if (intro.RequiredInput.Count != 1) + throw new ArgumentException($"unable to call {operationName}"); + + nint vop; + using( var op = Operation.NewFromName(operationName) ) + { + Introspect.Argument requiredArg = intro.RequiredInput[0]; + GObjectHacks.SetProperty(op, requiredArg.Name, arg); + vop = VipsOperation.Build(op); + if (vop == IntPtr.Zero) + { + VipsObject.UnrefOutputs(op); + throw new VipsException($"unable to call {operationName}"); + } + } + using (var op = new Operation(vop)) + { + object? result = op.Get(intro.RequiredOutput[0].Name); + VipsObject.UnrefOutputs(op); + return result; + } + } +}