/* * Copyright 2011 Joakim Sindholt * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * on the rights to use, copy, modify, merge, publish, distribute, sub * license, and/or sell copies of the Software, and to permit persons to whom * the Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL * THE AUTHOR(S) AND/OR THEIR SUPPLIERS BE LIABLE FOR ANY CLAIM, * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE * USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "swapchain9.h" #include "surface9.h" #include "device9.h" #include "nine_helpers.h" #include "nine_pipe.h" #include "nine_dump.h" #include "util/u_atomic.h" #include "util/u_inlines.h" #include "util/u_surface.h" #include "hud/hud_context.h" #include "frontend/drm_driver.h" #include "threadpool.h" #define DBG_CHANNEL DBG_SWAPCHAIN #define UNTESTED(n) DBG("UNTESTED point %d. Please tell if it worked\n", n) HRESULT NineSwapChain9_ctor( struct NineSwapChain9 *This, struct NineUnknownParams *pParams, BOOL implicit, ID3DPresent *pPresent, D3DPRESENT_PARAMETERS *pPresentationParameters, struct d3dadapter9_context *pCTX, HWND hFocusWindow, D3DDISPLAYMODEEX *mode ) { HRESULT hr; int i; DBG("This=%p pDevice=%p pPresent=%p pCTX=%p hFocusWindow=%p\n", This, pParams->device, pPresent, pCTX, hFocusWindow); hr = NineUnknown_ctor(&This->base, pParams); if (FAILED(hr)) return hr; This->screen = NineDevice9_GetScreen(This->base.device); This->implicit = implicit; This->actx = pCTX; This->present = pPresent; This->mode = NULL; ID3DPresent_AddRef(pPresent); if (This->base.device->minor_version_num > 2) { D3DPRESENT_PARAMETERS2 params2; memset(¶ms2, 0, sizeof(D3DPRESENT_PARAMETERS2)); params2.AllowDISCARDDelayedRelease = This->actx->discard_delayed_release; params2.TearFreeDISCARD = This->actx->tearfree_discard; ID3DPresent_SetPresentParameters2(pPresent, ¶ms2); } if (!pPresentationParameters->hDeviceWindow) pPresentationParameters->hDeviceWindow = hFocusWindow; This->rendering_done = FALSE; This->pool = NULL; for (i = 0; i < D3DPRESENT_BACK_BUFFERS_MAX_EX + 1; i++) { This->pending_presentation[i] = calloc(1, sizeof(BOOL)); if (!This->pending_presentation[i]) return E_OUTOFMEMORY; } return NineSwapChain9_Resize(This, pPresentationParameters, mode); } static D3DWindowBuffer * D3DWindowBuffer_create(struct NineSwapChain9 *This, struct pipe_resource *resource, int depth, int for_frontbuffer_reading) { D3DWindowBuffer *ret; struct pipe_context *pipe = nine_context_get_pipe_acquire(This->base.device); struct winsys_handle whandle; int stride, dmaBufFd; HRESULT hr; memset(&whandle, 0, sizeof(whandle)); whandle.type = WINSYS_HANDLE_TYPE_FD; This->screen->resource_get_handle(This->screen, pipe, resource, &whandle, for_frontbuffer_reading ? PIPE_HANDLE_USAGE_FRAMEBUFFER_WRITE : PIPE_HANDLE_USAGE_EXPLICIT_FLUSH); nine_context_get_pipe_release(This->base.device); stride = whandle.stride; dmaBufFd = whandle.handle; hr = ID3DPresent_NewD3DWindowBufferFromDmaBuf(This->present, dmaBufFd, resource->width0, resource->height0, stride, depth, 32, &ret); assert (SUCCEEDED(hr)); if (FAILED(hr)) { ERR("Failed to create new D3DWindowBufferFromDmaBuf\n"); return NULL; } return ret; } static void D3DWindowBuffer_release(struct NineSwapChain9 *This, D3DWindowBuffer *present_handle) { int i; /* IsBufferReleased API not available */ if (This->base.device->minor_version_num <= 2) { ID3DPresent_DestroyD3DWindowBuffer(This->present, present_handle); return; } /* Add it to the 'pending release' list */ for (i = 0; i < D3DPRESENT_BACK_BUFFERS_MAX_EX + 1; i++) { if (!This->present_handles_pending_release[i]) { This->present_handles_pending_release[i] = present_handle; break; } } if (i == (D3DPRESENT_BACK_BUFFERS_MAX_EX + 1)) { ERR("Server not releasing buffers...\n"); assert(false); } /* Destroy elements of the list released by the server */ for (i = 0; i < D3DPRESENT_BACK_BUFFERS_MAX_EX + 1; i++) { if (This->present_handles_pending_release[i] && ID3DPresent_IsBufferReleased(This->present, This->present_handles_pending_release[i])) { /* WaitBufferReleased also waits the presentation feedback * (which should arrive at about the same time), * while IsBufferReleased doesn't. DestroyD3DWindowBuffer unfortunately * checks it to release immediately all data, else the release * is postponed for This->present release. To avoid leaks (we may handle * a lot of resize), call WaitBufferReleased. */ ID3DPresent_WaitBufferReleased(This->present, This->present_handles_pending_release[i]); ID3DPresent_DestroyD3DWindowBuffer(This->present, This->present_handles_pending_release[i]); This->present_handles_pending_release[i] = NULL; } } } static int NineSwapChain9_GetBackBufferCountForParams( struct NineSwapChain9 *This, D3DPRESENT_PARAMETERS *pParams ); HRESULT NineSwapChain9_Resize( struct NineSwapChain9 *This, D3DPRESENT_PARAMETERS *pParams, D3DDISPLAYMODEEX *mode ) { struct NineDevice9 *pDevice = This->base.device; D3DSURFACE_DESC desc; HRESULT hr; struct pipe_resource *resource, tmplt; enum pipe_format pf; BOOL has_present_buffers = FALSE; int depth; unsigned i, oldBufferCount, newBufferCount; D3DMULTISAMPLE_TYPE multisample_type; DBG("This=%p pParams=%p\n", This, pParams); user_assert(pParams != NULL, E_POINTER); user_assert(pParams->SwapEffect, D3DERR_INVALIDCALL); user_assert((pParams->SwapEffect != D3DSWAPEFFECT_COPY) || (pParams->BackBufferCount <= 1), D3DERR_INVALIDCALL); user_assert(pDevice->ex || pParams->BackBufferCount <= D3DPRESENT_BACK_BUFFERS_MAX, D3DERR_INVALIDCALL); user_assert(!pDevice->ex || pParams->BackBufferCount <= D3DPRESENT_BACK_BUFFERS_MAX_EX, D3DERR_INVALIDCALL); user_assert(pDevice->ex || (pParams->SwapEffect == D3DSWAPEFFECT_FLIP) || (pParams->SwapEffect == D3DSWAPEFFECT_COPY) || (pParams->SwapEffect == D3DSWAPEFFECT_DISCARD), D3DERR_INVALIDCALL); DBG("pParams(%p):\n" "BackBufferWidth: %u\n" "BackBufferHeight: %u\n" "BackBufferFormat: %s\n" "BackBufferCount: %u\n" "MultiSampleType: %u\n" "MultiSampleQuality: %u\n" "SwapEffect: %u\n" "hDeviceWindow: %p\n" "Windowed: %i\n" "EnableAutoDepthStencil: %i\n" "AutoDepthStencilFormat: %s\n" "Flags: %s\n" "FullScreen_RefreshRateInHz: %u\n" "PresentationInterval: %x\n", pParams, pParams->BackBufferWidth, pParams->BackBufferHeight, d3dformat_to_string(pParams->BackBufferFormat), pParams->BackBufferCount, pParams->MultiSampleType, pParams->MultiSampleQuality, pParams->SwapEffect, pParams->hDeviceWindow, pParams->Windowed, pParams->EnableAutoDepthStencil, d3dformat_to_string(pParams->AutoDepthStencilFormat), nine_D3DPRESENTFLAG_to_str(pParams->Flags), pParams->FullScreen_RefreshRateInHz, pParams->PresentationInterval); if (pParams->BackBufferCount == 0) { pParams->BackBufferCount = 1; } if (pParams->BackBufferFormat == D3DFMT_UNKNOWN) { pParams->BackBufferFormat = D3DFMT_A8R8G8B8; } This->desired_fences = This->actx->throttling ? This->actx->throttling_value + 1 : 0; /* +1 because we add the fence of the current buffer before popping an old one */ if (This->desired_fences > DRI_SWAP_FENCES_MAX) This->desired_fences = DRI_SWAP_FENCES_MAX; if (This->actx->vblank_mode == 0) pParams->PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; else if (This->actx->vblank_mode == 3) pParams->PresentationInterval = D3DPRESENT_INTERVAL_ONE; if (mode && This->mode) { *(This->mode) = *mode; } else if (mode) { This->mode = malloc(sizeof(D3DDISPLAYMODEEX)); memcpy(This->mode, mode, sizeof(D3DDISPLAYMODEEX)); } else { free(This->mode); This->mode = NULL; } /* Note: It is the role of the backend to fill if necessary * BackBufferWidth and BackBufferHeight */ hr = ID3DPresent_SetPresentParameters(This->present, pParams, This->mode); if (hr != D3D_OK) return hr; oldBufferCount = This->num_back_buffers; newBufferCount = NineSwapChain9_GetBackBufferCountForParams(This, pParams); multisample_type = pParams->MultiSampleType; /* Map MultiSampleQuality to MultiSampleType */ hr = d3dmultisample_type_check(This->screen, pParams->BackBufferFormat, &multisample_type, pParams->MultiSampleQuality, NULL); if (FAILED(hr)) { return hr; } pf = d3d9_to_pipe_format_checked(This->screen, pParams->BackBufferFormat, PIPE_TEXTURE_2D, multisample_type, PIPE_BIND_RENDER_TARGET, FALSE, FALSE); if (This->actx->linear_framebuffer || (pf != PIPE_FORMAT_B8G8R8X8_UNORM && pf != PIPE_FORMAT_B8G8R8A8_UNORM) || pParams->SwapEffect != D3DSWAPEFFECT_DISCARD || multisample_type >= 2 || (This->actx->ref && This->actx->ref == This->screen)) has_present_buffers = TRUE; /* Note: the buffer depth has to match the window depth. * In practice, ARGB buffers can be used with windows * of depth 24. Windows of depth 32 are extremely rare. * So even if the buffer is ARGB, say it is depth 24. * It is common practice, for example that's how * glamor implements depth 24. * TODO: handle windows with other depths. Not possible in the short term. * For example 16 bits.*/ depth = 24; memset(&tmplt, 0, sizeof(tmplt)); tmplt.target = PIPE_TEXTURE_2D; tmplt.width0 = pParams->BackBufferWidth; tmplt.height0 = pParams->BackBufferHeight; tmplt.depth0 = 1; tmplt.last_level = 0; tmplt.array_size = 1; tmplt.usage = PIPE_USAGE_DEFAULT; tmplt.flags = 0; desc.Type = D3DRTYPE_SURFACE; desc.Pool = D3DPOOL_DEFAULT; desc.MultiSampleType = pParams->MultiSampleType; desc.MultiSampleQuality = pParams->MultiSampleQuality; desc.Width = pParams->BackBufferWidth; desc.Height = pParams->BackBufferHeight; for (i = 0; i < oldBufferCount; i++) { if (This->tasks[i]) _mesa_threadpool_wait_for_task(This->pool, &(This->tasks[i])); } memset(This->tasks, 0, sizeof(This->tasks)); if (This->pool) { _mesa_threadpool_destroy(This, This->pool); This->pool = NULL; } This->enable_threadpool = This->actx->thread_submit && (pParams->SwapEffect != D3DSWAPEFFECT_COPY); if (This->enable_threadpool) This->pool = _mesa_threadpool_create(This); if (!This->pool) This->enable_threadpool = FALSE; for (i = 0; i < oldBufferCount; i++) { D3DWindowBuffer_release(This, This->present_handles[i]); This->present_handles[i] = NULL; if (This->present_buffers[i]) pipe_resource_reference(&(This->present_buffers[i]), NULL); } if (newBufferCount != oldBufferCount) { for (i = newBufferCount; i < oldBufferCount; ++i) NineUnknown_Detach(NineUnknown(This->buffers[i])); for (i = oldBufferCount; i < newBufferCount; ++i) { This->buffers[i] = NULL; This->present_handles[i] = NULL; } } This->num_back_buffers = newBufferCount; for (i = 0; i < newBufferCount; ++i) { tmplt.bind = PIPE_BIND_SAMPLER_VIEW | PIPE_BIND_RENDER_TARGET; tmplt.nr_samples = multisample_type; tmplt.nr_storage_samples = multisample_type; if (!has_present_buffers) tmplt.bind |= NINE_BIND_PRESENTBUFFER_FLAGS; tmplt.format = d3d9_to_pipe_format_checked(This->screen, pParams->BackBufferFormat, PIPE_TEXTURE_2D, tmplt.nr_samples, tmplt.bind, FALSE, FALSE); if (tmplt.format == PIPE_FORMAT_NONE) return D3DERR_INVALIDCALL; resource = This->screen->resource_create(This->screen, &tmplt); if (!resource) { DBG("Failed to create pipe_resource.\n"); return D3DERR_OUTOFVIDEOMEMORY; } if (pParams->Flags & D3DPRESENTFLAG_LOCKABLE_BACKBUFFER) resource->flags |= NINE_RESOURCE_FLAG_LOCKABLE; if (This->buffers[i]) { NineSurface9_SetMultiSampleType(This->buffers[i], desc.MultiSampleType); NineSurface9_SetResourceResize(This->buffers[i], resource); if (has_present_buffers) pipe_resource_reference(&resource, NULL); } else { desc.Format = pParams->BackBufferFormat; desc.Usage = D3DUSAGE_RENDERTARGET; hr = NineSurface9_new(pDevice, NineUnknown(This), resource, NULL, 0, 0, 0, &desc, &This->buffers[i]); if (has_present_buffers) pipe_resource_reference(&resource, NULL); if (FAILED(hr)) { DBG("Failed to create RT surface.\n"); return hr; } This->buffers[i]->base.base.forward = FALSE; } if (has_present_buffers) { tmplt.format = PIPE_FORMAT_B8G8R8X8_UNORM; tmplt.bind = NINE_BIND_PRESENTBUFFER_FLAGS; tmplt.nr_samples = 0; tmplt.nr_storage_samples = 0; if (This->actx->linear_framebuffer) tmplt.bind |= PIPE_BIND_LINEAR; if (pParams->SwapEffect != D3DSWAPEFFECT_DISCARD) tmplt.bind |= PIPE_BIND_RENDER_TARGET; resource = This->screen->resource_create(This->screen, &tmplt); pipe_resource_reference(&(This->present_buffers[i]), resource); } This->present_handles[i] = D3DWindowBuffer_create(This, resource, depth, false); pipe_resource_reference(&resource, NULL); if (!This->present_handles[i]) { return D3DERR_DRIVERINTERNALERROR; } } if (pParams->EnableAutoDepthStencil) { tmplt.bind = d3d9_get_pipe_depth_format_bindings(pParams->AutoDepthStencilFormat); tmplt.nr_samples = multisample_type; tmplt.nr_storage_samples = multisample_type; tmplt.format = d3d9_to_pipe_format_checked(This->screen, pParams->AutoDepthStencilFormat, PIPE_TEXTURE_2D, tmplt.nr_samples, tmplt.bind, FALSE, FALSE); if (tmplt.format == PIPE_FORMAT_NONE) return D3DERR_INVALIDCALL; if (This->zsbuf) { resource = This->screen->resource_create(This->screen, &tmplt); if (!resource) { DBG("Failed to create pipe_resource for depth buffer.\n"); return D3DERR_OUTOFVIDEOMEMORY; } NineSurface9_SetMultiSampleType(This->zsbuf, desc.MultiSampleType); NineSurface9_SetResourceResize(This->zsbuf, resource); pipe_resource_reference(&resource, NULL); } else { hr = NineDevice9_CreateDepthStencilSurface(pDevice, pParams->BackBufferWidth, pParams->BackBufferHeight, pParams->AutoDepthStencilFormat, pParams->MultiSampleType, pParams->MultiSampleQuality, 0, (IDirect3DSurface9 **)&This->zsbuf, NULL); if (FAILED(hr)) { DBG("Failed to create ZS surface.\n"); return hr; } NineUnknown_ConvertRefToBind(NineUnknown(This->zsbuf)); } } This->params = *pParams; return D3D_OK; } /* Throttling: code adapted from the dri frontend */ /** * swap_fences_pop_front - pull a fence from the throttle queue * * If the throttle queue is filled to the desired number of fences, * pull fences off the queue until the number is less than the desired * number of fences, and return the last fence pulled. */ static struct pipe_fence_handle * swap_fences_pop_front(struct NineSwapChain9 *This) { struct pipe_screen *screen = This->screen; struct pipe_fence_handle *fence = NULL; if (This->desired_fences == 0) return NULL; if (This->cur_fences >= This->desired_fences) { screen->fence_reference(screen, &fence, This->swap_fences[This->tail]); screen->fence_reference(screen, &This->swap_fences[This->tail++], NULL); This->tail &= DRI_SWAP_FENCES_MASK; --This->cur_fences; } return fence; } /** * swap_fences_see_front - same than swap_fences_pop_front without * pulling * */ static struct pipe_fence_handle * swap_fences_see_front(struct NineSwapChain9 *This) { struct pipe_screen *screen = This->screen; struct pipe_fence_handle *fence = NULL; if (This->desired_fences == 0) return NULL; if (This->cur_fences >= This->desired_fences) { screen->fence_reference(screen, &fence, This->swap_fences[This->tail]); } return fence; } /** * swap_fences_push_back - push a fence onto the throttle queue at the back * * push a fence onto the throttle queue and pull fences of the queue * so that the desired number of fences are on the queue. */ static void swap_fences_push_back(struct NineSwapChain9 *This, struct pipe_fence_handle *fence) { struct pipe_screen *screen = This->screen; if (!fence || This->desired_fences == 0) return; while(This->cur_fences == This->desired_fences) swap_fences_pop_front(This); This->cur_fences++; screen->fence_reference(screen, &This->swap_fences[This->head++], fence); This->head &= DRI_SWAP_FENCES_MASK; } /** * swap_fences_unref - empty the throttle queue * * pulls fences of the throttle queue until it is empty. */ static void swap_fences_unref(struct NineSwapChain9 *This) { struct pipe_screen *screen = This->screen; while(This->cur_fences) { screen->fence_reference(screen, &This->swap_fences[This->tail++], NULL); This->tail &= DRI_SWAP_FENCES_MASK; --This->cur_fences; } } void NineSwapChain9_dtor( struct NineSwapChain9 *This ) { unsigned i; DBG("This=%p\n", This); if (This->pool) _mesa_threadpool_destroy(This, This->pool); for (i = 0; i < D3DPRESENT_BACK_BUFFERS_MAX_EX + 1; i++) { if (This->pending_presentation[i]) FREE(This->pending_presentation[i]); } for (i = 0; i < D3DPRESENT_BACK_BUFFERS_MAX_EX + 1; i++) { if (This->present_handles_pending_release[i]) ID3DPresent_DestroyD3DWindowBuffer(This->present, This->present_handles_pending_release[i]); } for (i = 0; i < This->num_back_buffers; i++) { if (This->buffers[i]) NineUnknown_Detach(NineUnknown(This->buffers[i])); if (This->present_handles[i]) ID3DPresent_DestroyD3DWindowBuffer(This->present, This->present_handles[i]); if (This->present_buffers[i]) pipe_resource_reference(&(This->present_buffers[i]), NULL); } if (This->zsbuf) NineUnknown_Unbind(NineUnknown(This->zsbuf)); if (This->present) ID3DPresent_Release(This->present); swap_fences_unref(This); NineUnknown_dtor(&This->base); } static void create_present_buffer( struct NineSwapChain9 *This, unsigned int width, unsigned int height, struct pipe_resource **resource, D3DWindowBuffer **present_handle) { struct pipe_resource tmplt; memset(&tmplt, 0, sizeof(tmplt)); tmplt.target = PIPE_TEXTURE_2D; tmplt.width0 = width; tmplt.height0 = height; tmplt.depth0 = 1; tmplt.last_level = 0; tmplt.array_size = 1; tmplt.usage = PIPE_USAGE_DEFAULT; tmplt.flags = 0; tmplt.format = PIPE_FORMAT_B8G8R8X8_UNORM; tmplt.bind = NINE_BIND_BACKBUFFER_FLAGS | NINE_BIND_PRESENTBUFFER_FLAGS; tmplt.nr_samples = 0; if (This->actx->linear_framebuffer) tmplt.bind |= PIPE_BIND_LINEAR; *resource = This->screen->resource_create(This->screen, &tmplt); *present_handle = D3DWindowBuffer_create(This, *resource, 24, true); if (!*present_handle) { pipe_resource_reference(resource, NULL); } } static void handle_draw_cursor_and_hud( struct NineSwapChain9 *This, struct pipe_resource *resource) { struct NineDevice9 *device = This->base.device; struct pipe_blit_info blit; struct pipe_context *pipe; if (device->cursor.software && device->cursor.visible && device->cursor.w) { memset(&blit, 0, sizeof(blit)); blit.src.resource = device->cursor.image; blit.src.level = 0; blit.src.format = device->cursor.image->format; blit.src.box.x = 0; blit.src.box.y = 0; blit.src.box.z = 0; blit.src.box.depth = 1; blit.src.box.width = device->cursor.w; blit.src.box.height = device->cursor.h; blit.dst.resource = resource; blit.dst.level = 0; blit.dst.format = resource->format; blit.dst.box.z = 0; blit.dst.box.depth = 1; blit.mask = PIPE_MASK_RGBA; blit.filter = PIPE_TEX_FILTER_NEAREST; blit.scissor_enable = FALSE; /* NOTE: blit messes up when box.x + box.width < 0, fix driver * NOTE2: device->cursor.pos contains coordinates relative to the screen. * This happens to be also the position of the cursor when we are fullscreen. * We don't use sw cursor for Windowed mode */ blit.dst.box.x = MAX2(device->cursor.pos.x, 0) - device->cursor.hotspot.x; blit.dst.box.y = MAX2(device->cursor.pos.y, 0) - device->cursor.hotspot.y; blit.dst.box.width = blit.src.box.width; blit.dst.box.height = blit.src.box.height; DBG("Blitting cursor(%ux%u) to (%i,%i).\n", blit.src.box.width, blit.src.box.height, blit.dst.box.x, blit.dst.box.y); blit.alpha_blend = TRUE; pipe = NineDevice9_GetPipe(This->base.device); pipe->blit(pipe, &blit); } if (device->hud && resource) { /* Implicit use of context pipe */ (void)NineDevice9_GetPipe(This->base.device); hud_run(device->hud, NULL, resource); /* XXX: no offset */ /* HUD doesn't clobber stipple */ nine_state_restore_non_cso(device); } } struct end_present_struct { struct pipe_screen *screen; struct pipe_fence_handle *fence_to_wait; ID3DPresent *present; D3DWindowBuffer *present_handle; BOOL *pending_presentation; HWND hDestWindowOverride; }; static void work_present(void *data) { struct end_present_struct *work = data; if (work->fence_to_wait) { (void) work->screen->fence_finish(work->screen, NULL, work->fence_to_wait, PIPE_TIMEOUT_INFINITE); work->screen->fence_reference(work->screen, &(work->fence_to_wait), NULL); } ID3DPresent_PresentBuffer(work->present, work->present_handle, work->hDestWindowOverride, NULL, NULL, NULL, 0); p_atomic_set(work->pending_presentation, FALSE); free(work); } static void pend_present(struct NineSwapChain9 *This, struct pipe_fence_handle *fence, HWND hDestWindowOverride) { struct end_present_struct *work = calloc(1, sizeof(struct end_present_struct)); work->screen = This->screen; This->screen->fence_reference(This->screen, &work->fence_to_wait, fence); work->present = This->present; work->present_handle = This->present_handles[0]; work->hDestWindowOverride = hDestWindowOverride; work->pending_presentation = This->pending_presentation[0]; p_atomic_set(work->pending_presentation, TRUE); This->tasks[0] = _mesa_threadpool_queue_task(This->pool, work_present, work); return; } static inline HRESULT present( struct NineSwapChain9 *This, const RECT *pSourceRect, const RECT *pDestRect, HWND hDestWindowOverride, const RGNDATA *pDirtyRegion, DWORD dwFlags ) { struct pipe_context *pipe; struct pipe_resource *resource; struct pipe_fence_handle *fence; HRESULT hr; struct pipe_blit_info blit; int target_width, target_height, target_depth, i; DBG("present: This=%p pSourceRect=%p pDestRect=%p " "pDirtyRegion=%p hDestWindowOverride=%p" "dwFlags=%d resource=%p\n", This, pSourceRect, pDestRect, pDirtyRegion, hDestWindowOverride, (int)dwFlags, This->buffers[0]->base.resource); if (pSourceRect) DBG("pSourceRect = (%u..%u)x(%u..%u)\n", pSourceRect->left, pSourceRect->right, pSourceRect->top, pSourceRect->bottom); if (pDestRect) DBG("pDestRect = (%u..%u)x(%u..%u)\n", pDestRect->left, pDestRect->right, pDestRect->top, pDestRect->bottom); /* TODO: in the case the source and destination rect have different size: * We need to allocate a new buffer, and do a blit to it to resize. * We can't use the present_buffer for that since when we created it, * we couldn't guess which size would have been needed. * If pDestRect or pSourceRect is null, we have to check the sizes * from the source size, and the destination window size. * In this case, either resize rngdata, or pass NULL instead */ /* Note: This->buffers[0]->level should always be 0 */ if (This->rendering_done) goto bypass_rendering; resource = This->buffers[0]->base.resource; if (This->params.SwapEffect == D3DSWAPEFFECT_DISCARD) handle_draw_cursor_and_hud(This, resource); hr = ID3DPresent_GetWindowInfo(This->present, hDestWindowOverride, &target_width, &target_height, &target_depth); (void)target_depth; /* Can happen with old Wine (presentation can still succeed), * or at window destruction. * Also disable for very old wine as D3DWindowBuffer_release * cannot do the DestroyD3DWindowBuffer workaround. */ if (FAILED(hr) || target_width == 0 || target_height == 0 || This->base.device->minor_version_num <= 2) { target_width = resource->width0; target_height = resource->height0; } /* Switch to using presentation buffers on window resize. * Note: Most apps should resize the d3d back buffers when * a window resize is detected, which will result in a call to * NineSwapChain9_Resize. Thus everything will get released, * and it will switch back to not using separate presentation * buffers. */ if (!This->present_buffers[0] && (target_width != resource->width0 || target_height != resource->height0)) { BOOL failure = false; struct pipe_resource *new_resource[This->num_back_buffers]; D3DWindowBuffer *new_handles[This->num_back_buffers]; for (i = 0; i < This->num_back_buffers; i++) { /* Note: if (!new_handles[i]), new_resource[i] * gets released and contains NULL */ create_present_buffer(This, target_width, target_height, &new_resource[i], &new_handles[i]); if (!new_handles[i]) failure = true; } if (failure) { for (i = 0; i < This->num_back_buffers; i++) { if (new_resource[i]) pipe_resource_reference(&new_resource[i], NULL); if (new_handles[i]) D3DWindowBuffer_release(This, new_handles[i]); } } else { for (i = 0; i < This->num_back_buffers; i++) { D3DWindowBuffer_release(This, This->present_handles[i]); This->present_handles[i] = new_handles[i]; pipe_resource_reference(&This->present_buffers[i], new_resource[i]); pipe_resource_reference(&new_resource[i], NULL); } } } pipe = NineDevice9_GetPipe(This->base.device); if (This->present_buffers[0]) { memset(&blit, 0, sizeof(blit)); blit.src.resource = resource; blit.src.level = 0; blit.src.format = resource->format; blit.src.box.z = 0; blit.src.box.depth = 1; blit.src.box.x = 0; blit.src.box.y = 0; blit.src.box.width = resource->width0; blit.src.box.height = resource->height0; /* Reallocate a new presentation buffer if the target window * size has changed */ if (target_width != This->present_buffers[0]->width0 || target_height != This->present_buffers[0]->height0) { struct pipe_resource *new_resource; D3DWindowBuffer *new_handle; create_present_buffer(This, target_width, target_height, &new_resource, &new_handle); /* Switch to the new buffer */ if (new_handle) { D3DWindowBuffer_release(This, This->present_handles[0]); This->present_handles[0] = new_handle; pipe_resource_reference(&This->present_buffers[0], new_resource); pipe_resource_reference(&new_resource, NULL); } } resource = This->present_buffers[0]; blit.dst.resource = resource; blit.dst.level = 0; blit.dst.format = resource->format; blit.dst.box.z = 0; blit.dst.box.depth = 1; blit.dst.box.x = 0; blit.dst.box.y = 0; blit.dst.box.width = resource->width0; blit.dst.box.height = resource->height0; blit.mask = PIPE_MASK_RGBA; blit.filter = (blit.dst.box.width == blit.src.box.width && blit.dst.box.height == blit.src.box.height) ? PIPE_TEX_FILTER_NEAREST : PIPE_TEX_FILTER_LINEAR; blit.scissor_enable = FALSE; blit.alpha_blend = FALSE; pipe->blit(pipe, &blit); } /* The resource we present has to resolve fast clears * if needed (and other things) */ pipe->flush_resource(pipe, resource); if (This->params.SwapEffect != D3DSWAPEFFECT_DISCARD) handle_draw_cursor_and_hud(This, resource); fence = NULL; pipe->flush(pipe, &fence, PIPE_FLUSH_END_OF_FRAME); /* Present now for thread_submit, because we have the fence. * It's possible we return WASSTILLDRAWING and still Present, * but it should be fine. */ if (This->enable_threadpool) pend_present(This, fence, hDestWindowOverride); if (fence) { swap_fences_push_back(This, fence); This->screen->fence_reference(This->screen, &fence, NULL); } This->rendering_done = TRUE; bypass_rendering: if (dwFlags & D3DPRESENT_DONOTWAIT) { UNTESTED(2); BOOL still_draw = FALSE; fence = swap_fences_see_front(This); if (fence) { still_draw = !This->screen->fence_finish(This->screen, NULL, fence, 0); This->screen->fence_reference(This->screen, &fence, NULL); } if (still_draw) return D3DERR_WASSTILLDRAWING; } /* Throttle rendering if needed */ fence = swap_fences_pop_front(This); if (fence) { (void) This->screen->fence_finish(This->screen, NULL, fence, PIPE_TIMEOUT_INFINITE); This->screen->fence_reference(This->screen, &fence, NULL); } This->rendering_done = FALSE; if (!This->enable_threadpool) { This->tasks[0]=NULL; hr = ID3DPresent_PresentBuffer(This->present, This->present_handles[0], hDestWindowOverride, pSourceRect, pDestRect, pDirtyRegion, dwFlags); if (FAILED(hr)) { UNTESTED(3);return hr; } } return D3D_OK; } HRESULT NINE_WINAPI NineSwapChain9_Present( struct NineSwapChain9 *This, const RECT *pSourceRect, const RECT *pDestRect, HWND hDestWindowOverride, const RGNDATA *pDirtyRegion, DWORD dwFlags ) { struct pipe_resource *res = NULL; D3DWindowBuffer *handle_temp; struct threadpool_task *task_temp; BOOL *pending_presentation_temp; int i; HRESULT hr; DBG("This=%p pSourceRect=%p pDestRect=%p hDestWindowOverride=%p " "pDirtyRegion=%p dwFlags=%d\n", This, pSourceRect, pDestRect, hDestWindowOverride, pDirtyRegion,dwFlags); if (This->base.device->ex) { if (NineSwapChain9_GetOccluded(This)) { DBG("Present is occluded. Returning S_PRESENT_OCCLUDED.\n"); return S_PRESENT_OCCLUDED; } } else { if (NineSwapChain9_GetOccluded(This) || NineSwapChain9_ResolutionMismatch(This)) { This->base.device->device_needs_reset = TRUE; } if (This->base.device->device_needs_reset) { DBG("Device is lost. Returning D3DERR_DEVICELOST.\n"); return D3DERR_DEVICELOST; } } nine_csmt_process(This->base.device); hr = present(This, pSourceRect, pDestRect, hDestWindowOverride, pDirtyRegion, dwFlags); if (hr == D3DERR_WASSTILLDRAWING) return hr; if (This->base.device->minor_version_num > 2 && This->actx->discard_delayed_release && This->params.SwapEffect == D3DSWAPEFFECT_DISCARD && This->params.PresentationInterval == D3DPRESENT_INTERVAL_IMMEDIATE) { int next_buffer = -1; while (next_buffer == -1) { /* Find a free backbuffer */ for (i = 1; i < This->num_back_buffers; i++) { if (!p_atomic_read(This->pending_presentation[i]) && ID3DPresent_IsBufferReleased(This->present, This->present_handles[i])) { DBG("Found buffer released: %d\n", i); next_buffer = i; break; } } if (next_buffer == -1) { DBG("Found no buffer released. Waiting for event\n"); ID3DPresent_WaitBufferReleaseEvent(This->present); } } /* Free the task (we already checked it is finished) */ if (This->tasks[next_buffer]) _mesa_threadpool_wait_for_task(This->pool, &(This->tasks[next_buffer])); assert(!*This->pending_presentation[next_buffer] && !This->tasks[next_buffer]); This->tasks[next_buffer] = This->tasks[0]; This->tasks[0] = NULL; pending_presentation_temp = This->pending_presentation[next_buffer]; This->pending_presentation[next_buffer] = This->pending_presentation[0]; This->pending_presentation[0] = pending_presentation_temp; /* Switch with the released buffer */ pipe_resource_reference(&res, This->buffers[0]->base.resource); NineSurface9_SetResourceResize( This->buffers[0], This->buffers[next_buffer]->base.resource); NineSurface9_SetResourceResize( This->buffers[next_buffer], res); pipe_resource_reference(&res, NULL); if (This->present_buffers[0]) { pipe_resource_reference(&res, This->present_buffers[0]); pipe_resource_reference(&This->present_buffers[0], This->present_buffers[next_buffer]); pipe_resource_reference(&This->present_buffers[next_buffer], res); pipe_resource_reference(&res, NULL); } handle_temp = This->present_handles[0]; This->present_handles[0] = This->present_handles[next_buffer]; This->present_handles[next_buffer] = handle_temp; } else { switch (This->params.SwapEffect) { case D3DSWAPEFFECT_OVERLAY: /* Not implemented, fallback to FLIP */ case D3DSWAPEFFECT_FLIPEX: /* Allows optimizations over FLIP for windowed mode. */ case D3DSWAPEFFECT_DISCARD: /* Allows optimizations over FLIP */ case D3DSWAPEFFECT_FLIP: /* rotate the queue */ pipe_resource_reference(&res, This->buffers[0]->base.resource); for (i = 1; i < This->num_back_buffers; i++) { NineSurface9_SetResourceResize(This->buffers[i - 1], This->buffers[i]->base.resource); } NineSurface9_SetResourceResize( This->buffers[This->num_back_buffers - 1], res); pipe_resource_reference(&res, NULL); if (This->present_buffers[0]) { pipe_resource_reference(&res, This->present_buffers[0]); for (i = 1; i < This->num_back_buffers; i++) pipe_resource_reference(&(This->present_buffers[i-1]), This->present_buffers[i]); pipe_resource_reference(&(This->present_buffers[This->num_back_buffers - 1]), res); pipe_resource_reference(&res, NULL); } handle_temp = This->present_handles[0]; for (i = 1; i < This->num_back_buffers; i++) { This->present_handles[i-1] = This->present_handles[i]; } This->present_handles[This->num_back_buffers - 1] = handle_temp; task_temp = This->tasks[0]; for (i = 1; i < This->num_back_buffers; i++) { This->tasks[i-1] = This->tasks[i]; } This->tasks[This->num_back_buffers - 1] = task_temp; pending_presentation_temp = This->pending_presentation[0]; for (i = 1; i < This->num_back_buffers; i++) { This->pending_presentation[i-1] = This->pending_presentation[i]; } This->pending_presentation[This->num_back_buffers - 1] = pending_presentation_temp; break; case D3DSWAPEFFECT_COPY: /* do nothing */ break; } if (This->tasks[0]) _mesa_threadpool_wait_for_task(This->pool, &(This->tasks[0])); assert(!*This->pending_presentation[0]); ID3DPresent_WaitBufferReleased(This->present, This->present_handles[0]); } This->base.device->context.changed.group |= NINE_STATE_FB; return hr; } HRESULT NINE_WINAPI NineSwapChain9_GetFrontBufferData( struct NineSwapChain9 *This, IDirect3DSurface9 *pDestSurface ) { struct NineSurface9 *dest_surface = NineSurface9(pDestSurface); struct NineDevice9 *pDevice = This->base.device; unsigned int width, height; struct pipe_resource *temp_resource; struct NineSurface9 *temp_surface; D3DWindowBuffer *temp_handle; D3DSURFACE_DESC desc; HRESULT hr; DBG("GetFrontBufferData: This=%p pDestSurface=%p\n", This, pDestSurface); user_assert(dest_surface->base.pool == D3DPOOL_SYSTEMMEM, D3DERR_INVALIDCALL); width = dest_surface->desc.Width; height = dest_surface->desc.Height; /* Note: front window size and destination size are supposed * to match. However it's not very clear what should get taken in Windowed * mode. It may need a fix */ create_present_buffer(This, width, height, &temp_resource, &temp_handle); if (!temp_resource || !temp_handle) { return D3DERR_INVALIDCALL; } desc.Type = D3DRTYPE_SURFACE; desc.Pool = D3DPOOL_DEFAULT; desc.MultiSampleType = D3DMULTISAMPLE_NONE; desc.MultiSampleQuality = 0; desc.Width = width; desc.Height = height; /* NineSurface9_CopyDefaultToMem needs same format. */ desc.Format = dest_surface->desc.Format; desc.Usage = D3DUSAGE_RENDERTARGET; hr = NineSurface9_new(pDevice, NineUnknown(This), temp_resource, NULL, 0, 0, 0, &desc, &temp_surface); pipe_resource_reference(&temp_resource, NULL); if (FAILED(hr)) { DBG("Failed to create temp FrontBuffer surface.\n"); return hr; } ID3DPresent_FrontBufferCopy(This->present, temp_handle); NineSurface9_CopyDefaultToMem(dest_surface, temp_surface); ID3DPresent_DestroyD3DWindowBuffer(This->present, temp_handle); NineUnknown_Destroy(NineUnknown(temp_surface)); return D3D_OK; } HRESULT NINE_WINAPI NineSwapChain9_GetBackBuffer( struct NineSwapChain9 *This, UINT iBackBuffer, D3DBACKBUFFER_TYPE Type, IDirect3DSurface9 **ppBackBuffer ) { DBG("GetBackBuffer: This=%p iBackBuffer=%d Type=%d ppBackBuffer=%p\n", This, iBackBuffer, Type, ppBackBuffer); (void)user_error(Type == D3DBACKBUFFER_TYPE_MONO); /* don't touch ppBackBuffer on error */ user_assert(ppBackBuffer != NULL, D3DERR_INVALIDCALL); user_assert(iBackBuffer < This->params.BackBufferCount, D3DERR_INVALIDCALL); NineUnknown_AddRef(NineUnknown(This->buffers[iBackBuffer])); *ppBackBuffer = (IDirect3DSurface9 *)This->buffers[iBackBuffer]; return D3D_OK; } HRESULT NINE_WINAPI NineSwapChain9_GetRasterStatus( struct NineSwapChain9 *This, D3DRASTER_STATUS *pRasterStatus ) { DBG("GetRasterStatus: This=%p pRasterStatus=%p\n", This, pRasterStatus); user_assert(pRasterStatus != NULL, E_POINTER); return ID3DPresent_GetRasterStatus(This->present, pRasterStatus); } HRESULT NINE_WINAPI NineSwapChain9_GetDisplayMode( struct NineSwapChain9 *This, D3DDISPLAYMODE *pMode ) { D3DDISPLAYMODEEX mode; D3DDISPLAYROTATION rot; HRESULT hr; DBG("GetDisplayMode: This=%p pMode=%p\n", This, pMode); user_assert(pMode != NULL, E_POINTER); hr = ID3DPresent_GetDisplayMode(This->present, &mode, &rot); if (SUCCEEDED(hr)) { pMode->Width = mode.Width; pMode->Height = mode.Height; pMode->RefreshRate = mode.RefreshRate; pMode->Format = mode.Format; } return hr; } HRESULT NINE_WINAPI NineSwapChain9_GetPresentParameters( struct NineSwapChain9 *This, D3DPRESENT_PARAMETERS *pPresentationParameters ) { DBG("GetPresentParameters: This=%p pPresentationParameters=%p\n", This, pPresentationParameters); user_assert(pPresentationParameters != NULL, E_POINTER); *pPresentationParameters = This->params; return D3D_OK; } IDirect3DSwapChain9Vtbl NineSwapChain9_vtable = { (void *)NineUnknown_QueryInterface, (void *)NineUnknown_AddRef, (void *)NineUnknown_Release, (void *)NineSwapChain9_Present, (void *)NineSwapChain9_GetFrontBufferData, (void *)NineSwapChain9_GetBackBuffer, (void *)NineSwapChain9_GetRasterStatus, (void *)NineSwapChain9_GetDisplayMode, (void *)NineUnknown_GetDevice, /* actually part of SwapChain9 iface */ (void *)NineSwapChain9_GetPresentParameters }; static const GUID *NineSwapChain9_IIDs[] = { &IID_IDirect3DSwapChain9, &IID_IUnknown, NULL }; HRESULT NineSwapChain9_new( struct NineDevice9 *pDevice, BOOL implicit, ID3DPresent *pPresent, D3DPRESENT_PARAMETERS *pPresentationParameters, struct d3dadapter9_context *pCTX, HWND hFocusWindow, struct NineSwapChain9 **ppOut ) { NINE_DEVICE_CHILD_NEW(SwapChain9, ppOut, pDevice, /* args */ implicit, pPresent, pPresentationParameters, pCTX, hFocusWindow, NULL); } BOOL NineSwapChain9_GetOccluded( struct NineSwapChain9 *This ) { if (This->base.device->minor_version_num > 0) { return ID3DPresent_GetWindowOccluded(This->present); } return FALSE; } BOOL NineSwapChain9_ResolutionMismatch( struct NineSwapChain9 *This ) { if (This->base.device->minor_version_num > 1) { return ID3DPresent_ResolutionMismatch(This->present); } return FALSE; } HANDLE NineSwapChain9_CreateThread( struct NineSwapChain9 *This, void *pFuncAddress, void *pParam ) { if (This->base.device->minor_version_num > 1) { return ID3DPresent_CreateThread(This->present, pFuncAddress, pParam); } return NULL; } void NineSwapChain9_WaitForThread( struct NineSwapChain9 *This, HANDLE thread ) { if (This->base.device->minor_version_num > 1) { (void) ID3DPresent_WaitForThread(This->present, thread); } } static int NineSwapChain9_GetBackBufferCountForParams( struct NineSwapChain9 *This, D3DPRESENT_PARAMETERS *pParams ) { int count = pParams->BackBufferCount; /* When we have flip behaviour, d3d9 expects we get back the screen buffer when we flip. * Here we don't get back the initial content of the screen. To emulate the behaviour * we allocate an additional buffer */ if (pParams->SwapEffect != D3DSWAPEFFECT_COPY) count++; /* With DISCARD, as there is no guarantee about the buffer contents, we can use * an arbitrary number of buffers */ if (pParams->SwapEffect == D3DSWAPEFFECT_DISCARD) { /* thread_submit's can have maximum count or This->actx->throttling_value + 1 * frames in flight being rendered and not shown. * Do not let count decrease that number */ if (This->actx->thread_submit && count < This->desired_fences) count = This->desired_fences; /* When we enable AllowDISCARDDelayedRelease, we must ensure * to have at least 4 buffers to meet INTERVAL_IMMEDIATE, * since the display server/compositor can hold 3 buffers * without releasing them: * . Buffer on screen. * . Buffer scheduled kernel side to be next on screen. * . Last buffer sent. */ if (This->base.device->minor_version_num > 2 && This->actx->discard_delayed_release && pParams->PresentationInterval == D3DPRESENT_INTERVAL_IMMEDIATE) { if (This->actx->thread_submit && count < 4) count = 4; /* When thread_submit is not used, 5 buffers are actually needed, * because in case a pageflip is missed because rendering wasn't finished, * the Xserver will hold 4 buffers. */ else if (!This->actx->thread_submit && count < 5) count = 5; } } return count; }