Code/Tensor/TensorKernels.Spatial.cs
using System;
using Sandbox;

namespace HumanoidMocapX.TensorInference;

public sealed partial class TensorKernels
{
    readonly ComputeShader spatial = new("shaders/tensor-inference/spatial/spatial_compute.shader");

    /// <summary>Packs each 2×2 HWC block into channels in top-left, bottom-left,
    /// top-right, bottom-right order. Rejects odd dimensions, short buffers and aliasing.</summary>
    public void Focus(GpuBuffer<float> input, GpuBuffer<float> output, int height, int width, int channels)
    {
        var count = ImageElements(height, width, channels);
        if (height % 2 != 0 || width % 2 != 0) throw new ArgumentException("Focus requires even image dimensions.");
        Spatial(input, output, height, width, channels, count, count, 0, 1);
    }

    /// <summary>Repeats each HWC pixel twice on both axes. Rejects invalid shapes,
    /// buffer bounds and aliasing.</summary>
    public void UpsampleNearest(GpuBuffer<float> input, GpuBuffer<float> output, int height, int width, int channels)
    {
        var count = ImageElements(height, width, channels);
        if (count > int.MaxValue / 4) throw new ArgumentException("Upsampled image exceeds the buffer index range.");
        Spatial(input, output, height, width, channels, count, count * 4, 1, 1);
    }

    /// <summary>Computes an HWC maximum with stride one and negative-infinity padding.
    /// Kernel must be odd and between 1 and 31. Rejects invalid shapes/buffers and aliasing.</summary>
    public void MaxPool(GpuBuffer<float> input, GpuBuffer<float> output, int height, int width, int channels, int kernel)
    {
        var count = ImageElements(height, width, channels);
        if (kernel < 1 || kernel > 31 || kernel % 2 == 0) throw new ArgumentException("Max pooling requires an odd kernel from 1 to 31.");
        Spatial(input, output, height, width, channels, count, count, 2, kernel);
    }

    static int ImageElements(int height, int width, int channels)
    {
        if (height < 1 || width < 1 || channels < 1 || height > int.MaxValue - 31 || width > int.MaxValue - 31
            || (long)height * width > int.MaxValue / channels)
            throw new ArgumentException("Image dimensions must be positive and fit GPU index limits.");
        return height * width * channels;
    }

    void Spatial(GpuBuffer<float> input, GpuBuffer<float> output, int height, int width, int channels,
        int inputCount, int outputCount, int operation, int kernel)
    {
        Require(input, inputCount, nameof(input)); Require(output, outputCount, nameof(output));
        if (ReferenceEquals(input, output)) throw new ArgumentException("Spatial output must not overlap its input.");
        var attributes = spatial.Attributes;
        attributes.Set("Input", input); attributes.Set("Output", output);
        attributes.Set("Height", height); attributes.Set("Width", width); attributes.Set("Channels", channels);
        attributes.Set("Count", outputCount); attributes.Set("Operation", operation); attributes.Set("Kernel", kernel);
        DispatchElements(spatial, outputCount); CompleteWrite(output);
    }
}