/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include shared,prim_shared

flat varying mediump vec4 v_color;
flat varying mediump vec3 v_mask_swizzle;
// Normalized bounds of the source image in the texture.
flat varying highp vec4 v_uv_bounds;

// Interpolated UV coordinates to sample.
varying highp vec2 v_uv;


#if defined(WR_FEATURE_GLYPH_TRANSFORM) && !defined(SWGL_CLIP_DIST)
varying highp vec4 v_uv_clip;
#endif

#ifdef WR_VERTEX_SHADER

#define VECS_PER_TEXT_RUN           1
#define GLYPHS_PER_GPU_BLOCK        2U

#ifdef WR_FEATURE_GLYPH_TRANSFORM
RectWithEndpoint transform_rect(RectWithEndpoint rect, mat2 transform) {
    vec2 size = rect_size(rect);
    vec2 center = transform * (rect.p0 + size * 0.5);
    vec2 radius = mat2(abs(transform[0]), abs(transform[1])) * (size * 0.5);
    return RectWithEndpoint(center - radius, center + radius);
}

bool rect_inside_rect(RectWithEndpoint little, RectWithEndpoint big) {
    return all(lessThanEqual(vec4(big.p0, little.p1), vec4(little.p0, big.p1)));
}
#endif //WR_FEATURE_GLYPH_TRANSFORM

struct Glyph {
    vec2 offset;
};

Glyph fetch_glyph(int specific_prim_address,
                  int glyph_index) {
    // Two glyphs are packed in each texel in the GPU cache.
    int glyph_address = specific_prim_address +
                        VECS_PER_TEXT_RUN +
                        int(uint(glyph_index) / GLYPHS_PER_GPU_BLOCK);
    vec4 data = fetch_from_gpu_cache_1(glyph_address);
    // Select XY or ZW based on glyph index.
    vec2 glyph = mix(data.xy, data.zw,
                     bvec2(uint(glyph_index) % GLYPHS_PER_GPU_BLOCK == 1U));

    return Glyph(glyph);
}

struct GlyphResource {
    vec4 uv_rect;
    vec2 offset;
    float scale;
};

GlyphResource fetch_glyph_resource(int address) {
    vec4 data[2] = fetch_from_gpu_cache_2(address);
    return GlyphResource(data[0], data[1].xy, data[1].z);
}

struct TextRun {
    vec4 color;
};

TextRun fetch_text_run(int address) {
    vec4 data = fetch_from_gpu_cache_1(address);
    return TextRun(data);
}

vec2 get_snap_bias(int subpx_dir) {
    // In subpixel mode, the subpixel offset has already been
    // accounted for while rasterizing the glyph. However, we
    // must still round with a subpixel bias rather than rounding
    // to the nearest whole pixel, depending on subpixel direciton.
    switch (subpx_dir) {
        case SUBPX_DIR_NONE:
        default:
            return vec2(0.5);
        case SUBPX_DIR_HORIZONTAL:
            // Glyphs positioned [-0.125, 0.125] get a
            // subpx position of zero. So include that
            // offset in the glyph position to ensure
            // we round to the correct whole position.
            return vec2(0.125, 0.5);
        case SUBPX_DIR_VERTICAL:
            return vec2(0.5, 0.125);
        case SUBPX_DIR_MIXED:
            return vec2(0.125);
    }
}

void main() {
    Instance instance = decode_instance_attributes();
    PrimitiveHeader ph = fetch_prim_header(instance.prim_header_address);
    Transform transform = fetch_transform(ph.transform_id);
    ClipArea clip_area = fetch_clip_area(instance.clip_address);
    PictureTask task = fetch_picture_task(ph.picture_task_address);

    int glyph_index = instance.segment_index;
    int subpx_dir = (instance.flags >> 8) & 0xff;
    int color_mode = instance.flags & 0xff;

    // Note that the reference frame relative offset is stored in the prim local
    // rect size during batching, instead of the actual size of the primitive.
    TextRun text = fetch_text_run(ph.specific_prim_address);
    vec2 text_offset = ph.local_rect.p1;

    // Note that the unsnapped reference frame relative offset has already
    // been subtracted from the prim local rect origin during batching.
    // It was done this way to avoid pushing both the snapped and the
    // unsnapped offsets to the shader.
    Glyph glyph = fetch_glyph(ph.specific_prim_address, glyph_index);
    glyph.offset += ph.local_rect.p0;

    GlyphResource res = fetch_glyph_resource(instance.resource_address);

    vec2 snap_bias = get_snap_bias(subpx_dir);

    // Glyph space refers to the pixel space used by glyph rasterization during frame
    // building. If a non-identity transform was used, WR_FEATURE_GLYPH_TRANSFORM will
    // be set. Otherwise, regardless of whether the raster space is LOCAL or SCREEN,
    // we ignored the transform during glyph rasterization, and need to snap just using
    // the device pixel scale and the raster scale.
#ifdef WR_FEATURE_GLYPH_TRANSFORM
    // Transform from local space to glyph space.
    mat2 glyph_transform = mat2(transform.m) * task.device_pixel_scale;
    vec2 glyph_translation = transform.m[3].xy * task.device_pixel_scale;

    // Transform from glyph space back to local space.
    mat2 glyph_transform_inv = inverse(glyph_transform);

    // Glyph raster pixels include the impact of the transform. This path can only be
    // entered for 3d transforms that can be coerced into a 2d transform; they have no
    // perspective, and have a 2d inverse. This is a looser condition than axis aligned
    // transforms because it also allows 2d rotations.
    vec2 raster_glyph_offset = floor(glyph_transform * glyph.offset + snap_bias);

    // We want to eliminate any subpixel translation in device space to ensure glyph
    // snapping is stable for equivalent glyph subpixel positions. Note that we must take
    // into account the translation from the transform for snapping purposes.
    vec2 raster_text_offset = floor(glyph_transform * text_offset + glyph_translation + 0.5) - glyph_translation;

    vec2 glyph_origin = res.offset + raster_glyph_offset + raster_text_offset;
    // Compute the glyph rect in glyph space.
    RectWithEndpoint glyph_rect = RectWithEndpoint(
        glyph_origin,
        glyph_origin + res.uv_rect.zw - res.uv_rect.xy
    );

    // The glyph rect is in glyph space, so transform it back to local space.
    RectWithEndpoint local_rect = transform_rect(glyph_rect, glyph_transform_inv);

    // Select the corner of the glyph's local space rect that we are processing.
    vec2 local_pos = mix(local_rect.p0, local_rect.p1, aPosition.xy);

    // If the glyph's local rect would fit inside the local clip rect, then select a corner from
    // the device space glyph rect to reduce overdraw of clipped pixels in the fragment shader.
    // Otherwise, fall back to clamping the glyph's local rect to the local clip rect.
    if (rect_inside_rect(local_rect, ph.local_clip_rect)) {
        local_pos = glyph_transform_inv * mix(glyph_rect.p0, glyph_rect.p1, aPosition.xy);
    }
#else
    float raster_scale = float(ph.user_data.x) / 65535.0;

    // Scale in which the glyph is snapped when rasterized.
    float glyph_raster_scale = raster_scale * task.device_pixel_scale;

    // Scale from glyph space to local space.
    float glyph_scale_inv = res.scale / glyph_raster_scale;

    // Glyph raster pixels do not include the impact of the transform. Instead it was
    // replaced with an identity transform during glyph rasterization. As such only the
    // impact of the raster scale (if in local space) and the device pixel scale (for both
    // local and screen space) are included.
    //
    // This implies one or more of the following conditions:
    // - The transform is an identity. In that case, setting WR_FEATURE_GLYPH_TRANSFORM
    //   should have the same output result as not. We just distingush which path to use
    //   based on the transform used during glyph rasterization. (Screen space).
    // - The transform contains an animation. We will imply local raster space in such
    //   cases to avoid constantly rerasterizing the glyphs.
    // - The transform has perspective or does not have a 2d inverse (Screen or local space).
    // - The transform's scale will result in result in very large rasterized glyphs and
    //   we clamped the size. This will imply local raster space.
    vec2 raster_glyph_offset = floor(glyph.offset * glyph_raster_scale + snap_bias) / res.scale;

    // Compute the glyph rect in local space.
    //
    // The transform may be animated, so we don't want to do any snapping here for the
    // text offset to avoid glyphs wiggling. The text offset should have been snapped
    // already for axis aligned transforms excluding any animations during frame building.
    vec2 glyph_origin = glyph_scale_inv * (res.offset + raster_glyph_offset) + text_offset;
    RectWithEndpoint glyph_rect = RectWithEndpoint(
        glyph_origin,
        glyph_origin + glyph_scale_inv * (res.uv_rect.zw - res.uv_rect.xy)
    );

    // Select the corner of the glyph rect that we are processing.
    vec2 local_pos = mix(glyph_rect.p0, glyph_rect.p1, aPosition.xy);
#endif

    VertexInfo vi = write_vertex(
        local_pos,
        ph.local_clip_rect,
        ph.z,
        transform,
        task
    );

#ifdef WR_FEATURE_GLYPH_TRANSFORM
    vec2 f = (glyph_transform * vi.local_pos - glyph_rect.p0) / rect_size(glyph_rect);
    #ifdef SWGL_CLIP_DIST
        gl_ClipDistance[0] = f.x;
        gl_ClipDistance[1] = f.y;
        gl_ClipDistance[2] = 1.0 - f.x;
        gl_ClipDistance[3] = 1.0 - f.y;
    #else
        v_uv_clip = vec4(f, 1.0 - f);
    #endif
#else
    vec2 f = (vi.local_pos - glyph_rect.p0) / rect_size(glyph_rect);
#endif

    write_clip(vi.world_pos, clip_area, task);

    switch (color_mode) {
        case COLOR_MODE_ALPHA:
            v_mask_swizzle = vec3(0.0, 1.0, 1.0);
            v_color = text.color;
            break;
        case COLOR_MODE_BITMAP_SHADOW:
            #ifdef SWGL_BLEND
                swgl_blendDropShadow(text.color);
                v_mask_swizzle = vec3(1.0, 0.0, 0.0);
                v_color = vec4(1.0);
            #else
                v_mask_swizzle = vec3(0.0, 1.0, 0.0);
                v_color = text.color;
            #endif
            break;
        case COLOR_MODE_COLOR_BITMAP:
            v_mask_swizzle = vec3(1.0, 0.0, 0.0);
            v_color = vec4(text.color.a);
            break;
        case COLOR_MODE_SUBPX_DUAL_SOURCE:
            #ifdef SWGL_BLEND
                swgl_blendSubpixelText(text.color);
                v_mask_swizzle = vec3(1.0, 0.0, 0.0);
                v_color = vec4(1.0);
            #else
                v_mask_swizzle = vec3(text.color.a, 0.0, 0.0);
                v_color = text.color;
            #endif
            break;
        default:
            v_mask_swizzle = vec3(0.0, 0.0, 0.0);
            v_color = vec4(1.0);
    }

    vec2 texture_size = vec2(TEX_SIZE(sColor0));
    vec2 st0 = res.uv_rect.xy / texture_size;
    vec2 st1 = res.uv_rect.zw / texture_size;

    v_uv = mix(st0, st1, f);
    v_uv_bounds = (res.uv_rect + vec4(0.5, 0.5, -0.5, -0.5)) / texture_size.xyxy;
}

#endif // WR_VERTEX_SHADER

#ifdef WR_FRAGMENT_SHADER

Fragment text_fs(void) {
    Fragment frag;

    vec2 tc = clamp(v_uv, v_uv_bounds.xy, v_uv_bounds.zw);
    vec4 mask = texture(sColor0, tc);
    // v_mask_swizzle.z != 0 means we are using an R8 texture as alpha,
    // and therefore must swizzle from the r channel to all channels.
    mask = mix(mask, mask.rrrr, bvec4(v_mask_swizzle.z != 0.0));
    #ifndef WR_FEATURE_DUAL_SOURCE_BLENDING
        mask.rgb = mask.rgb * v_mask_swizzle.x + mask.aaa * v_mask_swizzle.y;
    #endif

    #if defined(WR_FEATURE_GLYPH_TRANSFORM) && !defined(SWGL_CLIP_DIST)
        mask *= float(all(greaterThanEqual(v_uv_clip, vec4(0.0))));
    #endif

    frag.color = v_color * mask;

    #if defined(WR_FEATURE_DUAL_SOURCE_BLENDING) && !defined(SWGL_BLEND)
        frag.blend = mask * v_mask_swizzle.x + mask.aaaa * v_mask_swizzle.y;
    #endif

    return frag;
}


void main() {
    Fragment frag = text_fs();

    float clip_mask = do_clip();
    frag.color *= clip_mask;

    #if defined(WR_FEATURE_DEBUG_OVERDRAW)
        oFragColor = WR_DEBUG_OVERDRAW_COLOR;
    #elif defined(WR_FEATURE_DUAL_SOURCE_BLENDING) && !defined(SWGL_BLEND)
        oFragColor = frag.color;
        oFragBlend = frag.blend * clip_mask;
    #else
        write_output(frag.color);
    #endif
}

#if defined(SWGL_DRAW_SPAN) && defined(SWGL_BLEND) && defined(SWGL_CLIP_DIST)
void swgl_drawSpanRGBA8() {
    // Only support simple swizzles for now. More complex swizzles must either
    // be handled by blend overrides or the slow path.
    if (v_mask_swizzle.x != 0.0 && v_mask_swizzle.x != 1.0) {
        return;
    }

    #ifdef WR_FEATURE_DUAL_SOURCE_BLENDING
        swgl_commitTextureLinearRGBA8(sColor0, v_uv, v_uv_bounds);
    #else
        if (swgl_isTextureR8(sColor0)) {
            swgl_commitTextureLinearColorR8ToRGBA8(sColor0, v_uv, v_uv_bounds, v_color);
        } else {
            swgl_commitTextureLinearColorRGBA8(sColor0, v_uv, v_uv_bounds, v_color);
        }
    #endif
}
#endif

#endif // WR_FRAGMENT_SHADER
