
// CommonStrokeTessellatorOutputPatch.hpp - common GPU and CPU stroking patch output routines

#ifndef __CommonStrokeTessellatorOutputPatch_hpp__
#define __CommonStrokeTessellatorOutputPatch_hpp__

#include "CommonStrokeTessellatorPrivate.hpp"

void StrokeTessellatorMETHOD discardPatch()
{
    // Write an LOD value to zero to discard the patch.
    gl_TessLevelOuter[0] = 0;  // U=0 edge
#if 000  // Just need one LOD to zero to discard patch (faster this way).
    // Outer LOD (left, bottom, right, top)
    gl_TessLevelOuter[1] = 0;  // V=0 edge
    gl_TessLevelOuter[2] = 0;  // U=1 edge
    gl_TessLevelOuter[3] = 0;  // V=0 edge
    // Inner LOD (width by height)
    gl_TessLevelInner[0] = 0;  // U direction integral spacing
    gl_TessLevelInner[1] = 0;  // V direction integral spacing
#endif
}
void StrokeTessellatorMETHOD emitPatch(int W, int H)
{
    // Inner LOD
    gl_TessLevelInner[0] = W;  // U direction integral spacing
    gl_TessLevelInner[1] = H;  // V direction integral spacing

    // Outer LOD
    // 1 segmeent on either outer edge of patch minimizes useless seaming triangles between outer and innert edges
    gl_TessLevelOuter[0] = 1;  // U=0 edge
    gl_TessLevelOuter[1] = 1;  // V=0 edge
    gl_TessLevelOuter[2] = 1;  // U=1 edge
    gl_TessLevelOuter[3] = 1;  // V=0 edge
}
ivec2 StrokeTessellatorMETHOD getPatchWidthHeight(int strips, int steps_per_strip)
{
    CONST int W = 3*strips;           // 3 rows per strip in patch provides top & bottom of strip + row for bogus vertexes
    CONST int H = steps_per_strip+2;  // Extra two columns provides space for seaming triangles
    return ivec2(W,H);
}

// Output patch with minimum edge LOD but interior is a WxH rectangular grid.
void StrokeTessellatorMETHOD outputGridPatch(ivec2 WH)
{
    CONST int W = WH[0];
    CONST int H = WH[1];
    // Write rows and columns outputs.
    FOR_THREAD(1) row_columns_orientationBitMask_steps[TID] = H;
    FOR_THREAD(0) {
        row_columns_orientationBitMask_steps[TID] = W;
        emitPatch(W,H);
    }
}

#if ARC_LENGTH_TEXGEN
#define BUFMASK (524288-1)  // XXX put me someplace better
void StrokeTessellatorMETHOD writeLinkOffsetBuffer(uint offset, uint steps)
{
    // Write the start and end offsets into the link_offset buffer.
    CONST uint link_offset_ndx = linkOffsetIndex(gl_PrimitiveID);
    link_offset[link_offset_ndx+0] = (offset      ) & BUFMASK;
    link_offset[link_offset_ndx+1] = (offset+steps) & BUFMASK;
}
#endif

void StrokeTessellatorMETHOD outputArcLengthState(uint steps)
{
    // Configured to gather arc length?
    if (link_offset == NULL) {
        // No, just return.
        assert(pass_number == 0);
        return;
    }
#if ARC_LENGTH_TEXGEN
    FOR_THREAD(0) {
        if (pass_number == 0) {
            // atomicAdd performs an atomic addition of data
            // to the contents of mem and returns the original
            // contents of mem from before the addition occured. 
            uint offset = atomicAdd(next_distance_offset, steps);
            writeLinkOffsetBuffer(offset, steps);
        } else if (pass_number == 1) {
            // Do nothing, expecting the previous pass #0 for this same gl_PrimitiveID
            // to have written the proper distance offset to find the segment arc lengths
            // using on our same gl_PrimitiveID from the follow-on tessellation
            // evaluation shader instances spawned by this link.
        }
        // Write the primitive ID to be available to the tessellation evaluate shader.
        primitiveID_x_x_x[TID] = gl_PrimitiveID;
    }
#endif
}

void StrokeTessellatorMETHOD establishSteps(float steps)
{
    // Output steps count.
    FOR_THREAD(3) row_columns_orientationBitMask_steps[TID] = uint(steps);
    outputArcLengthState(uint(steps));
}

// Compute the number of rows and columns of an isoline patch that can
// guarantee an ordered sequence of line segments sufficient to render
// the given number of steps.
//
// See computeOrderedBlendingIndex to compute a vertex index from a
// (row,column) integer position of (u,v) on the patch of the returned
// dimension.  This index will be in the range [0,steps].
ivec2 StrokeTessellatorMETHOD computeOrderedBlendingGrid(int steps)
{
    int num_lines, num_segments;
    // Is the number of steps is below the threshold of 64?
    if (steps <= 64) {
        // Yes, we don't need special poisioning math.
        num_lines = 1;
        num_segments = steps;
    } else {
        // No, we need to poison every third column.
        num_segments = (steps + 20)/21;
        num_lines = 3*(steps + num_segments-1)/num_segments;
        // XXX both of these tests should always be true together.
        if ((num_lines-1)*num_segments >= 3*steps) {
            num_lines -= 1;
        }
        if ((num_lines-1)*num_segments >= 3*steps) {
            num_lines -= 1;
        }   
    }
    return getPatchWidthHeight(num_segments, num_lines);
}

// Compute the number of rows and columns of an isoline or quadrilateral
// patch sufficient to render the given given number of steps,
// without concern for guaranteeing any particular odering for the
// sequence of line segments.
//
// See computeunorderedBlendingIndex to compute a vertex index from a
// (row,column) integer position of (u,v) on the patch of the returned
// dimension.  This index will be in the range [0,steps].
ivec2 StrokeTessellatorMETHOD computeUnorderedBlendingGrid(float steps)
{
    CONST int strips = int((steps + max_steps_per_strip-1 + 0.5f) / max_steps_per_strip);
    CONST int steps_per_strip = int((steps + strips-1 + 0.5f) / strips);
    return getPatchWidthHeight(strips, steps_per_strip);
}

void StrokeTessellatorMETHOD outputLinkPatch(float clamped_steps)
{
    // Output steps count.
    establishSteps(clamped_steps);
    
    CONST ivec2 WH = computeUnorderedBlendingGrid(clamped_steps);

    outputGridPatch(WH);
}

void StrokeTessellatorMETHOD discardLinkPatch()
{
#if ARC_LENGTH_TEXGEN
    writeLinkOffsetBuffer(0, 0);
#endif
    discardPatch();
}

#endif // __CommonStrokeTessellatorOutputPatch_hpp__
