/*
 * WINED3D draw functions
 *
 * Copyright 2002-2004 Jason Edmeades
 * Copyright 2002-2004 Raphael Junqueira
 * Copyright 2004 Christian Costa
 * Copyright 2005 Oliver Stieber
 * Copyright 2006, 2008 Henri Verbeet
 * Copyright 2007-2008 Stefan Dösinger for CodeWeavers
 * Copyright 2009 Henri Verbeet for CodeWeavers
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include "config.h"
#include "wine/port.h"

#include "wined3d_private.h"

WINE_DEFAULT_DEBUG_CHANNEL(d3d_draw);
WINE_DECLARE_DEBUG_CHANNEL(d3d_perf);
WINE_DECLARE_DEBUG_CHANNEL(d3d);

#include <stdio.h>
#include <math.h>

/* Context activation is done by the caller. */
static void draw_primitive_arrays(struct wined3d_context *context, const struct wined3d_state *state,
        const void *idx_data, unsigned int idx_size, int base_vertex_idx, unsigned int start_idx,
        unsigned int count, unsigned int start_instance, unsigned int instance_count)
{
    const struct wined3d_ffp_attrib_ops *ops = &context->d3d_info->ffp_attrib_ops;
    GLenum idx_type = idx_size == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT;
    const struct wined3d_stream_info *si = &context->stream_info;
    unsigned int instanced_elements[ARRAY_SIZE(si->elements)];
    const struct wined3d_gl_info *gl_info = context->gl_info;
    unsigned int instanced_element_count = 0;
    unsigned int i, j;

    if (!instance_count)
    {
        if (!idx_size)
        {
            gl_info->gl_ops.gl.p_glDrawArrays(state->gl_primitive_type, start_idx, count);
            checkGLcall("glDrawArrays");
            return;
        }

        if (gl_info->supported[ARB_DRAW_ELEMENTS_BASE_VERTEX])
        {
            GL_EXTCALL(glDrawElementsBaseVertex(state->gl_primitive_type, count, idx_type,
                    (const char *)idx_data + (idx_size * start_idx), base_vertex_idx));
            checkGLcall("glDrawElementsBaseVertex");
            return;
        }

        gl_info->gl_ops.gl.p_glDrawElements(state->gl_primitive_type, count,
                idx_type, (const char *)idx_data + (idx_size * start_idx));
        checkGLcall("glDrawElements");
        return;
    }

    if (start_instance)
        FIXME("Start instance (%u) not supported.\n", start_instance);

    if (gl_info->supported[ARB_INSTANCED_ARRAYS])
    {
        if (!idx_size)
        {
            GL_EXTCALL(glDrawArraysInstanced(state->gl_primitive_type, start_idx, count, instance_count));
            checkGLcall("glDrawArraysInstanced");
            return;
        }

        if (gl_info->supported[ARB_DRAW_ELEMENTS_BASE_VERTEX])
        {
            GL_EXTCALL(glDrawElementsInstancedBaseVertex(state->gl_primitive_type, count, idx_type,
                        (const char *)idx_data + (idx_size * start_idx), instance_count, base_vertex_idx));
            checkGLcall("glDrawElementsInstancedBaseVertex");
            return;
        }

        GL_EXTCALL(glDrawElementsInstanced(state->gl_primitive_type, count, idx_type,
                    (const char *)idx_data + (idx_size * start_idx), instance_count));
        checkGLcall("glDrawElementsInstanced");
        return;
    }

    /* Instancing emulation by mixing immediate mode and arrays. */

    /* This is a nasty thing. MSDN says no hardware supports this and
     * applications have to use software vertex processing. We don't support
     * this for now.
     *
     * Shouldn't be too hard to support with OpenGL, in theory just call
     * glDrawArrays() instead of drawElements(). But the stream fequency value
     * has a different meaning in that situation. */
    if (!idx_size)
    {
        FIXME("Non-indexed instanced drawing is not supported\n");
        return;
    }

    for (i = 0; i < ARRAY_SIZE(si->elements); ++i)
    {
        if (!(si->use_map & (1u << i)))
            continue;

        if (state->streams[si->elements[i].stream_idx].flags & WINED3DSTREAMSOURCE_INSTANCEDATA)
            instanced_elements[instanced_element_count++] = i;
    }

    for (i = 0; i < instance_count; ++i)
    {
        /* Specify the instanced attributes using immediate mode calls. */
        for (j = 0; j < instanced_element_count; ++j)
        {
            const struct wined3d_stream_info_element *element;
            unsigned int element_idx;
            const BYTE *ptr;

            element_idx = instanced_elements[j];
            element = &si->elements[element_idx];
            ptr = element->data.addr + element->stride * i;
            if (element->data.buffer_object)
                ptr += (ULONG_PTR)wined3d_buffer_load_sysmem(state->streams[element->stream_idx].buffer, context);
            ops->generic[element->format->emit_idx](element_idx, ptr);
        }

        if (gl_info->supported[ARB_DRAW_ELEMENTS_BASE_VERTEX])
        {
            GL_EXTCALL(glDrawElementsBaseVertex(state->gl_primitive_type, count, idx_type,
                        (const char *)idx_data + (idx_size * start_idx), base_vertex_idx));
            checkGLcall("glDrawElementsBaseVertex");
        }
        else
        {
            gl_info->gl_ops.gl.p_glDrawElements(state->gl_primitive_type, count, idx_type,
                        (const char *)idx_data + (idx_size * start_idx));
            checkGLcall("glDrawElements");
        }
    }
}

static unsigned int get_stride_idx(const void *idx_data, unsigned int idx_size,
        unsigned int base_vertex_idx, unsigned int start_idx, unsigned int vertex_idx)
{
    if (!idx_data)
        return start_idx + vertex_idx;
    if (idx_size == 2)
        return ((const WORD *)idx_data)[start_idx + vertex_idx] + base_vertex_idx;
    return ((const DWORD *)idx_data)[start_idx + vertex_idx] + base_vertex_idx;
}

/* Context activation is done by the caller. */
static void draw_primitive_immediate_mode(struct wined3d_context *context, const struct wined3d_state *state,
        const struct wined3d_stream_info *si, const void *idx_data, unsigned int idx_size,
        int base_vertex_idx, unsigned int start_idx, unsigned int vertex_count, unsigned int instance_count)
{
    const BYTE *position = NULL, *normal = NULL, *diffuse = NULL, *specular = NULL;
    const struct wined3d_d3d_info *d3d_info = context->d3d_info;
    unsigned int coord_idx, stride_idx, texture_idx, vertex_idx;
    const struct wined3d_gl_info *gl_info = context->gl_info;
    const struct wined3d_stream_info_element *element;
    const BYTE *tex_coords[WINED3DDP_MAXTEXCOORD];
    unsigned int texture_unit, texture_stages;
    const struct wined3d_ffp_attrib_ops *ops;
    unsigned int untracked_material_count;
    unsigned int tex_mask = 0;
    BOOL specular_fog = FALSE;
    BOOL ps = use_ps(state);
    const void *ptr;

    static unsigned int once;

    if (!once++)
        FIXME_(d3d_perf)("Drawing using immediate mode.\n");
    else
        WARN_(d3d_perf)("Drawing using immediate mode.\n");

    if (!idx_size && idx_data)
        ERR("Non-NULL idx_data with 0 idx_size, this should never happen.\n");

    if (instance_count)
        FIXME("Instancing not implemented.\n");

    /* Immediate mode drawing can't make use of indices in a VBO - get the
     * data from the index buffer. */
    if (idx_size)
        idx_data = wined3d_buffer_load_sysmem(state->index_buffer, context) + state->index_offset;

    ops = &d3d_info->ffp_attrib_ops;

    gl_info->gl_ops.gl.p_glBegin(state->gl_primitive_type);

    if (use_vs(state) || d3d_info->ffp_generic_attributes)
    {
        for (vertex_idx = 0; vertex_idx < vertex_count; ++vertex_idx)
        {
            unsigned int use_map = si->use_map;
            unsigned int element_idx;

            stride_idx = get_stride_idx(idx_data, idx_size, base_vertex_idx, start_idx, vertex_idx);
            for (element_idx = MAX_ATTRIBS - 1; use_map; use_map &= ~(1u << element_idx), --element_idx)
            {
                if (!(use_map & 1u << element_idx))
                    continue;

                ptr = si->elements[element_idx].data.addr + si->elements[element_idx].stride * stride_idx;
                ops->generic[si->elements[element_idx].format->emit_idx](element_idx, ptr);
            }
        }

        gl_info->gl_ops.gl.p_glEnd();
        return;
    }

    if (si->use_map & (1u << WINED3D_FFP_POSITION))
        position = si->elements[WINED3D_FFP_POSITION].data.addr;

    if (si->use_map & (1u << WINED3D_FFP_NORMAL))
        normal = si->elements[WINED3D_FFP_NORMAL].data.addr;
    else
        gl_info->gl_ops.gl.p_glNormal3f(0.0f, 0.0f, 0.0f);

    untracked_material_count = context->num_untracked_materials;
    if (si->use_map & (1u << WINED3D_FFP_DIFFUSE))
    {
        element = &si->elements[WINED3D_FFP_DIFFUSE];
        diffuse = element->data.addr;

        if (untracked_material_count && element->format->id != WINED3DFMT_B8G8R8A8_UNORM)
            FIXME("Implement diffuse color tracking from %s.\n", debug_d3dformat(element->format->id));
    }
    else
    {
        gl_info->gl_ops.gl.p_glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
    }

    if (si->use_map & (1u << WINED3D_FFP_SPECULAR))
    {
        element = &si->elements[WINED3D_FFP_SPECULAR];
        specular = element->data.addr;

        /* Special case where the fog density is stored in the specular alpha channel. */
        if (state->render_states[WINED3D_RS_FOGENABLE]
                && (state->render_states[WINED3D_RS_FOGVERTEXMODE] == WINED3D_FOG_NONE
                    || si->elements[WINED3D_FFP_POSITION].format->id == WINED3DFMT_R32G32B32A32_FLOAT)
                && state->render_states[WINED3D_RS_FOGTABLEMODE] == WINED3D_FOG_NONE)
        {
            if (gl_info->supported[EXT_FOG_COORD])
            {
                if (element->format->id == WINED3DFMT_B8G8R8A8_UNORM)
                    specular_fog = TRUE;
                else
                    FIXME("Implement fog coordinates from %s.\n", debug_d3dformat(element->format->id));
            }
            else
            {
                static unsigned int once;

                if (!once++)
                    FIXME("Implement fog for transformed vertices in software.\n");
            }
        }
    }
    else if (gl_info->supported[EXT_SECONDARY_COLOR])
    {
        GL_EXTCALL(glSecondaryColor3fEXT)(0.0f, 0.0f, 0.0f);
    }

    texture_stages = d3d_info->limits.ffp_blend_stages;
    for (texture_idx = 0; texture_idx < texture_stages; ++texture_idx)
    {
        if (!gl_info->supported[ARB_MULTITEXTURE] && texture_idx > 0)
        {
            FIXME("Program using multiple concurrent textures which this OpenGL implementation doesn't support.\n");
            continue;
        }

        if (!ps && !state->textures[texture_idx])
            continue;

        texture_unit = context->tex_unit_map[texture_idx];
        if (texture_unit == WINED3D_UNMAPPED_STAGE)
            continue;

        coord_idx = state->texture_states[texture_idx][WINED3D_TSS_TEXCOORD_INDEX];
        if (coord_idx > 7)
        {
            TRACE("Skipping generated coordinates (%#x) for texture %u.\n", coord_idx, texture_idx);
            continue;
        }

        if (si->use_map & (1u << (WINED3D_FFP_TEXCOORD0 + coord_idx)))
        {
            tex_coords[coord_idx] = si->elements[WINED3D_FFP_TEXCOORD0 + coord_idx].data.addr;
            tex_mask |= (1u << texture_idx);
        }
        else
        {
            TRACE("Setting default coordinates for texture %u.\n", texture_idx);
            if (gl_info->supported[ARB_MULTITEXTURE])
                GL_EXTCALL(glMultiTexCoord4fARB(GL_TEXTURE0_ARB + texture_unit, 0.0f, 0.0f, 0.0f, 1.0f));
            else
                gl_info->gl_ops.gl.p_glTexCoord4f(0.0f, 0.0f, 0.0f, 1.0f);
        }
    }

    /* Blending data and point sizes are not supported by this function. They
     * are not supported by the fixed function pipeline at all. A FIXME for
     * them is printed after decoding the vertex declaration. */
    for (vertex_idx = 0; vertex_idx < vertex_count; ++vertex_idx)
    {
        unsigned int tmp_tex_mask;

        stride_idx = get_stride_idx(idx_data, idx_size, base_vertex_idx, start_idx, vertex_idx);

        if (normal)
        {
            ptr = normal + stride_idx * si->elements[WINED3D_FFP_NORMAL].stride;
            ops->normal[si->elements[WINED3D_FFP_NORMAL].format->emit_idx](ptr);
        }

        if (diffuse)
        {
            ptr = diffuse + stride_idx * si->elements[WINED3D_FFP_DIFFUSE].stride;
            ops->diffuse[si->elements[WINED3D_FFP_DIFFUSE].format->emit_idx](ptr);

            if (untracked_material_count)
            {
                struct wined3d_color color;
                unsigned int i;

                wined3d_color_from_d3dcolor(&color, *(const DWORD *)ptr);
                for (i = 0; i < untracked_material_count; ++i)
                {
                    gl_info->gl_ops.gl.p_glMaterialfv(GL_FRONT_AND_BACK, context->untracked_materials[i], &color.r);
                }
            }
        }

        if (specular)
        {
            ptr = specular + stride_idx * si->elements[WINED3D_FFP_SPECULAR].stride;
            ops->specular[si->elements[WINED3D_FFP_SPECULAR].format->emit_idx](ptr);

            if (specular_fog)
                GL_EXTCALL(glFogCoordfEXT((float)(*(const DWORD *)ptr >> 24)));
        }

        tmp_tex_mask = tex_mask;
        for (texture_idx = 0; tmp_tex_mask; tmp_tex_mask >>= 1, ++texture_idx)
        {
            if (!(tmp_tex_mask & 1))
                continue;

            coord_idx = state->texture_states[texture_idx][WINED3D_TSS_TEXCOORD_INDEX];
            ptr = tex_coords[coord_idx] + (stride_idx * si->elements[WINED3D_FFP_TEXCOORD0 + coord_idx].stride);
            ops->texcoord[si->elements[WINED3D_FFP_TEXCOORD0 + coord_idx].format->emit_idx](
                    GL_TEXTURE0_ARB + context->tex_unit_map[texture_idx], ptr);
        }

        if (position)
        {
            ptr = position + stride_idx * si->elements[WINED3D_FFP_POSITION].stride;
            ops->position[si->elements[WINED3D_FFP_POSITION].format->emit_idx](ptr);
        }
    }

    gl_info->gl_ops.gl.p_glEnd();
    checkGLcall("glEnd and previous calls");
}

static void remove_vbos(struct wined3d_context *context,
        const struct wined3d_state *state, struct wined3d_stream_info *s)
{
    unsigned int i;

    for (i = 0; i < ARRAY_SIZE(s->elements); ++i)
    {
        struct wined3d_stream_info_element *e;

        if (!(s->use_map & (1u << i)))
            continue;

        e = &s->elements[i];
        if (e->data.buffer_object)
        {
            struct wined3d_buffer *vb = state->streams[e->stream_idx].buffer;
            e->data.buffer_object = 0;
            e->data.addr += (ULONG_PTR)wined3d_buffer_load_sysmem(vb, context);
        }
    }
}

/* Routine common to the draw primitive and draw indexed primitive routines */
void draw_primitive(struct wined3d_device *device, const struct wined3d_state *state,
        int base_vertex_idx, unsigned int start_idx, unsigned int index_count,
        unsigned int start_instance, unsigned int instance_count, BOOL indexed)
{
    const struct wined3d_fb_state *fb = state->fb;
    const struct wined3d_stream_info *stream_info;
    struct wined3d_event_query *ib_query = NULL;
    struct wined3d_rendertarget_view *dsv, *rtv;
    struct wined3d_stream_info si_emulated;
    const struct wined3d_gl_info *gl_info;
    struct wined3d_context *context;
    unsigned int i, idx_size = 0;
    const void *idx_data = NULL;
    BOOL emulation = FALSE;

    if (!index_count)
        return;

    if (!(rtv = fb->render_targets[0]))
        rtv = fb->depth_stencil;
    if (rtv)
        context = context_acquire(device, wined3d_texture_from_resource(rtv->resource), rtv->sub_resource_idx);
    else
        context = context_acquire(device, NULL, 0);
    if (!context->valid)
    {
        context_release(context);
        WARN("Invalid context, skipping draw.\n");
        return;
    }
    gl_info = context->gl_info;

    for (i = 0; i < gl_info->limits.buffers; ++i)
    {
        struct wined3d_texture *rt;

        if (!(rtv = fb->render_targets[i]) || rtv->format->id == WINED3DFMT_NULL)
            continue;

        rt = wined3d_texture_from_resource(rtv->resource);
        if (state->render_states[WINED3D_RS_COLORWRITEENABLE])
        {
            wined3d_texture_load_location(rt, rtv->sub_resource_idx, context, rtv->resource->draw_binding);
            wined3d_texture_invalidate_location(rt, rtv->sub_resource_idx, ~rtv->resource->draw_binding);
        }
        else
        {
            wined3d_texture_prepare_location(rt, rtv->sub_resource_idx, context, rtv->resource->draw_binding);
        }
    }

    if ((dsv = fb->depth_stencil))
    {
        /* Note that this depends on the context_acquire() call above to set
         * context->render_offscreen properly. We don't currently take the
         * Z-compare function into account, but we could skip loading the
         * depthstencil for D3DCMP_NEVER and D3DCMP_ALWAYS as well. Also note
         * that we never copy the stencil data.*/
        DWORD location = context->render_offscreen ? dsv->resource->draw_binding : WINED3D_LOCATION_DRAWABLE;
        struct wined3d_surface *ds = wined3d_rendertarget_view_get_surface(dsv);

        if (state->render_states[WINED3D_RS_ZWRITEENABLE] || state->render_states[WINED3D_RS_ZENABLE])
            wined3d_texture_load_location(ds->container, dsv->sub_resource_idx, context, location);
        else
            wined3d_texture_prepare_location(ds->container, dsv->sub_resource_idx, context, location);
    }

    if (!context_apply_draw_state(context, device, state))
    {
        context_release(context);
        WARN("Unable to apply draw state, skipping draw.\n");
        return;
    }

    if (dsv && state->render_states[WINED3D_RS_ZWRITEENABLE])
    {
        struct wined3d_surface *ds = wined3d_rendertarget_view_get_surface(dsv);
        DWORD location = context->render_offscreen ? ds->container->resource.draw_binding : WINED3D_LOCATION_DRAWABLE;

        wined3d_texture_validate_location(ds->container, dsv->sub_resource_idx, location);
        wined3d_texture_invalidate_location(ds->container, dsv->sub_resource_idx, ~location);
    }

    if ((!gl_info->supported[WINED3D_GL_VERSION_2_0]
            || !gl_info->supported[NV_POINT_SPRITE])
            && context->render_offscreen
            && state->render_states[WINED3D_RS_POINTSPRITEENABLE]
            && state->gl_primitive_type == GL_POINTS)
    {
        FIXME("Point sprite coordinate origin switching not supported.\n");
    }

    stream_info = &context->stream_info;
    if (context->instance_count)
        instance_count = context->instance_count;

    if (indexed)
    {
        struct wined3d_buffer *index_buffer = state->index_buffer;
        if (!index_buffer->buffer_object || !stream_info->all_vbo)
        {
            idx_data = index_buffer->resource.heap_memory;
        }
        else
        {
            ib_query = index_buffer->query;
            idx_data = NULL;
        }
        idx_data = (const BYTE *)idx_data + state->index_offset;

        if (state->index_format == WINED3DFMT_R16_UINT)
            idx_size = 2;
        else
            idx_size = 4;
    }

    if (!use_vs(state))
    {
        if (!stream_info->position_transformed && context->num_untracked_materials
                && state->render_states[WINED3D_RS_LIGHTING])
        {
            static BOOL warned;

            if (!warned++)
                FIXME("Using software emulation because not all material properties could be tracked.\n");
            else
                WARN_(d3d_perf)("Using software emulation because not all material properties could be tracked.\n");
            emulation = TRUE;
        }
        else if (context->fog_coord && state->render_states[WINED3D_RS_FOGENABLE])
        {
            static BOOL warned;

            /* Either write a pipeline replacement shader or convert the
             * specular alpha from unsigned byte to a float in the vertex
             * buffer. */
            if (!warned++)
                FIXME("Using software emulation because manual fog coordinates are provided.\n");
            else
                WARN_(d3d_perf)("Using software emulation because manual fog coordinates are provided.\n");
            emulation = TRUE;
        }

        if (emulation)
        {
            si_emulated = context->stream_info;
            remove_vbos(context, state, &si_emulated);
            stream_info = &si_emulated;
        }
    }

    if (context->use_immediate_mode_draw || emulation)
        draw_primitive_immediate_mode(context, state, stream_info, idx_data,
                idx_size, base_vertex_idx, start_idx, index_count, instance_count);
    else
        draw_primitive_arrays(context, state, idx_data, idx_size, base_vertex_idx,
                start_idx, index_count, start_instance, instance_count);

    if (context->uses_uavs)
    {
        GL_EXTCALL(glMemoryBarrier(GL_ALL_BARRIER_BITS));
        checkGLcall("glMemoryBarrier");
    }

    if (ib_query)
        wined3d_event_query_issue(ib_query, device);
    for (i = 0; i < context->num_buffer_queries; ++i)
        wined3d_event_query_issue(context->buffer_queries[i], device);

    if (wined3d_settings.strict_draw_ordering)
        gl_info->gl_ops.gl.p_glFlush(); /* Flush to ensure ordering across contexts. */

    context_release(context);

    TRACE("Done all gl drawing.\n");
}

void dispatch_compute(struct wined3d_device *device, const struct wined3d_state *state,
        unsigned int group_count_x, unsigned int group_count_y, unsigned int group_count_z)
{
    const struct wined3d_gl_info *gl_info;
    struct wined3d_context *context;

    context = context_acquire(device, NULL, 0);
    if (!context->valid)
    {
        context_release(context);
        WARN("Invalid context, skipping dispatch.\n");
        return;
    }
    gl_info = context->gl_info;

    if (!gl_info->supported[ARB_COMPUTE_SHADER])
    {
        context_release(context);
        FIXME("OpenGL implementation does not support compute shaders.\n");
        return;
    }

    context_apply_compute_state(context, device, state);

    if (!state->shader[WINED3D_SHADER_TYPE_COMPUTE])
    {
        context_release(context);
        WARN("No compute shader bound, skipping dispatch.\n");
        return;
    }

    GL_EXTCALL(glDispatchCompute(group_count_x, group_count_y, group_count_z));
    checkGLcall("glDispatchCompute");

    GL_EXTCALL(glMemoryBarrier(GL_ALL_BARRIER_BITS));
    checkGLcall("glMemoryBarrier");

    if (wined3d_settings.strict_draw_ordering)
        gl_info->gl_ops.gl.p_glFlush(); /* Flush to ensure ordering across contexts. */

    context_release(context);
}
