/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES Utilities
 * ------------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

'use strict';
goog.provide('framework.referencerenderer.rrFragmentOperations');
goog.require('framework.common.tcuTexture');
goog.require('framework.common.tcuTextureUtil');
goog.require('framework.delibs.debase.deMath');
goog.require('framework.referencerenderer.rrRenderState');

goog.scope(function() {

var rrFragmentOperations = framework.referencerenderer.rrFragmentOperations;
var deMath = framework.delibs.debase.deMath;
var rrRenderState = framework.referencerenderer.rrRenderState;
var tcuTexture = framework.common.tcuTexture;
var tcuTextureUtil = framework.common.tcuTextureUtil;

/** Return oldValue with the bits indicated by mask replaced by corresponding bits of newValue.
 * @param {number} oldValue
 * @param {number} newValue
 * @param {number} mask
 * @return {number}
 */
rrFragmentOperations.maskedBitReplace = function(oldValue, newValue, mask) {
    return (oldValue & ~mask) | (newValue & mask);
};

/**
 * @param {Array<number>} point
 * @param {?} rect
 * @return {boolean}
 */
rrFragmentOperations.isInsideRect = function(point, rect) {
    return deMath.deInBounds32(point[0], rect.left, rect.left + rect.width) &&
           deMath.deInBounds32(point[1], rect.bottom, rect.bottom + rect.height);
};

/**
 * @constructor
 * @param {Array<number>} coefficents
 * @param {Array<number>} coords
 * @param {number} depth
 */
rrFragmentOperations.Fragment = function(coefficents, coords, depth) {
    /** @type {Array<number>} */ this.barycentric = coefficents;
    /** @type {Array<number>} */ this.pixelCoord = coords;
    /** @type {boolean} */ this.isAlive = true;
    /** @type {boolean} */ this.stencilPassed = true;
    /** @type {boolean} */ this.depthPassed = true;
    /** @type {Array<number>} */ this.sampleDepths = [depth];
    /** @type {Array<number>} */ this.clampedBlendSrcColor = [];
    /** @type {Array<number>} */ this.clampedBlendSrc1Color = [];
    /** @type {Array<number>} */ this.clampedBlendDstColor = [];
    /** @type {Array<number>} */ this.blendSrcFactorRGB = [];
    /** @type {number} */ this.blendSrcFactorA = NaN;
    /** @type {Array<number>} */ this.blendDstFactorRGB = [];
    /** @type {number} */ this.blendDstFactorA = NaN;
    /** @type {Array<number>} */ this.blendedRGB = [];
    /** @type {number} */ this.blendedA = NaN;
    /** @type {Array<number>} */ this.signedValue = []; //!< integer targets
    /** @type {Array<number>} */ this.unsignedValue = []; //!< unsigned integer targets
    /** @type {Array<number>} */ this.value = []; /*TODO: what type should it be? */
    /** @type {Array<number>} */ this.value1 = []; /*TODO: what type should it be? */
};

/**
 * @param {Array<rrFragmentOperations.Fragment>} inputFragments Fragments to write
 * @param {rrRenderState.WindowRectangle} scissorRect
 */
rrFragmentOperations.executeScissorTest = function(inputFragments, scissorRect) {
    for (var i = 0; i < inputFragments.length; i++) {
        var frag = inputFragments[i];
        if (frag.isAlive) {
            if (!rrFragmentOperations.isInsideRect(frag.pixelCoord, scissorRect))
                frag.isAlive = false;
        }
    }
};

/**
 * @param {Array<rrFragmentOperations.Fragment>} inputFragments Fragments to write
 * @param {rrRenderState.StencilState} stencilState
 * @param {number} numStencilBits
 * @param {tcuTexture.PixelBufferAccess} stencilBuffer
 */
rrFragmentOperations.executeStencilCompare = function(inputFragments, stencilState, numStencilBits, stencilBuffer) {
    var clampedStencilRef = deMath.clamp(stencilState.ref, 0, (1 << numStencilBits) - 1);

    /**
     * @param {function(number=,number=):boolean} expression
     */
    var sample_register_stencil_compare = function(expression) {
        for (var i = 0; i < inputFragments.length; i++) {
            var frag = inputFragments[i];
            if (frag.isAlive) {
                var fragSampleNdx = 0;
                var stencilBufferValue = stencilBuffer.getPixStencil(fragSampleNdx, frag.pixelCoord[0], frag.pixelCoord[1]);
                var maskedRef = stencilState.compMask & clampedStencilRef;
                var maskedBuf = stencilState.compMask & stencilBufferValue;
                frag.stencilPassed = expression(maskedRef, maskedBuf);
            }
        }
    };

    switch (stencilState.func) {
        case rrRenderState.TestFunc.NEVER: sample_register_stencil_compare(function(maskedRef, maskedBuf) { return false;}); break;
        case rrRenderState.TestFunc.ALWAYS: sample_register_stencil_compare(function(maskedRef, maskedBuf) { return true;}); break;
        case rrRenderState.TestFunc.LESS: sample_register_stencil_compare(function(maskedRef, maskedBuf) { return maskedRef < maskedBuf;}); break;
        case rrRenderState.TestFunc.LEQUAL: sample_register_stencil_compare(function(maskedRef, maskedBuf) { return maskedRef <= maskedBuf;}); break;
        case rrRenderState.TestFunc.GREATER: sample_register_stencil_compare(function(maskedRef, maskedBuf) { return maskedRef > maskedBuf;}); break;
        case rrRenderState.TestFunc.GEQUAL: sample_register_stencil_compare(function(maskedRef, maskedBuf) { return maskedRef >= maskedBuf;}); break;
        case rrRenderState.TestFunc.EQUAL: sample_register_stencil_compare(function(maskedRef, maskedBuf) { return maskedRef == maskedBuf;}); break;
        case rrRenderState.TestFunc.NOTEQUAL: sample_register_stencil_compare(function(maskedRef, maskedBuf) { return maskedRef != maskedBuf;}); break;
        default:
            throw new Error('Unrecognized stencil test function:' + stencilState.func);
    }
};

/**
 * @param {Array<rrFragmentOperations.Fragment>} inputFragments Fragments to write
 * @param {rrRenderState.StencilState} stencilState
 * @param {number} numStencilBits
 * @param {tcuTexture.PixelBufferAccess} stencilBuffer
 */
rrFragmentOperations.executeStencilSFail = function(inputFragments, stencilState, numStencilBits, stencilBuffer) {
    var clampedStencilRef = deMath.clamp(stencilState.ref, 0, (1 << numStencilBits) - 1);
    /**
     * @param {function(number,number):number} expression
     */
    var sample_register_sfail = function(expression) {
       for (var i = 0; i < inputFragments.length; i++) {
            var frag = inputFragments[i];
            if (frag.isAlive && !frag.stencilPassed) {
                var fragSampleNdx = 0;
                var stencilBufferValue = stencilBuffer.getPixStencil(fragSampleNdx, frag.pixelCoord[0], frag.pixelCoord[1]);
                stencilBuffer.setPixStencil(rrFragmentOperations.maskedBitReplace(stencilBufferValue, expression(stencilBufferValue, numStencilBits), stencilState.writeMask), fragSampleNdx, frag.pixelCoord[0], frag.pixelCoord[1]);
                frag.isAlive = false;
            }
        }
    };

    switch (stencilState.sFail) {
        case rrRenderState.StencilOp.KEEP:
            sample_register_sfail(function(stencilBufferValue, numStencilBits) { return stencilBufferValue;}); break;
        case rrRenderState.StencilOp.ZERO:
            sample_register_sfail(function(stencilBufferValue, numStencilBits) { return 0;}); break;
        case rrRenderState.StencilOp.REPLACE:
            sample_register_sfail(function(stencilBufferValue, numStencilBits) { return clampedStencilRef;}); break;
        case rrRenderState.StencilOp.INCR:
            sample_register_sfail(function(stencilBufferValue, numStencilBits) { return deMath.clamp(stencilBufferValue + 1, 0, (1 << numStencilBits) - 1);}); break;
        case rrRenderState.StencilOp.DECR:
            sample_register_sfail(function(stencilBufferValue, numStencilBits) { return deMath.clamp(stencilBufferValue - 1, 0, (1 << numStencilBits) - 1);}); break;
        case rrRenderState.StencilOp.INCR_WRAP:
            sample_register_sfail(function(stencilBufferValue, numStencilBits) { return (stencilBufferValue + 1) & ((1 << numStencilBits) - 1);}); break;
        case rrRenderState.StencilOp.DECR_WRAP:
            sample_register_sfail(function(stencilBufferValue, numStencilBits) { return (stencilBufferValue - 1) & ((1 << numStencilBits) - 1);}); break;
        case rrRenderState.StencilOp.INVERT:
            sample_register_sfail(function(stencilBufferValue, numStencilBits) { return (~stencilBufferValue) & ((1 << numStencilBits) - 1);}); break;
        default:
            throw new Error('Unrecognized stencil op:' + stencilState.sFail);
    }

};

/**
 * @param {Array<rrFragmentOperations.Fragment>} inputFragments Fragments to write
 * @param {rrRenderState.TestFunc} depthFunc
 * @param {tcuTexture.PixelBufferAccess} depthBuffer
 */
rrFragmentOperations.executeDepthCompare = function(inputFragments, depthFunc, depthBuffer) {
    /**
     * @param {function(number=,number=):boolean} expression
     */
    var sample_register_depth_compare = function(expression) {
      for (var i = 0; i < inputFragments.length; i++) {
            var frag = inputFragments[i];
            if (frag.isAlive) {
                var fragSampleNdx = 0;
                var depthBufferValue = depthBuffer.getPixDepth(fragSampleNdx, frag.pixelCoord[0], frag.pixelCoord[1]);
                var sampleDepthFloat = frag.sampleDepths[fragSampleNdx];
                var sampleDepth = deMath.clamp(sampleDepthFloat, 0.0, 1.0);
                frag.depthPassed = expression(sampleDepth, depthBufferValue);
            }
        }
    };

    switch (depthFunc) {
        case rrRenderState.TestFunc.NEVER: sample_register_depth_compare(function(sampleDepth, depthBufferValue) { return false;}); break;
        case rrRenderState.TestFunc.ALWAYS: sample_register_depth_compare(function(sampleDepth, depthBufferValue) { return true;}); break;
        case rrRenderState.TestFunc.LESS: sample_register_depth_compare(function(sampleDepth, depthBufferValue) { return sampleDepth < depthBufferValue;}); break;
        case rrRenderState.TestFunc.LEQUAL: sample_register_depth_compare(function(sampleDepth, depthBufferValue) { return sampleDepth <= depthBufferValue;}); break;
        case rrRenderState.TestFunc.GREATER: sample_register_depth_compare(function(sampleDepth, depthBufferValue) { return sampleDepth > depthBufferValue;}); break;
        case rrRenderState.TestFunc.GEQUAL: sample_register_depth_compare(function(sampleDepth, depthBufferValue) { return sampleDepth >= depthBufferValue;}); break;
        case rrRenderState.TestFunc.EQUAL: sample_register_depth_compare(function(sampleDepth, depthBufferValue) { return sampleDepth == depthBufferValue;}); break;
        case rrRenderState.TestFunc.NOTEQUAL: sample_register_depth_compare(function(sampleDepth, depthBufferValue) { return sampleDepth != depthBufferValue;}); break;
        default:
            throw new Error('Unrecognized depth function:' + depthFunc);
    }
};

/**
 * @param {Array<rrFragmentOperations.Fragment>} inputFragments Fragments to write
 * @param {tcuTexture.PixelBufferAccess} depthBuffer
 */
rrFragmentOperations.executeDepthWrite = function(inputFragments, depthBuffer) {
      for (var i = 0; i < inputFragments.length; i++) {
            var frag = inputFragments[i];
            if (frag.isAlive && frag.depthPassed) {
                var fragSampleNdx = 0;
                var clampedDepth = deMath.clamp(frag.sampleDepths[fragSampleNdx], 0.0, 1.0);
                depthBuffer.setPixDepth(clampedDepth, fragSampleNdx, frag.pixelCoord[0], frag.pixelCoord[1]);
            }
        }
};

/**
 * @param {Array<rrFragmentOperations.Fragment>} inputFragments Fragments to write
 * @param {rrRenderState.StencilState} stencilState
 * @param {number} numStencilBits
 * @param {tcuTexture.PixelBufferAccess} stencilBuffer
 */
rrFragmentOperations.executeStencilDpFailAndPass = function(inputFragments, stencilState, numStencilBits, stencilBuffer) {
   var clampedStencilRef = deMath.clamp(stencilState.ref, 0, (1 << numStencilBits) - 1);

    /**
     * @param {function(boolean):boolean} condition
     * @param {function(number,number):number} expression
     */
    var sample_register_dpfail_or_dppass = function(condition, expression) {
          for (var i = 0; i < inputFragments.length; i++) {
                var frag = inputFragments[i];
                if (frag.isAlive && condition(frag.depthPassed)) {
                    var fragSampleNdx = 0;
                    var stencilBufferValue = stencilBuffer.getPixStencil(fragSampleNdx, frag.pixelCoord[0], frag.pixelCoord[1]);
                    stencilBuffer.setPixStencil(rrFragmentOperations.maskedBitReplace(stencilBufferValue, expression(stencilBufferValue, numStencilBits), stencilState.writeMask), fragSampleNdx, frag.pixelCoord[0], frag.pixelCoord[1]);
                }
        }
    };

    var switch_dpfail_or_dppass = function(op_name, condition) {
        switch (stencilState[op_name]) {
            case rrRenderState.StencilOp.KEEP: sample_register_dpfail_or_dppass(condition, function(stencilBufferValue, numStencilBits) { return stencilBufferValue;}); break;
            case rrRenderState.StencilOp.ZERO: sample_register_dpfail_or_dppass(condition, function(stencilBufferValue, numStencilBits) { return 0;}); break;
            case rrRenderState.StencilOp.REPLACE: sample_register_dpfail_or_dppass(condition, function(stencilBufferValue, numStencilBits) { return clampedStencilRef;}); break;
            case rrRenderState.StencilOp.INCR: sample_register_dpfail_or_dppass(condition, function(stencilBufferValue, numStencilBits) { return deMath.clamp(stencilBufferValue + 1, 0, (1 << numStencilBits) - 1);}); break;
            case rrRenderState.StencilOp.DECR: sample_register_dpfail_or_dppass(condition, function(stencilBufferValue, numStencilBits) { return deMath.clamp(stencilBufferValue - 1, 0, (1 << numStencilBits) - 1);}); break;
            case rrRenderState.StencilOp.INCR_WRAP: sample_register_dpfail_or_dppass(condition, function(stencilBufferValue, numStencilBits) { return (stencilBufferValue + 1) & ((1 << numStencilBits) - 1);}); break;
            case rrRenderState.StencilOp.DECR_WRAP: sample_register_dpfail_or_dppass(condition, function(stencilBufferValue, numStencilBits) { return (stencilBufferValue - 1) & ((1 << numStencilBits) - 1);}); break;
            case rrRenderState.StencilOp.INVERT: sample_register_dpfail_or_dppass(condition, function(stencilBufferValue, numStencilBits) { return (~stencilBufferValue) & ((1 << numStencilBits) - 1);}); break;
            default:
                throw new Error('Unrecognized stencil operation:' + op_name);
        }
    };

    var passed = function(depthPassed) { return depthPassed;};
    var failed = function(depthPassed) { return !depthPassed;};

    switch_dpfail_or_dppass('dpFail', failed);
    switch_dpfail_or_dppass('dpPass', passed);
};

/**
 * @param {Array<rrFragmentOperations.Fragment>} inputFragments Fragments to write
 * @param {Array<number>} blendColor
 * @param {rrRenderState.BlendState} blendRGBState
 */
rrFragmentOperations.executeBlendFactorComputeRGB = function(inputFragments, blendColor, blendRGBState) {
    /**
     * @param {string} factor_name
     * @param {function(Array<number>, Array<number>, Array<number>):Array<number>} expression
     */
    var sample_register_blend_factor = function(factor_name, expression) {
       for (var i = 0; i < inputFragments.length; i++) {
            var frag = inputFragments[i];
            if (frag.isAlive) {
                var src = frag.clampedBlendSrcColor;
                var src1 = frag.clampedBlendSrc1Color;
                var dst = frag.clampedBlendDstColor;
                frag[factor_name] = deMath.clampVector(expression(src, src1, dst), 0, 1);
            }
        }
    };

    var switch_src_or_dst_factor_rgb = function(func_name, factor_name) {
        switch (blendRGBState[func_name]) {
            case rrRenderState.BlendFunc.ZERO:
                sample_register_blend_factor(factor_name, function(src, src1, dst) { return [0, 0, 0];}); break;
            case rrRenderState.BlendFunc.ONE:
                sample_register_blend_factor(factor_name, function(src, src1, dst) { return [1, 1, 1];}); break;
            case rrRenderState.BlendFunc.SRC_COLOR:
                sample_register_blend_factor(factor_name, function(src, src1, dst) { return deMath.swizzle(src, [0, 1, 2]);}); break;
            case rrRenderState.BlendFunc.ONE_MINUS_SRC_COLOR:
                sample_register_blend_factor(factor_name, function(src, src1, dst) { return deMath.subtract([1, 1, 1], deMath.swizzle(src, [0, 1, 2]));}); break;
            case rrRenderState.BlendFunc.DST_COLOR:
                sample_register_blend_factor(factor_name, function(src, src1, dst) { return deMath.swizzle(dst, [0, 1, 2]);}); break;
            case rrRenderState.BlendFunc.ONE_MINUS_DST_COLOR:
                sample_register_blend_factor(factor_name, function(src, src1, dst) { return deMath.subtract([1, 1, 1], deMath.swizzle(dst, [0, 1, 2]));}); break;
            case rrRenderState.BlendFunc.SRC_ALPHA:
                sample_register_blend_factor(factor_name, function(src, src1, dst) { return [src[3], src[3], src[3]];}); break;
            case rrRenderState.BlendFunc.ONE_MINUS_SRC_ALPHA:
                sample_register_blend_factor(factor_name, function(src, src1, dst) { return [1.0 - src[3], 1.0 - src[3], 1.0 - src[3]];}); break;
            case rrRenderState.BlendFunc.DST_ALPHA:
                sample_register_blend_factor(factor_name, function(src, src1, dst) { return [dst[3], dst[3], dst[3]];}); break;
            case rrRenderState.BlendFunc.ONE_MINUS_DST_ALPHA:
                sample_register_blend_factor(factor_name, function(src, src1, dst) { return [1.0 - dst[3], 1.0 - dst[3], 1.0 - dst[3]];}); break;
            case rrRenderState.BlendFunc.CONSTANT_COLOR:
                sample_register_blend_factor(factor_name, function(src, src1, dst) { return deMath.swizzle(blendColor, [0, 1, 2]);}); break;
            case rrRenderState.BlendFunc.ONE_MINUS_CONSTANT_COLOR:
                sample_register_blend_factor(factor_name, function(src, src1, dst) { return deMath.subtract([1, 1, 1], deMath.swizzle(blendColor, [0, 1, 2]));}); break;
            case rrRenderState.BlendFunc.CONSTANT_ALPHA:
                sample_register_blend_factor(factor_name, function(src, src1, dst) { return [blendColor[3], blendColor[3], blendColor[3]];}); break;
            case rrRenderState.BlendFunc.ONE_MINUS_CONSTANT_ALPHA:
                sample_register_blend_factor(factor_name, function(src, src1, dst) { return [1.0 - blendColor[3], 1.0 - blendColor[3], 1.0 - blendColor[3]];}); break;
            case rrRenderState.BlendFunc.SRC_ALPHA_SATURATE:
                sample_register_blend_factor(factor_name, function(src, src1, dst) { return [Math.min(src[3], 1.0 - dst[3]), Math.min(src[3], 1.0 - dst[3]), Math.min(src[3], 1.0 - dst[3])];}); break;
            case rrRenderState.BlendFunc.SRC1_COLOR:
                sample_register_blend_factor(factor_name, function(src, src1, dst) { return deMath.swizzle(src1, [0, 1, 2]);}); break;
            case rrRenderState.BlendFunc.ONE_MINUS_SRC1_COLOR:
                sample_register_blend_factor(factor_name, function(src, src1, dst) { return deMath.subtract([1, 1, 1], deMath.swizzle(src1, [0, 1, 2]));}); break;
            case rrRenderState.BlendFunc.SRC1_ALPHA:
                sample_register_blend_factor(factor_name, function(src, src1, dst) { return [src1[3], src1[3], src1[3]];}); break;
            case rrRenderState.BlendFunc.ONE_MINUS_SRC1_ALPHA:
                sample_register_blend_factor(factor_name, function(src, src1, dst) { return [1.0 - src1[3], 1.0 - src1[3], 1.0 - src1[3]];}); break;
            default:
                throw new Error('Unrecognized blend function:' + func_name);
            }
    };

    switch_src_or_dst_factor_rgb('srcFunc', 'blendSrcFactorRGB');
    switch_src_or_dst_factor_rgb('dstFunc', 'blendDstFactorRGB');

};

/**
 * @param {Array<rrFragmentOperations.Fragment>} inputFragments Fragments to write
 * @param {Array<number>} blendColor
 * @param {rrRenderState.BlendState} blendAState
 */
rrFragmentOperations.executeBlendFactorComputeA = function(inputFragments, blendColor, blendAState) {
    /**
     * @param {string} factor_name
     * @param {function(Array<number>, Array<number>, Array<number>):number} expression
     */
    var sample_register_blend_factor = function(factor_name, expression) {
       for (var i = 0; i < inputFragments.length; i++) {
            var frag = inputFragments[i];
            if (frag.isAlive) {
                var src = frag.clampedBlendSrcColor;
                var src1 = frag.clampedBlendSrc1Color;
                var dst = frag.clampedBlendDstColor;
                frag[factor_name] = deMath.clamp(expression(src, src1, dst), 0, 1);
            }
        }
    };

    var swictch_src_or_dst_factor_a = function(func_name, factor_name) {
        switch (blendAState[func_name]) {
            case rrRenderState.BlendFunc.ZERO:
                sample_register_blend_factor(factor_name, function(src, src1, dst) { return 0.0;}); break;
            case rrRenderState.BlendFunc.ONE:
                sample_register_blend_factor(factor_name, function(src, src1, dst) { return 1.0;}); break;
            case rrRenderState.BlendFunc.SRC_COLOR:
                sample_register_blend_factor(factor_name, function(src, src1, dst) { return src[3];}); break;
            case rrRenderState.BlendFunc.ONE_MINUS_SRC_COLOR:
                sample_register_blend_factor(factor_name, function(src, src1, dst) { return 1.0 - src[3];}); break;
            case rrRenderState.BlendFunc.DST_COLOR:
                sample_register_blend_factor(factor_name, function(src, src1, dst) { return dst[3];}); break;
            case rrRenderState.BlendFunc.ONE_MINUS_DST_COLOR:
                sample_register_blend_factor(factor_name, function(src, src1, dst) { return 1.0 - dst[3];}); break;
            case rrRenderState.BlendFunc.SRC_ALPHA:
                sample_register_blend_factor(factor_name, function(src, src1, dst) { return src[3];}); break;
            case rrRenderState.BlendFunc.ONE_MINUS_SRC_ALPHA:
                sample_register_blend_factor(factor_name, function(src, src1, dst) { return 1.0 - src[3];}); break;
            case rrRenderState.BlendFunc.DST_ALPHA:
                sample_register_blend_factor(factor_name, function(src, src1, dst) { return dst[3];}); break;
            case rrRenderState.BlendFunc.ONE_MINUS_DST_ALPHA:
                sample_register_blend_factor(factor_name, function(src, src1, dst) { return 1.0 - dst[3];}); break;
            case rrRenderState.BlendFunc.CONSTANT_COLOR:
                sample_register_blend_factor(factor_name, function(src, src1, dst) { return blendColor[3];}); break;
            case rrRenderState.BlendFunc.ONE_MINUS_CONSTANT_COLOR:
                sample_register_blend_factor(factor_name, function(src, src1, dst) { return 1.0 - blendColor[3];}); break;
            case rrRenderState.BlendFunc.CONSTANT_ALPHA:
                sample_register_blend_factor(factor_name, function(src, src1, dst) { return blendColor[3];}); break;
            case rrRenderState.BlendFunc.ONE_MINUS_CONSTANT_ALPHA:
                sample_register_blend_factor(factor_name, function(src, src1, dst) { return 1.0 - blendColor[3];}); break;
            case rrRenderState.BlendFunc.SRC_ALPHA_SATURATE:
                sample_register_blend_factor(factor_name, function(src, src1, dst) { return 1.0;}); break;
            case rrRenderState.BlendFunc.SRC1_COLOR:
                sample_register_blend_factor(factor_name, function(src, src1, dst) { return src1[3];}); break;
            case rrRenderState.BlendFunc.ONE_MINUS_SRC1_COLOR:
                sample_register_blend_factor(factor_name, function(src, src1, dst) { return 1.0 - src1[3];}); break;
            case rrRenderState.BlendFunc.SRC1_ALPHA:
                sample_register_blend_factor(factor_name, function(src, src1, dst) { return src1[3];}); break;
            case rrRenderState.BlendFunc.ONE_MINUS_SRC1_ALPHA:
                sample_register_blend_factor(factor_name, function(src, src1, dst) { return 1.0 - src1[3];}); break;
            default:
                throw new Error('Unrecognized blend function:' + func_name);
        }
    };

    swictch_src_or_dst_factor_a('srcFunc', 'blendSrcFactorA');
    swictch_src_or_dst_factor_a('dstFunc', 'blendDstFactorA');
};

/**
 * @param {Array<rrFragmentOperations.Fragment>} inputFragments Fragments to write
 * @param {rrRenderState.BlendState} blendRGBState
 * @param {rrRenderState.BlendState} blendAState
 */
rrFragmentOperations.executeBlend = function(inputFragments, blendRGBState, blendAState) {
    var sample_register_blended_color = function(color_name, expression) {
       for (var i = 0; i < inputFragments.length; i++) {
            var frag = inputFragments[i];
            if (frag.isAlive) {
                var src = frag.clampedBlendSrcColor;
                var dst = frag.clampedBlendDstColor;
                frag[color_name] = expression(src, dst, frag);
            }
        }
    };

    switch (blendRGBState.equation) {
        case rrRenderState.BlendEquation.ADD:
            sample_register_blended_color('blendedRGB', function(src, dst, frag) { return deMath.add(deMath.multiply(deMath.swizzle(src, [0, 1, 2]), frag.blendSrcFactorRGB), deMath.multiply(deMath.swizzle(dst, [0, 1, 2]), frag.blendDstFactorRGB));}); break;
        case rrRenderState.BlendEquation.SUBTRACT:
            sample_register_blended_color('blendedRGB', function(src, dst, frag) { return deMath.subtract(deMath.multiply(deMath.swizzle(src, [0, 1, 2]), frag.blendSrcFactorRGB), deMath.multiply(deMath.swizzle(dst, [0, 1, 2]), frag.blendDstFactorRGB));}); break;
        case rrRenderState.BlendEquation.REVERSE_SUBTRACT:
            sample_register_blended_color('blendedRGB', function(src, dst, frag) { return deMath.subtract(deMath.multiply(deMath.swizzle(dst, [0, 1, 2]), frag.blendDstFactorRGB), deMath.multiply(deMath.swizzle(src, [0, 1, 2]), frag.blendSrcFactorRGB));}); break;
        case rrRenderState.BlendEquation.MIN:
            sample_register_blended_color('blendedRGB', function(src, dst, frag) { return deMath.min(deMath.swizzle(src, [0, 1, 2]), deMath.swizzle(dst, [0, 1, 2]));}); break;
        case rrRenderState.BlendEquation.MAX:
            sample_register_blended_color('blendedRGB', function(src, dst, frag) { return deMath.max(deMath.swizzle(src, [0, 1, 2]), deMath.swizzle(dst, [0, 1, 2]));}); break;
        default:
            throw new Error('Unrecognized blend equation:' + blendRGBState.equation);
    }

    switch (blendAState.equation) {
        case rrRenderState.BlendEquation.ADD:
            sample_register_blended_color('blendedA', function(src, dst, frag) { return src[3] * frag.blendSrcFactorA + dst[3] * frag.blendDstFactorA;}); break;
        case rrRenderState.BlendEquation.SUBTRACT:
            sample_register_blended_color('blendedA', function(src, dst, frag) { return src[3] * frag.blendSrcFactorA - dst[3] * frag.blendDstFactorA;}); break;
        case rrRenderState.BlendEquation.REVERSE_SUBTRACT:
            sample_register_blended_color('blendedA', function(src, dst, frag) { return dst[3] * frag.blendDstFactorA - src[3] * frag.blendSrcFactorA;}); break;
        case rrRenderState.BlendEquation.MIN:
            sample_register_blended_color('blendedA', function(src, dst, frag) { return Math.min(src[3], dst[3]);}); break;
        case rrRenderState.BlendEquation.MAX:
            sample_register_blended_color('blendedA', function(src, dst, frag) { return Math.max(src[3], dst[3]);}); break;
        default:
            throw new Error('Unrecognized blend equation:' + blendAState.equation);
    }
};

/**
 * @param {Array<rrFragmentOperations.Fragment>} inputFragments
 * @param {boolean} isSRGB
 * @param {tcuTexture.PixelBufferAccess} colorBuffer
 */
rrFragmentOperations.executeColorWrite = function(inputFragments, isSRGB, colorBuffer) {
       for (var i = 0; i < inputFragments.length; i++) {
            var frag = inputFragments[i];
            if (frag.isAlive) {
                var combinedColor = frag.blendedRGB.slice();
                combinedColor[3] = frag.blendedA;
                if (isSRGB)
                    combinedColor = tcuTextureUtil.linearToSRGB(combinedColor);

                colorBuffer.setPixel(combinedColor, 0, frag.pixelCoord[0], frag.pixelCoord[1]);
        }
    }
};

/**
 * @param {Array<rrFragmentOperations.Fragment>} inputFragments
 * @param {Array<boolean>} colorMaskFactor
 * @param {Array<boolean>} colorMaskNegationFactor
 * @param {boolean} isSRGB
 * @param {tcuTexture.PixelBufferAccess} colorBuffer
 */
rrFragmentOperations.executeMaskedColorWrite = function(inputFragments, colorMaskFactor, colorMaskNegationFactor, isSRGB, colorBuffer) {
       for (var i = 0; i < inputFragments.length; i++) {
            var frag = inputFragments[i];
            if (frag.isAlive) {
                var fragSampleNdx = 0;
                var originalColor = colorBuffer.getPixel(fragSampleNdx, frag.pixelCoord[0], frag.pixelCoord[1]);
                var newColor = frag.blendedRGB.slice();
                newColor[3] = frag.blendedA;

                if (isSRGB)
                    newColor = tcuTextureUtil.linearToSRGB(newColor);

                newColor = deMath.add(deMath.multiply(colorMaskFactor, newColor), deMath.multiply(colorMaskNegationFactor, originalColor));

                colorBuffer.setPixel(newColor, fragSampleNdx, frag.pixelCoord[0], frag.pixelCoord[1]);
            }
        }
};

/**
 * @param {Array<rrFragmentOperations.Fragment>} inputFragments
 * @param {Array<boolean>} colorMask
 * @param {tcuTexture.PixelBufferAccess} colorBuffer
 */
rrFragmentOperations.executeSignedValueWrite = function(inputFragments, colorMask, colorBuffer) {
      for (var i = 0; i < inputFragments.length; i++) {
            var frag = inputFragments[i];
            if (frag.isAlive) {
                var fragSampleNdx = 0;
                var originalValue = colorBuffer.getPixelInt(fragSampleNdx, frag.pixelCoord[0], frag.pixelCoord[1]);
                var newValue = tcuTextureUtil.select(frag.signedValue, originalValue, colorMask);

                colorBuffer.setPixelInt(newValue, fragSampleNdx, frag.pixelCoord[0], frag.pixelCoord[1]);
            }
        }
};

/**
 * @param {Array<rrFragmentOperations.Fragment>} inputFragments
 * @param {Array<boolean>} colorMask
 * @param {tcuTexture.PixelBufferAccess} colorBuffer
 */
rrFragmentOperations.executeUnsignedValueWrite = function(inputFragments, colorMask, colorBuffer) {
      for (var i = 0; i < inputFragments.length; i++) {
            var frag = inputFragments[i];
            if (frag.isAlive) {
                var fragSampleNdx = 0;
                var originalValue = colorBuffer.getPixelInt(fragSampleNdx, frag.pixelCoord[0], frag.pixelCoord[1]);
                var newValue = tcuTextureUtil.select(frag.unsignedValue, originalValue, colorMask);

                colorBuffer.setPixelInt(newValue, fragSampleNdx, frag.pixelCoord[0], frag.pixelCoord[1]);
            }
        }
};

/**
 * @constructor
 */
rrFragmentOperations.FragmentProcessor = function() {
    /* TODO: implement */
};

});
