/* Called before first DDLock/after last DDUnlock to map/unmap surface memory from given process address space
 * We go easy way and map whole framebuffer and offscreen DirectDraw heap every time.
 */
DWORD APIENTRY VBoxDispDDMapMemory(PDD_MAPMEMORYDATA lpMapMemory)
{
    PVBOXDISPDEV pDev = (PVBOXDISPDEV) lpMapMemory->lpDD->dhpdev;
    VIDEO_SHARE_MEMORY smem;
    int rc;
    LOGF_ENTER();

    lpMapMemory->ddRVal = DDERR_GENERIC;

    memset(&smem, 0, sizeof(smem));
    smem.ProcessHandle = lpMapMemory->hProcess;

    if (lpMapMemory->bMap)
    {
        VIDEO_SHARE_MEMORY_INFORMATION  smemInfo;

        smem.ViewSize = pDev->layout.offDDrawHeap + pDev->layout.cbDDrawHeap;

        rc = VBoxDispMPShareVideoMemory(pDev->hDriver, &smem, &smemInfo);
        VBOX_WARNRC_RETV(rc, DDHAL_DRIVER_HANDLED);

        lpMapMemory->fpProcess = (FLATPTR) smemInfo.VirtualAddress;
    }
    else
    {
        smem.RequestedVirtualAddress = (PVOID) lpMapMemory->fpProcess;

        rc = VBoxDispMPUnshareVideoMemory(pDev->hDriver, &smem);
        VBOX_WARNRC_RETV(rc, DDHAL_DRIVER_HANDLED);
    }


    lpMapMemory->ddRVal = DD_OK;
    LOGF_LEAVE();
    return DDHAL_DRIVER_HANDLED;
}
/* Returns video modes supported by our device/driver
 * Note: If we fail here we'd be asked to enter 800x600@4bpp mode later in VBoxDispDrvEnablePDEV.
 */
ULONG APIENTRY VBoxDispDrvGetModes(HANDLE hDriver, ULONG cjSize, DEVMODEW *pdm)
{
    int rc;
    VIDEO_MODE_INFORMATION *pModesTable;
    ULONG cModes;
    LOGF_ENTER();

    rc = VBoxDispMPGetVideoModes(hDriver, &pModesTable, &cModes);
    VBOX_WARNRC_RETV(rc, 0);

    if (!pdm) /* return size of buffer required to store all supported modes */
    {
        EngFreeMem(pModesTable);
        LOGF_LEAVE();
        return cModes * sizeof(DEVMODEW);
    }

    ULONG mode, cMaxNodes=cjSize/sizeof(DEVMODEW);

    for (mode=0; mode<cModes && mode<cMaxNodes; ++mode, ++pdm)
    {
        memset(pdm, 0, sizeof(DEVMODEW));
        memcpy(pdm->dmDeviceName, VBOXDISP_DEVICE_NAME, sizeof(VBOXDISP_DEVICE_NAME));

        pdm->dmSpecVersion   = DM_SPECVERSION;
        pdm->dmDriverVersion = DM_SPECVERSION;
        pdm->dmSize          = sizeof(DEVMODEW);
        pdm->dmDriverExtra   = 0;

        pdm->dmBitsPerPel       = pModesTable[mode].NumberOfPlanes*pModesTable[mode].BitsPerPlane;
        pdm->dmPelsWidth        = pModesTable[mode].VisScreenWidth;
        pdm->dmPelsHeight       = pModesTable[mode].VisScreenHeight;
        pdm->dmDisplayFrequency = pModesTable[mode].Frequency;
        pdm->dmDisplayFlags     = 0;
        pdm->dmFields           = DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT|DM_DISPLAYFREQUENCY|DM_DISPLAYFLAGS;
    }
    EngFreeMem(pModesTable);

    LOG(("%d mode(s) reported", mode));

    LOGF_LEAVE();
    return mode * sizeof(DEVMODEW);
}
/* Called to reset device to default mode or to mode specified with dhpdev */
BOOL APIENTRY VBoxDispDrvAssertMode(DHPDEV dhpdev, BOOL bEnable)
{
    PVBOXDISPDEV pDev = (PVBOXDISPDEV) dhpdev;
    DWORD dwrc;
    int rc;
    LOGF_ENTER();

    if (!bEnable)
    {
        LOGF(("!bEnable"));
#ifdef VBOX_WITH_VIDEOHWACCEL
        /* tells we can not process host commands any more and ensures that
         * we've completed processing of the host VHWA commands
         */
        VBoxDispVHWADisable(pDev);
#endif

        /* disable VBVA */
        if (pDev->hgsmi.bSupported)
        {
            VBoxVBVADisable(&pDev->vbvaCtx, &pDev->hgsmi.ctx, -1);
        }

        /* reset the device to default mode */
        rc = VBoxDispMPResetDevice(pDev->hDriver);
        VBOX_WARNRC_RETV(rc, FALSE);
    }
    else
    {
        LOGF(("bEnable"));

        /* switch device to previous pDev mode */
        rc = VBoxDispMPSetCurrentMode(pDev->hDriver, pDev->mode.ulIndex);
        VBOX_WARNRC_RETV(rc, NULL);

        /* enable VBVA */
        if (pDev->hgsmi.bSupported)
        {
            if (pDev->mode.ulBitsPerPel==16 || pDev->mode.ulBitsPerPel==24 || pDev->mode.ulBitsPerPel==32)
            {
                VBVABUFFER *pVBVA = (VBVABUFFER *)((uint8_t *)pDev->memInfo.VideoRamBase+pDev->layout.offVBVABuffer);
                pDev->hgsmi.bSupported = VBoxVBVAEnable(&pDev->vbvaCtx, &pDev->hgsmi.ctx, pVBVA, -1);
                LogRel(("VBoxDisp[%d]: VBVA %senabled\n", pDev->iDevice, pDev->hgsmi.bSupported? "":"not "));
            }
        }

        /* inform host */
        if (pDev->hgsmi.bSupported)
        {
            VBoxHGSMIProcessDisplayInfo(&pDev->hgsmi.ctx, pDev->iDevice, pDev->orgDev.x, pDev->orgDev.y,
                                        0, abs(pDev->mode.lScanlineStride), pDev->mode.ulWidth, pDev->mode.ulHeight,
                                        (uint16_t)pDev->mode.ulBitsPerPel, VBVA_SCREEN_F_ACTIVE);
        }

#ifdef VBOX_WITH_VIDEOHWACCEL
        /* tells we can process host commands */
       VBoxDispVHWAEnable(pDev);
#endif

        /* Associate back GDI bitmap residing in our framebuffer memory with GDI's handle to our device */
        dwrc = EngAssociateSurface((HSURF)pDev->surface.hBitmap, pDev->hDevGDI, 0);
        if (dwrc != NO_ERROR)
        {
            WARN(("EngAssociateSurface on bitmap failed with %#x", dwrc));
            return FALSE;
        }

        /* Associate device managed surface with GDI's handle to our device */
        dwrc = EngAssociateSurface(pDev->surface.hSurface, pDev->hDevGDI, pDev->flDrawingHooks);
        if (dwrc != NO_ERROR)
        {
            WARN(("EngAssociateSurface on surface failed with %#x", dwrc));
            return FALSE;
        }
    }

    LOGF_LEAVE();
    return TRUE;
}
/* Called to create and associate surface with device */
HSURF APIENTRY VBoxDispDrvEnableSurface(DHPDEV dhpdev)
{
    int rc;
    PVBOXDISPDEV pDev = (PVBOXDISPDEV)dhpdev;

    LOGF_ENTER();

    /* Switch device to mode requested in VBoxDispDrvEnablePDEV */
    rc = VBoxDispMPSetCurrentMode(pDev->hDriver, pDev->mode.ulIndex);
    VBOX_WARNRC_RETV(rc, NULL);

    /* Map fb and vram */
    rc = VBoxDispMPMapMemory(pDev, &pDev->memInfo);
    VBOX_WARNRC_RETV(rc, NULL);

    /* Clear mapped memory, to avoid garbage while video mode is switching */
    /* @todo: VIDEO_MODE_NO_ZERO_MEMORY does nothing in miniport's IOCTL_VIDEO_SET_CURRENT_MODE*/
    memset(pDev->memInfo.FrameBufferBase, 0, pDev->mode.ulHeight * abs(pDev->mode.lScanlineStride));

    /* Allocate memory for pointer attrs */
    rc = VBoxDispInitPointerAttrs(pDev);
    VBOX_WARNRC_RETV(rc, NULL);

    /* Init VBVA */
    rc = VBoxDispVBVAInit(pDev);
    VBOX_WARNRC_RETV(rc, NULL);

    /* Enable VBVA */
    if (pDev->hgsmi.bSupported)
    {
        if (pDev->mode.ulBitsPerPel==16 || pDev->mode.ulBitsPerPel==24 || pDev->mode.ulBitsPerPel==32)
        {
            VBVABUFFER *pVBVA = (VBVABUFFER *)((uint8_t *)pDev->memInfo.VideoRamBase+pDev->layout.offVBVABuffer);
            pDev->hgsmi.bSupported = VBoxVBVAEnable(&pDev->vbvaCtx, &pDev->hgsmi.ctx, pVBVA, -1);
            LogRel(("VBoxDisp[%d]: VBVA %senabled\n", pDev->iDevice, pDev->hgsmi.bSupported? "":"not "));
        }
    }

    /* Inform host */
    if (pDev->hgsmi.bSupported)
    {
        VBoxHGSMIProcessDisplayInfo(&pDev->hgsmi.ctx, pDev->iDevice, pDev->orgDev.x, pDev->orgDev.y,
                                    0, abs(pDev->mode.lScanlineStride), pDev->mode.ulWidth, pDev->mode.ulHeight,
                                    (uint16_t)pDev->mode.ulBitsPerPel, VBVA_SCREEN_F_ACTIVE);
    }

#ifdef VBOX_WITH_VIDEOHWACCEL
    VBoxDispVHWAEnable(pDev);
#endif

    /* Set device palette if needed */
    if (pDev->mode.ulBitsPerPel == 8)
    {
        rc = VBoxDispSetPalette8BPP(pDev);
        VBOX_WARNRC_RETV(rc, NULL);
    }

    pDev->orgDisp.x = 0;
    pDev->orgDisp.y = 0;

    /* Create GDI managed bitmap, which resides in our framebuffer memory */
    ULONG iFormat;
    SIZEL size;

    switch (pDev->mode.ulBitsPerPel)
    {
        case 8:
        {
            iFormat = BMF_8BPP;
            break;
        }
        case 16:
        {
            iFormat = BMF_16BPP;
            break;
        }
        case 24:
        {
            iFormat = BMF_24BPP;
            break;
        }
        case 32:
        {
            iFormat = BMF_32BPP;
            break;
        }
    }

    size.cx = pDev->mode.ulWidth;
    size.cy = pDev->mode.ulHeight;

    pDev->surface.hBitmap = EngCreateBitmap(size, pDev->mode.lScanlineStride, iFormat,
                                            pDev->mode.lScanlineStride>0 ? BMF_TOPDOWN:0,
                                            pDev->memInfo.FrameBufferBase);
    if (!pDev->surface.hBitmap)
    {
        WARN(("EngCreateBitmap failed!"));
        return NULL;
    }
    pDev->surface.psoBitmap = EngLockSurface((HSURF)pDev->surface.hBitmap);

    /* Create device-managed surface */
    pDev->surface.hSurface = EngCreateDeviceSurface((DHSURF)pDev, size, iFormat);
    if (!pDev->surface.hSurface)
    {
        WARN(("EngCreateDeviceSurface failed!"));
        VBoxDispDrvDisableSurface(dhpdev);
        return NULL;
    }

    FLONG flHooks = HOOK_BITBLT|HOOK_TEXTOUT|HOOK_FILLPATH|HOOK_COPYBITS|HOOK_STROKEPATH|HOOK_LINETO|
                    HOOK_PAINT|HOOK_STRETCHBLT;

    /* Associate created surface with our device */
    if (!EngAssociateSurface(pDev->surface.hSurface, pDev->hDevGDI, flHooks))
    {
        WARN(("EngAssociateSurface failed!"));
        VBoxDispDrvDisableSurface(dhpdev);
        return NULL;
    }

    pDev->surface.ulFormat = iFormat;
    pDev->flDrawingHooks = flHooks;

    LOG(("Created surface %p for physical device %p", pDev->surface.hSurface, pDev));

    LOGF_LEAVE();
    return pDev->surface.hSurface;
}