[6/10] WineD3D: Improve render target to texture blits

Stefan Dösinger stefan at codeweavers.com
Mon Feb 12 12:18:46 CST 2007


-------------- next part --------------
From 469510a4d4bace9660b38aa30efd4ca1da7aa917 Mon Sep 17 00:00:00 2001
From: Stefan Doesinger <stefan at codeweavers.com>
Date: Mon, 5 Feb 2007 07:30:39 +0100
Subject: [PATCH] WineD3D: Improve render target to texture blits

---
 dlls/wined3d/surface.c |  319 +++++++++++++++++++++++++++++++++++++++++-------
 1 files changed, 276 insertions(+), 43 deletions(-)

diff --git a/dlls/wined3d/surface.c b/dlls/wined3d/surface.c
index be08c34..04b5d9e 100644
--- a/dlls/wined3d/surface.c
+++ b/dlls/wined3d/surface.c
@@ -2191,6 +2191,240 @@ static HRESULT WINAPI IWineD3DSurfaceImpl_Flip(IWineD3DSurface *iface, IWineD3DS
     return IWineD3DDevice_Present(D3D, NULL, NULL, 0, NULL);
 }
 
+/* Does a direct frame buffer -> texture copy. Stretching is done
+ * with single pixel copy calls
+ */
+static inline void fb_copy_to_texture_direct(IWineD3DSurfaceImpl *This, IWineD3DSurface *SrcSurface, IWineD3DSwapChainImpl *swapchain, WINED3DRECT *srect, WINED3DRECT *drect, BOOL upsidedown) {
+    IWineD3DDeviceImpl *myDevice = This->resource.wineD3DDevice;
+    float xrel, yrel;
+    UINT row;
+    BOOL warned = FALSE; /* deliberately not static */
+    IWineD3DSurfaceImpl *Src = (IWineD3DSurfaceImpl *) SrcSurface;
+
+    ENTER_GL();
+
+    ActivateContext(myDevice, SrcSurface, CTXUSAGE_BLIT);
+
+    /* Bind the target texture */
+    glBindTexture(GL_TEXTURE_2D, This->glDescription.textureName);
+    checkGLcall("glBindTexture");
+    if(swapchain->backBuffer && SrcSurface == swapchain->backBuffer[0]) {
+        glReadBuffer(GL_BACK);
+    } else {
+        glReadBuffer(GL_FRONT);
+    }
+    checkGLcall("glReadBuffer");
+
+    xrel = (float) (srect->x2 - srect->x1) / (float) (drect->x2 - drect->x1);
+    yrel = (float) (srect->y2 - srect->y1) / (float) (drect->y2 - drect->y1);
+
+    if( (xrel - 1.0 < -eps) || (xrel - 1.0 > eps)) {
+        FIXME("Doing a pixel by pixel copy from the framebuffer to a texture, expect major performance issues\n");
+    }
+
+    if(upsidedown &&
+       !((xrel - 1.0 < -eps) || (xrel - 1.0 > eps)) &&
+       !((yrel - 1.0 < -eps) || (yrel - 1.0 > eps))) {
+        /* Upside down copy without stretching is nice, one glCopyTexSubImage call will do */
+
+        glCopyTexSubImage2D(This->glDescription.target,
+                            This->glDescription.level,
+                            drect->x1, drect->y1, /* xoffset, yoffset */
+                            srect->x1, Src->currentDesc.Height - srect->y2,
+                            drect->x2 - drect->x1, drect->y2 - drect->y1);
+    } else {
+        UINT yoffset = Src->currentDesc.Height - srect->y1 + drect->y1 - 1;
+        /* I have to process this row by row to swap the image,
+         * otherwise it would be upside down, so streching in y direction
+         * doesn't cost extra time
+         *
+         * However, streching in x direction can be avoided if not necessary
+         */
+        for(row = drect->y1; row < drect->y2; row++) {
+            if( (xrel - 1.0 < -eps) || (xrel - 1.0 > eps)) {
+                /* Well, that stuff works, but it's very slow.
+                 * find a better way instead
+                 */
+                UINT col;
+
+                if(!warned) {
+                    warned = TRUE;
+                    FIXME("Doing a pixel by pixel render target -> texture copy, expect performance issues\n");
+                }
+
+                for(col = drect->x1; col < drect->x2; col++) {
+                    glCopyTexSubImage2D(This->glDescription.target,
+                                        This->glDescription.level,
+                                        drect->x1 + col, row, /* xoffset, yoffset */
+                                        srect->x1 + col * xrel, yoffset - (int) (row * yrel),
+                                        1, 1);
+                }
+            } else {
+                glCopyTexSubImage2D(This->glDescription.target,
+                                    This->glDescription.level,
+                                    drect->x1, row, /* xoffset, yoffset */
+                                    srect->x1, yoffset - (int) (row * yrel),
+                                    drect->x2-drect->x1, 1);
+            }
+        }
+    }
+
+    vcheckGLcall("glCopyTexSubImage2D");
+    LEAVE_GL();
+}
+
+/* Uses the hardware to stretch and flip the image */
+static inline void fb_copy_to_texture_hwstretch(IWineD3DSurfaceImpl *This, IWineD3DSurface *SrcSurface, IWineD3DSwapChainImpl *swapchain, WINED3DRECT *srect, WINED3DRECT *drect, BOOL upsidedown) {
+    GLuint backup, src;
+    IWineD3DDeviceImpl *myDevice = This->resource.wineD3DDevice;
+    IWineD3DSurfaceImpl *Src = (IWineD3DSurfaceImpl *) SrcSurface;
+    float left, right, top, bottom; /* Texture coordinates */
+    UINT fbwidth = Src->currentDesc.Width;
+    UINT fbheight = Src->currentDesc.Height;
+
+    TRACE("Using hwstretch blit\n");
+    /* Activate the Proper context for reading from the source surface, set it up for blitting */
+    ENTER_GL();
+    ActivateContext(myDevice, SrcSurface, CTXUSAGE_BLIT);
+
+    /* Backup the back buffer and copy the source buffer into a texture to draw an upside down stretched quad. If
+     * we are reading from the back buffer, the backup can be used as source texture
+     */
+    glGenTextures(1, &backup);
+    checkGLcall("glGenTextures(1, &backup)");
+    glBindTexture(GL_TEXTURE_2D, backup);
+    checkGLcall("glBindTexture(GL_TEXTURE_2D, backup)");
+
+    glReadBuffer(GL_BACK);
+    checkGLcall("glReadBuffer(GL_BACK)");
+
+    /* TODO: Only back up the part that will be overwritten */
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, Src->pow2Width, Src->pow2Height, 0,
+                 GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+    checkGLcall("glTexImage2D");
+
+    glCopyTexSubImage2D(GL_TEXTURE_2D, 0,
+                        0, 0 /* read offsets */,
+                        0, 0,
+                        fbwidth,
+                        fbheight);
+
+    checkGLcall("glCopyTexSubImage2D");
+
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+    checkGLcall("glTexParameteri");
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+    checkGLcall("glTexParameteri");
+
+    if((IWineD3DSurface *) This == swapchain->backBuffer[0] || TRUE) {
+        src = backup;
+    } else {
+        glReadBuffer(GL_FRONT);
+        checkGLcall("glReadBuffer(GL_FRONT)");
+
+        glGenTextures(1, &src);
+        checkGLcall("glGenTextures(1, &src)");
+        glBindTexture(GL_TEXTURE_2D, src);
+        checkGLcall("glBindTexture(GL_TEXTURE_2D, src)");
+
+        /* TODO: Only copy the part that will be read. Use srect->x1, srect->y2 as origin, but with the width watch
+         * out for power of 2 sizes
+         */
+        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, Src->pow2Width, Src->pow2Height, 0,
+                    GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+        checkGLcall("glTexImage2D");
+        glCopyTexSubImage2D(GL_TEXTURE_2D, 0,
+                            0, 0 /* read offsets */,
+                            0, 0,
+                            fbwidth,
+                            fbheight);
+
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+        checkGLcall("glTexParameteri");
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+        checkGLcall("glTexParameteri");
+
+        glReadBuffer(GL_BACK);
+        checkGLcall("glReadBuffer(GL_BACK)");
+    }
+    checkGLcall("glEnd and previous");
+
+    left = (float) srect->x1 / (float) Src->pow2Width;
+    right = (float) srect->x2 / (float) Src->pow2Width;
+
+    if(upsidedown) {
+        top = (float) (Src->currentDesc.Height - srect->y1) / (float) Src->pow2Height;
+        bottom = (float) (Src->currentDesc.Height - srect->y2) / (float) Src->pow2Height;
+    } else {
+        top = (float) (Src->currentDesc.Height - srect->y2) / (float) Src->pow2Height;
+        bottom = (float) (Src->currentDesc.Height - srect->y1) / (float) Src->pow2Height;
+    }
+
+    /* draw the source texture stretched and upside down. The correct surface is bound already */
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
+
+    glBegin(GL_QUADS);
+        /* bottom left */
+        glTexCoord2f(left, bottom);
+        glVertex2i(0, fbheight);
+
+        /* top left */
+        glTexCoord2f(left, top);
+        glVertex2i(0, fbheight - drect->y2 - drect->y1);
+
+        /* top right */
+        glTexCoord2f(right, top);
+        glVertex2i(drect->x2 - drect->x1, fbheight - drect->y2 - drect->y1);
+
+        /* bottom right */
+        glTexCoord2f(right, bottom);
+        glVertex2i(drect->x2 - drect->x1, fbheight);
+    glEnd();
+    checkGLcall("glEnd and previous");
+
+    /* Now read the stretched and upside down image into the destination texture */
+    glBindTexture(This->glDescription.target, This->glDescription.textureName);
+    checkGLcall("glBindTexture");
+    glCopyTexSubImage2D(This->glDescription.target,
+                        0,
+                        drect->x1, drect->y1, /* xoffset, yoffset */
+                        0, 0, /* We blitted the image to the origin */
+                        drect->x2 - drect->x1, drect->y2 - drect->y1);
+    checkGLcall("glCopyTexSubImage2D");
+
+    /* Write the back buffer backup back */
+    glBindTexture(GL_TEXTURE_2D, backup);
+    checkGLcall("glBindTexture(GL_TEXTURE_2D, backup)");
+
+    glBegin(GL_QUADS);
+        /* top left */
+        glTexCoord2f(0.0, (float) fbheight / (float) Src->pow2Height);
+        glVertex2i(0, 0);
+
+        /* bottom left */
+        glTexCoord2f(0.0, 0.0);
+        glVertex2i(0, fbheight);
+
+        /* bottom right */
+        glTexCoord2f((float) fbwidth / (float) Src->pow2Width, 0.0);
+        glVertex2i(fbwidth, Src->currentDesc.Height);
+
+        /* top right */
+        glTexCoord2f((float) fbwidth / (float) Src->pow2Width, (float) fbheight / (float) Src->pow2Height);
+        glVertex2i(fbwidth, 0);
+    glEnd();
+
+    /* Cleanup */
+    if(src != backup) {
+        glDeleteTextures(1, &src);
+        checkGLcall("glDeleteTextures(1, &src)");
+    }
+    glDeleteTextures(1, &backup);
+    checkGLcall("glDeleteTextures(1, &backup)");
+    LEAVE_GL();
+}
+
 /* Not called from the VTable */
 static HRESULT IWineD3DSurfaceImpl_BltOverride(IWineD3DSurfaceImpl *This, RECT *DestRect, IWineD3DSurface *SrcSurface, RECT *SrcRect, DWORD Flags, DDBLTFX *DDBltFx) {
     WINED3DRECT rect;
@@ -2432,73 +2666,72 @@ static HRESULT IWineD3DSurfaceImpl_BltOverride(IWineD3DSurfaceImpl *This, RECT *
             (swapchain->backBuffer && SrcSurface == swapchain->backBuffer[0]) ) {
             if( ( (IWineD3DSurface *) This != swapchain->frontBuffer) &&
                 ( swapchain->backBuffer && (IWineD3DSurface *) This != swapchain->backBuffer[0]) ) {
-                UINT row;
                 WINED3DRECT srect;
-                float xrel, yrel;
+                BOOL upsideDown, stretchx;
 
                 TRACE("Blt from rendertarget to texture\n");
 
                 /* Call preload for the surface to make sure it isn't dirty */
                 IWineD3DSurface_PreLoad((IWineD3DSurface *) This);
 
+                /* Make sure that the top pixel is always above the bottom pixel, and keep a seperate upside down flag
+                 * glCopyTexSubImage is a bit picky about the parameters we pass to it
+                 */
                 if(SrcRect) {
+                    if(SrcRect->top < SrcRect->bottom) {
+                        srect.y1 = SrcRect->top;
+                        srect.y2 = SrcRect->bottom;
+                        upsideDown = FALSE;
+                    } else {
+                        srect.y1 = SrcRect->bottom;
+                        srect.y2 = SrcRect->top;
+                        upsideDown = TRUE;
+                    }
                     srect.x1 = SrcRect->left;
-                    srect.y1 = SrcRect->top;
                     srect.x2 = SrcRect->right;
-                    srect.y2 = SrcRect->bottom;
                 } else {
                     srect.x1 = 0;
                     srect.y1 = 0;
                     srect.x2 = Src->currentDesc.Width;
                     srect.y2 = Src->currentDesc.Height;
+                    upsideDown = FALSE;
+                }
+                if(rect.x1 > rect.x2) {
+                    UINT tmp = rect.x2;
+                    rect.x2 = rect.x1;
+                    rect.x1 = tmp;
+                    upsideDown = !upsideDown;
                 }
 
-                ENTER_GL();
-
-                /* Bind the target texture */
-                glBindTexture(GL_TEXTURE_2D, This->glDescription.textureName);
-                checkGLcall("glBindTexture");
-                if(swapchain->backBuffer && SrcSurface == swapchain->backBuffer[0]) {
-                    glReadBuffer(GL_BACK);
+                if(rect.x2 - rect.x1 != srect.x2 - srect.x1) {
+                    stretchx = TRUE;
                 } else {
-                    glReadBuffer(GL_FRONT);
+                    stretchx = FALSE;
                 }
-                checkGLcall("glReadBuffer");
-
-                xrel = (float) (srect.x2 - srect.x1) / (float) (rect.x2 - rect.x1);
-                yrel = (float) (srect.y2 - srect.y1) / (float) (rect.y2 - rect.y1);
 
-                /* I have to process this row by row to swap the image,
-                 * otherwise it would be upside down, so streching in y direction
-                 * doesn't cost extra time
+                /* Blt is a pretty powerful call, while glCopyTexSubImage2D is not. glCopyTexSubImage cannot
+                 * flip the image nor scale it. If GL_EXT_framebuffer_blit is available it can be used(hopefully,
+                 * not implemented by now). Otherwise:
                  *
-                 * However, streching in x direction can be avoided if not necessary
+                 * -> If the app asks for a unscaled, upside down copy, just perform one glCopyTexSubImage2D call
+                 * -> If the app wants a image width an unscaled width, copy it line per line
+                 * -> If the app wants a image that is scaled on the x axis, and the destination rectangle is smaller
+                 *    than the frame buffer, draw an upside down scaled image onto the fb, read it back and restore the
+                 *    back buffer. This is slower than reading line per line, thus not used for flipping
+                 * -> If the app wants a scaled image with a dest rect that is bigger than the fb, it has to be copied
+                 *    pixel by pixel
                  */
-                for(row = rect.y1; row < rect.y2; row++) {
-                    if( (xrel - 1.0 < -eps) || (xrel - 1.0 > eps)) {
-                        /* Well, that stuff works, but it's very slow.
-                         * find a better way instead
-                         */
-                        UINT col;
-                        for(col = rect.x1; col < rect.x2; col++) {
-                            glCopyTexSubImage2D(GL_TEXTURE_2D,
-                                                0, /* level */
-                                                rect.x1 + col, This->currentDesc.Height - row - 1, /* xoffset, yoffset */
-                                                srect.x1 + col * xrel, Src->currentDesc.Height - srect.y2 + row * yrel,
-                                                1, 1);
-                        }
-                    } else {
-                        glCopyTexSubImage2D(GL_TEXTURE_2D,
-                                            0, /* level */
-                                            rect.x1, rect.y2 + rect.y1 - row - 1, /* xoffset, yoffset */
-                                            srect.x1, row - rect.y1,
-                                            rect.x2-rect.x1, 1);
-                    }
+                if(FALSE /* GL_SUPPORT(EXT_FRAMEBUFFER_BLIT) */) {
+                    TRACE("Using GL_EXT_framebuffer_blit for copying\n");
+                } else if((!stretchx) || rect.x2 - rect.x1 > Src->currentDesc.Width ||
+                                         rect.y2 - rect.y1 > Src->currentDesc.Height) {
+                    TRACE("No stretching in x direction, using direct framebuffer -> texture copy\n");
+                    fb_copy_to_texture_direct(This, SrcSurface, swapchain, &srect, &rect, upsideDown);
+                } else {
+                    TRACE("Using hardware stretching to flip / stretch the texture\n");
+                    fb_copy_to_texture_hwstretch(This, SrcSurface, swapchain, &srect, &rect, upsideDown);
                 }
 
-                vcheckGLcall("glCopyTexSubImage2D");
-                LEAVE_GL();
-
                 if(!(This->Flags & SFLAG_DONOTFREE)) {
                     HeapFree(GetProcessHeap(), 0, This->resource.allocatedMemory);
                     This->resource.allocatedMemory = NULL;
-- 
1.4.4.3



More information about the wine-patches mailing list