/* * 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); /* 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); } } } static BOOL use_transform_feedback(const struct wined3d_state *state) { const struct wined3d_shader *shader; if (!(shader = state->shader[WINED3D_SHADER_TYPE_GEOMETRY])) return FALSE; return shader->u.gs.so_desc.element_count; } static void context_pause_transform_feedback(struct wined3d_context *context, BOOL force) { const struct wined3d_gl_info *gl_info = context->gl_info; if (!context->transform_feedback_active || context->transform_feedback_paused) return; if (gl_info->supported[ARB_TRANSFORM_FEEDBACK2]) { GL_EXTCALL(glPauseTransformFeedback()); checkGLcall("glPauseTransformFeedback"); context->transform_feedback_paused = 1; return; } WARN("Cannot pause transform feedback operations.\n"); if (force) context_end_transform_feedback(context); } static GLenum gl_tfb_primitive_type_from_d3d(enum wined3d_primitive_type primitive_type) { GLenum gl_primitive_type = gl_primitive_type_from_d3d(primitive_type); switch (gl_primitive_type) { case GL_POINTS: return GL_POINTS; case GL_LINE_STRIP: case GL_LINE_STRIP_ADJACENCY: case GL_LINES_ADJACENCY: case GL_LINES: return GL_LINES; case GL_TRIANGLE_FAN: case GL_TRIANGLE_STRIP: case GL_TRIANGLE_STRIP_ADJACENCY: case GL_TRIANGLES_ADJACENCY: case GL_TRIANGLES: return GL_TRIANGLES; default: return gl_primitive_type; } } /* 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) { BOOL emulation = FALSE, rasterizer_discard = FALSE; const struct wined3d_fb_state *fb = state->fb; const struct wined3d_stream_info *stream_info; struct wined3d_rendertarget_view *dsv, *rtv; struct wined3d_stream_info si_emulated; struct wined3d_fence *ib_fence = NULL; const struct wined3d_gl_info *gl_info; struct wined3d_context *context; unsigned int i, idx_size = 0; const void *idx_data = NULL; 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; if (!use_transform_feedback(state)) context_pause_transform_feedback(context, TRUE); for (i = 0; i < gl_info->limits.buffers; ++i) { if (!(rtv = fb->render_targets[i]) || rtv->format->id == WINED3DFMT_NULL) continue; if (state->render_states[WINED3D_RS_COLORWRITEENABLE]) { wined3d_rendertarget_view_load_location(rtv, context, rtv->resource->draw_binding); wined3d_rendertarget_view_invalidate_location(rtv, ~rtv->resource->draw_binding); } else { wined3d_rendertarget_view_prepare_location(rtv, 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; if (state->render_states[WINED3D_RS_ZWRITEENABLE] || state->render_states[WINED3D_RS_ZENABLE]) wined3d_rendertarget_view_load_location(dsv, context, location); else wined3d_rendertarget_view_prepare_location(dsv, 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]) { DWORD location = context->render_offscreen ? dsv->resource->draw_binding : WINED3D_LOCATION_DRAWABLE; wined3d_rendertarget_view_validate_location(dsv, location); wined3d_rendertarget_view_invalidate_location(dsv, ~location); } 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_fence = index_buffer->fence; 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 (use_transform_feedback(state)) { const struct wined3d_shader *shader = state->shader[WINED3D_SHADER_TYPE_GEOMETRY]; if (shader->u.gs.so_desc.rasterizer_stream_idx == WINED3D_NO_RASTERIZER_STREAM) { glEnable(GL_RASTERIZER_DISCARD); checkGLcall("enable rasterizer discard"); rasterizer_discard = TRUE; } if (context->transform_feedback_paused) { GL_EXTCALL(glResumeTransformFeedback()); checkGLcall("glResumeTransformFeedback"); context->transform_feedback_paused = 0; } else if (!context->transform_feedback_active) { GLenum mode = gl_tfb_primitive_type_from_d3d(shader->u.gs.output_type); GL_EXTCALL(glBeginTransformFeedback(mode)); checkGLcall("glBeginTransformFeedback"); context->transform_feedback_active = 1; } } if (state->gl_primitive_type == GL_PATCHES) { GL_EXTCALL(glPatchParameteri(GL_PATCH_VERTICES, state->gl_patch_vertices)); checkGLcall("glPatchParameteri"); } 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"); } context_pause_transform_feedback(context, FALSE); if (rasterizer_discard) { glDisable(GL_RASTERIZER_DISCARD); checkGLcall("disable rasterizer discard"); } if (ib_fence) wined3d_fence_issue(ib_fence, device); for (i = 0; i < context->buffer_fence_count; ++i) wined3d_fence_issue(context->buffer_fences[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, const struct wined3d_dispatch_parameters *parameters) { 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; } if (parameters->indirect) { const struct wined3d_indirect_dispatch_parameters *indirect = ¶meters->u.indirect; struct wined3d_buffer *buffer = indirect->buffer; wined3d_buffer_load(buffer, context, state); GL_EXTCALL(glBindBuffer(GL_DISPATCH_INDIRECT_BUFFER, buffer->buffer_object)); GL_EXTCALL(glDispatchComputeIndirect((GLintptr)indirect->offset)); GL_EXTCALL(glBindBuffer(GL_DISPATCH_INDIRECT_BUFFER, 0)); } else { const struct wined3d_direct_dispatch_parameters *direct = ¶meters->u.direct; GL_EXTCALL(glDispatchCompute(direct->group_count_x, direct->group_count_y, direct->group_count_z)); } checkGLcall("dispatch compute"); 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); }