diff --git a/Directory.Build.props b/Directory.Build.props
index 914a541..8b31436 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -8,7 +8,7 @@
1.0.0
- 0.1.0-preview.4
+ 0.1.0-preview.6
$(MSBuildThisFileDirectory)..\Sdcb.SimdOnnx\src\Sdcb.SimdOnnx\Sdcb.SimdOnnx.csproj
diff --git a/README.md b/README.md
index 883ba42..87c2f9e 100644
--- a/README.md
+++ b/README.md
@@ -118,7 +118,8 @@ The ONNX engine (protobuf reader, graph compiler, CPU SIMD kernels, Vulkan / Met
- `src/Sdcb.SimdGcdrNet/Extensions` — engine extensions (`GcdrExtensions`): fused operators
(`UpHeadOp`, `ChannelShiftOp`) with their CPU kernels (Vector / AVX-512 / NEON / WASM SIMD) and
graph passes
-- `src/Sdcb.SimdGcdrNet/Kernels` — `PixelOps` (interleaved pixels ↔ tensors, padding)
+- `src/Sdcb.SimdGcdrNet/Kernels` — `PixelOps` (glue between the two nets — division correction / concat — and the
+ Metal pipeline's BGR24 rows; pixels ↔ tensors go through `Sdcb.SimdOnnx.Imaging.ImageTensor`)
- `src/Sdcb.SimdGcdrNet/Backends/Vulkan` — `VkPipeline2` (fused gc + dr pipeline on SimdOnnx's
Vulkan backend); `Shaders` holds the GCDRNet GLSL kernels, `tools/build-shaders.bat` recompiles
`.comp` → `.spv` (committed; dev-time only, not part of `dotnet build`)
diff --git a/src/Sdcb.SimdGcdrNet/GcdrEnhancer.cs b/src/Sdcb.SimdGcdrNet/GcdrEnhancer.cs
index 4028547..d5e4191 100644
--- a/src/Sdcb.SimdGcdrNet/GcdrEnhancer.cs
+++ b/src/Sdcb.SimdGcdrNet/GcdrEnhancer.cs
@@ -3,6 +3,8 @@
using Sdcb.SimdGcdrNet.Extensions;
using Sdcb.SimdGcdrNet.Kernels;
using Sdcb.SimdOnnx.Engine;
+using Sdcb.SimdOnnx.Imaging;
+using Sdcb.SimdOnnx.Kernels;
using Sdcb.SimdOnnx.Onnx;
using VkBackend = Sdcb.SimdOnnx.Backends.Vulkan.VkBackend;
using VkDeviceLostException = Sdcb.SimdOnnx.Backends.Vulkan.VkDeviceLostException;
@@ -99,6 +101,8 @@ public static GcdrEnhancer FromStreams(Stream gcnetOnnx, Stream drnetOnnx,
// pipeline sessions keep I/O in plan layout: the glue kernels below read/write
// channels-last directly, so the plan has no boundary relayouts
+ // the network sees B,G,R in [0, 1]
+ private static readonly PixelNormalization s_unit = PixelNormalization.Unit();
private static readonly CompileOptions CpuPlan = new(CompileOptions.DefaultNhwc, PhysicalIo: true, Extensions: GcdrExtensions.Instance);
private static bool CpuNhwc => CpuPlan.Nhwc;
@@ -178,7 +182,6 @@ public void Enhance(ReadOnlySpan src, int width, int height, int srcStride
Span dst, int dstStride, GcdrPixelFormat format = GcdrPixelFormat.Bgr24)
{
int bpp = format.BytesPerPixel();
- bool swap = format.SwapRB();
ArgumentOutOfRangeException.ThrowIfLessThan(width, 1);
ArgumentOutOfRangeException.ThrowIfLessThan(height, 1);
ArgumentOutOfRangeException.ThrowIfLessThan(srcStride, width * bpp);
@@ -235,8 +238,9 @@ public void Enhance(ReadOnlySpan src, int width, int height, int srcStride
// stage 0: fill GC input — BGR u8 → float /255, top/left replicate pad
Span x = gc.Input;
- if (CpuNhwc) PixelOps.BgrToNhwcPad(src, width, height, srcStride, x, h, w, ph, pw, bpp, swap);
- else PixelOps.BgrToNchwPad(src, width, height, srcStride, x, h, w, ph, pw, bpp, swap);
+ TensorLayout layout = CpuNhwc ? TensorLayout.Nhwc : TensorLayout.Nchw;
+ ImageTensor.FromPixels(src, width, height, srcStride, (PixelFormat)format, x, layout, s_unit,
+ w, h, padLeft: pw, padTop: ph, threads: Avx2Kernels.Threads);
// stage 1: GC-Net → shading map
gc.Run();
@@ -259,10 +263,9 @@ public void Enhance(ReadOnlySpan src, int width, int height, int srcStride
dr.Run();
// stage 4: crop pad → u8 in the caller's format (alpha from src)
- if (CpuNhwc)
- PixelOps.NhwcToBgrCrop(dr.Output, h, w, ph, pw, dst, width, height, dstStride, bpp, swap, src, srcStride);
- else
- PixelOps.NchwToBgrCrop(dr.Output, h, w, ph, pw, dst, width, height, dstStride, bpp, swap, src, srcStride);
+ ImageTensor.ToPixels(dr.Output, layout, w, h, dst, width, height, dstStride, (PixelFormat)format,
+ cropLeft: pw, cropTop: ph, alphaSource: bpp == 4 ? src : default, alphaStride: srcStride,
+ threads: Avx2Kernels.Threads);
}
finally { cpuPool.Return(worker); }
}
diff --git a/src/Sdcb.SimdGcdrNet/Kernels/PixelOps.cs b/src/Sdcb.SimdGcdrNet/Kernels/PixelOps.cs
index 8ff88dd..55fed60 100644
--- a/src/Sdcb.SimdGcdrNet/Kernels/PixelOps.cs
+++ b/src/Sdcb.SimdGcdrNet/Kernels/PixelOps.cs
@@ -5,38 +5,11 @@
namespace Sdcb.SimdGcdrNet.Kernels;
///
-/// Pipeline glue kernels: BGR↔NCHW float conversion, replicate padding,
-/// division correction, output crop. All vectorized over the innermost dim.
+/// Pipeline glue kernels between the two nets (division correction, concat) and the Metal
+/// pipeline's BGR24 rows; pixels ↔ tensors go through Sdcb.SimdOnnx.Imaging.ImageTensor.
///
internal static class PixelOps
{
- ///
- /// Interleaved u8 HWC ( 3 or 4 bytes per pixel, B,G,R order unless
- /// ) → float NCHW /255 with top/left replicate padding.
- /// dst layout: [3][h][w] float, channels B,G,R.
- ///
- public static void BgrToNchwPad(ReadOnlySpan src, int width, int height, int srcStride,
- Span dst, int h, int w, int padTop, int padLeft, int bpp = 3, bool swapRB = false)
- {
- int hw = h * w;
- int ob = swapRB ? 2 : 0, or = 2 - ob;
- for (int y = 0; y < h; y++)
- {
- int sy = Math.Max(0, y - padTop);
- ReadOnlySpan row = src.Slice(sy * srcStride, width * bpp);
- Span d0 = dst.Slice(y * w, w);
- Span d1 = dst.Slice(hw + y * w, w);
- Span d2 = dst.Slice(2 * hw + y * w, w);
- for (int x = 0; x < w; x++)
- {
- int sx = Math.Clamp(x - padLeft, 0, width - 1) * bpp;
- d0[x] = row[sx + ob] / 255f;
- d1[x] = row[sx + 1] / 255f;
- d2[x] = row[sx + or] / 255f;
- }
- }
- }
-
///
/// corrected = clamp(x / max(shadow, 1e-6), 0, 1) for all 3·hw elements.
///
@@ -58,37 +31,6 @@ public static void DivClampInto(ReadOnlySpan x, ReadOnlySpan shado
dst[i] = Math.Clamp(x[i] / Math.Max(shadow[i], 1e-6f), 0f, 1f);
}
- ///
- /// float NCHW [3,h,w] (B,G,R) → interleaved u8 HWC, cropping (padTop,padLeft) margins.
- /// 4: the alpha byte of each pixel is copied from
- /// (same layout, row stride ).
- ///
- public static void NchwToBgrCrop(ReadOnlySpan src, int h, int w,
- int padTop, int padLeft, Span dst, int width, int height, int dstStride,
- int bpp = 3, bool swapRB = false, ReadOnlySpan alphaSrc = default, int alphaStride = 0)
- {
- int hw = h * w;
- int ob = swapRB ? 2 : 0, or = 2 - ob;
- for (int y = 0; y < height; y++)
- {
- int sy = y + padTop;
- ReadOnlySpan r0 = src.Slice(sy * w, w);
- ReadOnlySpan r1 = src.Slice(hw + sy * w, w);
- ReadOnlySpan r2 = src.Slice(2 * hw + sy * w, w);
- Span drow = dst.Slice(y * dstStride, width * bpp);
- ReadOnlySpan arow = bpp == 4 ? alphaSrc.Slice(y * alphaStride, width * 4) : default;
- for (int x = 0; x < width; x++)
- {
- int sx = x + padLeft, d = x * bpp;
- drow[d + ob] = ToU8(r0[sx]);
- drow[d + 1] = ToU8(r1[sx]);
- drow[d + or] = ToU8(r2[sx]);
- if (bpp == 4) drow[d + 3] = arow[d + 3];
- }
- }
- static byte ToU8(float v) => (byte)Math.Clamp((int)(v * 255f + 0.5f), 0, 255);
- }
-
// The NHWC glue below runs once per image on the calling thread between the two nets;
// single-threaded it was ~90 ms of a ~630 ms 3.7 MP frame on 32 threads. Every element is
// independent, so row bands go wide (same per-element ops, same bits).
@@ -102,39 +44,6 @@ private static void Rows(int rows, long elemsPerRow, Action body)
Parallel.For(0, bands, PO, b => body((int)((long)rows * b / bands), (int)((long)rows * (b + 1) / bands)));
}
- ///
- /// Interleaved u8 HWC ( 3 or 4 bytes per pixel, B,G,R order unless
- /// ) → float NHWC [h][w][3] (B,G,R) /255 with top/left replicate padding.
- ///
- public static unsafe void BgrToNhwcPad(ReadOnlySpan src, int width, int height, int srcStride,
- Span dst, int h, int w, int padTop, int padLeft, int bpp = 3, bool swapRB = false)
- {
- if (src.Length < (long)(height - 1) * srcStride + width * bpp || dst.Length < (long)h * w * 3)
- throw new ArgumentException("pad buffer sizes");
- int ob = swapRB ? 2 : 0, or = 2 - ob;
- fixed (byte* sp0 = src)
- fixed (float* dp0 = dst)
- {
- byte* sp = sp0;
- float* dp = dp0;
- Rows(h, (long)w * 3, (y0, y1) =>
- {
- for (int y = y0; y < y1; y++)
- {
- byte* row = sp + (long)Math.Max(0, y - padTop) * srcStride;
- float* d = dp + (long)y * w * 3;
- for (int x = 0; x < w; x++)
- {
- int sx = Math.Clamp(x - padLeft, 0, width - 1) * bpp;
- d[x * 3] = row[sx + ob] / 255f;
- d[x * 3 + 1] = row[sx + 1] / 255f;
- d[x * 3 + 2] = row[sx + or] / 255f;
- }
- }
- });
- }
- }
-
///
/// NHWC div6: dst[px,0..2] = x[px], dst[px,3..5] = clamp(x / max(shadow,1e-6), 0, 1).
/// x / shadow are [hw][3]; dst is [hw][6].
@@ -162,53 +71,6 @@ public static unsafe void DivConcatNhwc(ReadOnlySpan x, ReadOnlySpan
- /// float NHWC [h][w][3] (B,G,R) → interleaved u8 HWC, cropping (padTop,padLeft) margins.
- /// 4: the alpha byte of each pixel is copied from
- /// (same layout, row stride ;
- /// may alias ).
- ///
- public static unsafe void NhwcToBgrCrop(ReadOnlySpan src, int h, int w,
- int padTop, int padLeft, Span dst, int width, int height, int dstStride,
- int bpp = 3, bool swapRB = false, ReadOnlySpan alphaSrc = default, int alphaStride = 0)
- {
- if (src.Length < (long)h * w * 3 || dst.Length < (long)(height - 1) * dstStride + width * bpp)
- throw new ArgumentException("crop buffer sizes");
- if (bpp == 4 && alphaSrc.Length < (long)(height - 1) * alphaStride + width * 4)
- throw new ArgumentException("alpha source size");
- int ob = swapRB ? 2 : 0, or = 2 - ob;
- fixed (float* sp0 = src)
- fixed (byte* dp0 = dst, ap0 = alphaSrc)
- {
- float* sp = sp0;
- byte* dp = dp0, ap = ap0;
- Rows(height, (long)width * 3, (y0, y1) =>
- {
- for (int y = y0; y < y1; y++)
- {
- float* r = sp + ((long)(y + padTop) * w + padLeft) * 3;
- byte* drow = dp + (long)y * dstStride;
- if (bpp == 3 && !swapRB)
- {
- for (int i = 0; i < width * 3; i++)
- drow[i] = ToU8(r[i]);
- continue;
- }
- byte* arow = ap + (long)y * alphaStride;
- for (int x = 0; x < width; x++)
- {
- byte* d = drow + x * bpp;
- d[ob] = ToU8(r[x * 3]);
- d[1] = ToU8(r[x * 3 + 1]);
- d[or] = ToU8(r[x * 3 + 2]);
- if (bpp == 4) d[3] = arow[x * 4 + 3];
- }
- }
- });
- }
- static byte ToU8(float v) => (byte)Math.Clamp((int)(v * 255f + 0.5f), 0, 255);
- }
-
/// One row of interleaved u8 pixels in → tightly packed BGR24.
public static void RowToBgr24(ReadOnlySpan src, Span bgr, int width, GcdrPixelFormat format)
{