Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<SdcbSimdGcdrNetModelsVersion Condition="'$(SdcbSimdGcdrNetModelsVersion)' == ''">1.0.0</SdcbSimdGcdrNetModelsVersion>
<!-- Engine package (exact pin: the library builds against engine internals). UseLocalSimdOnnx=true
switches to ../Sdcb.SimdOnnx as a project reference for side-by-side development. -->
<SdcbSimdOnnxVersion Condition="'$(SdcbSimdOnnxVersion)' == ''">0.1.0-preview.4</SdcbSimdOnnxVersion>
<SdcbSimdOnnxVersion Condition="'$(SdcbSimdOnnxVersion)' == ''">0.1.0-preview.6</SdcbSimdOnnxVersion>
<SimdOnnxProject Condition="'$(SimdOnnxProject)' == ''">$(MSBuildThisFileDirectory)..\Sdcb.SimdOnnx\src\Sdcb.SimdOnnx\Sdcb.SimdOnnx.csproj</SimdOnnxProject>
<!-- a stable package pinned to an engine preview (NU5104): accepted while the engine is a preview on
the ProGet test feed; a nuget.org release of this package needs the engine released first -->
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
Expand Down
17 changes: 10 additions & 7 deletions src/Sdcb.SimdGcdrNet/GcdrEnhancer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -178,7 +182,6 @@ public void Enhance(ReadOnlySpan<byte> src, int width, int height, int srcStride
Span<byte> 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);
Expand Down Expand Up @@ -235,8 +238,9 @@ public void Enhance(ReadOnlySpan<byte> src, int width, int height, int srcStride

// stage 0: fill GC input — BGR u8 → float /255, top/left replicate pad
Span<float> 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();
Expand All @@ -259,10 +263,9 @@ public void Enhance(ReadOnlySpan<byte> 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); }
}
Expand Down
142 changes: 2 additions & 140 deletions src/Sdcb.SimdGcdrNet/Kernels/PixelOps.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,11 @@
namespace Sdcb.SimdGcdrNet.Kernels;

/// <summary>
/// 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.
/// </summary>
internal static class PixelOps
{
/// <summary>
/// Interleaved u8 HWC (<paramref name="bpp"/> 3 or 4 bytes per pixel, B,G,R order unless
/// <paramref name="swapRB"/>) → float NCHW /255 with top/left replicate padding.
/// dst layout: [3][h][w] float, channels B,G,R.
/// </summary>
public static void BgrToNchwPad(ReadOnlySpan<byte> src, int width, int height, int srcStride,
Span<float> 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<byte> row = src.Slice(sy * srcStride, width * bpp);
Span<float> d0 = dst.Slice(y * w, w);
Span<float> d1 = dst.Slice(hw + y * w, w);
Span<float> 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;
}
}
}

/// <summary>
/// corrected = clamp(x / max(shadow, 1e-6), 0, 1) for all 3·hw elements.
/// </summary>
Expand All @@ -58,37 +31,6 @@ public static void DivClampInto(ReadOnlySpan<float> x, ReadOnlySpan<float> shado
dst[i] = Math.Clamp(x[i] / Math.Max(shadow[i], 1e-6f), 0f, 1f);
}

/// <summary>
/// float NCHW [3,h,w] (B,G,R) → interleaved u8 HWC, cropping (padTop,padLeft) margins.
/// <paramref name="bpp"/> 4: the alpha byte of each pixel is copied from
/// <paramref name="alphaSrc"/> (same layout, row stride <paramref name="alphaStride"/>).
/// </summary>
public static void NchwToBgrCrop(ReadOnlySpan<float> src, int h, int w,
int padTop, int padLeft, Span<byte> dst, int width, int height, int dstStride,
int bpp = 3, bool swapRB = false, ReadOnlySpan<byte> 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<float> r0 = src.Slice(sy * w, w);
ReadOnlySpan<float> r1 = src.Slice(hw + sy * w, w);
ReadOnlySpan<float> r2 = src.Slice(2 * hw + sy * w, w);
Span<byte> drow = dst.Slice(y * dstStride, width * bpp);
ReadOnlySpan<byte> 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).
Expand All @@ -102,39 +44,6 @@ private static void Rows(int rows, long elemsPerRow, Action<int, int> body)
Parallel.For(0, bands, PO, b => body((int)((long)rows * b / bands), (int)((long)rows * (b + 1) / bands)));
}

/// <summary>
/// Interleaved u8 HWC (<paramref name="bpp"/> 3 or 4 bytes per pixel, B,G,R order unless
/// <paramref name="swapRB"/>) → float NHWC [h][w][3] (B,G,R) /255 with top/left replicate padding.
/// </summary>
public static unsafe void BgrToNhwcPad(ReadOnlySpan<byte> src, int width, int height, int srcStride,
Span<float> 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;
}
}
});
}
}

/// <summary>
/// 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].
Expand Down Expand Up @@ -162,53 +71,6 @@ public static unsafe void DivConcatNhwc(ReadOnlySpan<float> x, ReadOnlySpan<floa
}
}

/// <summary>
/// float NHWC [h][w][3] (B,G,R) → interleaved u8 HWC, cropping (padTop,padLeft) margins.
/// <paramref name="bpp"/> 4: the alpha byte of each pixel is copied from
/// <paramref name="alphaSrc"/> (same layout, row stride <paramref name="alphaStride"/>;
/// may alias <paramref name="dst"/>).
/// </summary>
public static unsafe void NhwcToBgrCrop(ReadOnlySpan<float> src, int h, int w,
int padTop, int padLeft, Span<byte> dst, int width, int height, int dstStride,
int bpp = 3, bool swapRB = false, ReadOnlySpan<byte> 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);
}

/// <summary>One row of interleaved u8 pixels in <paramref name="format"/> → tightly packed BGR24.</summary>
public static void RowToBgr24(ReadOnlySpan<byte> src, Span<byte> bgr, int width, GcdrPixelFormat format)
{
Expand Down
Loading