buffer.c 48.4 KB
Newer Older
1
/*
2 3 4
 * Copyright 2002-2005 Jason Edmeades
 * Copyright 2002-2005 Raphael Junqueira
 * Copyright 2004 Christian Costa
5
 * Copyright 2005 Oliver Stieber
6
 * Copyright 2007-2010 Stefan Dösinger for CodeWeavers
7
 * Copyright 2009-2010 Henri Verbeet for CodeWeavers
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
 *
 * 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);

32 33 34 35
#define VB_MAXDECLCHANGES     100     /* After that number of decl changes we stop converting */
#define VB_RESETDECLCHANGE    1000    /* Reset the decl changecount after that number of draws */
#define VB_MAXFULLCONVERSIONS 5       /* Number of full conversions before we stop converting */
#define VB_RESETFULLCONVS     20      /* Reset full conversion counts after that number of draws */
36

37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
static inline BOOL buffer_add_dirty_area(struct wined3d_buffer *This, UINT offset, UINT size)
{
    if (!This->buffer_object) return TRUE;

    if (This->maps_size <= This->modified_areas)
    {
        void *new = HeapReAlloc(GetProcessHeap(), 0, This->maps,
                                This->maps_size * 2 * sizeof(*This->maps));
        if (!new)
        {
            ERR("Out of memory\n");
            return FALSE;
        }
        else
        {
            This->maps = new;
            This->maps_size *= 2;
        }
    }

57 58 59 60 61 62 63
    if(offset > This->resource.size || offset + size > This->resource.size)
    {
        WARN("Invalid range dirtified, marking entire buffer dirty\n");
        offset = 0;
        size = This->resource.size;
    }
    else if(!offset && !size)
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
    {
        size = This->resource.size;
    }

    This->maps[This->modified_areas].offset = offset;
    This->maps[This->modified_areas].size = size;
    This->modified_areas++;
    return TRUE;
}

static inline void buffer_clear_dirty_areas(struct wined3d_buffer *This)
{
    This->modified_areas = 0;
}

79
static BOOL buffer_is_dirty(const struct wined3d_buffer *buffer)
80
{
81
    return !!buffer->modified_areas;
82 83
}

84
static BOOL buffer_is_fully_dirty(const struct wined3d_buffer *buffer)
85 86 87
{
    unsigned int i;

88
    for (i = 0; i < buffer->modified_areas; ++i)
89
    {
90
        if (!buffer->maps[i].offset && buffer->maps[i].size == buffer->resource.size)
91 92 93 94 95
            return TRUE;
    }
    return FALSE;
}

96
/* Context activation is done by the caller */
97
static void delete_gl_buffer(struct wined3d_buffer *This, const struct wined3d_gl_info *gl_info)
98 99 100 101 102 103 104 105
{
    if(!This->buffer_object) return;

    ENTER_GL();
    GL_EXTCALL(glDeleteBuffersARB(1, &This->buffer_object));
    checkGLcall("glDeleteBuffersARB");
    LEAVE_GL();
    This->buffer_object = 0;
106

107 108 109 110 111
    if(This->query)
    {
        wined3d_event_query_destroy(This->query);
        This->query = NULL;
    }
112
    This->flags &= ~WINED3D_BUFFER_APPLESYNC;
113 114
}

115
/* Context activation is done by the caller. */
116
static void buffer_create_buffer_object(struct wined3d_buffer *This, const struct wined3d_gl_info *gl_info)
117
{
118
    GLenum error, gl_usage;
119

120
    TRACE("Creating an OpenGL vertex buffer object for wined3d_buffer %p with usage %s.\n",
121
            This, debug_d3dusage(This->resource.usage));
122

123
    ENTER_GL();
124

125 126 127 128 129 130
    /* Make sure that the gl error is cleared. Do not use checkGLcall
    * here because checkGLcall just prints a fixme and continues. However,
    * if an error during VBO creation occurs we can fall back to non-vbo operation
    * with full functionality(but performance loss)
    */
    while (glGetError() != GL_NO_ERROR);
131

132 133 134 135 136
    /* Basically the FVF parameter passed to CreateVertexBuffer is no good.
     * The vertex declaration from the device determines how the data in the
     * buffer is interpreted. This means that on each draw call the buffer has
     * to be verified to check if the rhw and color values are in the correct
     * format. */
137

138 139 140
    GL_EXTCALL(glGenBuffersARB(1, &This->buffer_object));
    error = glGetError();
    if (!This->buffer_object || error != GL_NO_ERROR)
141
    {
142
        ERR("Failed to create a VBO with error %s (%#x)\n", debug_glerror(error), error);
143
        LEAVE_GL();
144
        goto fail;
145 146
    }

147 148
    if (This->buffer_type_hint == GL_ELEMENT_ARRAY_BUFFER_ARB)
        device_invalidate_state(This->resource.device, STATE_INDEXBUFFER);
149
    GL_EXTCALL(glBindBufferARB(This->buffer_type_hint, This->buffer_object));
150 151
    error = glGetError();
    if (error != GL_NO_ERROR)
152
    {
153
        ERR("Failed to bind the VBO with error %s (%#x)\n", debug_glerror(error), error);
154
        LEAVE_GL();
155
        goto fail;
156 157
    }

158
    /* Don't use static, because dx apps tend to update the buffer
159 160 161
     * quite often even if they specify 0 usage.
     */
    if(This->resource.usage & WINED3DUSAGE_DYNAMIC)
162
    {
163 164
        TRACE("Gl usage = GL_STREAM_DRAW_ARB\n");
        gl_usage = GL_STREAM_DRAW_ARB;
165 166 167 168 169 170

        if(gl_info->supported[APPLE_FLUSH_BUFFER_RANGE])
        {
            GL_EXTCALL(glBufferParameteriAPPLE(This->buffer_type_hint, GL_BUFFER_FLUSHING_UNMAP_APPLE, GL_FALSE));
            checkGLcall("glBufferParameteriAPPLE(This->buffer_type_hint, GL_BUFFER_FLUSHING_UNMAP_APPLE, GL_FALSE)");
            This->flags |= WINED3D_BUFFER_FLUSH;
171 172 173 174

            GL_EXTCALL(glBufferParameteriAPPLE(This->buffer_type_hint, GL_BUFFER_SERIALIZED_MODIFY_APPLE, GL_FALSE));
            checkGLcall("glBufferParameteriAPPLE(This->buffer_type_hint, GL_BUFFER_SERIALIZED_MODIFY_APPLE, GL_FALSE)");
            This->flags |= WINED3D_BUFFER_APPLESYNC;
175
        }
176
        /* No setup is needed here for GL_ARB_map_buffer_range */
177
    }
178 179 180 181 182
    else
    {
        TRACE("Gl usage = GL_DYNAMIC_DRAW_ARB\n");
        gl_usage = GL_DYNAMIC_DRAW_ARB;
    }
183

184
    /* Reserve memory for the buffer. The amount of data won't change
185 186 187 188 189
     * so we are safe with calling glBufferData once and
     * calling glBufferSubData on updates. Upload the actual data in case
     * we're not double buffering, so we can release the heap mem afterwards
     */
    GL_EXTCALL(glBufferDataARB(This->buffer_type_hint, This->resource.size, This->resource.allocatedMemory, gl_usage));
190
    error = glGetError();
191
    LEAVE_GL();
192
    if (error != GL_NO_ERROR)
193
    {
194 195
        ERR("glBufferDataARB failed with error %s (%#x)\n", debug_glerror(error), error);
        goto fail;
196 197
    }

198 199
    This->buffer_object_size = This->resource.size;
    This->buffer_object_usage = gl_usage;
200 201 202

    if(This->flags & WINED3D_BUFFER_DOUBLEBUFFER)
    {
203 204 205 206 207
        if(!buffer_add_dirty_area(This, 0, 0))
        {
            ERR("buffer_add_dirty_area failed, this is not expected\n");
            goto fail;
        }
208 209 210 211 212 213 214
    }
    else
    {
        HeapFree(GetProcessHeap(), 0, This->resource.heapMemory);
        This->resource.allocatedMemory = NULL;
        This->resource.heapMemory = NULL;
    }
215

216
    return;
217

218 219 220
fail:
    /* Clean up all vbo init, but continue because we can work without a vbo :-) */
    ERR("Failed to create a vertex buffer object. Continuing, but performance issues may occur\n");
221
    delete_gl_buffer(This, gl_info);
222
    buffer_clear_dirty_areas(This);
223 224
}

225 226
static BOOL buffer_process_converted_attribute(struct wined3d_buffer *This,
        const enum wined3d_buffer_conversion_type conversion_type,
227
        const struct wined3d_stream_info_element *attrib, DWORD *stride_this_run)
228 229 230
{
    DWORD attrib_size;
    BOOL ret = FALSE;
231
    unsigned int i;
232 233 234 235 236 237 238
    DWORD_PTR data;

    /* Check for some valid situations which cause us pain. One is if the buffer is used for
     * constant attributes(stride = 0), the other one is if the buffer is used on two streams
     * with different strides. In the 2nd case we might have to drop conversion entirely,
     * it is possible that the same bytes are once read as FLOAT2 and once as UBYTE4N.
     */
239
    if (!attrib->stride)
240 241
    {
        FIXME("%s used with stride 0, let's hope we get the vertex stride from somewhere else\n",
242
                debug_d3dformat(attrib->format->id));
243
    }
244
    else if(attrib->stride != *stride_this_run && *stride_this_run)
245
    {
246
        FIXME("Got two concurrent strides, %d and %d\n", attrib->stride, *stride_this_run);
247 248 249
    }
    else
    {
250
        *stride_this_run = attrib->stride;
251 252 253 254 255 256 257
        if (This->stride != *stride_this_run)
        {
            /* We rely that this happens only on the first converted attribute that is found,
             * if at all. See above check
             */
            TRACE("Reconverting because converted attributes occur, and the stride changed\n");
            This->stride = *stride_this_run;
258 259 260
            HeapFree(GetProcessHeap(), HEAP_ZERO_MEMORY, This->conversion_map);
            This->conversion_map = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
                    sizeof(*This->conversion_map) * This->stride);
261 262 263 264
            ret = TRUE;
        }
    }

265
    data = ((DWORD_PTR)attrib->data.addr) % This->stride;
266
    attrib_size = attrib->format->component_count * attrib->format->component_size;
267 268
    for (i = 0; i < attrib_size; ++i)
    {
269 270
        DWORD_PTR idx = (data + i) % This->stride;
        if (This->conversion_map[idx] != conversion_type)
271
        {
272 273
            TRACE("Byte %ld in vertex changed\n", idx);
            TRACE("It was type %d, is %d now\n", This->conversion_map[idx], conversion_type);
274
            ret = TRUE;
275
            This->conversion_map[idx] = conversion_type;
276 277 278 279 280 281
        }
    }

    return ret;
}

282 283
static BOOL buffer_check_attribute(struct wined3d_buffer *This, const struct wined3d_stream_info *si,
        UINT attrib_idx, const BOOL check_d3dcolor, const BOOL is_ffp_position, const BOOL is_ffp_color,
284
        DWORD *stride_this_run)
285
{
286
    const struct wined3d_stream_info_element *attrib = &si->elements[attrib_idx];
287
    enum wined3d_format_id format;
288 289 290 291 292
    BOOL ret = FALSE;

    /* Ignore attributes that do not have our vbo. After that check we can be sure that the attribute is
     * there, on nonexistent attribs the vbo is 0.
     */
293
    if (!(si->use_map & (1 << attrib_idx))
294
            || attrib->data.buffer_object != This->buffer_object)
295
        return FALSE;
296

297
    format = attrib->format->id;
298
    /* Look for newly appeared conversion */
299
    if (check_d3dcolor && format == WINED3DFMT_B8G8R8A8_UNORM)
300
    {
301
        ret = buffer_process_converted_attribute(This, CONV_D3DCOLOR, attrib, stride_this_run);
302

303
        if (!is_ffp_color) FIXME("Test for non-color fixed function WINED3DFMT_B8G8R8A8_UNORM format\n");
304
    }
305
    else if (is_ffp_position && format == WINED3DFMT_R32G32B32A32_FLOAT)
306
    {
307
        ret = buffer_process_converted_attribute(This, CONV_POSITIONT, attrib, stride_this_run);
308 309 310
    }
    else if (This->conversion_map)
    {
311
        ret = buffer_process_converted_attribute(This, CONV_NONE, attrib, stride_this_run);
312 313 314 315 316 317
    }

    return ret;
}

static BOOL buffer_find_decl(struct wined3d_buffer *This)
318
{
319
    struct wined3d_device *device = This->resource.device;
320
    const struct wined3d_gl_info *gl_info = &device->adapter->gl_info;
321
    const struct wined3d_stream_info *si = &device->strided_streams;
322
    const struct wined3d_state *state = &device->stateBlock->state;
323 324
    UINT stride_this_run = 0;
    BOOL ret = FALSE;
325
    BOOL support_d3dcolor = gl_info->supported[ARB_VERTEX_ARRAY_BGRA];
326 327

    /* In d3d7 the vertex buffer declaration NEVER changes because it is stored in the d3d7 vertex buffer.
328 329
     * Once we have our declaration there is no need to look it up again. Index buffers also never need
     * conversion, so once the (empty) conversion structure is created don't bother checking again
330
     */
331 332
    if (This->flags & WINED3D_BUFFER_HASDESC)
    {
333
        if(This->resource.usage & WINED3DUSAGE_STATICDECL) return FALSE;
334
    }
335

336 337 338 339 340 341 342 343 344 345 346 347 348 349
    if (use_vs(state))
    {
        TRACE("Vertex shaders used, no VBO conversion is needed\n");
        if(This->conversion_map)
        {
            HeapFree(GetProcessHeap(), 0, This->conversion_map);
            This->conversion_map = NULL;
            This->stride = 0;
            return TRUE;
        }

        return FALSE;
    }

350 351 352 353
    TRACE("Finding vertex buffer conversion information\n");
    /* Certain declaration types need some fixups before we can pass them to
     * opengl. This means D3DCOLOR attributes with fixed function vertex
     * processing, FLOAT4 POSITIONT with fixed function, and FLOAT16 if
354
     * GL_ARB_half_float_vertex is not supported.
355
     *
356
     * Note for d3d8 and d3d9:
357 358 359 360 361
     * The vertex buffer FVF doesn't help with finding them, we have to use
     * the decoded vertex declaration and pick the things that concern the
     * current buffer. A problem with this is that this can change between
     * draws, so we have to validate the information and reprocess the buffer
     * if it changes, and avoid false positives for performance reasons.
362 363 364
     * WineD3D doesn't even know the vertex buffer any more, it is managed
     * by the client libraries and passed to SetStreamSource and ProcessVertices
     * as needed.
365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395
     *
     * We have to distinguish between vertex shaders and fixed function to
     * pick the way we access the strided vertex information.
     *
     * This code sets up a per-byte array with the size of the detected
     * stride of the arrays in the buffer. For each byte we have a field
     * that marks the conversion needed on this byte. For example, the
     * following declaration with fixed function vertex processing:
     *
     *      POSITIONT, FLOAT4
     *      NORMAL, FLOAT3
     *      DIFFUSE, FLOAT16_4
     *      SPECULAR, D3DCOLOR
     *
     * Will result in
     * {                 POSITIONT                    }{             NORMAL                }{    DIFFUSE          }{SPECULAR }
     * [P][P][P][P][P][P][P][P][P][P][P][P][P][P][P][P][0][0][0][0][0][0][0][0][0][0][0][0][F][F][F][F][F][F][F][F][C][C][C][C]
     *
     * Where in this example map P means 4 component position conversion, 0
     * means no conversion, F means FLOAT16_2 conversion and C means D3DCOLOR
     * conversion (red / blue swizzle).
     *
     * If we're doing conversion and the stride changes we have to reconvert
     * the whole buffer. Note that we do not mind if the semantic changes,
     * we only care for the conversion type. So if the NORMAL is replaced
     * with a TEXCOORD, nothing has to be done, or if the DIFFUSE is replaced
     * with a D3DCOLOR BLENDWEIGHT we can happily dismiss the change. Some
     * conversion types depend on the semantic as well, for example a FLOAT4
     * texcoord needs no conversion while a FLOAT4 positiont needs one
     */

396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419
    ret = buffer_check_attribute(This, si, WINED3D_FFP_POSITION,
            TRUE, TRUE,  FALSE, &stride_this_run) || ret;
    ret = buffer_check_attribute(This, si, WINED3D_FFP_NORMAL,
            TRUE, FALSE, FALSE, &stride_this_run) || ret;
    ret = buffer_check_attribute(This, si, WINED3D_FFP_DIFFUSE,
            !support_d3dcolor, FALSE, TRUE,  &stride_this_run) || ret;
    ret = buffer_check_attribute(This, si, WINED3D_FFP_SPECULAR,
            !support_d3dcolor, FALSE, TRUE,  &stride_this_run) || ret;
    ret = buffer_check_attribute(This, si, WINED3D_FFP_TEXCOORD0,
            TRUE, FALSE, FALSE, &stride_this_run) || ret;
    ret = buffer_check_attribute(This, si, WINED3D_FFP_TEXCOORD1,
            TRUE, FALSE, FALSE, &stride_this_run) || ret;
    ret = buffer_check_attribute(This, si, WINED3D_FFP_TEXCOORD2,
            TRUE, FALSE, FALSE, &stride_this_run) || ret;
    ret = buffer_check_attribute(This, si, WINED3D_FFP_TEXCOORD3,
            TRUE, FALSE, FALSE, &stride_this_run) || ret;
    ret = buffer_check_attribute(This, si, WINED3D_FFP_TEXCOORD4,
            TRUE, FALSE, FALSE, &stride_this_run) || ret;
    ret = buffer_check_attribute(This, si, WINED3D_FFP_TEXCOORD5,
            TRUE, FALSE, FALSE, &stride_this_run) || ret;
    ret = buffer_check_attribute(This, si, WINED3D_FFP_TEXCOORD6,
            TRUE, FALSE, FALSE, &stride_this_run) || ret;
    ret = buffer_check_attribute(This, si, WINED3D_FFP_TEXCOORD7,
            TRUE, FALSE, FALSE, &stride_this_run) || ret;
420

421
    if (!stride_this_run && This->conversion_map)
422 423 424
    {
        /* Sanity test */
        if (!ret) ERR("no converted attributes found, old conversion map exists, and no declaration change?\n");
425 426
        HeapFree(GetProcessHeap(), 0, This->conversion_map);
        This->conversion_map = NULL;
427 428 429 430 431 432 433 434
        This->stride = 0;
    }

    if (ret) TRACE("Conversion information changed\n");

    return ret;
}

435
static inline void fixup_d3dcolor(DWORD *dst_color)
436
{
437
    DWORD src_color = *dst_color;
438

439 440 441 442 443 444 445 446 447 448 449 450 451
    /* Color conversion like in drawStridedSlow. watch out for little endianity
     * If we want that stuff to work on big endian machines too we have to consider more things
     *
     * 0xff000000: Alpha mask
     * 0x00ff0000: Blue mask
     * 0x0000ff00: Green mask
     * 0x000000ff: Red mask
     */
    *dst_color = 0;
    *dst_color |= (src_color & 0xff00ff00);         /* Alpha Green */
    *dst_color |= (src_color & 0x00ff0000) >> 16;   /* Red */
    *dst_color |= (src_color & 0x000000ff) << 16;   /* Blue */
}
452

453 454
static inline void fixup_transformed_pos(float *p)
{
455
    /* rhw conversion like in position_float4(). */
456
    if (p[3] != 1.0f && p[3] != 0.0f)
457
    {
458
        float w = 1.0f / p[3];
459 460 461 462
        p[0] *= w;
        p[1] *= w;
        p[2] *= w;
        p[3] = w;
463 464
    }
}
465

466
/* Context activation is done by the caller. */
467 468
void buffer_get_memory(struct wined3d_buffer *buffer, const struct wined3d_gl_info *gl_info,
        struct wined3d_bo_address *data)
469
{
470
    data->buffer_object = buffer->buffer_object;
471
    if (!buffer->buffer_object)
472
    {
473
        if (buffer->flags & WINED3D_BUFFER_CREATEBO)
474
        {
475 476 477
            buffer_create_buffer_object(buffer, gl_info);
            buffer->flags &= ~WINED3D_BUFFER_CREATEBO;
            if (buffer->buffer_object)
478
            {
479 480 481
                data->buffer_object = buffer->buffer_object;
                data->addr = NULL;
                return;
482 483
            }
        }
484
        data->addr = buffer->resource.allocatedMemory;
485 486
    }
    else
487
    {
488
        data->addr = NULL;
489
    }
490
}
491

492
ULONG CDECL wined3d_buffer_incref(struct wined3d_buffer *buffer)
493
{
494
    ULONG refcount = InterlockedIncrement(&buffer->resource.ref);
495

496
    TRACE("%p increasing refcount to %u.\n", buffer, refcount);
497 498 499 500

    return refcount;
}

501
/* Context activation is done by the caller. */
502
BYTE *buffer_get_sysmem(struct wined3d_buffer *This, const struct wined3d_gl_info *gl_info)
503
{
504 505
    /* AllocatedMemory exists if the buffer is double buffered or has no buffer object at all */
    if(This->resource.allocatedMemory) return This->resource.allocatedMemory;
506 507 508

    This->resource.heapMemory = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, This->resource.size + RESOURCE_ALIGNMENT);
    This->resource.allocatedMemory = (BYTE *)(((ULONG_PTR)This->resource.heapMemory + (RESOURCE_ALIGNMENT - 1)) & ~(RESOURCE_ALIGNMENT - 1));
509 510

    if (This->buffer_type_hint == GL_ELEMENT_ARRAY_BUFFER_ARB)
511
        device_invalidate_state(This->resource.device, STATE_INDEXBUFFER);
512

513 514 515 516 517 518 519 520 521
    ENTER_GL();
    GL_EXTCALL(glBindBufferARB(This->buffer_type_hint, This->buffer_object));
    GL_EXTCALL(glGetBufferSubDataARB(This->buffer_type_hint, 0, This->resource.size, This->resource.allocatedMemory));
    LEAVE_GL();
    This->flags |= WINED3D_BUFFER_DOUBLEBUFFER;

    return This->resource.allocatedMemory;
}

522
/* Do not call while under the GL lock. */
523
static void buffer_unload(struct wined3d_resource *resource)
524
{
525
    struct wined3d_buffer *buffer = buffer_from_resource(resource);
526

527
    TRACE("buffer %p.\n", buffer);
528

529
    if (buffer->buffer_object)
530
    {
531
        struct wined3d_device *device = resource->device;
532
        struct wined3d_context *context;
533

534
        context = context_acquire(device, NULL);
535 536

        /* Download the buffer, but don't permanently enable double buffering */
537
        if (!(buffer->flags & WINED3D_BUFFER_DOUBLEBUFFER))
538
        {
539 540
            buffer_get_sysmem(buffer, context->gl_info);
            buffer->flags &= ~WINED3D_BUFFER_DOUBLEBUFFER;
541 542
        }

543 544 545
        delete_gl_buffer(buffer, context->gl_info);
        buffer->flags |= WINED3D_BUFFER_CREATEBO; /* Recreate the buffer object next load */
        buffer_clear_dirty_areas(buffer);
546 547

        context_release(context);
548

549 550 551 552 553
        HeapFree(GetProcessHeap(), 0, buffer->conversion_map);
        buffer->conversion_map = NULL;
        buffer->stride = 0;
        buffer->conversion_stride = 0;
        buffer->flags &= ~WINED3D_BUFFER_HASDESC;
554
    }
555

556
    resource_unload(resource);
557
}
558

559
/* Do not call while under the GL lock. */
560
ULONG CDECL wined3d_buffer_decref(struct wined3d_buffer *buffer)
561
{
562
    ULONG refcount = InterlockedDecrement(&buffer->resource.ref);
563

564
    TRACE("%p decreasing refcount to %u.\n", buffer, refcount);
565 566

    if (!refcount)
567
    {
568 569 570 571 572
        buffer_unload(&buffer->resource);
        resource_cleanup(&buffer->resource);
        buffer->resource.parent_ops->wined3d_object_destroyed(buffer->resource.parent);
        HeapFree(GetProcessHeap(), 0, buffer->maps);
        HeapFree(GetProcessHeap(), 0, buffer);
573 574
    }

575 576
    return refcount;
}
577

578
void * CDECL wined3d_buffer_get_parent(const struct wined3d_buffer *buffer)
579
{
580
    TRACE("buffer %p.\n", buffer);
581

582
    return buffer->resource.parent;
583
}
584

585
DWORD CDECL wined3d_buffer_set_priority(struct wined3d_buffer *buffer, DWORD priority)
586
{
587
    return resource_set_priority(&buffer->resource, priority);
588 589
}

590
DWORD CDECL wined3d_buffer_get_priority(const struct wined3d_buffer *buffer)
591
{
592
    return resource_get_priority(&buffer->resource);
593 594
}

595 596
/* The caller provides a context and binds the buffer */
static void buffer_sync_apple(struct wined3d_buffer *This, DWORD flags, const struct wined3d_gl_info *gl_info)
597
{
598 599
    enum wined3d_event_query_result ret;

600 601 602 603 604
    /* No fencing needs to be done if the app promises not to overwrite
     * existing data */
    if(flags & WINED3DLOCK_NOOVERWRITE) return;
    if(flags & WINED3DLOCK_DISCARD)
    {
605
        ENTER_GL();
606 607
        GL_EXTCALL(glBufferDataARB(This->buffer_type_hint, This->resource.size, NULL, This->buffer_object_usage));
        checkGLcall("glBufferDataARB\n");
608
        LEAVE_GL();
609 610 611
        return;
    }

612 613 614 615
    if(!This->query)
    {
        TRACE("Creating event query for buffer %p\n", This);

616
        if (!wined3d_event_query_supported(gl_info))
617
        {
618
            FIXME("Event queries not supported, dropping async buffer locks.\n");
619 620
            goto drop_query;
        }
621 622 623 624 625 626 627 628

        This->query = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*This->query));
        if (!This->query)
        {
            ERR("Failed to allocate event query memory, dropping async buffer locks.\n");
            goto drop_query;
        }

629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659
        /* Since we don't know about old draws a glFinish is needed once */
        wglFinish();
        return;
    }
    TRACE("Synchronizing buffer %p\n", This);
    ret = wined3d_event_query_finish(This->query, This->resource.device);
    switch(ret)
    {
        case WINED3D_EVENT_QUERY_NOT_STARTED:
        case WINED3D_EVENT_QUERY_OK:
            /* All done */
            return;

        case WINED3D_EVENT_QUERY_WRONG_THREAD:
            WARN("Cannot synchronize buffer lock due to a thread conflict\n");
            goto drop_query;

        default:
            ERR("wined3d_event_query_finish returned %u, dropping async buffer locks\n", ret);
            goto drop_query;
    }

drop_query:
    if(This->query)
    {
        wined3d_event_query_destroy(This->query);
        This->query = NULL;
    }

    wglFinish();
    ENTER_GL();
660 661
    GL_EXTCALL(glBufferParameteriAPPLE(This->buffer_type_hint, GL_BUFFER_SERIALIZED_MODIFY_APPLE, GL_TRUE));
    checkGLcall("glBufferParameteriAPPLE(This->buffer_type_hint, GL_BUFFER_SERIALIZED_MODIFY_APPLE, GL_TRUE)");
662
    LEAVE_GL();
663 664 665
    This->flags &= ~WINED3D_BUFFER_APPLESYNC;
}

666
/* The caller provides a GL context */
667
static void buffer_direct_upload(struct wined3d_buffer *This, const struct wined3d_gl_info *gl_info, DWORD flags)
668
{
669 670
    BYTE *map;
    UINT start = 0, len = 0;
671

672 673 674 675 676 677 678 679 680 681 682
    ENTER_GL();

    /* This potentially invalidates the element array buffer binding, but the
     * caller always takes care of this. */
    GL_EXTCALL(glBindBufferARB(This->buffer_type_hint, This->buffer_object));
    checkGLcall("glBindBufferARB");
    if (gl_info->supported[ARB_MAP_BUFFER_RANGE])
    {
        GLbitfield mapflags;
        mapflags = GL_MAP_WRITE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT;
        if (flags & WINED3D_BUFFER_DISCARD)
683 684
            mapflags |= GL_MAP_INVALIDATE_BUFFER_BIT;
        if (flags & WINED3D_BUFFER_NOSYNC)
685 686 687 688 689 690 691 692
            mapflags |= GL_MAP_UNSYNCHRONIZED_BIT;
        map = GL_EXTCALL(glMapBufferRange(This->buffer_type_hint, 0,
                    This->resource.size, mapflags));
        checkGLcall("glMapBufferRange");
    }
    else
    {
        if (This->flags & WINED3D_BUFFER_APPLESYNC)
693
        {
694 695 696
            DWORD syncflags = 0;
            if (flags & WINED3D_BUFFER_DISCARD) syncflags |= WINED3DLOCK_DISCARD;
            if (flags & WINED3D_BUFFER_NOSYNC) syncflags |= WINED3DLOCK_NOOVERWRITE;
697
            LEAVE_GL();
698 699
            buffer_sync_apple(This, syncflags, gl_info);
            ENTER_GL();
700
        }
701 702 703 704 705 706 707 708 709
        map = GL_EXTCALL(glMapBufferARB(This->buffer_type_hint, GL_WRITE_ONLY_ARB));
        checkGLcall("glMapBufferARB");
    }
    if (!map)
    {
        LEAVE_GL();
        ERR("Failed to map opengl buffer\n");
        return;
    }
710

711 712 713 714 715
    while (This->modified_areas)
    {
        This->modified_areas--;
        start = This->maps[This->modified_areas].offset;
        len = This->maps[This->modified_areas].size;
716

717
        memcpy(map + start, This->resource.allocatedMemory + start, len);
718

719 720 721 722
        if (gl_info->supported[ARB_MAP_BUFFER_RANGE])
        {
            GL_EXTCALL(glFlushMappedBufferRange(This->buffer_type_hint, start, len));
            checkGLcall("glFlushMappedBufferRange");
723
        }
724 725 726 727 728 729 730 731 732 733
        else if (This->flags & WINED3D_BUFFER_FLUSH)
        {
            GL_EXTCALL(glFlushMappedBufferRangeAPPLE(This->buffer_type_hint, start, len));
            checkGLcall("glFlushMappedBufferRangeAPPLE");
        }
    }
    GL_EXTCALL(glUnmapBufferARB(This->buffer_type_hint));
    checkGLcall("glUnmapBufferARB");

    LEAVE_GL();
734 735
}

736
/* Do not call while under the GL lock. */
737
void CDECL wined3d_buffer_preload(struct wined3d_buffer *buffer)
738
{
739
    DWORD flags = buffer->flags & (WINED3D_BUFFER_NOSYNC | WINED3D_BUFFER_DISCARD);
740
    struct wined3d_device *device = buffer->resource.device;
741
    UINT start = 0, end = 0, len = 0, vertices;
742
    const struct wined3d_gl_info *gl_info;
743
    struct wined3d_context *context;
744 745 746 747
    BOOL decl_changed = FALSE;
    unsigned int i, j;
    BYTE *data;

748 749 750
    TRACE("buffer %p.\n", buffer);

    buffer->flags &= ~(WINED3D_BUFFER_NOSYNC | WINED3D_BUFFER_DISCARD);
751

752
    if (!buffer->buffer_object)
753 754
    {
        /* TODO: Make converting independent from VBOs */
755
        if (buffer->flags & WINED3D_BUFFER_CREATEBO)
756
        {
757 758 759
            context = context_acquire(device, NULL);
            buffer_create_buffer_object(buffer, context->gl_info);
            context_release(context);
760
            buffer->flags &= ~WINED3D_BUFFER_CREATEBO;
761 762 763
        }
        else
        {
764
            /* Not doing any conversion */
765
            return;
766 767 768 769
        }
    }

    /* Reading the declaration makes only sense if the stateblock is finalized and the buffer bound to a stream */
770
    if (device->isInDraw && buffer->bind_count > 0)
771
    {
772 773
        decl_changed = buffer_find_decl(buffer);
        buffer->flags |= WINED3D_BUFFER_HASDESC;
774
    }
775

776
    if (!decl_changed && !(buffer->flags & WINED3D_BUFFER_HASDESC && buffer_is_dirty(buffer)))
777
    {
778 779 780 781 782
        ++buffer->draw_count;
        if (buffer->draw_count > VB_RESETDECLCHANGE)
            buffer->decl_change_count = 0;
        if (buffer->draw_count > VB_RESETFULLCONVS)
            buffer->full_conversion_count = 0;
783 784
        return;
    }
785 786 787 788 789

    /* If applications change the declaration over and over, reconverting all the time is a huge
     * performance hit. So count the declaration changes and release the VBO if there are too many
     * of them (and thus stop converting)
     */
790
    if (decl_changed)
791
    {
792 793
        ++buffer->decl_change_count;
        buffer->draw_count = 0;
794

795 796
        if (buffer->decl_change_count > VB_MAXDECLCHANGES
                || (buffer->conversion_map && (buffer->resource.usage & WINED3DUSAGE_DYNAMIC)))
797
        {
798
            FIXME("Too many declaration changes or converting dynamic buffer, stopping converting\n");
799

800 801
            buffer_unload(&buffer->resource);
            buffer->flags &= ~WINED3D_BUFFER_CREATEBO;
802

803 804 805 806 807
            /* The stream source state handler might have read the memory of
             * the vertex buffer already and got the memory in the vbo which
             * is not valid any longer. Dirtify the stream source to force a
             * reload. This happens only once per changed vertexbuffer and
             * should occur rather rarely. */
808
            device_invalidate_state(device, STATE_STREAMSRC);
809
            return;
810
        }
811 812 813

        /* The declaration changed, reload the whole buffer */
        WARN("Reloading buffer because of decl change\n");
814 815
        buffer_clear_dirty_areas(buffer);
        if (!buffer_add_dirty_area(buffer, 0, 0))
816 817
        {
            ERR("buffer_add_dirty_area failed, this is not expected\n");
818
            return;
819 820 821 822 823
        }
        /* Avoid unfenced updates, we might overwrite more areas of the buffer than the application
         * cleared for unsynchronized updates
         */
        flags = 0;
824 825 826 827 828 829 830
    }
    else
    {
        /* However, it is perfectly fine to change the declaration every now and then. We don't want a game that
         * changes it every minute drop the VBO after VB_MAX_DECL_CHANGES minutes. So count draws without
         * decl changes and reset the decl change count after a specific number of them
         */
831
        if (buffer->conversion_map && buffer_is_fully_dirty(buffer))
832
        {
833 834
            ++buffer->full_conversion_count;
            if (buffer->full_conversion_count > VB_MAXFULLCONVERSIONS)
835
            {
836 837 838
                FIXME("Too many full buffer conversions, stopping converting.\n");
                buffer_unload(&buffer->resource);
                buffer->flags &= ~WINED3D_BUFFER_CREATEBO;
839 840
                if (buffer->bind_count)
                    device_invalidate_state(device, STATE_STREAMSRC);
841
                return;
842 843 844 845
            }
        }
        else
        {
846 847 848 849 850
            ++buffer->draw_count;
            if (buffer->draw_count > VB_RESETDECLCHANGE)
                buffer->decl_change_count = 0;
            if (buffer->draw_count > VB_RESETFULLCONVS)
                buffer->full_conversion_count = 0;
851
        }
852 853
    }

854
    if (buffer->buffer_type_hint == GL_ELEMENT_ARRAY_BUFFER_ARB)
855
        device_invalidate_state(device, STATE_INDEXBUFFER);
856

857
    if (!buffer->conversion_map)
858
    {
859 860 861 862 863 864
        /* That means that there is nothing to fixup. Just upload from
         * buffer->resource.allocatedMemory directly into the vbo. Do not
         * free the system memory copy because drawPrimitive may need it if
         * the stride is 0, for instancing emulation, vertex blending
         * emulation or shader emulation. */
        TRACE("No conversion needed.\n");
865

866
        /* Nothing to do because we locked directly into the vbo */
867
        if (!(buffer->flags & WINED3D_BUFFER_DOUBLEBUFFER))
868 869 870
        {
            return;
        }
871

872
        context = context_acquire(device, NULL);
873
        buffer_direct_upload(buffer, context->gl_info, flags);
874 875

        context_release(context);
876 877 878
        return;
    }

879 880 881 882
    context = context_acquire(device, NULL);
    gl_info = context->gl_info;

    if(!(buffer->flags & WINED3D_BUFFER_DOUBLEBUFFER))
883
    {
884
        buffer_get_sysmem(buffer, gl_info);
885 886
    }

887
    /* Now for each vertex in the buffer that needs conversion */
888
    vertices = buffer->resource.size / buffer->stride;
889

890
    data = HeapAlloc(GetProcessHeap(), 0, buffer->resource.size);
891

892 893 894 895 896
    while(buffer->modified_areas)
    {
        buffer->modified_areas--;
        start = buffer->maps[buffer->modified_areas].offset;
        len = buffer->maps[buffer->modified_areas].size;
897 898
        end = start + len;

899
        memcpy(data + start, buffer->resource.allocatedMemory + start, end - start);
900
        for (i = start / buffer->stride; i < min((end / buffer->stride) + 1, vertices); ++i)
901
        {
902
            for (j = 0; j < buffer->stride; ++j)
903
            {
904
                switch (buffer->conversion_map[j])
905 906
                {
                    case CONV_NONE:
907 908
                        /* Done already */
                        j += 3;
909
                        break;
910 911 912
                    case CONV_D3DCOLOR:
                        fixup_d3dcolor((DWORD *) (data + i * buffer->stride + j));
                        j += 3;
913 914
                        break;

915 916 917
                    case CONV_POSITIONT:
                        fixup_transformed_pos((float *) (data + i * buffer->stride + j));
                        j += 15;
918
                        break;
919 920
                    default:
                        FIXME("Unimplemented conversion %d in shifted conversion\n", buffer->conversion_map[j]);
921 922 923 924 925
                }
            }
        }

        ENTER_GL();
926
        GL_EXTCALL(glBindBufferARB(buffer->buffer_type_hint, buffer->buffer_object));
927
        checkGLcall("glBindBufferARB");
928
        GL_EXTCALL(glBufferSubDataARB(buffer->buffer_type_hint, start, len, data + start));
929 930 931 932 933
        checkGLcall("glBufferSubDataARB");
        LEAVE_GL();
    }

    HeapFree(GetProcessHeap(), 0, data);
934
    context_release(context);
935 936
}

937
static DWORD buffer_sanitize_flags(const struct wined3d_buffer *buffer, DWORD flags)
938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958
{
    /* Not all flags make sense together, but Windows never returns an error. Catch the
     * cases that could cause issues */
    if(flags & WINED3DLOCK_READONLY)
    {
        if(flags & WINED3DLOCK_DISCARD)
        {
            WARN("WINED3DLOCK_READONLY combined with WINED3DLOCK_DISCARD, ignoring flags\n");
            return 0;
        }
        if(flags & WINED3DLOCK_NOOVERWRITE)
        {
            WARN("WINED3DLOCK_READONLY combined with WINED3DLOCK_NOOVERWRITE, ignoring flags\n");
            return 0;
        }
    }
    else if((flags & (WINED3DLOCK_DISCARD | WINED3DLOCK_NOOVERWRITE)) == (WINED3DLOCK_DISCARD | WINED3DLOCK_NOOVERWRITE))
    {
        WARN("WINED3DLOCK_DISCARD and WINED3DLOCK_NOOVERWRITE used together, ignoring\n");
        return 0;
    }
959 960 961 962 963
    else if (flags & (WINED3DLOCK_DISCARD | WINED3DLOCK_NOOVERWRITE) && !(buffer->resource.usage & WINED3DUSAGE_DYNAMIC))
    {
        WARN("DISCARD or NOOVERWRITE lock on non-dynamic buffer, ignoring\n");
        return 0;
    }
964 965 966 967

    return flags;
}

968 969
static GLbitfield buffer_gl_map_flags(DWORD d3d_flags)
{
970
    GLbitfield ret = 0;
971

972 973 974 975
    if (!(d3d_flags & WINED3DLOCK_READONLY))
        ret |= GL_MAP_WRITE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT;
    if (!(d3d_flags & (WINED3DLOCK_DISCARD | WINED3DLOCK_NOOVERWRITE)))
        ret |= GL_MAP_READ_BIT;
976

977 978 979
    if (d3d_flags & WINED3DLOCK_DISCARD)
        ret |= GL_MAP_INVALIDATE_BUFFER_BIT;
    if (d3d_flags & WINED3DLOCK_NOOVERWRITE)
980 981 982 983 984
        ret |= GL_MAP_UNSYNCHRONIZED_BIT;

    return ret;
}

985
struct wined3d_resource * CDECL wined3d_buffer_get_resource(struct wined3d_buffer *buffer)
986
{
987
    TRACE("buffer %p.\n", buffer);
988

989
    return &buffer->resource;
990 991
}

992
HRESULT CDECL wined3d_buffer_map(struct wined3d_buffer *buffer, UINT offset, UINT size, BYTE **data, DWORD flags)
993
{
994
    BOOL dirty = buffer_is_dirty(buffer);
995
    LONG count;
996

997
    TRACE("buffer %p, offset %u, size %u, data %p, flags %#x\n", buffer, offset, size, data, flags);
998

999
    flags = buffer_sanitize_flags(buffer, flags);
1000 1001
    if (!(flags & WINED3DLOCK_READONLY))
    {
1002
        if (!buffer_add_dirty_area(buffer, offset, size)) return E_OUTOFMEMORY;
1003
    }
1004

1005
    count = InterlockedIncrement(&buffer->lock_count);
1006

1007
    if (buffer->buffer_object)
1008
    {
1009
        if (!(buffer->flags & WINED3D_BUFFER_DOUBLEBUFFER))
1010
        {
1011
            if (count == 1)
1012
            {
1013
                struct wined3d_device *device = buffer->resource.device;
1014 1015
                struct wined3d_context *context;
                const struct wined3d_gl_info *gl_info;
1016

1017
                context = context_acquire(device, NULL);
1018
                gl_info = context->gl_info;
1019

1020
                ENTER_GL();
1021 1022 1023

                if (buffer->buffer_type_hint == GL_ELEMENT_ARRAY_BUFFER_ARB)
                    context_invalidate_state(context, STATE_INDEXBUFFER);
1024
                GL_EXTCALL(glBindBufferARB(buffer->buffer_type_hint, buffer->buffer_object));
1025 1026 1027 1028

                if (gl_info->supported[ARB_MAP_BUFFER_RANGE])
                {
                    GLbitfield mapflags = buffer_gl_map_flags(flags);
1029 1030
                    buffer->resource.allocatedMemory = GL_EXTCALL(glMapBufferRange(buffer->buffer_type_hint,
                            0, buffer->resource.size, mapflags));
1031
                    checkGLcall("glMapBufferRange");
1032 1033 1034
                }
                else
                {
1035
                    if (buffer->flags & WINED3D_BUFFER_APPLESYNC)
1036 1037
                    {
                        LEAVE_GL();
1038
                        buffer_sync_apple(buffer, flags, gl_info);
1039 1040
                        ENTER_GL();
                    }
1041 1042
                    buffer->resource.allocatedMemory = GL_EXTCALL(glMapBufferARB(buffer->buffer_type_hint,
                            GL_READ_WRITE_ARB));
1043
                    checkGLcall("glMapBufferARB");
1044 1045
                }
                LEAVE_GL();
1046

1047
                if (((DWORD_PTR)buffer->resource.allocatedMemory) & (RESOURCE_ALIGNMENT - 1))
1048
                {
1049
                    WARN("Pointer %p is not %u byte aligned.\n", buffer->resource.allocatedMemory, RESOURCE_ALIGNMENT);
1050 1051

                    ENTER_GL();
1052
                    GL_EXTCALL(glUnmapBufferARB(buffer->buffer_type_hint));
1053 1054
                    checkGLcall("glUnmapBufferARB");
                    LEAVE_GL();
1055
                    buffer->resource.allocatedMemory = NULL;
1056

1057 1058 1059 1060 1061 1062 1063 1064 1065
                    if (buffer->resource.usage & WINED3DUSAGE_DYNAMIC)
                    {
                        /* The extra copy is more expensive than not using VBOs at
                         * all on the Nvidia Linux driver, which is the only driver
                         * that returns unaligned pointers
                         */
                        TRACE("Dynamic buffer, dropping VBO\n");
                        buffer_unload(&buffer->resource);
                        buffer->flags &= ~WINED3D_BUFFER_CREATEBO;
1066 1067
                        if (buffer->bind_count)
                            device_invalidate_state(device, STATE_STREAMSRC);
1068 1069 1070 1071 1072 1073
                    }
                    else
                    {
                        TRACE("Falling back to doublebuffered operation\n");
                        buffer_get_sysmem(buffer, gl_info);
                    }
1074
                    TRACE("New pointer is %p.\n", buffer->resource.allocatedMemory);
1075
                }
1076 1077 1078 1079 1080 1081
                context_release(context);
            }
        }
        else
        {
            if (dirty)
1082
            {
1083
                if (buffer->flags & WINED3D_BUFFER_NOSYNC && !(flags & WINED3DLOCK_NOOVERWRITE))
1084
                {
1085
                    buffer->flags &= ~WINED3D_BUFFER_NOSYNC;
1086
                }
1087
            }
1088
            else if(flags & WINED3DLOCK_NOOVERWRITE)
1089
            {
1090
                buffer->flags |= WINED3D_BUFFER_NOSYNC;
1091 1092 1093 1094
            }

            if (flags & WINED3DLOCK_DISCARD)
            {
1095
                buffer->flags |= WINED3D_BUFFER_DISCARD;
1096
            }
1097 1098
        }
    }
1099

1100
    *data = buffer->resource.allocatedMemory + offset;
1101

1102 1103
    TRACE("Returning memory at %p (base %p, offset %u).\n", *data, buffer->resource.allocatedMemory, offset);
    /* TODO: check Flags compatibility with buffer->currentDesc.Usage (see MSDN) */
1104 1105 1106 1107

    return WINED3D_OK;
}

1108
void CDECL wined3d_buffer_unmap(struct wined3d_buffer *buffer)
1109
{
1110
    ULONG i;
1111

1112
    TRACE("buffer %p.\n", buffer);
1113

1114 1115 1116 1117
    /* In the case that the number of Unmap calls > the
     * number of Map calls, d3d returns always D3D_OK.
     * This is also needed to prevent Map from returning garbage on
     * the next call (this will happen if the lock_count is < 0). */
1118
    if (!buffer->lock_count)
1119
    {
1120
        WARN("Unmap called without a previous map call.\n");
1121
        return;
1122 1123
    }

1124
    if (InterlockedDecrement(&buffer->lock_count))
1125 1126
    {
        /* Delay loading the buffer until everything is unlocked */
1127
        TRACE("Ignoring unmap.\n");
1128
        return;
1129 1130
    }

1131
    if (!(buffer->flags & WINED3D_BUFFER_DOUBLEBUFFER) && buffer->buffer_object)
1132
    {
1133
        struct wined3d_device *device = buffer->resource.device;
1134
        const struct wined3d_gl_info *gl_info;
1135
        struct wined3d_context *context;
1136

1137
        context = context_acquire(device, NULL);
1138
        gl_info = context->gl_info;
1139

1140
        ENTER_GL();
1141 1142 1143

        if (buffer->buffer_type_hint == GL_ELEMENT_ARRAY_BUFFER_ARB)
            context_invalidate_state(context, STATE_INDEXBUFFER);
1144
        GL_EXTCALL(glBindBufferARB(buffer->buffer_type_hint, buffer->buffer_object));
1145

1146 1147
        if (gl_info->supported[ARB_MAP_BUFFER_RANGE])
        {
1148
            for (i = 0; i < buffer->modified_areas; ++i)
1149
            {
1150 1151
                GL_EXTCALL(glFlushMappedBufferRange(buffer->buffer_type_hint,
                        buffer->maps[i].offset, buffer->maps[i].size));
1152 1153 1154
                checkGLcall("glFlushMappedBufferRange");
            }
        }
1155
        else if (buffer->flags & WINED3D_BUFFER_FLUSH)
1156
        {
1157
            for (i = 0; i < buffer->modified_areas; ++i)
1158
            {
1159 1160
                GL_EXTCALL(glFlushMappedBufferRangeAPPLE(buffer->buffer_type_hint,
                        buffer->maps[i].offset, buffer->maps[i].size));
1161 1162 1163 1164
                checkGLcall("glFlushMappedBufferRangeAPPLE");
            }
        }

1165
        GL_EXTCALL(glUnmapBufferARB(buffer->buffer_type_hint));
1166
        LEAVE_GL();
1167
        if (wined3d_settings.strict_draw_ordering) wglFlush(); /* Flush to ensure ordering across contexts. */
1168
        context_release(context);
1169

1170 1171
        buffer->resource.allocatedMemory = NULL;
        buffer_clear_dirty_areas(buffer);
1172
    }
1173
    else if (buffer->flags & WINED3D_BUFFER_HASDESC)
1174
    {
1175
        wined3d_buffer_preload(buffer);
1176 1177 1178
    }
}

1179 1180 1181 1182 1183
static const struct wined3d_resource_ops buffer_resource_ops =
{
    buffer_unload,
};

1184
static HRESULT buffer_init(struct wined3d_buffer *buffer, struct wined3d_device *device,
1185
        UINT size, DWORD usage, enum wined3d_format_id format_id, WINED3DPOOL pool, GLenum bind_hint,
1186
        const char *data, void *parent, const struct wined3d_parent_ops *parent_ops)
1187
{
1188
    const struct wined3d_gl_info *gl_info = &device->adapter->gl_info;
1189 1190
    const struct wined3d_format *format = wined3d_get_format(gl_info, format_id);
    HRESULT hr;
1191
    BOOL dynamic_buffer_ok;
1192 1193 1194 1195 1196 1197 1198

    if (!size)
    {
        WARN("Size 0 requested, returning WINED3DERR_INVALIDCALL\n");
        return WINED3DERR_INVALIDCALL;
    }

1199 1200 1201
    hr = resource_init(&buffer->resource, device, WINED3DRTYPE_BUFFER, format,
            WINED3DMULTISAMPLE_NONE, 0, usage, pool, size, 1, 1, size,
            parent, parent_ops, &buffer_resource_ops);
1202 1203 1204 1205 1206 1207 1208 1209
    if (FAILED(hr))
    {
        WARN("Failed to initialize resource, hr %#x\n", hr);
        return hr;
    }
    buffer->buffer_type_hint = bind_hint;

    TRACE("size %#x, usage %#x, format %s, memory @ %p, iface @ %p.\n", buffer->resource.size, buffer->resource.usage,
1210
            debug_d3dformat(buffer->resource.format->id), buffer->resource.allocatedMemory, buffer);
1211

1212
    dynamic_buffer_ok = gl_info->supported[APPLE_FLUSH_BUFFER_RANGE] || gl_info->supported[ARB_MAP_BUFFER_RANGE];
1213

1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228
    /* Observations show that drawStridedSlow is faster on dynamic VBs than converting +
     * drawStridedFast (half-life 2 and others).
     *
     * Basically converting the vertices in the buffer is quite expensive, and observations
     * show that drawStridedSlow is faster than converting + uploading + drawStridedFast.
     * Therefore do not create a VBO for WINED3DUSAGE_DYNAMIC buffers.
     */
    if (!gl_info->supported[ARB_VERTEX_BUFFER_OBJECT])
    {
        TRACE("Not creating a vbo because GL_ARB_vertex_buffer is not supported\n");
    }
    else if(buffer->resource.pool == WINED3DPOOL_SYSTEMMEM)
    {
        TRACE("Not creating a vbo because the vertex buffer is in system memory\n");
    }
1229
    else if(!dynamic_buffer_ok && (buffer->resource.usage & WINED3DUSAGE_DYNAMIC))
1230
    {
1231
        TRACE("Not creating a vbo because the buffer has dynamic usage and no GL support\n");
1232 1233 1234 1235 1236 1237
    }
    else
    {
        buffer->flags |= WINED3D_BUFFER_CREATEBO;
    }

1238 1239 1240 1241
    if (data)
    {
        BYTE *ptr;

1242
        hr = wined3d_buffer_map(buffer, 0, size, &ptr, 0);
1243 1244 1245
        if (FAILED(hr))
        {
            ERR("Failed to map buffer, hr %#x\n", hr);
1246 1247
            buffer_unload(&buffer->resource);
            resource_cleanup(&buffer->resource);
1248 1249 1250 1251 1252
            return hr;
        }

        memcpy(ptr, data, size);

1253
        wined3d_buffer_unmap(buffer);
1254 1255
    }

1256 1257 1258 1259
    buffer->maps = HeapAlloc(GetProcessHeap(), 0, sizeof(*buffer->maps));
    if (!buffer->maps)
    {
        ERR("Out of memory\n");
1260 1261
        buffer_unload(&buffer->resource);
        resource_cleanup(&buffer->resource);
1262 1263 1264 1265
        return E_OUTOFMEMORY;
    }
    buffer->maps_size = 1;

1266 1267
    return WINED3D_OK;
}
1268

1269
HRESULT CDECL wined3d_buffer_create(struct wined3d_device *device, struct wined3d_buffer_desc *desc, const void *data,
1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302
        void *parent, const struct wined3d_parent_ops *parent_ops, struct wined3d_buffer **buffer)
{
    struct wined3d_buffer *object;
    HRESULT hr;

    TRACE("device %p, desc %p, data %p, parent %p, buffer %p\n", device, desc, data, parent, buffer);

    object = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*object));
    if (!object)
    {
        ERR("Failed to allocate memory\n");
        return E_OUTOFMEMORY;
    }

    FIXME("Ignoring access flags (pool)\n");

    hr = buffer_init(object, device, desc->byte_width, desc->usage, WINED3DFMT_UNKNOWN,
            WINED3DPOOL_MANAGED, GL_ARRAY_BUFFER_ARB, data, parent, parent_ops);
    if (FAILED(hr))
    {
        WARN("Failed to initialize buffer, hr %#x.\n", hr);
        HeapFree(GetProcessHeap(), 0, object);
        return hr;
    }
    object->desc = *desc;

    TRACE("Created buffer %p.\n", object);

    *buffer = object;

    return WINED3D_OK;
}

1303
HRESULT CDECL wined3d_buffer_create_vb(struct wined3d_device *device, UINT size, DWORD usage, WINED3DPOOL pool,
1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343
        void *parent, const struct wined3d_parent_ops *parent_ops, struct wined3d_buffer **buffer)
{
    struct wined3d_buffer *object;
    HRESULT hr;

    TRACE("device %p, size %u, usage %#x, pool %#x, parent %p, parent_ops %p, buffer %p.\n",
            device, size, usage, pool, parent, parent_ops, buffer);

    if (pool == WINED3DPOOL_SCRATCH)
    {
        /* The d3d9 tests shows that this is not allowed. It doesn't make much
         * sense anyway, SCRATCH buffers wouldn't be usable anywhere. */
        WARN("Vertex buffer in WINED3DPOOL_SCRATCH requested, returning WINED3DERR_INVALIDCALL.\n");
        *buffer = NULL;
        return WINED3DERR_INVALIDCALL;
    }

    object = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*object));
    if (!object)
    {
        ERR("Out of memory\n");
        *buffer = NULL;
        return WINED3DERR_OUTOFVIDEOMEMORY;
    }

    hr = buffer_init(object, device, size, usage, WINED3DFMT_VERTEXDATA,
            pool, GL_ARRAY_BUFFER_ARB, NULL, parent, parent_ops);
    if (FAILED(hr))
    {
        WARN("Failed to initialize buffer, hr %#x.\n", hr);
        HeapFree(GetProcessHeap(), 0, object);
        return hr;
    }

    TRACE("Created buffer %p.\n", object);
    *buffer = object;

    return WINED3D_OK;
}

1344
HRESULT CDECL wined3d_buffer_create_ib(struct wined3d_device *device, UINT size, DWORD usage, WINED3DPOOL pool,
1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375
        void *parent, const struct wined3d_parent_ops *parent_ops, struct wined3d_buffer **buffer)
{
    struct wined3d_buffer *object;
    HRESULT hr;

    TRACE("device %p, size %u, usage %#x, pool %#x, parent %p, parent_ops %p, buffer %p.\n",
            device, size, usage, pool, parent, parent_ops, buffer);

    object = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*object));
    if (!object)
    {
        ERR("Out of memory\n");
        *buffer = NULL;
        return WINED3DERR_OUTOFVIDEOMEMORY;
    }

    hr = buffer_init(object, device, size, usage | WINED3DUSAGE_STATICDECL,
            WINED3DFMT_UNKNOWN, pool, GL_ELEMENT_ARRAY_BUFFER_ARB, NULL,
            parent, parent_ops);
    if (FAILED(hr))
    {
        WARN("Failed to initialize buffer, hr %#x\n", hr);
        HeapFree(GetProcessHeap(), 0, object);
        return hr;
    }

    TRACE("Created buffer %p.\n", object);
    *buffer = object;

    return WINED3D_OK;
}