From 1689c0fa3c6ab8c8afa1f939ffabcb250a8d4bae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20D=C3=B6singer?= Date: Wed, 2 Dec 2009 20:35:46 +0100 Subject: [PATCH 04/19] WineD3D: Infrastructure to render swapchains to a FBO This will be required for a number of things: -> Presenting to a window with different sizes than the back buffer -> Presenting partial rectangles -> Performant support of changing destination override windows -> Work around bugs in OSX --- dlls/wined3d/context.c | 2 +- dlls/wined3d/device.c | 8 +++ dlls/wined3d/surface.c | 44 +++++++++---- dlls/wined3d/swapchain.c | 132 +++++++++++++++++++++++++++++++++++++++- dlls/wined3d/wined3d_private.h | 1 + 5 files changed, 169 insertions(+), 18 deletions(-) diff --git a/dlls/wined3d/context.c b/dlls/wined3d/context.c index 5bd06be..b56890e 100644 --- a/dlls/wined3d/context.c +++ b/dlls/wined3d/context.c @@ -1893,7 +1893,7 @@ static inline struct wined3d_context *FindContext(IWineD3DDeviceImpl *This, IWin context = findThreadContextForSwapChain(swapchain, tid); old_render_offscreen = context->render_offscreen; - context->render_offscreen = FALSE; + context->render_offscreen = ((IWineD3DSwapChainImpl *)swapchain)->render_to_fbo; /* The context != This->activeContext will catch a NOP context change. This can occur * if we are switching back to swapchain rendering in case of FBO or Back Buffer offscreen * rendering. No context change is needed in that case diff --git a/dlls/wined3d/device.c b/dlls/wined3d/device.c index fe4bb4d..1edd71c 100644 --- a/dlls/wined3d/device.c +++ b/dlls/wined3d/device.c @@ -1082,6 +1082,7 @@ static HRESULT WINAPI IWineD3DDeviceImpl_CreateSwapChain(IWineD3DDevice *iface, object->wineD3DDevice = This; object->parent = parent; object->ref = 1; + object->render_to_fbo = FALSE; *ppSwapChain = (IWineD3DSwapChain *)object; @@ -1880,6 +1881,9 @@ static HRESULT WINAPI IWineD3DDeviceImpl_Init3D(IWineD3DDevice *iface, switch(wined3d_settings.offscreen_rendering_mode) { case ORM_FBO: + This->offscreenBuffer = GL_COLOR_ATTACHMENT0; + break; + case ORM_PBUFFER: This->offscreenBuffer = GL_BACK; break; @@ -6906,6 +6910,10 @@ static HRESULT WINAPI IWineD3DDeviceImpl_Reset(IWineD3DDevice* iface, WINED3DPRE } hr = create_primary_opengl_context(iface, (IWineD3DSwapChain *) swapchain); + if(SUCCEEDED(hr)) + { + swapchain->context[0]->render_offscreen = swapchain->render_to_fbo; + } IWineD3DSwapChain_Release((IWineD3DSwapChain *) swapchain); /* All done. There is no need to reload resources or shaders, this will happen automatically on the diff --git a/dlls/wined3d/surface.c b/dlls/wined3d/surface.c index cdc3286..f11fca8 100644 --- a/dlls/wined3d/surface.c +++ b/dlls/wined3d/surface.c @@ -668,6 +668,10 @@ GLenum surface_get_gl_buffer(IWineD3DSurface *iface, IWineD3DSwapChain *swapchai TRACE("(%p) : swapchain %p\n", This, swapchain); if (swapchain_impl->backBuffer && swapchain_impl->backBuffer[0] == iface) { + if(swapchain_impl->render_to_fbo) { + TRACE("Returning GL_COLOR_ATTACHMENT0\n"); + return GL_COLOR_ATTACHMENT0; + } TRACE("Returning GL_BACK\n"); return GL_BACK; } else if (swapchain_impl->frontBuffer == iface) { @@ -959,22 +963,25 @@ static void read_from_framebuffer(IWineD3DSurfaceImpl *This, CONST RECT *rect, v * There is no need to keep track of the current read buffer or reset it, every part of the code * that reads sets the read buffer as desired. */ - if (SUCCEEDED(IWineD3DSurface_GetContainer((IWineD3DSurface *) This, &IID_IWineD3DSwapChain, (void **)&swapchain))) + if (surface_is_offscreen((IWineD3DSurface *) This)) { - GLenum buffer = surface_get_gl_buffer((IWineD3DSurface *) This, (IWineD3DSwapChain *)swapchain); - TRACE("Locking %#x buffer\n", buffer); - glReadBuffer(buffer); - checkGLcall("glReadBuffer"); - - IWineD3DSwapChain_Release((IWineD3DSwapChain *) swapchain); - srcIsUpsideDown = FALSE; - } else { /* Locking the primary render target which is not on a swapchain(=offscreen render target). * Read from the back buffer */ TRACE("Locking offscreen render target\n"); glReadBuffer(myDevice->offscreenBuffer); srcIsUpsideDown = TRUE; + } else { + GLenum buffer; + + IWineD3DSurface_GetContainer((IWineD3DSurface *) This, &IID_IWineD3DSwapChain, (void **)&swapchain); + buffer = surface_get_gl_buffer((IWineD3DSurface *) This, (IWineD3DSwapChain *)swapchain); + TRACE("Locking %#x buffer\n", buffer); + glReadBuffer(buffer); + checkGLcall("glReadBuffer"); + + srcIsUpsideDown = FALSE; + IWineD3DSwapChain_Release((IWineD3DSwapChain *) swapchain); } /* TODO: Get rid of the extra rectangle comparison and construction of a full surface rectangle */ @@ -3102,7 +3109,7 @@ static inline void fb_copy_to_texture_direct(IWineD3DSurfaceImpl *This, IWineD3D /* Bind the target texture */ glBindTexture(This->texture_target, This->texture_name); checkGLcall("glBindTexture"); - if(!swapchain) { + if(surface_is_offscreen(SrcSurface)) { TRACE("Reading from an offscreen target\n"); upsidedown = !upsidedown; glReadBuffer(myDevice->offscreenBuffer); @@ -3241,12 +3248,12 @@ static inline void fb_copy_to_texture_hwstretch(IWineD3DSurfaceImpl *This, IWine Src->Flags &= ~SFLAG_INTEXTURE; } - if(swapchain) { - glReadBuffer(surface_get_gl_buffer(SrcSurface, (IWineD3DSwapChain *)swapchain)); - } else { + if(surface_is_offscreen(SrcSurface)) { TRACE("Reading from an offscreen target\n"); upsidedown = !upsidedown; glReadBuffer(myDevice->offscreenBuffer); + } else { + glReadBuffer(surface_get_gl_buffer(SrcSurface, (IWineD3DSwapChain *)swapchain)); } /* TODO: Only back up the part that will be overwritten */ @@ -5129,8 +5136,17 @@ static HRESULT WINAPI IWineD3DSurfaceImpl_DrawOverlay(IWineD3DSurface *iface) { BOOL surface_is_offscreen(IWineD3DSurface *iface) { IWineD3DSurfaceImpl *This = (IWineD3DSurfaceImpl *) iface; + IWineD3DSwapChainImpl *swapchain = (IWineD3DSwapChainImpl *) This->container; + + /* Not on a swapchain - must be offscreen */ + if (!(This->Flags & SFLAG_SWAPCHAIN)) return TRUE; + + /* The front buffer is always onscreen */ + if(iface == swapchain->frontBuffer) return FALSE; - return !(This->Flags & SFLAG_SWAPCHAIN); + /* If the swapchain is rendered to an FBO, the backbuffer is + * offscreen, otherwise onscreen */ + return swapchain->render_to_fbo; } const IWineD3DSurfaceVtbl IWineD3DSurface_Vtbl = diff --git a/dlls/wined3d/swapchain.c b/dlls/wined3d/swapchain.c index a23166a..979d39a 100644 --- a/dlls/wined3d/swapchain.c +++ b/dlls/wined3d/swapchain.c @@ -95,6 +95,113 @@ static void WINAPI IWineD3DSwapChainImpl_Destroy(IWineD3DSwapChain *iface) HeapFree(GetProcessHeap(), 0, This); } +static inline void swapchain_blit(IWineD3DSwapChainImpl *This, struct wined3d_context *context) +{ + RECT window; + IWineD3DSurfaceImpl *backbuffer = ((IWineD3DSurfaceImpl *) This->backBuffer[0]); + UINT w = backbuffer->currentDesc.Width; + UINT h = backbuffer->currentDesc.Height; + GLenum gl_filter; + const struct wined3d_gl_info *gl_info = context->gl_info; + + GetClientRect(This->win_handle, &window); + if(w == window.right && h == window.bottom) gl_filter = GL_NEAREST; + else gl_filter = GL_LINEAR; + + if(gl_info->supported[EXT_FRAMEBUFFER_BLIT]) + { + ENTER_GL(); + context_bind_fbo(context, GL_READ_FRAMEBUFFER, &context->src_fbo); + context_attach_surface_fbo(context, GL_READ_FRAMEBUFFER, 0, This->backBuffer[0]); + context_attach_depth_stencil_fbo(context, GL_READ_FRAMEBUFFER, NULL, FALSE); + + context_bind_fbo(context, GL_DRAW_FRAMEBUFFER, NULL); + glDrawBuffer(GL_BACK); + + glDisable(GL_SCISSOR_TEST); + IWineD3DDeviceImpl_MarkStateDirty(This->wineD3DDevice, STATE_RENDER(WINED3DRS_SCISSORTESTENABLE)); + + /* Note that the texture is upside down */ + gl_info->fbo_ops.glBlitFramebuffer(0, 0, w, h, + window.left, window.bottom, window.right, window.top, + GL_COLOR_BUFFER_BIT, gl_filter); + checkGLcall("Swapchain present blit(EXT_framebuffer_blit)\n"); + LEAVE_GL(); + } + else + { + struct wined3d_context *context2; + float tex_left = 0; + float tex_top = 0; + float tex_right = w; + float tex_bottom = h; + + context2 = context_acquire(This->wineD3DDevice, This->backBuffer[0], CTXUSAGE_BLIT); + + if(backbuffer->Flags & SFLAG_NORMCOORD) + { + tex_left /= w; + tex_right /= w; + tex_top /= h; + tex_bottom /= h; + } + + ENTER_GL(); + context_bind_fbo(context2, GL_DRAW_FRAMEBUFFER, NULL); + + /* Set up the texture. The surface is not in a IWineD3D*Texture container, + * so there are no d3d texture settings to dirtify + */ + glBindTexture(backbuffer->texture_target, backbuffer->texture_name); + glEnable(backbuffer->texture_target); + glTexParameteri(backbuffer->texture_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(backbuffer->texture_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + glDrawBuffer(GL_BACK); + + /* Set the viewport to the destination rectandle, disable any projection + * transformation set up by CTXUSAGE_BLIT, and draw a (-1,-1)-(1,1) quad. + * + * Back up viewport and matrix to avoid breaking last_was_blit + * + * Note that CTXUSAGE_BLIT set up viewport and ortho to match the surface + * size - we want the GL drawable(=window) size. + */ + glPushAttrib(GL_VIEWPORT_BIT); + glViewport(window.left, window.top, window.right, window.bottom); + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + + glBegin(GL_QUADS); + /* bottom left */ + glTexCoord2f(tex_left, tex_bottom); + glVertex2i(-1, -1); + + /* top left */ + glTexCoord2f(tex_left, tex_top); + glVertex2i(-1, 1); + + /* top right */ + glTexCoord2f(tex_right, tex_top); + glVertex2i(1, 1); + + /* bottom right */ + glTexCoord2f(tex_right, tex_bottom); + glVertex2i(1, -1); + glEnd(); + + glPopMatrix(); + glPopAttrib(); + + glDisable(backbuffer->texture_target); + checkGLcall("Swapchain present blit(manual)\n"); + LEAVE_GL(); + + context_release(context2); + } +} + static HRESULT WINAPI IWineD3DSwapChainImpl_Present(IWineD3DSwapChain *iface, CONST RECT *pSourceRect, CONST RECT *pDestRect, HWND hDestWindowOverride, CONST RGNDATA *pDirtyRegion, DWORD dwFlags) { IWineD3DSwapChainImpl *This = (IWineD3DSwapChainImpl *)iface; struct wined3d_context *context; @@ -160,6 +267,22 @@ static HRESULT WINAPI IWineD3DSwapChainImpl_Present(IWineD3DSwapChain *iface, CO IWineD3DSwapChain_SetDestWindowOverride(iface, hDestWindowOverride); } + if(This->render_to_fbo) + { + /* This codepath should only be hit with the COPY swapeffect. Otherwise a backbuffer- + * window size mismatch is impossible(fullscreen) and src and dst rectangles are + * not allowed(they need the COPY swapeffect) + * + * The DISCARD swap effect is ok as well since any backbuffer content is allowed after + * the swap + */ + if(This->presentParms.SwapEffect == WINED3DSWAPEFFECT_FLIP ) + { + FIXME("Render-to-fbo with WINED3DSWAPEFFECT_FLIP\n"); + } + swapchain_blit(This, context); + } + SwapBuffers(This->context[0]->hdc); /* TODO: cycle through the swapchain buffers */ TRACE("SwapBuffers called, Starting new frame\n"); @@ -239,9 +362,12 @@ static HRESULT WINAPI IWineD3DSwapChainImpl_Present(IWineD3DSwapChain *iface, CO WINED3DCLEAR_TARGET, 0xff00ffff, 1.0f, 0); } - if(((IWineD3DSurfaceImpl *) This->frontBuffer)->Flags & SFLAG_INSYSMEM || - ((IWineD3DSurfaceImpl *) This->backBuffer[0])->Flags & SFLAG_INSYSMEM ) { - /* Both memory copies of the surfaces are ok, flip them around too instead of dirtifying */ + if(!This->render_to_fbo && + ( ((IWineD3DSurfaceImpl *) This->frontBuffer)->Flags & SFLAG_INSYSMEM || + ((IWineD3DSurfaceImpl *) This->backBuffer[0])->Flags & SFLAG_INSYSMEM ) ) { + /* Both memory copies of the surfaces are ok, flip them around too instead of dirtifying + * Doesn't work with render_to_fbo because we're not flipping + */ IWineD3DSurfaceImpl *front = (IWineD3DSurfaceImpl *) This->frontBuffer; IWineD3DSurfaceImpl *back = (IWineD3DSurfaceImpl *) This->backBuffer[0]; diff --git a/dlls/wined3d/wined3d_private.h b/dlls/wined3d/wined3d_private.h index a64691c..e385933 100644 --- a/dlls/wined3d/wined3d_private.h +++ b/dlls/wined3d/wined3d_private.h @@ -2413,6 +2413,7 @@ typedef struct IWineD3DSwapChainImpl DWORD orig_width, orig_height; WINED3DFORMAT orig_fmt; WINED3DGAMMARAMP orig_gamma; + BOOL render_to_fbo; long prev_time, frames; /* Performance tracking */ unsigned int vSyncCounter; -- 1.6.4.4