/*
 * Copyright 2018 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "GrMtlCopyManager.h"

#include "GrSurface.h"

#include "GrMtlBuffer.h"
#include "GrMtlCopyPipelineState.h"
#include "GrMtlGpu.h"
#include "GrMtlResourceProvider.h"
#include "GrMtlUtil.h"

#include "SkPoint.h"
#include "SkRect.h"
#include "SkTraceEvent.h"

#import <simd/simd.h>

void GrMtlCopyManager::createCopyProgramBuffer() {
    // Create per vertex attribute data for copy as draw
    static const simd::float2 vdata[4] = {
        {0, 0},
        {0, 1},
        {1, 0},
        {1, 1},
    };
    sk_sp<GrMtlBuffer> mtlBuffer = GrMtlBuffer::Make(fGpu, sizeof(vdata), GrGpuBufferType::kVertex,
                                                     kStatic_GrAccessPattern, vdata);
    fVertexAttributeBuffer = mtlBuffer->mtlBuffer();
}

void GrMtlCopyManager::createCopyProgramShaders() {
     // Create shaders required by pipeline state
    const GrShaderCaps* shaderCaps = fGpu->caps()->shaderCaps();
    const char* version = shaderCaps->versionDeclString();
    SkString vertShaderText(version);
    vertShaderText.appendf(
        "#extension GL_ARB_separate_shader_objects : enable\n"
        "#extension GL_ARB_shading_language_420pack : enable\n"
        "layout(set = %d"/*kUniform_BufferIndex*/", binding = 0) uniform vertexUniformBuffer {"
            "float4 uPosXform;"
            "float4 uTexCoordXform;"
        "};"
        "layout(location = 0) in float2 inPosition;"
        "layout(location = 1) out float2 vTexCoord;"

        "// Copy Program VS\n"
        "void main() {"
            "vTexCoord = inPosition * uTexCoordXform.xy + uTexCoordXform.zw;"
            "sk_Position.xy = inPosition * uPosXform.xy + uPosXform.zw;"
            "sk_Position.zw = float2(0, 1);"
        "}",
        kUniform_BufferIndex
    );

    SkString fragShaderText(version);
    fragShaderText.append(
        "#extension GL_ARB_separate_shader_objects : enable\n"
        "#extension GL_ARB_shading_language_420pack : enable\n"

        "layout(set = 1, binding = 0) uniform sampler2D uTexture;"
        "layout(location = 1) in float2 vTexCoord;"

        "// Copy Program FS\n"
        "void main() {"
            "sk_FragColor = texture(uTexture, vTexCoord);"
        "}"
    );

    SkSL::Program::Settings settings;
    SkSL::Program::Inputs inputs;
    id<MTLLibrary> vertexLibrary = GrCompileMtlShaderLibrary(fGpu, vertShaderText.c_str(),
                                                             SkSL::Program::kVertex_Kind,
                                                             settings, &inputs);
    SkASSERT(inputs.isEmpty());
    SkASSERT(vertexLibrary);

    id<MTLLibrary> fragmentLibrary = GrCompileMtlShaderLibrary(fGpu, fragShaderText.c_str(),
                                                               SkSL::Program::kFragment_Kind,
                                                               settings, &inputs);
    SkASSERT(inputs.isEmpty());
    SkASSERT(fragmentLibrary);

    id<MTLFunction> vertexFunction = [vertexLibrary newFunctionWithName: @"vertexMain"];
    id<MTLFunction> fragmentFunction = [fragmentLibrary newFunctionWithName: @"fragmentMain"];
    SkASSERT(vertexFunction);
    SkASSERT(fragmentFunction);

    fVertexFunction = vertexFunction;
    fFragmentFunction = fragmentFunction;
}

void GrMtlCopyManager::createCopyProgramVertexDescriptor() {
    // Create vertex descriptor for pipeline state
    // Expected [[stage_in]] (vertex attribute) MSL format for copies:
    //
    // struct Input {
    //     float2 inPosition [[attribute(0)]];
    // };
    MTLVertexDescriptor* vertexDescriptor = [[MTLVertexDescriptor alloc] init];
    vertexDescriptor.attributes[0].format = MTLVertexFormatFloat2;
    vertexDescriptor.attributes[0].offset = 0;
    vertexDescriptor.attributes[0].bufferIndex = kAttribute_BufferIndex;

    vertexDescriptor.layouts[kAttribute_BufferIndex].stepFunction = MTLVertexStepFunctionPerVertex;
    vertexDescriptor.layouts[kAttribute_BufferIndex].stepRate = 1;
    vertexDescriptor.layouts[kAttribute_BufferIndex].stride = sizeof(simd::float2);

    fVertexDescriptor = vertexDescriptor;
}

void GrMtlCopyManager::createCopyProgram() {
    TRACE_EVENT0("skia", TRACE_FUNC);

    MTLSamplerDescriptor* samplerDescriptor = [[MTLSamplerDescriptor alloc] init];
    fSamplerState = [fGpu->device() newSamplerStateWithDescriptor: samplerDescriptor];

    this->createCopyProgramBuffer();
    this->createCopyProgramShaders();
    this->createCopyProgramVertexDescriptor();
}

bool GrMtlCopyManager::copySurfaceAsDraw(GrSurface* dst, GrSurfaceOrigin dstOrigin,
                                         GrSurface* src, GrSurfaceOrigin srcOrigin,
                                         const SkIRect& srcRect, const SkIPoint& dstPoint,
                                         bool canDiscardOutsideDstRect) {
    SkASSERT(fGpu->mtlCaps().canCopyAsDraw(dst->config(), SkToBool(dst->asRenderTarget()),
                                           src->config(), SkToBool(src->asTexture())));

    id<MTLTexture> dstTex = GrGetMTLTextureFromSurface(dst, false);
    id<MTLTexture> srcTex = GrGetMTLTextureFromSurface(src, false);

    if (fSamplerState == nil) {
        SkASSERT(fVertexAttributeBuffer == nil);
        SkASSERT(fVertexFunction == nil);
        SkASSERT(fFragmentFunction == nil);
        SkASSERT(fVertexDescriptor == nil);

        this->createCopyProgram();
    }

    if (!(fSamplerState && fVertexAttributeBuffer && fVertexFunction &&
          fFragmentFunction && fVertexDescriptor)) {
        SkASSERT(false);
        return false;
    }

    // UPDATE UNIFORM DESCRIPTOR SET
    int w = srcRect.width();
    int h = srcRect.height();

    // dst rect edges in NDC (-1 to 1)
    int dw = dstTex.width;
    int dh = dstTex.height;
    float dx0 = 2.f * dstPoint.fX / dw - 1.f;
    float dx1 = 2.f * (dstPoint.fX + w) / dw - 1.f;
    float dy0 = 2.f * dstPoint.fY / dh - 1.f;
    float dy1 = 2.f * (dstPoint.fY + h) / dh - 1.f;
    if (kBottomLeft_GrSurfaceOrigin == dstOrigin) {
        dy0 = -dy0;
        dy1 = -dy1;
    }

    float sx0 = (float)srcRect.fLeft;
    float sx1 = (float)(srcRect.fLeft + w);
    float sy0 = (float)srcRect.fTop;
    float sy1 = (float)(srcRect.fTop + h);
    int sh = srcTex.height;
    if (kBottomLeft_GrSurfaceOrigin == srcOrigin) {
        sy0 = sh - sy0;
        sy1 = sh - sy1;
    }

    // src rect edges in normalized texture space (0 to 1).
    int sw = srcTex.width;
    sx0 /= sw;
    sx1 /= sw;
    sy0 /= sh;
    sy1 /= sh;

    const simd::float4 vertexUniformBuffer[2] = {
        {dx1 - dx0, dy1 - dy0, dx0, dy0}, // posXform
        {sx1 - sx0, sy1 - sy0, sx0, sy0}, // texCoordXform
    };

    MTLRenderPassDescriptor* renderPassDesc = [[MTLRenderPassDescriptor alloc] init];
    renderPassDesc.colorAttachments[0].texture = dstTex;
    renderPassDesc.colorAttachments[0].slice = 0;
    renderPassDesc.colorAttachments[0].level = 0;
    renderPassDesc.colorAttachments[0].loadAction = canDiscardOutsideDstRect ? MTLLoadActionDontCare
                                                                             : MTLLoadActionLoad;
    renderPassDesc.colorAttachments[0].storeAction = MTLStoreActionStore;

    id<MTLRenderCommandEncoder> renderCmdEncoder =
            [fGpu->commandBuffer() renderCommandEncoderWithDescriptor: renderPassDesc];
    GrMtlCopyPipelineState* copyPipelineState =
            fGpu->resourceProvider().findOrCreateCopyPipelineState(dstTex.pixelFormat,
                                                                   fVertexFunction,
                                                                   fFragmentFunction,
                                                                   fVertexDescriptor);
    [renderCmdEncoder setRenderPipelineState: copyPipelineState->mtlCopyPipelineState()];
    [renderCmdEncoder setVertexBuffer: fVertexAttributeBuffer
                               offset: 0
                              atIndex: kAttribute_BufferIndex];
    [renderCmdEncoder setVertexBytes: vertexUniformBuffer
                              length: sizeof(vertexUniformBuffer)
                             atIndex: kUniform_BufferIndex];
    [renderCmdEncoder setFragmentTexture: srcTex
                                 atIndex: 0];
    [renderCmdEncoder setFragmentSamplerState: fSamplerState
                                      atIndex: 0];
    [renderCmdEncoder drawPrimitives: MTLPrimitiveTypeTriangleStrip
                         vertexStart: 0
                         vertexCount: 4];
    [renderCmdEncoder endEncoding];
    return true;
}

bool GrMtlCopyManager::IsCompatible(const GrMtlCopyPipelineState* pipelineState,
                                    MTLPixelFormat dstPixelFormat) {
    return pipelineState->fPixelFormat == dstPixelFormat;
}