query.c 19.8 KB
Newer Older
1 2
/*
 * Copyright 2005 Oliver Stieber
3
 * Copyright 2007-2008 Stefan Dösinger for CodeWeavers
4
 * Copyright 2009-2010 Henri Verbeet for CodeWeavers.
5 6 7 8 9 10 11 12 13 14 15 16 17
 *
 * 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
18
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 20 21 22
 */


#include "config.h"
23
#include "wine/port.h"
24 25
#include "wined3d_private.h"

26 27
WINE_DEFAULT_DEBUG_CHANNEL(d3d);

28
BOOL wined3d_event_query_supported(const struct wined3d_gl_info *gl_info)
29
{
30
    return gl_info->supported[ARB_SYNC] || gl_info->supported[NV_FENCE] || gl_info->supported[APPLE_FENCE];
31 32
}

33
void wined3d_event_query_destroy(struct wined3d_event_query *query)
34 35 36 37 38
{
    if (query->context) context_free_event_query(query);
    HeapFree(GetProcessHeap(), 0, query);
}

39
static enum wined3d_event_query_result wined3d_event_query_test(const struct wined3d_event_query *query,
40
        const struct wined3d_device *device)
41 42 43 44 45 46 47 48
{
    struct wined3d_context *context;
    const struct wined3d_gl_info *gl_info;
    enum wined3d_event_query_result ret;
    BOOL fence_result;

    TRACE("(%p) : device %p\n", query, device);

49
    if (!query->context)
50 51 52 53 54 55 56 57 58 59 60
    {
        TRACE("Query not started\n");
        return WINED3D_EVENT_QUERY_NOT_STARTED;
    }

    if (!query->context->gl_info->supported[ARB_SYNC] && query->context->tid != GetCurrentThreadId())
    {
        WARN("Event query tested from wrong thread\n");
        return WINED3D_EVENT_QUERY_WRONG_THREAD;
    }

61
    context = context_acquire(device, query->context->current_rt);
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
    gl_info = context->gl_info;

    ENTER_GL();

    if (gl_info->supported[ARB_SYNC])
    {
        GLenum gl_ret = GL_EXTCALL(glClientWaitSync(query->object.sync, 0, 0));
        checkGLcall("glClientWaitSync");

        switch (gl_ret)
        {
            case GL_ALREADY_SIGNALED:
            case GL_CONDITION_SATISFIED:
                ret = WINED3D_EVENT_QUERY_OK;
                break;

            case GL_TIMEOUT_EXPIRED:
                ret = WINED3D_EVENT_QUERY_WAITING;
                break;

            case GL_WAIT_FAILED:
            default:
                ERR("glClientWaitSync returned %#x.\n", gl_ret);
                ret = WINED3D_EVENT_QUERY_ERROR;
        }
    }
    else if (gl_info->supported[APPLE_FENCE])
    {
        fence_result = GL_EXTCALL(glTestFenceAPPLE(query->object.id));
        checkGLcall("glTestFenceAPPLE");
        if (fence_result) ret = WINED3D_EVENT_QUERY_OK;
        else ret = WINED3D_EVENT_QUERY_WAITING;
    }
    else if (gl_info->supported[NV_FENCE])
    {
        fence_result = GL_EXTCALL(glTestFenceNV(query->object.id));
        checkGLcall("glTestFenceNV");
        if (fence_result) ret = WINED3D_EVENT_QUERY_OK;
        else ret = WINED3D_EVENT_QUERY_WAITING;
    }
    else
    {
104 105
        ERR("Event query created despite lack of GL support\n");
        ret = WINED3D_EVENT_QUERY_ERROR;
106 107 108 109 110 111 112 113
    }

    LEAVE_GL();

    context_release(context);
    return ret;
}

114
enum wined3d_event_query_result wined3d_event_query_finish(const struct wined3d_event_query *query,
115
        const struct wined3d_device *device)
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
{
    struct wined3d_context *context;
    const struct wined3d_gl_info *gl_info;
    enum wined3d_event_query_result ret;

    TRACE("(%p)\n", query);

    if (!query->context)
    {
        TRACE("Query not started\n");
        return WINED3D_EVENT_QUERY_NOT_STARTED;
    }
    gl_info = query->context->gl_info;

    if (query->context->tid != GetCurrentThreadId() && !gl_info->supported[ARB_SYNC])
    {
        /* A glFinish does not reliably wait for draws in other contexts. The caller has
         * to find its own way to cope with the thread switch
         */
        WARN("Event query finished from wrong thread\n");
        return WINED3D_EVENT_QUERY_WRONG_THREAD;
    }

139
    context = context_acquire(device, query->context->current_rt);
140 141 142 143

    ENTER_GL();
    if (gl_info->supported[ARB_SYNC])
    {
144 145 146 147
        /* Apple seems to be into arbitrary limits, and timeouts larger than
         * 0xfffffffffffffbff immediately return GL_TIMEOUT_EXPIRED. We don't
         * really care and can live with waiting a few μs less. (OS X 10.7.4). */
        GLenum gl_ret = GL_EXTCALL(glClientWaitSync(query->object.sync, GL_SYNC_FLUSH_COMMANDS_BIT, ~(GLuint64)0xffff));
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
        checkGLcall("glClientWaitSync");

        switch (gl_ret)
        {
            case GL_ALREADY_SIGNALED:
            case GL_CONDITION_SATISFIED:
                ret = WINED3D_EVENT_QUERY_OK;
                break;

                /* We don't expect a timeout for a ~584 year wait */
            default:
                ERR("glClientWaitSync returned %#x.\n", gl_ret);
                ret = WINED3D_EVENT_QUERY_ERROR;
        }
    }
    else if (context->gl_info->supported[APPLE_FENCE])
    {
        GL_EXTCALL(glFinishFenceAPPLE(query->object.id));
        checkGLcall("glFinishFenceAPPLE");
        ret = WINED3D_EVENT_QUERY_OK;
    }
    else if (context->gl_info->supported[NV_FENCE])
    {
        GL_EXTCALL(glFinishFenceNV(query->object.id));
        checkGLcall("glFinishFenceNV");
        ret = WINED3D_EVENT_QUERY_OK;
    }
    else
    {
        ERR("Event query created without GL support\n");
        ret = WINED3D_EVENT_QUERY_ERROR;
    }
    LEAVE_GL();

    context_release(context);
    return ret;
}

186
void wined3d_event_query_issue(struct wined3d_event_query *query, const struct wined3d_device *device)
187 188 189 190 191 192 193 194 195
{
    const struct wined3d_gl_info *gl_info;
    struct wined3d_context *context;

    if (query->context)
    {
        if (!query->context->gl_info->supported[ARB_SYNC] && query->context->tid != GetCurrentThreadId())
        {
            context_free_event_query(query);
196
            context = context_acquire(device, NULL);
197 198 199 200
            context_alloc_event_query(context, query);
        }
        else
        {
201
            context = context_acquire(device, query->context->current_rt);
202 203 204 205
        }
    }
    else
    {
206
        context = context_acquire(device, NULL);
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
        context_alloc_event_query(context, query);
    }

    gl_info = context->gl_info;

    ENTER_GL();

    if (gl_info->supported[ARB_SYNC])
    {
        if (query->object.sync) GL_EXTCALL(glDeleteSync(query->object.sync));
        checkGLcall("glDeleteSync");
        query->object.sync = GL_EXTCALL(glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0));
        checkGLcall("glFenceSync");
    }
    else if (gl_info->supported[APPLE_FENCE])
    {
        GL_EXTCALL(glSetFenceAPPLE(query->object.id));
        checkGLcall("glSetFenceAPPLE");
    }
    else if (gl_info->supported[NV_FENCE])
    {
        GL_EXTCALL(glSetFenceNV(query->object.id, GL_ALL_COMPLETED_NV));
        checkGLcall("glSetFenceNV");
    }

    LEAVE_GL();

    context_release(context);
}

237
ULONG CDECL wined3d_query_incref(struct wined3d_query *query)
238
{
239
    ULONG refcount = InterlockedIncrement(&query->ref);
240

241
    TRACE("%p increasing refcount to %u.\n", query, refcount);
242

243
    return refcount;
244 245
}

246 247
ULONG CDECL wined3d_query_decref(struct wined3d_query *query)
{
248
    ULONG refcount = InterlockedDecrement(&query->ref);
249

250
    TRACE("%p decreasing refcount to %u.\n", query, refcount);
251

252
    if (!refcount)
253
    {
254 255 256 257
        /* Queries are specific to the GL context that created them. Not
         * deleting the query will obviously leak it, but that's still better
         * than potentially deleting a different query with the same id in this
         * context, and (still) leaking the actual query. */
258
        if (query->type == WINED3D_QUERY_TYPE_EVENT)
259
        {
260 261
            struct wined3d_event_query *event_query = query->extendedData;
            if (event_query) wined3d_event_query_destroy(event_query);
262
        }
263
        else if (query->type == WINED3D_QUERY_TYPE_OCCLUSION)
264
        {
265
            struct wined3d_occlusion_query *oq = query->extendedData;
266

267 268
            if (oq->context) context_free_occlusion_query(oq);
            HeapFree(GetProcessHeap(), 0, query->extendedData);
269 270
        }

271
        HeapFree(GetProcessHeap(), 0, query);
272
    }
273 274

    return refcount;
275 276
}

277 278
HRESULT CDECL wined3d_query_get_data(struct wined3d_query *query,
        void *data, UINT data_size, DWORD flags)
279
{
280 281
    TRACE("query %p, data %p, data_size %u, flags %#x.\n",
            query, data, data_size, flags);
282 283 284 285

    return query->query_ops->query_get_data(query, data, data_size, flags);
}

286
UINT CDECL wined3d_query_get_data_size(const struct wined3d_query *query)
287
{
288
    TRACE("query %p.\n", query);
289 290 291 292

    return query->data_size;
}

293
HRESULT CDECL wined3d_query_issue(struct wined3d_query *query, DWORD flags)
294
{
295
    TRACE("query %p, flags %#x.\n", query, flags);
296 297 298 299

    return query->query_ops->query_issue(query, flags);
}

300
static HRESULT wined3d_occlusion_query_ops_get_data(struct wined3d_query *query,
301 302
        void *pData, DWORD dwSize, DWORD flags)
{
303
    struct wined3d_occlusion_query *oq = query->extendedData;
304
    struct wined3d_device *device = query->device;
305
    const struct wined3d_gl_info *gl_info = &device->adapter->gl_info;
306
    struct wined3d_context *context;
307
    DWORD* data = pData;
308 309
    GLuint available;
    GLuint samples;
310
    HRESULT res;
311

312
    TRACE("(%p) : type D3DQUERY_OCCLUSION, pData %p, dwSize %#x, flags %#x.\n", query, pData, dwSize, flags);
313

314 315
    if (!oq->context)
        query->state = QUERY_CREATED;
316

317
    if (query->state == QUERY_CREATED)
318
    {
319 320 321
        /* D3D allows GetData on a new query, OpenGL doesn't. So just invent the data ourselves */
        TRACE("Query wasn't yet started, returning S_OK\n");
        if(data) *data = 0;
322 323 324
        return S_OK;
    }

325
    if (query->state == QUERY_BUILDING)
326
    {
327 328
        /* Msdn says this returns an error, but our tests show that S_FALSE is returned */
        TRACE("Query is building, returning S_FALSE\n");
329
        return S_FALSE;
330
    }
331

332
    if (!gl_info->supported[ARB_OCCLUSION_QUERY])
333
    {
334
        WARN("%p Occlusion queries not supported. Returning 1.\n", query);
335
        *data = 1;
336 337 338
        return S_OK;
    }

339
    if (oq->context->tid != GetCurrentThreadId())
340
    {
341
        FIXME("%p Wrong thread, returning 1.\n", query);
342 343 344 345
        *data = 1;
        return S_OK;
    }

346
    context = context_acquire(query->device, oq->context->current_rt);
347

348 349
    ENTER_GL();

350
    GL_EXTCALL(glGetQueryObjectuivARB(oq->id, GL_QUERY_RESULT_AVAILABLE_ARB, &available));
351
    checkGLcall("glGetQueryObjectuivARB(GL_QUERY_RESULT_AVAILABLE)");
352
    TRACE("available %#x.\n", available);
353 354 355 356 357

    if (available)
    {
        if (data)
        {
358
            GL_EXTCALL(glGetQueryObjectuivARB(oq->id, GL_QUERY_RESULT_ARB, &samples));
359
            checkGLcall("glGetQueryObjectuivARB(GL_QUERY_RESULT)");
360
            TRACE("Returning %d samples.\n", samples);
361 362
            *data = samples;
        }
363 364
        res = S_OK;
    }
365 366 367 368 369 370 371
    else
    {
        res = S_FALSE;
    }

    LEAVE_GL();

372 373
    context_release(context);

374 375 376
    return res;
}

377
static HRESULT wined3d_event_query_ops_get_data(struct wined3d_query *query,
378 379
        void *pData, DWORD dwSize, DWORD flags)
{
380
    struct wined3d_event_query *event_query = query->extendedData;
381
    BOOL *data = pData;
382
    enum wined3d_event_query_result ret;
383

384
    TRACE("query %p, pData %p, dwSize %#x, flags %#x.\n", query, pData, dwSize, flags);
385

386
    if (!pData || !dwSize) return S_OK;
387
    if (!event_query)
388
    {
389
        WARN("Event query not supported by GL, reporting GPU idle.\n");
390 391 392
        *data = TRUE;
        return S_OK;
    }
393

394
    ret = wined3d_event_query_test(event_query, query->device);
395
    switch(ret)
396
    {
397 398 399 400 401 402 403 404 405 406
        case WINED3D_EVENT_QUERY_OK:
        case WINED3D_EVENT_QUERY_NOT_STARTED:
            *data = TRUE;
            break;

        case WINED3D_EVENT_QUERY_WAITING:
            *data = FALSE;
            break;

        case WINED3D_EVENT_QUERY_WRONG_THREAD:
407
            FIXME("(%p) Wrong thread, reporting GPU idle.\n", query);
408 409 410 411 412 413
            *data = TRUE;
            break;

        case WINED3D_EVENT_QUERY_ERROR:
            ERR("The GL event query failed, returning D3DERR_INVALIDCALL\n");
            return WINED3DERR_INVALIDCALL;
414 415
    }

416 417
    return S_OK;
}
418

419
enum wined3d_query_type CDECL wined3d_query_get_type(const struct wined3d_query *query)
420 421 422 423
{
    TRACE("query %p.\n", query);

    return query->type;
424 425
}

426
static HRESULT wined3d_event_query_ops_issue(struct wined3d_query *query, DWORD flags)
427
{
428
    TRACE("query %p, flags %#x.\n", query, flags);
429

430
    TRACE("(%p) : flags %#x, type D3DQUERY_EVENT\n", query, flags);
431
    if (flags & WINED3DISSUE_END)
432
    {
433
        struct wined3d_event_query *event_query = query->extendedData;
434 435

        /* Faked event query support */
436
        if (!event_query) return WINED3D_OK;
437

438
        wined3d_event_query_issue(event_query, query->device);
439
    }
440
    else if (flags & WINED3DISSUE_BEGIN)
441
    {
442 443 444 445
        /* Started implicitly at device creation */
        ERR("Event query issued with START flag - what to do?\n");
    }

446
    if (flags & WINED3DISSUE_BEGIN)
447
        query->state = QUERY_BUILDING;
448
    else
449
        query->state = QUERY_SIGNALLED;
450 451 452 453

    return WINED3D_OK;
}

454
static HRESULT wined3d_occlusion_query_ops_issue(struct wined3d_query *query, DWORD flags)
455
{
456
    struct wined3d_device *device = query->device;
457
    const struct wined3d_gl_info *gl_info = &device->adapter->gl_info;
458

459 460
    TRACE("query %p, flags %#x.\n", query, flags);

461
    if (gl_info->supported[ARB_OCCLUSION_QUERY])
462
    {
463
        struct wined3d_occlusion_query *oq = query->extendedData;
464
        struct wined3d_context *context;
465

466
        /* This is allowed according to msdn and our tests. Reset the query and restart */
467
        if (flags & WINED3DISSUE_BEGIN)
468
        {
469
            if (query->state == QUERY_BUILDING)
470
            {
471
                if (oq->context->tid != GetCurrentThreadId())
472 473
                {
                    FIXME("Wrong thread, can't restart query.\n");
474

475 476 477
                    context_free_occlusion_query(oq);
                    context = context_acquire(query->device, NULL);
                    context_alloc_occlusion_query(context, oq);
478 479 480
                }
                else
                {
481
                    context = context_acquire(query->device, oq->context->current_rt);
482 483

                    ENTER_GL();
484 485
                    GL_EXTCALL(glEndQueryARB(GL_SAMPLES_PASSED_ARB));
                    checkGLcall("glEndQuery()");
486
                    LEAVE_GL();
487 488
                }
            }
489 490
            else
            {
491 492 493
                if (oq->context) context_free_occlusion_query(oq);
                context = context_acquire(query->device, NULL);
                context_alloc_occlusion_query(context, oq);
494 495 496
            }

            ENTER_GL();
497
            GL_EXTCALL(glBeginQueryARB(GL_SAMPLES_PASSED_ARB, oq->id));
498 499
            checkGLcall("glBeginQuery()");
            LEAVE_GL();
500 501

            context_release(context);
502
        }
503 504
        if (flags & WINED3DISSUE_END)
        {
505 506 507 508
            /* Msdn says _END on a non-building occlusion query returns an error, but
             * our tests show that it returns OK. But OpenGL doesn't like it, so avoid
             * generating an error
             */
509
            if (query->state == QUERY_BUILDING)
510
            {
511
                if (oq->context->tid != GetCurrentThreadId())
512 513 514 515 516
                {
                    FIXME("Wrong thread, can't end query.\n");
                }
                else
                {
517
                    context = context_acquire(query->device, oq->context->current_rt);
518 519

                    ENTER_GL();
520 521
                    GL_EXTCALL(glEndQueryARB(GL_SAMPLES_PASSED_ARB));
                    checkGLcall("glEndQuery()");
522
                    LEAVE_GL();
523 524

                    context_release(context);
525 526
                }
            }
527
        }
528 529 530 531
    }
    else
    {
        FIXME("%p Occlusion queries not supported.\n", query);
532
    }
533

534
    if (flags & WINED3DISSUE_BEGIN)
535
        query->state = QUERY_BUILDING;
536
    else
537
        query->state = QUERY_SIGNALLED;
538

539 540 541
    return WINED3D_OK; /* can be WINED3DERR_INVALIDCALL.    */
}

542
static const struct wined3d_query_ops event_query_ops =
543
{
544 545 546 547 548 549 550 551
    wined3d_event_query_ops_get_data,
    wined3d_event_query_ops_issue,
};

static const struct wined3d_query_ops occlusion_query_ops =
{
    wined3d_occlusion_query_ops_get_data,
    wined3d_occlusion_query_ops_issue,
552
};
553

554
static HRESULT query_init(struct wined3d_query *query, struct wined3d_device *device, enum wined3d_query_type type)
555 556 557 558 559
{
    const struct wined3d_gl_info *gl_info = &device->adapter->gl_info;

    switch (type)
    {
560
        case WINED3D_QUERY_TYPE_OCCLUSION:
561 562 563
            TRACE("Occlusion query.\n");
            if (!gl_info->supported[ARB_OCCLUSION_QUERY])
            {
564
                WARN("Unsupported in local OpenGL implementation: ARB_OCCLUSION_QUERY.\n");
565 566
                return WINED3DERR_NOTAVAILABLE;
            }
567 568
            query->query_ops = &occlusion_query_ops;
            query->data_size = sizeof(DWORD);
569 570 571 572 573 574 575 576 577
            query->extendedData = HeapAlloc(GetProcessHeap(), 0, sizeof(struct wined3d_occlusion_query));
            if (!query->extendedData)
            {
                ERR("Failed to allocate occlusion query extended data.\n");
                return E_OUTOFMEMORY;
            }
            ((struct wined3d_occlusion_query *)query->extendedData)->context = NULL;
            break;

578
        case WINED3D_QUERY_TYPE_EVENT:
579
            TRACE("Event query.\n");
580
            if (!wined3d_event_query_supported(gl_info))
581 582 583 584 585 586 587
            {
                /* Half-Life 2 needs this query. It does not render the main
                 * menu correctly otherwise. Pretend to support it, faking
                 * this query does not do much harm except potentially
                 * lowering performance. */
                FIXME("Event query: Unimplemented, but pretending to be supported.\n");
            }
588 589
            query->query_ops = &event_query_ops;
            query->data_size = sizeof(BOOL);
590 591
            query->extendedData = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(struct wined3d_event_query));
            if (!query->extendedData)
592
            {
593 594
                ERR("Failed to allocate event query memory.\n");
                return E_OUTOFMEMORY;
595 596 597
            }
            break;

598 599 600 601 602 603 604 605 606 607 608 609
        case WINED3D_QUERY_TYPE_VCACHE:
        case WINED3D_QUERY_TYPE_RESOURCE_MANAGER:
        case WINED3D_QUERY_TYPE_VERTEX_STATS:
        case WINED3D_QUERY_TYPE_TIMESTAMP:
        case WINED3D_QUERY_TYPE_TIMESTAMP_DISJOINT:
        case WINED3D_QUERY_TYPE_TIMESTAMP_FREQ:
        case WINED3D_QUERY_TYPE_PIPELINE_TIMINGS:
        case WINED3D_QUERY_TYPE_INTERFACE_TIMINGS:
        case WINED3D_QUERY_TYPE_VERTEX_TIMINGS:
        case WINED3D_QUERY_TYPE_PIXEL_TIMINGS:
        case WINED3D_QUERY_TYPE_BANDWIDTH_TIMINGS:
        case WINED3D_QUERY_TYPE_CACHE_UTILIZATION:
610 611 612 613 614 615 616 617 618 619 620 621
        default:
            FIXME("Unhandled query type %#x.\n", type);
            return WINED3DERR_NOTAVAILABLE;
    }

    query->type = type;
    query->state = QUERY_CREATED;
    query->device = device;
    query->ref = 1;

    return WINED3D_OK;
}
622

623
HRESULT CDECL wined3d_query_create(struct wined3d_device *device,
624
        enum wined3d_query_type type, struct wined3d_query **query)
625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650
{
    struct wined3d_query *object;
    HRESULT hr;

    TRACE("device %p, type %#x, query %p.\n", device, type, query);

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

    hr = query_init(object, device, type);
    if (FAILED(hr))
    {
        WARN("Failed to initialize query, hr %#x.\n", hr);
        HeapFree(GetProcessHeap(), 0, object);
        return hr;
    }

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

    return WINED3D_OK;
}