/******************************************************************************
 * The MIT License (MIT)
 *
 * Copyright (c) 2024-2026 Baldur Karlsson
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 ******************************************************************************/

#include "d3d12_test.h"

RD_TEST(D3D12_RTAS_Zoo, D3D12GraphicsTest)
{
  static constexpr const char *Description = "Test of different AS edge-cases and formats.";

  std::string rtshaders = R"EOSHADER(

RaytracingAccelerationStructure Scene : register(t0);
RWTexture2D<float4> RenderTarget : register(u1);

struct RayPayload
{
    float4 color;
};

[shader("raygeneration")]
void gen()
{
    float2 lerpValues = (float2)DispatchRaysIndex() / (float2)DispatchRaysDimensions();

    {
        RayDesc ray;
        ray.Origin = float3(0, 0, 5);
        ray.Direction = float3(lerp(-1.0f, 1.0f, lerpValues.x),
                               lerp(-1.0f, 1.0f, lerpValues.y),
                               -1.0f);
        ray.TMin = 0.001;
        ray.TMax = 10000.0;
        RayPayload payload = { float4(0, 0, 1, 1) };
        TraceRay(Scene, RAY_FLAG_NONE, ~0, 0, 0, 0, ray, payload);

        // Write the raytraced color to the output texture.
        RenderTarget[DispatchRaysIndex().xy] = payload.color;
    }
}

[shader("closesthit")]
void chit(inout RayPayload payload, in BuiltInTriangleIntersectionAttributes attrs)
{
    payload.color = float4(0, 1, 0, 1);
}

[shader("miss")]
void miss(inout RayPayload payload)
{
    payload.color = float4(1, 0, 0, 1);
}

)EOSHADER";

  void Prepare(int argc, char **argv)
  {
    D3D12GraphicsTest::Prepare(argc, argv);

    if(!Avail.empty())
      return;

    if(opts5.RaytracingTier == D3D12_RAYTRACING_TIER_NOT_SUPPORTED)
      Avail = "RT is not supported";

    if(!m_DXILSupport)
      Avail = "DXIL can't be compiled";
  }

  static const uint64_t scratchSpace = 1 * 1024 * 1024;

  static const uint64_t blasOffset = 0;
  static const uint64_t blasSize = 1 * 1024 * 1024;
  static const uint64_t tlasOffset = blasOffset + blasSize;
  static const uint64_t tlasSize = 1 * 1024 * 1024;
  static const uint64_t asbSize = tlasOffset + tlasSize;

  int main()
  {
    // initialise, create window, create device, etc
    if(!Init())
      return 3;

    ID3D12ResourcePtr asb = MakeBuffer().ASB().Size(asbSize);

    asb->SetName(L"asb");

    ID3D12ResourcePtr uav = MakeBuffer().UAV().Size(scratchSpace);

    {
      ID3D12GraphicsCommandList4Ptr cmd = GetCommandBuffer();

      Reset(cmd);

      D3D12_RAYTRACING_GEOMETRY_DESC geom = {};
      geom.Type = D3D12_RAYTRACING_GEOMETRY_TYPE_TRIANGLES;
      geom.Triangles.VertexBuffer.StartAddress = DefaultTriVB->GetGPUVirtualAddress();
      geom.Triangles.VertexBuffer.StrideInBytes = sizeof(DefaultA2V);
      geom.Triangles.VertexFormat = DXGI_FORMAT_R32G32B32_FLOAT;
      geom.Triangles.VertexCount = 3;
      geom.Flags = D3D12_RAYTRACING_GEOMETRY_FLAG_OPAQUE;

      D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_DESC desc = {};
      desc.DestAccelerationStructureData = asb->GetGPUVirtualAddress();
      desc.ScratchAccelerationStructureData = uav->GetGPUVirtualAddress();
      desc.Inputs.DescsLayout = D3D12_ELEMENTS_LAYOUT_ARRAY;
      desc.Inputs.Type = D3D12_RAYTRACING_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL;
      desc.Inputs.NumDescs = 1;
      desc.Inputs.pGeometryDescs = &geom;

      cmd->BuildRaytracingAccelerationStructure(&desc, 0, NULL);

      D3D12_RAYTRACING_ACCELERATION_STRUCTURE_PREBUILD_INFO prebuild = {};
      dev5->GetRaytracingAccelerationStructurePrebuildInfo(&desc.Inputs, &prebuild);

      TEST_ASSERT(prebuild.ScratchDataSizeInBytes < scratchSpace, "Insufficient scratch space");
      TEST_ASSERT(prebuild.ResultDataMaxSizeInBytes < tlasOffset, "BLAS too large");

      cmd->Close();

      Submit({cmd});
    }

    D3D12_RAYTRACING_INSTANCE_DESC instances[8] = {};
    for(int i = 0; i < 8; i++)
    {
      instances[i].AccelerationStructure = asb->GetGPUVirtualAddress();
      instances[i].InstanceMask = 1;
      instances[i].Transform[0][0] = 1.0f;
      instances[i].Transform[1][1] = 1.0f;
      instances[i].Transform[2][2] = 1.0f;

      instances[i].Transform[0][3] = -4.0f + i * 1.0f;
    }

    ID3D12ResourcePtr instData = MakeBuffer().Size(sizeof(instances)).Upload();

    byte *instUpload = Map(instData, 0);

    instData->SetName(L"instData");

    D3D12_GPU_VIRTUAL_ADDRESS instancesIndirect[8] = {};
    for(int i = 0; i < 8; i++)
      instancesIndirect[i] =
          instData->GetGPUVirtualAddress() + i * sizeof(D3D12_RAYTRACING_INSTANCE_DESC);

    ID3D12ResourcePtr instIndirectData = MakeBuffer().Data(instancesIndirect);

    instIndirectData->SetName(L"instIndirectData");

    ID3D12StateObjectPtr rtpso;

    std::vector<D3D12_STATE_SUBOBJECT> subObjs;

    ID3DBlobPtr lib = Compile(rtshaders, "", "lib_6_3");
    D3D12_DXIL_LIBRARY_DESC libDesc = {};
    libDesc.DXILLibrary.BytecodeLength = lib->GetBufferSize();
    libDesc.DXILLibrary.pShaderBytecode = lib->GetBufferPointer();

    ID3D12RootSignaturePtr rootsig = MakeSig({
        tableParam(D3D12_SHADER_VISIBILITY_ALL, D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 0, 0, 100, 0),
        tableParam(D3D12_SHADER_VISIBILITY_ALL, D3D12_DESCRIPTOR_RANGE_TYPE_UAV, 0, 0, 100, 0),
    });
    D3D12_GLOBAL_ROOT_SIGNATURE rootSigDesc = {rootsig};

    D3D12_RAYTRACING_SHADER_CONFIG shadDesc = {};
    shadDesc.MaxPayloadSizeInBytes = 16;
    shadDesc.MaxAttributeSizeInBytes = 8;
    D3D12_RAYTRACING_PIPELINE_CONFIG pipeDesc = {};
    pipeDesc.MaxTraceRecursionDepth = 1;
    D3D12_HIT_GROUP_DESC hitgroupDesc = {};
    hitgroupDesc.ClosestHitShaderImport = L"chit";
    hitgroupDesc.Type = D3D12_HIT_GROUP_TYPE_TRIANGLES;
    hitgroupDesc.HitGroupExport = L"hitgroup";

    subObjs.push_back({D3D12_STATE_SUBOBJECT_TYPE_GLOBAL_ROOT_SIGNATURE, &rootSigDesc});
    subObjs.push_back({D3D12_STATE_SUBOBJECT_TYPE_DXIL_LIBRARY, &libDesc});
    subObjs.push_back({D3D12_STATE_SUBOBJECT_TYPE_RAYTRACING_SHADER_CONFIG, &shadDesc});
    subObjs.push_back({D3D12_STATE_SUBOBJECT_TYPE_RAYTRACING_PIPELINE_CONFIG, &pipeDesc});
    subObjs.push_back({D3D12_STATE_SUBOBJECT_TYPE_HIT_GROUP, &hitgroupDesc});

    D3D12_STATE_OBJECT_DESC stateDesc = {};
    stateDesc.NumSubobjects = (UINT)subObjs.size();
    stateDesc.pSubobjects = subObjs.data();
    stateDesc.Type = D3D12_STATE_OBJECT_TYPE_RAYTRACING_PIPELINE;

    dev5->CreateStateObject(&stateDesc, __uuidof(ID3D12StateObject), (void **)&rtpso);
    ID3D12StateObjectPropertiesPtr rtpso_props = rtpso;

    std::vector<byte> tablesData;

    tablesData.resize(D3D12_RAYTRACING_SHADER_TABLE_BYTE_ALIGNMENT * 3);

    memcpy(tablesData.data(), rtpso_props->GetShaderIdentifier(L"gen"),
           D3D12_SHADER_IDENTIFIER_SIZE_IN_BYTES);
    memcpy(tablesData.data() + D3D12_RAYTRACING_SHADER_TABLE_BYTE_ALIGNMENT,
           rtpso_props->GetShaderIdentifier(L"miss"), D3D12_SHADER_IDENTIFIER_SIZE_IN_BYTES);
    memcpy(tablesData.data() + D3D12_RAYTRACING_SHADER_TABLE_BYTE_ALIGNMENT * 2,
           rtpso_props->GetShaderIdentifier(L"hitgroup"), D3D12_SHADER_IDENTIFIER_SIZE_IN_BYTES);

    ID3D12ResourcePtr tables = MakeBuffer().Data(tablesData);

    ID3D12ResourcePtr uavtex = MakeTexture(DXGI_FORMAT_R32G32B32A32_FLOAT, screenWidth, screenHeight)
                                   .UAV()
                                   .InitialState(D3D12_RESOURCE_STATE_UNORDERED_ACCESS);

    MakeUAV(uavtex).CreateCPU(1);
    MakeAS(asb).Offset(tlasOffset).CreateCPU(0);

    while(Running())
    {
      ID3D12GraphicsCommandListPtr cmd = GetCommandBuffer();
      ID3D12GraphicsCommandList4Ptr cmd4 = cmd;

      Reset(cmd);

      for(int i = 0; i < 8; i++)
      {
        instances[i].InstanceID = curFrame;
        instances[i].Transform[1][3] = 2.5f * cosf(i * 0.29f + 0.05f * curFrame);
      }
      memcpy(instUpload, instances, sizeof(instances));

      cmd->SetDescriptorHeaps(1, &m_CBVUAVSRV.GetInterfacePtr());

      cmd->SetComputeRootSignature(rootsig);
      cmd4->SetPipelineState1(rtpso);
      cmd->SetComputeRootDescriptorTable(0, m_CBVUAVSRV->GetGPUDescriptorHandleForHeapStart());
      cmd->SetComputeRootDescriptorTable(1, m_CBVUAVSRV->GetGPUDescriptorHandleForHeapStart());

      D3D12_DISPATCH_RAYS_DESC rayDispatch = {};
      rayDispatch.Width = screenWidth;
      rayDispatch.Height = screenHeight;
      rayDispatch.Depth = 1;
      rayDispatch.RayGenerationShaderRecord.StartAddress = tables->GetGPUVirtualAddress();
      rayDispatch.RayGenerationShaderRecord.SizeInBytes = D3D12_SHADER_IDENTIFIER_SIZE_IN_BYTES;
      rayDispatch.MissShaderTable.StartAddress =
          tables->GetGPUVirtualAddress() + D3D12_RAYTRACING_SHADER_TABLE_BYTE_ALIGNMENT;
      rayDispatch.MissShaderTable.SizeInBytes = D3D12_SHADER_IDENTIFIER_SIZE_IN_BYTES;
      rayDispatch.MissShaderTable.StrideInBytes = D3D12_SHADER_IDENTIFIER_SIZE_IN_BYTES;
      rayDispatch.HitGroupTable.StartAddress =
          tables->GetGPUVirtualAddress() + D3D12_RAYTRACING_SHADER_TABLE_BYTE_ALIGNMENT * 2;
      rayDispatch.HitGroupTable.StrideInBytes = 0;
      rayDispatch.HitGroupTable.SizeInBytes = D3D12_SHADER_IDENTIFIER_SIZE_IN_BYTES;

      // first frame the TLAS hasn't been built yet
      if(curFrame > 1)
        cmd4->DispatchRays(&rayDispatch);

      ResourceBarrier(cmd);

      cmd->Close();

      Submit({cmd});

      cmd = GetCommandBuffer();
      cmd4 = cmd;

      Reset(cmd);

      D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_DESC desc = {};
      desc.DestAccelerationStructureData = asb->GetGPUVirtualAddress() + tlasOffset;
      desc.ScratchAccelerationStructureData = uav->GetGPUVirtualAddress();
      desc.Inputs.DescsLayout = D3D12_ELEMENTS_LAYOUT_ARRAY_OF_POINTERS;
      desc.Inputs.Type = D3D12_RAYTRACING_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL;
      desc.Inputs.NumDescs = 8;
      desc.Inputs.InstanceDescs = instIndirectData->GetGPUVirtualAddress();

      cmd4->BuildRaytracingAccelerationStructure(&desc, 0, NULL);

      ResourceBarrier(cmd);

      cmd->Close();

      Submit({cmd});

      cmd = GetCommandBuffer();
      cmd4 = cmd;

      Reset(cmd);

      cmd->SetDescriptorHeaps(1, &m_CBVUAVSRV.GetInterfacePtr());

      cmd->SetComputeRootSignature(rootsig);
      cmd4->SetPipelineState1(rtpso);
      cmd->SetComputeRootDescriptorTable(0, m_CBVUAVSRV->GetGPUDescriptorHandleForHeapStart());
      cmd->SetComputeRootDescriptorTable(1, m_CBVUAVSRV->GetGPUDescriptorHandleForHeapStart());

      cmd4->DispatchRays(&rayDispatch);

      ID3D12ResourcePtr bb = StartUsingBackbuffer(cmd, D3D12_RESOURCE_STATE_RENDER_TARGET);

      ResourceBarrier(cmd, uavtex, D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
                      D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);

      blitToSwap(cmd, uavtex, bb);

      ResourceBarrier(cmd, uavtex, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
                      D3D12_RESOURCE_STATE_UNORDERED_ACCESS);

      FinishUsingBackbuffer(cmd, D3D12_RESOURCE_STATE_RENDER_TARGET);

      cmd->Close();

      SubmitAndPresent({cmd});
    }

    return 0;
  }
};

REGISTER_TEST();
