Editor/Gpu/SamGpuImageConditioning.cs
// C# adaptation of SAM 3D Body's camera and prompt encoding. See LICENSE-SAM and THIRD_PARTY_NOTICES.md.
// Copyright (c) Meta Platforms, Inc. and affiliates.
using System;
using System.Threading;
using Sandbox;
using HumanoidMocapX.TensorInference;

namespace HumanoidMocapX.Inference;

/// <summary>Conditions GPU DINO patch features for one body/hand decoder. Caller owns
/// storage and submits Run inside a graphics batch; no image readback is required.</summary>
internal sealed class SamGpuImageConditioning
{
    readonly DinoShape shape;
    readonly GpuBuffer<float> masked, combined, projected, weight, mask, norm, bias;
    internal GpuBuffer<float> Positions { get; }

    internal SamGpuImageConditioning(FlowGpuStorage storage, FlowWeights weights, DinoShape shape, bool hand = false)
    {
        if (!shape.IsSam) throw new ArgumentException("GPU image conditioning requires a SAM shape.", nameof(shape));
        this.shape = shape; var width = shape.Channels; var name = hand ? "ray_cond_emb_hand" : "ray_cond_emb";
        weight = storage.Weight(weights, name + ".conv.weight", 1, 1, width + SamRays.Channels, width);
        norm = storage.Weight(weights, name + ".norm.weight", width); bias = storage.Weight(weights, name + ".norm.bias", width);
        var parameters = new float[width * 2]; weights.Get("prompt_encoder.no_mask_embed.weight", width, 1).Data.CopyTo(parameters, 0);
        mask = storage.Upload<float>(parameters);
        masked = storage.Create<float>(shape.Patches * width); projected = storage.Create<float>(shape.Patches * width);
        combined = storage.Create<float>(shape.Patches * (width + SamRays.Channels));
        Positions = storage.Upload<float>(SamImageConditioning.Positions(weights, shape, hand));
    }

    /// <summary>Consumes finite raw DINO features and SamRays output; writes normalized
    /// context. Rejects invalid buffers or aliasing. Positional context remains in Positions.</summary>
    internal void Run(TensorKernels kernels, GpuBuffer<float> image, GpuBuffer<float> rays,
        GpuBuffer<float> output, CancellationToken cancellation = default)
    {
        cancellation.ThrowIfCancellationRequested();
        if (image is null || !image.IsValid || image.ElementCount != shape.Patches * shape.Channels
            || rays is null || !rays.IsValid || rays.ElementCount != shape.Patches * SamRays.Channels
            || output is null || !output.IsValid || output.ElementCount != image.ElementCount
            || ReferenceEquals(image, output) || ReferenceEquals(rays, output) || ReferenceEquals(Positions, output))
            throw new ArgumentException("SAM GPU image context buffers have invalid dimensions or overlap.");
        kernels.Modulate(image, mask, masked, shape.Patches, shape.Channels, 0, shape.Channels);
        kernels.ConcatColumns(masked, rays, combined, shape.Patches, shape.Channels, SamRays.Channels);
        kernels.Linear(combined, weight, null, projected, shape.Patches, shape.Channels + SamRays.Channels, shape.Channels);
        kernels.Normalize(projected, norm, bias, output, shape.Patches, shape.Channels, SamLayerNorm.Epsilon);
    }
}