/* $Id: server_rpw.cpp $ */

/** @file
 * VBox crOpenGL: Read Pixels worker
 */

/*
 * Copyright (C) 2010-2013 Oracle Corporation
 *
 * This file is part of VirtualBox Open Source Edition (OSE), as
 * available from http://www.virtualbox.org. This file is free software;
 * you can redistribute it and/or modify it under the terms of the GNU
 * General Public License (GPL) as published by the Free Software
 * Foundation, in version 2 as it comes in the "COPYING" file of the
 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
 */
#include "server.h"
#include "cr_string.h"
#include "cr_mem.h"
#include "cr_vreg.h"
#include "render/renderspu.h"

static void crServerRpwWorkerGpuSubmit(PRTLISTNODE pWorkList)
{
    CR_SERVER_RPW_ENTRY *pCurEntry;
    RTListForEach(pWorkList, pCurEntry, CR_SERVER_RPW_ENTRY, WorkerWorkEntry)
    {
        cr_server.head_spu->dispatch_table.BindTexture(GL_TEXTURE_2D, CR_SERVER_RPW_ENTRY_TEX(pCurEntry, Worker));

        if (CR_SERVER_RPW_ENTRY_PBO_IS_ACTIVE(pCurEntry))
        {
            cr_server.head_spu->dispatch_table.BindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, CR_SERVER_RPW_ENTRY_PBO_CUR(pCurEntry));
            /*read the texture, note pixels are NULL for PBO case as it's offset in the buffer*/
            cr_server.head_spu->dispatch_table.GetTexImage(GL_TEXTURE_2D, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
            cr_server.head_spu->dispatch_table.BindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, 0);
            CR_SERVER_RPW_ENTRY_PBO_FLIP(pCurEntry);
        }
        else
        {
            void *pvData = crAlloc(4*pCurEntry->Size.cx*pCurEntry->Size.cy);
            if (pvData)
            {
                cr_server.head_spu->dispatch_table.GetTexImage(GL_TEXTURE_2D, 0, GL_BGRA, GL_UNSIGNED_BYTE, pvData);

                pCurEntry->pfnData(pCurEntry, pvData);

                crFree(pvData);
            }
            else
            {
                crWarning("crAlloc failed");
            }
        }

        cr_server.head_spu->dispatch_table.BindTexture(GL_TEXTURE_2D, 0);
    }
}

static void crServerRpwWorkerGpuComplete(PRTLISTNODE pGpuSubmitedList)
{
    CR_SERVER_RPW_ENTRY *pCurEntry;
    RTListForEach(pGpuSubmitedList, pCurEntry, CR_SERVER_RPW_ENTRY, GpuSubmittedEntry)
    {
        Assert(CR_SERVER_RPW_ENTRY_PBO_IS_ACTIVE(pCurEntry));

        cr_server.head_spu->dispatch_table.BindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, CR_SERVER_RPW_ENTRY_PBO_COMPLETED(pCurEntry));

        void *pvData = cr_server.head_spu->dispatch_table.MapBufferARB(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY);

        pCurEntry->pfnData(pCurEntry, pvData);

        cr_server.head_spu->dispatch_table.UnmapBufferARB(GL_PIXEL_PACK_BUFFER_ARB);

        cr_server.head_spu->dispatch_table.BufferDataARB(GL_PIXEL_PACK_BUFFER_ARB, pCurEntry->Size.cx*pCurEntry->Size.cy*4, 0, GL_STREAM_READ_ARB);

        cr_server.head_spu->dispatch_table.BindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, 0);
    }
}

static void crServerRpwWorkerGpuMarkGpuCompletedSubmitedLocked(PRTLISTNODE pGpuSubmitedList, PRTLISTNODE pWorkList)
{
    CR_SERVER_RPW_ENTRY *pCurEntry, *pNextEntry;
    RTListForEachSafe(pGpuSubmitedList, pCurEntry, pNextEntry, CR_SERVER_RPW_ENTRY, GpuSubmittedEntry)
    {
        CR_SERVER_RPW_ENTRY_TEX_INVALIDATE(pCurEntry, Gpu);
        RTListNodeRemove(&pCurEntry->GpuSubmittedEntry);
    }

    Assert(RTListIsEmpty(pGpuSubmitedList));

    RTListForEachSafe(pWorkList, pCurEntry, pNextEntry, CR_SERVER_RPW_ENTRY, WorkerWorkEntry)
    {
        Assert(CR_SERVER_RPW_ENTRY_TEX_IS_VALID(pCurEntry, Worker));
        RTListNodeRemove(&pCurEntry->WorkerWorkEntry);
        if (CR_SERVER_RPW_ENTRY_PBO_IS_ACTIVE(pCurEntry))
        {
            /* PBO mode, put to the GPU submitted queue*/
            RTListAppend(pGpuSubmitedList, &pCurEntry->GpuSubmittedEntry);
            CR_SERVER_RPW_ENTRY_TEX_PROMOTE(pCurEntry, Worker, Gpu);
        }
        else
        {
            /* no PBO, we are already done entry data processing, free it right away */
            Assert(!CR_SERVER_RPW_ENTRY_TEX_IS_VALID(pCurEntry, Gpu));
            CR_SERVER_RPW_ENTRY_TEX_INVALIDATE(pCurEntry, Worker);
        }
    }
}

static void crServerRpwWorkerGetWorkLocked(CR_SERVER_RPW *pWorker, PRTLISTNODE pWorkList)
{
    CR_SERVER_RPW_ENTRY *pCurEntry, *pNextEntry;
    RTListForEachSafe(&pWorker->WorkList, pCurEntry, pNextEntry, CR_SERVER_RPW_ENTRY, WorkEntry)
    {
        RTListNodeRemove(&pCurEntry->WorkEntry);
        RTListAppend(pWorkList, &pCurEntry->WorkerWorkEntry);
        CR_SERVER_RPW_ENTRY_TEX_PROMOTE(pCurEntry, Submitted, Worker);
    }
}

static DECLCALLBACK(int) crServerRpwWorkerThread(RTTHREAD ThreadSelf, void *pvUser)
{
    CR_SERVER_RPW *pWorker = (CR_SERVER_RPW *)pvUser;
    RTMSINTERVAL cWaitMillis = RT_INDEFINITE_WAIT;
    RTLISTNODE WorkList, GpuSubmittedList;
    CR_SERVER_RPW_CTL_TYPE enmCtlType = CR_SERVER_RPW_CTL_TYPE_UNDEFINED;
    CR_SERVER_RPW_ENTRY *pCtlEntry = NULL;
    CRMuralInfo *pDummyMural = crServerGetDummyMural(pWorker->ctxVisBits);
    bool fExit = false;
    bool fForceComplete = false;
    bool fNotifyCmdCompleted = false;

    CRASSERT(pDummyMural);

    int rc = RTSemEventSignal(pWorker->Ctl.hCompleteEvent);
    if (!RT_SUCCESS(rc))
    {
        crWarning("RTSemEventSignal failed rc %d", rc);
        return rc;
    }

    RTListInit(&WorkList);
    RTListInit(&GpuSubmittedList);

    cr_server.head_spu->dispatch_table.MakeCurrent(pDummyMural->spuWindow, 0, pWorker->ctxId);

    rc = RTCritSectEnter(&pWorker->CritSect);
    if (!RT_SUCCESS(rc))
    {
        crWarning("RTCritSectEnter failed, rc %d", rc);
        goto end;
    }

    for (;;)
    {
        /* the crit sect is locked here */

        if (pWorker->Ctl.enmType != CR_SERVER_RPW_CTL_TYPE_UNDEFINED)
        {
            enmCtlType = pWorker->Ctl.enmType;
            pCtlEntry = pWorker->Ctl.pEntry;
            pWorker->Ctl.enmType = CR_SERVER_RPW_CTL_TYPE_UNDEFINED;
            pWorker->Ctl.pEntry = NULL;
        }

        crServerRpwWorkerGetWorkLocked(pWorker, &WorkList);

        RTCritSectLeave(&pWorker->CritSect);

        if (enmCtlType != CR_SERVER_RPW_CTL_TYPE_UNDEFINED)
        {
            switch (enmCtlType)
            {
                case CR_SERVER_RPW_CTL_TYPE_WAIT_COMPLETE:
                    break;
                case CR_SERVER_RPW_CTL_TYPE_TERM:
                    fExit = true;
                    break;
                default:
                    crWarning("unexpected CtlType %d", enmCtlType);
                    break;
            }
            enmCtlType = CR_SERVER_RPW_CTL_TYPE_UNDEFINED;
            pCtlEntry = NULL;
            fNotifyCmdCompleted = true;
        }

        bool fNewItems = !RTListIsEmpty(&WorkList);
        bool fCompleted = false;

        if (fNewItems)
        {
            crServerRpwWorkerGpuSubmit(&WorkList);
        }

        if (!RTListIsEmpty(&GpuSubmittedList))
        {
            if (fForceComplete || fNewItems)
            {
                crServerRpwWorkerGpuComplete(&GpuSubmittedList);
                fForceComplete = false;
                fCompleted = true;
            }
        }

        rc = RTCritSectEnter(&pWorker->CritSect);
        if (!RT_SUCCESS(rc))
        {
            crWarning("RTCritSectEnter failed, rc %d", rc);
            break;
        }

        /* fNewGpuItems means new entries arrived. WorkList contains new GPU submitted data
         * fCompleted means completion was performed, GpuSubmittedList contains old GPU submitted data,
         * which is now completed and should be released */
        if (fNewItems || fCompleted)
        {
            crServerRpwWorkerGpuMarkGpuCompletedSubmitedLocked(&GpuSubmittedList, &WorkList);
        }

        if (fExit || !fNewItems)
        {
            RTCritSectLeave(&pWorker->CritSect);

            if (fNotifyCmdCompleted)
            {
                rc = RTSemEventSignal(pWorker->Ctl.hCompleteEvent);
                if (!RT_SUCCESS(rc))
                {
                    crWarning("RTSemEventSignal failed rc %d", rc);
                    break;
                }
                fNotifyCmdCompleted = false;
            }

            if (fExit)
                break;

            if (!RTListIsEmpty(&GpuSubmittedList))
                cWaitMillis = 17; /* ~60Hz */
            else
                cWaitMillis = RT_INDEFINITE_WAIT;

            rc = RTSemEventWait(pWorker->hSubmitEvent, cWaitMillis);
            if (!RT_SUCCESS(rc) && rc != VERR_TIMEOUT)
            {
                crWarning("RTSemEventWait failed, rc %d", rc);
                break;
            }

            if (rc == VERR_TIMEOUT)
            {
                Assert(!RTListIsEmpty(&GpuSubmittedList));
                fForceComplete = true;
            }

            rc = RTCritSectEnter(&pWorker->CritSect);
            if (!RT_SUCCESS(rc))
            {
                crWarning("RTCritSectEnter failed, rc %d", rc);
                break;
            }
        }
    }

end:
    cr_server.head_spu->dispatch_table.MakeCurrent(0, 0, 0);

    return rc;
}

static int crServerRpwCtlNotify(CR_SERVER_RPW *pWorker, CR_SERVER_RPW_ENTRY *pEntry)
{
    int rc = RTSemEventSignal(pWorker->hSubmitEvent);
    if (RT_SUCCESS(rc))
    {
        rc = RTSemEventWait(pWorker->Ctl.hCompleteEvent, RT_INDEFINITE_WAIT);
        if (RT_SUCCESS(rc))
        {
            rc = pWorker->Ctl.rc;
            if (!RT_SUCCESS(rc))
            {
                crWarning("WdCtl command failed rc %d", rc);
            }
        }
        else
        {
            crWarning("RTSemEventWait failed rc %d", rc);
        }
    }
    else
    {
        int tmpRc;
        crWarning("RTSemEventSignal failed rc %d", rc);
        tmpRc = RTCritSectEnter(&pWorker->CritSect);
        if (RT_SUCCESS(tmpRc))
        {
            pWorker->Ctl.enmType = CR_SERVER_RPW_CTL_TYPE_UNDEFINED;
            pWorker->Ctl.pEntry = NULL;
            RTCritSectLeave(&pWorker->CritSect);
        }
        else
        {
            crWarning("RTSemEventSignal failed tmpRc %d", tmpRc);
        }
    }

    return rc;
}

static int crServerRpwCtl(CR_SERVER_RPW *pWorker, CR_SERVER_RPW_CTL_TYPE enmType, CR_SERVER_RPW_ENTRY *pEntry)
{
    int rc = RTCritSectEnter(&pWorker->CritSect);
    if (RT_SUCCESS(rc))
    {
        pWorker->Ctl.enmType = enmType;
        pWorker->Ctl.pEntry = pEntry;
        RTCritSectLeave(&pWorker->CritSect);
    }
    else
    {
        crWarning("RTCritSectEnter failed rc %d", rc);
        return rc;
    }

    rc = crServerRpwCtlNotify(pWorker, pEntry);
    if (!RT_SUCCESS(rc))
    {
        crWarning("crServerRpwCtlNotify failed rc %d", rc);
        return rc;
    }
    return VINF_SUCCESS;
}

int crServerRpwInit(CR_SERVER_RPW *pWorker)
{
    int rc;

    memset(pWorker, 0, sizeof (*pWorker));

    RTListInit(&pWorker->WorkList);

    rc = RTCritSectInit(&pWorker->CritSect);
    if (RT_SUCCESS(rc))
    {
        rc =  RTSemEventCreate(&pWorker->hSubmitEvent);
        if (RT_SUCCESS(rc))
        {
            rc =  RTSemEventCreate(&pWorker->Ctl.hCompleteEvent);
            if (RT_SUCCESS(rc))
            {
                CRASSERT(cr_server.MainContextInfo.CreateInfo.realVisualBits);
                CRASSERT(cr_server.MainContextInfo.SpuContext);

                pWorker->ctxId = cr_server.head_spu->dispatch_table.CreateContext("", cr_server.MainContextInfo.CreateInfo.realVisualBits, cr_server.MainContextInfo.SpuContext);
                if (pWorker->ctxId)
                {
                    CRMuralInfo *pDummyMural;
                    pWorker->ctxVisBits = cr_server.MainContextInfo.CreateInfo.realVisualBits;
                    pDummyMural = crServerGetDummyMural(pWorker->ctxVisBits);
                    if (pDummyMural)
                    {
                        /* since CreateContext does not actually create it on some platforms, e.g. on win,
                         * we need to do MakeCurrent to ensure it is created.
                         * There is some black magic in doing that to work around ogl driver bugs
                         * (i.e. we need to switch offscreen rendering off before doing make current) */
                        CR_SERVER_CTX_SWITCH CtxSwitch;

                        crServerCtxSwitchPrepare(&CtxSwitch, NULL);

                        cr_server.head_spu->dispatch_table.Flush();

                        cr_server.head_spu->dispatch_table.MakeCurrent(pDummyMural->spuWindow, 0, pWorker->ctxId);

                        if (cr_server.currentCtxInfo)
                        {
                            CRASSERT(cr_server.currentMural);
                            cr_server.head_spu->dispatch_table.MakeCurrent(cr_server.currentMural->spuWindow, 0,
                                    cr_server.currentCtxInfo->SpuContext > 0 ? cr_server.currentCtxInfo->SpuContext : cr_server.MainContextInfo.SpuContext);
                        }
                        else
                            cr_server.head_spu->dispatch_table.MakeCurrent(CR_RENDER_DEFAULT_WINDOW_ID, 0, CR_RENDER_DEFAULT_CONTEXT_ID);

                        crServerCtxSwitchPostprocess(&CtxSwitch);

                        rc = RTThreadCreate(&pWorker->hThread, crServerRpwWorkerThread, pWorker, 0, RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "CrServerDw");
                        if (RT_SUCCESS(rc))
                        {
                            rc = RTSemEventWait(pWorker->Ctl.hCompleteEvent, RT_INDEFINITE_WAIT);
                            if (RT_SUCCESS(rc))
                            {
                                return VINF_SUCCESS;
                            }
                            else
                            {
                                crWarning("RTSemEventWait failed rc %d", rc);
                            }
                        }
                        else
                        {
                            crWarning("RTThreadCreate failed rc %d", rc);
                        }
                    }
                    else
                    {
                        crWarning("Failed to get dummy mural");
                        rc = VERR_GENERAL_FAILURE;
                    }
                    cr_server.head_spu->dispatch_table.DestroyContext(pWorker->ctxId);
                }
                else
                {
                    crWarning("CreateContext failed rc %d", rc);
                }

                RTSemEventDestroy(pWorker->Ctl.hCompleteEvent);
            }
            else
            {
                crWarning("RTSemEventCreate failed rc %d", rc);
            }
            RTSemEventDestroy(pWorker->hSubmitEvent);
        }
        else
        {
            crWarning("RTSemEventCreate failed rc %d", rc);
        }

        RTCritSectDelete(&pWorker->CritSect);
    }
    else
    {
        crWarning("RTCritSectInit failed rc %d", rc);
    }

    return rc;
}

int crServerRpwEntryResizeCleaned(CR_SERVER_RPW *pWorker, CR_SERVER_RPW_ENTRY *pEntry, uint32_t width, uint32_t height)
{
    CRContext *pContext;
    if (!width || !height)
    {
        return VINF_SUCCESS;
    }

    if (!cr_server.currentCtxInfo)
    {
        CRMuralInfo *pDummy = crServerGetDummyMural(cr_server.MainContextInfo.CreateInfo.realVisualBits);
        if (!pDummy)
        {
            crWarning("crServerGetDummyMural failed");
            return VERR_GENERAL_FAILURE;
        }


        crServerPerformMakeCurrent(pDummy, &cr_server.MainContextInfo);
    }

    Assert(width);
    Assert(height);

    pContext = cr_server.currentCtxInfo->pContext;

    if (crStateIsBufferBound(GL_PIXEL_UNPACK_BUFFER_ARB))
    {
        cr_server.head_spu->dispatch_table.BindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, 0);
    }

    for (int i = 0; i < 4; ++i)
    {
        cr_server.head_spu->dispatch_table.GenTextures(1, &pEntry->aidWorkerTexs[i]);

        cr_server.head_spu->dispatch_table.BindTexture(GL_TEXTURE_2D, pEntry->aidWorkerTexs[i]);
        cr_server.head_spu->dispatch_table.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        cr_server.head_spu->dispatch_table.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        cr_server.head_spu->dispatch_table.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
        cr_server.head_spu->dispatch_table.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
        cr_server.head_spu->dispatch_table.TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height,
                       0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
    }

    pEntry->iTexDraw = -pEntry->iTexDraw;

    if (crStateIsBufferBound(GL_PIXEL_UNPACK_BUFFER_ARB))
    {
        cr_server.head_spu->dispatch_table.BindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, pContext->bufferobject.unpackBuffer->hwid);
    }

    if (cr_server.bUsePBOForReadback)
    {
        for (int i = 0; i < 2; ++i)
        {
            cr_server.head_spu->dispatch_table.GenBuffersARB(1, &pEntry->aidPBOs[i]);
            cr_server.head_spu->dispatch_table.BindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, pEntry->aidPBOs[i]);
            cr_server.head_spu->dispatch_table.BufferDataARB(GL_PIXEL_PACK_BUFFER_ARB, width*height*4, 0, GL_STREAM_READ_ARB);
        }

        if (crStateIsBufferBound(GL_PIXEL_PACK_BUFFER_ARB))
        {
            cr_server.head_spu->dispatch_table.BindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, pContext->bufferobject.packBuffer->hwid);
        }
        else
        {
            cr_server.head_spu->dispatch_table.BindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, 0);
        }
        pEntry->iCurPBO = 0;
    }


    GLuint uid = pContext->texture.unit[pContext->texture.curTextureUnit].currentTexture2D->hwid;
    cr_server.head_spu->dispatch_table.BindTexture(GL_TEXTURE_2D, uid);


    pEntry->Size.cx = width;
    pEntry->Size.cy = height;

    crServerRpwEntryDbgVerify(pEntry);

    return VINF_SUCCESS;
}

int crServerRpwEntryCleanup(CR_SERVER_RPW *pWorker, CR_SERVER_RPW_ENTRY *pEntry)
{
    if (!pEntry->Size.cx)
        return VINF_SUCCESS;

    int rc = crServerRpwEntryCancel(pWorker, pEntry);
    if (!RT_SUCCESS(rc))
    {
        crWarning("crServerRpwEntryCancel failed rc %d", rc);
        return rc;
    }

    if (!cr_server.currentCtxInfo)
    {
        CRMuralInfo *pDummy = crServerGetDummyMural(cr_server.MainContextInfo.CreateInfo.realVisualBits);
        if (!pDummy)
        {
            crWarning("crServerGetDummyMural failed");
            return VERR_GENERAL_FAILURE;
        }


        crServerPerformMakeCurrent(pDummy, &cr_server.MainContextInfo);
    }

    cr_server.head_spu->dispatch_table.DeleteTextures(4, pEntry->aidWorkerTexs);

    if (CR_SERVER_RPW_ENTRY_PBO_IS_ACTIVE(pEntry))
    {
        cr_server.head_spu->dispatch_table.DeleteBuffersARB(2, pEntry->aidPBOs);
        memset(pEntry->aidPBOs, 0, sizeof (pEntry->aidPBOs));
        pEntry->iCurPBO = -1;
    }

    memset(pEntry->aidWorkerTexs, 0, sizeof (pEntry->aidWorkerTexs));
    CR_SERVER_RPW_ENTRY_TEX_IS_VALID(pEntry, Submitted);
    CR_SERVER_RPW_ENTRY_TEX_IS_VALID(pEntry, Worker);
    CR_SERVER_RPW_ENTRY_TEX_IS_VALID(pEntry, Gpu);
    pEntry->iTexDraw = -1;
    pEntry->iTexSubmitted = -2;
    pEntry->iTexWorker = -3;
    pEntry->iTexGpu = -4;
    pEntry->Size.cx = 0;
    pEntry->Size.cy = 0;
    return VINF_SUCCESS;
}

int crServerRpwEntryResize(CR_SERVER_RPW *pWorker, CR_SERVER_RPW_ENTRY *pEntry, uint32_t width, uint32_t height)
{
    if (!width || !height)
    {
        width = 0;
        height = 0;
    }

    if (width == pEntry->Size.cx && width == pEntry->Size.cy)
        return VINF_SUCCESS;

    int rc = crServerRpwEntryCleanup(pWorker, pEntry);
    if (!RT_SUCCESS(rc))
    {
        crWarning("crServerRpwEntryCleanup failed rc %d", rc);
        return rc;
    }

    rc = crServerRpwEntryResizeCleaned(pWorker, pEntry, width, height);
    if (!RT_SUCCESS(rc))
    {
        crWarning("crServerRpwEntryResizeCleaned failed rc %d", rc);
    }
    return rc;
}

int crServerRpwEntryInit(CR_SERVER_RPW *pWorker, CR_SERVER_RPW_ENTRY *pEntry, uint32_t width, uint32_t height, PFNCR_SERVER_RPW_DATA pfnData)
{
    memset(pEntry, 0, sizeof (*pEntry));

    pEntry->iTexDraw = -1;
    pEntry->iTexSubmitted = -2;
    pEntry->iTexWorker = -3;
    pEntry->iTexGpu = -4;
    pEntry->iCurPBO = -1;
    pEntry->pfnData = pfnData;
    int rc = crServerRpwEntryResizeCleaned(pWorker, pEntry, width, height);
    if (!RT_SUCCESS(rc))
    {
        crWarning("crServerRpwEntryResizeCleaned failed rc %d", rc);
        return rc;
    }
    return VINF_SUCCESS;
}

int crServerRpwEntrySubmit(CR_SERVER_RPW *pWorker, CR_SERVER_RPW_ENTRY *pEntry)
{
    if (!CR_SERVER_RPW_ENTRY_TEX_IS_VALID(pEntry, Draw))
    {
        crWarning("submitting empty entry, ignoting");
        Assert(!pEntry->Size.cx);
        Assert(!pEntry->Size.cy);
        return VERR_INVALID_PARAMETER;
    }

    Assert(pEntry->Size.cx);
    Assert(pEntry->Size.cy);

    int rc = RTCritSectEnter(&pWorker->CritSect);
    if (RT_SUCCESS(rc))
    {
        Assert(pWorker->Ctl.enmType == CR_SERVER_RPW_CTL_TYPE_UNDEFINED);
        if (!CR_SERVER_RPW_ENTRY_TEX_IS_VALID(pEntry, Submitted))
        {
            CR_SERVER_RPW_ENTRY_TEX_PROMOTE_KEEPVALID(pEntry, Draw, Submitted);
            RTListAppend(&pWorker->WorkList, &pEntry->WorkEntry);
        }
        else
        {
            CR_SERVER_RPW_ENTRY_TEX_XCHG_VALID(pEntry, Draw, Submitted);
        }
        RTCritSectLeave(&pWorker->CritSect);

        RTSemEventSignal(pWorker->hSubmitEvent);
    }
    else
    {
        crWarning("RTCritSectEnter failed rc %d", rc);
        return rc;
    }

    return rc;
}

static int crServerRpwEntryCancelCtl(CR_SERVER_RPW *pWorker, CR_SERVER_RPW_ENTRY *pEntry, CR_SERVER_RPW_CTL_TYPE enmType)
{
    if (CR_SERVER_RPW_CTL_TYPE_TERM == enmType && pEntry)
    {
        crWarning("Entry should be null for term request");
        pEntry = NULL;
    }

    int rc = RTCritSectEnter(&pWorker->CritSect);
    if (RT_SUCCESS(rc))
    {
        if (pEntry)
        {
            if (CR_SERVER_RPW_ENTRY_TEX_IS_VALID(pEntry, Submitted))
            {
                CR_SERVER_RPW_ENTRY_TEX_INVALIDATE(pEntry, Submitted);
                RTListNodeRemove(&pEntry->WorkEntry);
            }

            if (!CR_SERVER_RPW_ENTRY_TEX_IS_VALID(pEntry, Worker) && !CR_SERVER_RPW_ENTRY_TEX_IS_VALID(pEntry, Gpu))
            {
                /* can cancel it wight away */
                RTCritSectLeave(&pWorker->CritSect);
                return VINF_SUCCESS;
            }
        }
        else
        {
            CR_SERVER_RPW_ENTRY *pCurEntry, *pNextEntry;
            RTListForEachSafe(&pWorker->WorkList, pCurEntry, pNextEntry, CR_SERVER_RPW_ENTRY, WorkEntry)
            {
                CR_SERVER_RPW_ENTRY_TEX_IS_VALID(pCurEntry, Submitted);
                CR_SERVER_RPW_ENTRY_TEX_INVALIDATE(pEntry, Submitted);
                RTListNodeRemove(&pCurEntry->WorkEntry);
            }
        }
        pWorker->Ctl.enmType = enmType;
        pWorker->Ctl.pEntry = pEntry;
        RTCritSectLeave(&pWorker->CritSect);
    }
    else
    {
        crWarning("RTCritSectEnter failed rc %d", rc);
        return rc;
    }

    rc = crServerRpwCtlNotify(pWorker, pEntry);
    if (!RT_SUCCESS(rc))
    {
        crWarning("crServerRpwCtlNotify failed rc %d", rc);
    }
    return VINF_SUCCESS;
}

int crServerRpwEntryWaitComplete(CR_SERVER_RPW *pWorker, CR_SERVER_RPW_ENTRY *pEntry)
{
    int rc = crServerRpwCtl(pWorker, CR_SERVER_RPW_CTL_TYPE_WAIT_COMPLETE, pEntry);
    if (!RT_SUCCESS(rc))
    {
        crWarning("crServerRpwCtl failed rc %d", rc);
    }
    return rc;
}

int crServerRpwEntryCancel(CR_SERVER_RPW *pWorker, CR_SERVER_RPW_ENTRY *pEntry)
{
    return crServerRpwEntryCancelCtl(pWorker, pEntry, CR_SERVER_RPW_CTL_TYPE_WAIT_COMPLETE);
}

static int crServerRpwCtlTerm(CR_SERVER_RPW *pWorker)
{
    int rc = crServerRpwEntryCancelCtl(pWorker, NULL, CR_SERVER_RPW_CTL_TYPE_TERM);
    if (!RT_SUCCESS(rc))
    {
        crWarning("crServerRpwCtl failed rc %d", rc);
    }
    return rc;
}

int crServerRpwTerm(CR_SERVER_RPW *pWorker)
{
    int rc = crServerRpwCtlTerm(pWorker);
    if (!RT_SUCCESS(rc))
    {
        crWarning("crServerRpwCtlTerm failed rc %d", rc);
        return rc;
    }

    rc = RTThreadWait(pWorker->hThread, RT_INDEFINITE_WAIT, NULL);
    if (!RT_SUCCESS(rc))
    {
        crWarning("RTThreadWait failed rc %d", rc);
        return rc;
    }

    RTSemEventDestroy(pWorker->Ctl.hCompleteEvent);
    RTSemEventDestroy(pWorker->hSubmitEvent);
    RTCritSectDelete(&pWorker->CritSect);

    return VINF_SUCCESS;
}
