#pragma only_renderers d3d11 playstation xboxone xboxseries vulkan metal glcore ps5

#pragma kernel ResampleSpatially

#include "PatchUtil.hlsl"
#include "RingBuffer.hlsl"
#include "Packages/com.unity.render-pipelines.core/Runtime/Sampling/QuasiRandom.hlsl"
#include "RestirEstimation.hlsl"

StructuredBuffer<uint> _RingConfigBuffer;
StructuredBuffer<Realization> _InputPatchRealizations;
StructuredBuffer<PatchUtil::PatchGeometry> _PatchGeometries;
RWStructuredBuffer<Realization> _OutputPatchRealizations;
StructuredBuffer<uint> _CellPatchIndices;
StructuredBuffer<int3> _CascadeOffsets;

uint _FrameIdx;
uint _CascadeCount;
uint _GridSize;
uint _SampleCount;
float _FilterSize;
float _VoxelMinSize;
uint _RingConfigOffset;
float3 _GridTargetPos;

[numthreads(64, 1, 1)]
void ResampleSpatially(uint patchIdx : SV_DispatchThreadID)
{
    if (RingBuffer::IsPositionUnused(_RingConfigBuffer, _RingConfigOffset, patchIdx))
        return;

    Reservoir reservoir;

    const PatchUtil::PatchGeometry patchGeo = _PatchGeometries[patchIdx];
    const int cascadeResolution = PatchUtil::ResolveCascadeIndex(_GridTargetPos, patchGeo.position, _GridSize, _CascadeCount, _VoxelMinSize);
    const uint cascadeIdx = cascadeResolution != -1 ? cascadeResolution : _CascadeCount - 1;

    QrngKronecker rng;
    rng.Init(uint2(patchIdx, 0), _FrameIdx * _SampleCount);

    {
        const Realization realization = _InputPatchRealizations[patchIdx];
        const float candidateWeight = TargetFunction(realization.sample) * realization.weight * realization.confidence;
        reservoir.InitWithSample(realization.sample, realization.confidence, candidateWeight);
    }

    for (uint sampleIdx = 0; sampleIdx < _SampleCount; ++sampleIdx)
    {
        float3 up;
        float3 right;
        OrthoBasisFromVector(patchGeo.normal, up, right);

        const float2 jitter = (float2(rng.GetFloat(0), rng.GetFloat(1)) * 2.0f - 1.0f) * _FilterSize;
        const float3 displacement = (up * jitter.x + right * jitter.y) * PatchUtil::GetVoxelSize(_VoxelMinSize, cascadeIdx);
        const float3 jitteredPosition = patchGeo.position + displacement;

        uint neighbourPatchIdx = PatchUtil::FindPatchIndex(_GridTargetPos, _CellPatchIndices, _GridSize, _CascadeOffsets, _CascadeCount, _VoxelMinSize, jitteredPosition, patchGeo.normal);
        if (neighbourPatchIdx != PatchUtil::invalidPatchIndex)
        {
            Realization realization = _InputPatchRealizations[neighbourPatchIdx];
            float candidateWeight = TargetFunction(realization.sample) * realization.weight * realization.confidence;
            reservoir.Update(realization.sample, realization.confidence, candidateWeight, rng.GetFloat(2));
        }

        rng.NextSample();
    }

    _OutputPatchRealizations[patchIdx] = CreateRealizationFromReservoir(reservoir);
}
