#include "ToneMappingPass.h"

using namespace Seele;

ToneMappingPass::ToneMappingPass(Gfx::PGraphics graphics) : RenderPass(graphics) {
    tonemappingLayout = graphics->createDescriptorLayout("pToneMappingParams");
    tonemappingLayout->addDescriptorBinding(Gfx::DescriptorBinding{
        .name = "offset",
        .uniformLength = sizeof(Vector4),
    });
    tonemappingLayout->addDescriptorBinding(Gfx::DescriptorBinding{
        .name = "slope",
        .uniformLength = sizeof(Vector4),
    });
    tonemappingLayout->addDescriptorBinding(Gfx::DescriptorBinding{
        .name = "power",
        .uniformLength = sizeof(Vector4),
    });
    tonemappingLayout->addDescriptorBinding(Gfx::DescriptorBinding{
        .name = "sat",
        .uniformLength = sizeof(float),
    });
    tonemappingLayout->addDescriptorBinding(Gfx::DescriptorBinding{
        .name = "hdrInputTexture",
        .descriptorType = Gfx::SE_DESCRIPTOR_TYPE_SAMPLED_IMAGE,
    });
    tonemappingLayout->addDescriptorBinding(Gfx::DescriptorBinding{
        .name = "hdrSampler",
        .descriptorType = Gfx::SE_DESCRIPTOR_TYPE_SAMPLER,
    });
    tonemappingLayout->addDescriptorBinding(Gfx::DescriptorBinding{
        .name = "averageLuminance",
        .descriptorType = Gfx::SE_DESCRIPTOR_TYPE_STORAGE_BUFFER,
    });
    tonemappingLayout->create();
    tonemappingPipelineLayout = graphics->createPipelineLayout("ToneMappingLayout");
    tonemappingPipelineLayout->addDescriptorLayout(tonemappingLayout);

    graphics->beginShaderCompilation(ShaderCompilationInfo{
        .name = "ToneMapping",
        .modules = {"FullScreenQuad", "ToneMapping"},
        .entryPoints =
            {
                {"quadMain", "FullScreenQuad"},
                {"toneMapping", "ToneMapping"},
            },
        .rootSignature = tonemappingPipelineLayout,
    });
    tonemappingPipelineLayout->create();
    vert = graphics->createVertexShader({0});
    frag = graphics->createFragmentShader({1});

    histogramLayout = graphics->createDescriptorLayout("pHistogramParams");
    histogramLayout->addDescriptorBinding(Gfx::DescriptorBinding{
        .name = "minLogLum",
        .uniformLength = sizeof(float),
    });
    histogramLayout->addDescriptorBinding(Gfx::DescriptorBinding{
        .name = "inverseLogLumRange",
        .uniformLength = sizeof(float),
    });
    histogramLayout->addDescriptorBinding(Gfx::DescriptorBinding{
        .name = "timeCoeff",
        .uniformLength = sizeof(float),
    });
    histogramLayout->addDescriptorBinding(Gfx::DescriptorBinding{
        .name = "numPixels",
        .uniformLength = sizeof(uint32),
    });
    histogramLayout->addDescriptorBinding(Gfx::DescriptorBinding{
        .name = "hdrImage",
        .descriptorType = Gfx::SE_DESCRIPTOR_TYPE_SAMPLED_IMAGE,
    });
    histogramLayout->addDescriptorBinding(Gfx::DescriptorBinding{
        .name = "histogram",
        .descriptorType = Gfx::SE_DESCRIPTOR_TYPE_STORAGE_BUFFER,
    });
    histogramLayout->addDescriptorBinding(Gfx::DescriptorBinding{
        .name = "averageLuminance",
        .descriptorType = Gfx::SE_DESCRIPTOR_TYPE_STORAGE_BUFFER,
    });
    histogramLayout->create();
    histogramPipelineLayout = graphics->createPipelineLayout("HistogramPipelineLayout");
    histogramPipelineLayout->addDescriptorLayout(histogramLayout);

    graphics->beginShaderCompilation(ShaderCompilationInfo{
        .name = "Exposure",
        .modules = {"Exposure"},
        .entryPoints =
            {
                {"histogram", "Exposure"},
                {"exposure", "Exposure"},
            },
        .rootSignature = histogramPipelineLayout,
    });
    histogramPipelineLayout->create();
    histogramShader = graphics->createComputeShader({0});
    histogramPipeline = graphics->createComputePipeline(Gfx::ComputePipelineCreateInfo{
        .computeShader = histogramShader,
        .pipelineLayout = histogramPipelineLayout,
    });
    exposureShader = graphics->createComputeShader({1});
    exposurePipeline = graphics->createComputePipeline(Gfx::ComputePipelineCreateInfo{
        .computeShader = exposureShader,
        .pipelineLayout = histogramPipelineLayout,
    });
    histogramBuffer = graphics->createShaderBuffer(ShaderBufferCreateInfo{
        .sourceData =
            {
                .size = sizeof(uint32) * 256,
            },
        .name = "HistogramBuffer",
    });
    luminanceBuffer = graphics->createShaderBuffer(ShaderBufferCreateInfo{
        .sourceData =
            {
                .size = sizeof(uint32),
            },
        .name = "LuminanceBuffer",
    });
    sampler = graphics->createSampler({});
}

ToneMappingPass::~ToneMappingPass() {}

void ToneMappingPass::beginFrame(const Component::Camera& cam, const Component::Transform& transform) {
    updateViewParameters(cam, transform);
    viewParamsSet = createViewParamsSet();
}

void ToneMappingPass::render() {
    graphics->beginDebugRegion("ToneMapping");
    hdrInputTexture.getTextureView()->changeLayout(Gfx::SE_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, Gfx::SE_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
                                               Gfx::SE_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, Gfx::SE_ACCESS_SHADER_READ_BIT,
                                               Gfx::SE_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | Gfx::SE_PIPELINE_STAGE_COMPUTE_SHADER_BIT);

    histogramLayout->reset();
    histogramSet = histogramLayout->allocateDescriptorSet();
    histogramSet->updateConstants("minLogLum", 0, &minLogLum);
    histogramSet->updateConstants("inverseLogLumRange", 0, &inverseLogLumRange);
    histogramSet->updateConstants("timeCoeff", 0, &timeCoeff);
    histogramSet->updateConstants("numPixels", 0, &numPixels);
    histogramSet->updateTexture("hdrImage", 0, hdrInputTexture.getTextureView());
    histogramSet->updateBuffer("histogram", 0, histogramBuffer);
    histogramSet->updateBuffer("averageLuminance", 0, luminanceBuffer);
    histogramSet->writeChanges();

    {
        Gfx::OComputeCommand computeCommand = graphics->createComputeCommand("HistogramCommand");
        computeCommand->bindPipeline(histogramPipeline);
        computeCommand->bindDescriptor(histogramSet);
        computeCommand->dispatch(threadGroups.x, threadGroups.y, 1);
        graphics->executeCommands(std::move(computeCommand));
    }
    histogramBuffer->pipelineBarrier(Gfx::SE_ACCESS_SHADER_WRITE_BIT, Gfx::SE_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
                                     Gfx::SE_ACCESS_SHADER_READ_BIT | Gfx::SE_ACCESS_SHADER_WRITE_BIT,
                                     Gfx::SE_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
    {
        Gfx::OComputeCommand computeCommand = graphics->createComputeCommand("ExposureCommand");
        computeCommand->bindPipeline(exposurePipeline);
        computeCommand->bindDescriptor(histogramSet);
        computeCommand->dispatch(1, 1, 1);
        graphics->executeCommands(std::move(computeCommand));
    }
    luminanceBuffer->pipelineBarrier(Gfx::SE_ACCESS_SHADER_READ_BIT | Gfx::SE_ACCESS_SHADER_WRITE_BIT,
                                     Gfx::SE_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
                                     Gfx::SE_ACCESS_SHADER_READ_BIT | Gfx::SE_ACCESS_SHADER_WRITE_BIT,
                                     Gfx::SE_PIPELINE_STAGE_COMPUTE_SHADER_BIT | Gfx::SE_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);

    tonemappingLayout->reset();
    Gfx::ODescriptorSet tonemappingSet = tonemappingLayout->allocateDescriptorSet();
    tonemappingSet->updateConstants("offset", 0, &offset);
    tonemappingSet->updateConstants("slope", 0, &slope);
    tonemappingSet->updateConstants("power", 0, &power);
    tonemappingSet->updateConstants("sat", 0, &sat);
    tonemappingSet->updateTexture("hdrInputTexture", 0, hdrInputTexture.getTextureView());
    tonemappingSet->updateSampler("hdrSampler", 0, sampler);
    tonemappingSet->updateBuffer("averageLuminance", 0, luminanceBuffer);
    tonemappingSet->writeChanges();
    graphics->beginRenderPass(renderPass);
    Gfx::ORenderCommand command = graphics->createRenderCommand("ToneMapping");
    command->setViewport(viewport);
    command->bindPipeline(pipeline);
    command->bindDescriptor(tonemappingSet);
    command->draw(3, 1, 0, 0);
    graphics->executeCommands(std::move(command));
    graphics->endRenderPass();
    graphics->endDebugRegion();
}

void ToneMappingPass::endFrame() {}

void ToneMappingPass::publishOutputs() {
    numPixels = viewport->getWidth() * viewport->getHeight();
    threadGroups = UVector2((viewport->getWidth() + 15) / 16, (viewport->getHeight() + 15) / 16);
    colorAttachment = Gfx::RenderTargetAttachment(viewport, Gfx::SE_IMAGE_LAYOUT_UNDEFINED, Gfx::SE_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
                                                  Gfx::SE_ATTACHMENT_LOAD_OP_DONT_CARE, Gfx::SE_ATTACHMENT_STORE_OP_STORE);
    resources->registerRenderPassOutput("TONEMAPPING_COLOR", colorAttachment);
}

void ToneMappingPass::createRenderPass() {
    hdrInputTexture = resources->requestRenderTarget("BASEPASS_COLOR");
    Gfx::RenderTargetLayout targetLayout = Gfx::RenderTargetLayout{
        .colorAttachments = {colorAttachment},
    };
    Array<Gfx::SubPassDependency> dependency = {
        {
            .srcSubpass = ~0U,
            .dstSubpass = 0,
            .srcStage = Gfx::SE_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
            .dstStage = Gfx::SE_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
            .srcAccess = Gfx::SE_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
            .dstAccess = Gfx::SE_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
        },
        {
            .srcSubpass = 0,
            .dstSubpass = ~0U,
            .srcStage = Gfx::SE_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
            .dstStage = Gfx::SE_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
            .srcAccess = Gfx::SE_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
            .dstAccess = Gfx::SE_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
        },
    };
    renderPass = graphics->createRenderPass(targetLayout, dependency, viewport->getRenderArea(), "ToneMappingPass");
    pipeline = graphics->createGraphicsPipeline(Gfx::LegacyPipelineCreateInfo{
        .vertexShader = vert,
        .fragmentShader = frag,
        .renderPass = renderPass,
        .pipelineLayout = tonemappingPipelineLayout,
        .colorBlend =
            {
                .attachmentCount = 1,
            },
    });
}
