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 79 80
    {
        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;
}

static inline BOOL buffer_is_dirty(struct wined3d_buffer *This)
{
81
    return !!This->modified_areas;
82 83 84 85 86 87 88 89
}

static inline BOOL buffer_is_fully_dirty(struct wined3d_buffer *This)
{
    unsigned int i;

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

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

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

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

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

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

125
    ENTER_GL();
126

127 128 129 130 131 132
    /* 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);
133

134 135 136 137 138
    /* 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. */
139

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

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

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

        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;
173 174 175 176

            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;
177
        }
178
        /* No setup is needed here for GL_ARB_map_buffer_range */
179
    }
180 181 182 183 184
    else
    {
        TRACE("Gl usage = GL_DYNAMIC_DRAW_ARB\n");
        gl_usage = GL_DYNAMIC_DRAW_ARB;
    }
185

186
    /* Reserve memory for the buffer. The amount of data won't change
187 188 189 190 191
     * 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));
192
    error = glGetError();
193
    LEAVE_GL();
194
    if (error != GL_NO_ERROR)
195
    {
196 197
        ERR("glBufferDataARB failed with error %s (%#x)\n", debug_glerror(error), error);
        goto fail;
198 199
    }

200 201
    This->buffer_object_size = This->resource.size;
    This->buffer_object_usage = gl_usage;
202 203 204

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

218
    return;
219

220 221 222
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");
223
    delete_gl_buffer(This, gl_info);
224
    buffer_clear_dirty_areas(This);
225 226
}

227 228
static BOOL buffer_process_converted_attribute(struct wined3d_buffer *This,
        const enum wined3d_buffer_conversion_type conversion_type,
229
        const struct wined3d_stream_info_element *attrib, DWORD *stride_this_run)
230 231 232
{
    DWORD attrib_size;
    BOOL ret = FALSE;
233
    unsigned int i;
234 235 236 237 238 239 240
    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.
     */
241
    if (!attrib->stride)
242 243
    {
        FIXME("%s used with stride 0, let's hope we get the vertex stride from somewhere else\n",
244
                debug_d3dformat(attrib->format->id));
245
    }
246
    else if(attrib->stride != *stride_this_run && *stride_this_run)
247
    {
248
        FIXME("Got two concurrent strides, %d and %d\n", attrib->stride, *stride_this_run);
249 250 251
    }
    else
    {
252
        *stride_this_run = attrib->stride;
253 254 255 256 257 258 259
        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;
260 261 262
            HeapFree(GetProcessHeap(), HEAP_ZERO_MEMORY, This->conversion_map);
            This->conversion_map = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
                    sizeof(*This->conversion_map) * This->stride);
263 264 265 266
            ret = TRUE;
        }
    }

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

    return ret;
}

284 285
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,
286
        DWORD *stride_this_run)
287
{
288
    const struct wined3d_stream_info_element *attrib = &si->elements[attrib_idx];
289
    enum wined3d_format_id format;
290 291 292 293 294
    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.
     */
295
    if (!(si->use_map & (1 << attrib_idx))
296
            || attrib->data.buffer_object != This->buffer_object)
297
        return FALSE;
298

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

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

    return ret;
}

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

    /* In d3d7 the vertex buffer declaration NEVER changes because it is stored in the d3d7 vertex buffer.
330 331
     * 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
332
     */
333 334
    if (This->flags & WINED3D_BUFFER_HASDESC)
    {
335
        if(This->resource.usage & WINED3DUSAGE_STATICDECL) return FALSE;
336
    }
337

338 339 340 341 342 343 344 345 346 347 348 349 350 351
    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;
    }

352 353 354 355
    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
356
     * GL_ARB_half_float_vertex is not supported.
357
     *
358
     * Note for d3d8 and d3d9:
359 360 361 362 363
     * 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.
364 365 366
     * 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.
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 396 397
     *
     * 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
     */

398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421
    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;
422

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

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

    return ret;
}

437
static inline void fixup_d3dcolor(DWORD *dst_color)
438
{
439
    DWORD src_color = *dst_color;
440

441 442 443 444 445 446 447 448 449 450 451 452 453
    /* 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 */
}
454

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

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

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

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

    return refcount;
}

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

    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));
511 512

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

515 516 517 518 519 520 521 522 523
    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;
}

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

529
    TRACE("buffer %p.\n", buffer);
530

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

536
        context = context_acquire(device, NULL);
537 538

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

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

        context_release(context);
550

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

558
    resource_unload(resource);
559
}
560

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

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

    if (!refcount)
569
    {
570 571 572 573 574
        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);
575 576
    }

577 578
    return refcount;
}
579

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

584
    return buffer->resource.parent;
585
}
586

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

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

597 598
/* 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)
599
{
600 601
    enum wined3d_event_query_result ret;

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

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

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

        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;
        }

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 660 661
        /* 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();
662 663
    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)");
664
    LEAVE_GL();
665 666 667
    This->flags &= ~WINED3D_BUFFER_APPLESYNC;
}

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

674 675 676 677 678 679 680 681 682 683 684
    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)
685 686
            mapflags |= GL_MAP_INVALIDATE_BUFFER_BIT;
        if (flags & WINED3D_BUFFER_NOSYNC)
687 688 689 690 691 692 693 694
            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)
695
        {
696 697 698
            DWORD syncflags = 0;
            if (flags & WINED3D_BUFFER_DISCARD) syncflags |= WINED3DLOCK_DISCARD;
            if (flags & WINED3D_BUFFER_NOSYNC) syncflags |= WINED3DLOCK_NOOVERWRITE;
699
            LEAVE_GL();
700 701
            buffer_sync_apple(This, syncflags, gl_info);
            ENTER_GL();
702
        }
703 704 705 706 707 708 709 710 711
        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;
    }
712

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

719
        memcpy(map + start, This->resource.allocatedMemory + start, len);
720

721 722 723 724
        if (gl_info->supported[ARB_MAP_BUFFER_RANGE])
        {
            GL_EXTCALL(glFlushMappedBufferRange(This->buffer_type_hint, start, len));
            checkGLcall("glFlushMappedBufferRange");
725
        }
726 727 728 729 730 731 732 733 734 735
        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();
736 737
}

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

750 751 752
    TRACE("buffer %p.\n", buffer);

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

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

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

778
    if (!decl_changed && !(buffer->flags & WINED3D_BUFFER_HASDESC && buffer_is_dirty(buffer)))
779
    {
780 781 782 783 784
        ++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;
785 786
        return;
    }
787 788 789 790 791

    /* 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)
     */
792
    if (decl_changed)
793
    {
794 795
        ++buffer->decl_change_count;
        buffer->draw_count = 0;
796

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

802 803
            buffer_unload(&buffer->resource);
            buffer->flags &= ~WINED3D_BUFFER_CREATEBO;
804

805 806 807 808 809
            /* 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. */
810
            device_invalidate_state(device, STATE_STREAMSRC);
811
            return;
812
        }
813 814 815

        /* The declaration changed, reload the whole buffer */
        WARN("Reloading buffer because of decl change\n");
816 817
        buffer_clear_dirty_areas(buffer);
        if (!buffer_add_dirty_area(buffer, 0, 0))
818 819
        {
            ERR("buffer_add_dirty_area failed, this is not expected\n");
820
            return;
821 822 823 824 825
        }
        /* Avoid unfenced updates, we might overwrite more areas of the buffer than the application
         * cleared for unsynchronized updates
         */
        flags = 0;
826 827 828 829 830 831 832
    }
    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
         */
833
        if (buffer->conversion_map && buffer_is_fully_dirty(buffer))
834
        {
835 836
            ++buffer->full_conversion_count;
            if (buffer->full_conversion_count > VB_MAXFULLCONVERSIONS)
837
            {
838 839 840
                FIXME("Too many full buffer conversions, stopping converting.\n");
                buffer_unload(&buffer->resource);
                buffer->flags &= ~WINED3D_BUFFER_CREATEBO;
841 842
                if (buffer->bind_count)
                    device_invalidate_state(device, STATE_STREAMSRC);
843
                return;
844 845 846 847
            }
        }
        else
        {
848 849 850 851 852
            ++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;
853
        }
854 855
    }

856
    if (buffer->buffer_type_hint == GL_ELEMENT_ARRAY_BUFFER_ARB)
857
        device_invalidate_state(device, STATE_INDEXBUFFER);
858

859
    if (!buffer->conversion_map)
860
    {
861 862 863 864 865 866
        /* 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");
867

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

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

        context_release(context);
878 879 880
        return;
    }

881 882 883 884
    context = context_acquire(device, NULL);
    gl_info = context->gl_info;

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

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

892
    data = HeapAlloc(GetProcessHeap(), 0, buffer->resource.size);
893

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

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

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

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

    HeapFree(GetProcessHeap(), 0, data);
936
    context_release(context);
937 938
}

939
static DWORD buffer_sanitize_flags(struct wined3d_buffer *buffer, DWORD flags)
940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960
{
    /* 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;
    }
961 962 963 964 965
    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;
    }
966 967 968 969

    return flags;
}

970 971
static GLbitfield buffer_gl_map_flags(DWORD d3d_flags)
{
972
    GLbitfield ret = 0;
973

974 975 976 977
    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;
978

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

    return ret;
}

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

991
    return &buffer->resource;
992 993
}

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

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

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

1007
    count = InterlockedIncrement(&buffer->lock_count);
1008

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

1019
                context = context_acquire(device, NULL);
1020
                gl_info = context->gl_info;
1021

1022
                ENTER_GL();
1023 1024 1025

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

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

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

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

1059 1060 1061 1062 1063 1064 1065 1066 1067
                    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;
1068 1069
                        if (buffer->bind_count)
                            device_invalidate_state(device, STATE_STREAMSRC);
1070 1071 1072 1073 1074 1075
                    }
                    else
                    {
                        TRACE("Falling back to doublebuffered operation\n");
                        buffer_get_sysmem(buffer, gl_info);
                    }
1076
                    TRACE("New pointer is %p.\n", buffer->resource.allocatedMemory);
1077
                }
1078 1079 1080 1081 1082 1083
                context_release(context);
            }
        }
        else
        {
            if (dirty)
1084
            {
1085
                if (buffer->flags & WINED3D_BUFFER_NOSYNC && !(flags & WINED3DLOCK_NOOVERWRITE))
1086
                {
1087
                    buffer->flags &= ~WINED3D_BUFFER_NOSYNC;
1088
                }
1089
            }
1090
            else if(flags & WINED3DLOCK_NOOVERWRITE)
1091
            {
1092
                buffer->flags |= WINED3D_BUFFER_NOSYNC;
1093 1094 1095 1096
            }

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

1102
    *data = buffer->resource.allocatedMemory + offset;
1103

1104 1105
    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) */
1106 1107 1108 1109

    return WINED3D_OK;
}

1110
void CDECL wined3d_buffer_unmap(struct wined3d_buffer *buffer)
1111
{
1112
    ULONG i;
1113

1114
    TRACE("buffer %p.\n", buffer);
1115

1116 1117 1118 1119
    /* 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). */
1120
    if (!buffer->lock_count)
1121
    {
1122
        WARN("Unmap called without a previous map call.\n");
1123
        return;
1124 1125
    }

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

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

1139
        context = context_acquire(device, NULL);
1140
        gl_info = context->gl_info;
1141

1142
        ENTER_GL();
1143 1144 1145

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

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

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

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

1181 1182 1183 1184 1185
static const struct wined3d_resource_ops buffer_resource_ops =
{
    buffer_unload,
};

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

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

1201 1202 1203
    hr = resource_init(&buffer->resource, device, WINED3DRTYPE_BUFFER, format,
            WINED3DMULTISAMPLE_NONE, 0, usage, pool, size, 1, 1, size,
            parent, parent_ops, &buffer_resource_ops);
1204 1205 1206 1207 1208 1209 1210 1211
    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,
1212
            debug_d3dformat(buffer->resource.format->id), buffer->resource.allocatedMemory, buffer);
1213

1214
    dynamic_buffer_ok = gl_info->supported[APPLE_FLUSH_BUFFER_RANGE] || gl_info->supported[ARB_MAP_BUFFER_RANGE];
1215

1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230
    /* 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");
    }
1231
    else if(!dynamic_buffer_ok && (buffer->resource.usage & WINED3DUSAGE_DYNAMIC))
1232
    {
1233
        TRACE("Not creating a vbo because the buffer has dynamic usage and no GL support\n");
1234 1235 1236 1237 1238 1239
    }
    else
    {
        buffer->flags |= WINED3D_BUFFER_CREATEBO;
    }

1240 1241 1242 1243
    if (data)
    {
        BYTE *ptr;

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

        memcpy(ptr, data, size);

1255
        wined3d_buffer_unmap(buffer);
1256 1257
    }

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

1268 1269
    return WINED3D_OK;
}
1270

1271
HRESULT CDECL wined3d_buffer_create(struct wined3d_device *device, struct wined3d_buffer_desc *desc, const void *data,
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 1303 1304
        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;
}

1305
HRESULT CDECL wined3d_buffer_create_vb(struct wined3d_device *device, UINT size, DWORD usage, WINED3DPOOL pool,
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 1344 1345
        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;
}

1346
HRESULT CDECL wined3d_buffer_create_ib(struct wined3d_device *device, UINT size, DWORD usage, WINED3DPOOL pool,
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 1376 1377
        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;
}