1bf215546Sopenharmony_ci/**************************************************************************
2bf215546Sopenharmony_ci *
3bf215546Sopenharmony_ci * Copyright 2008-2009 VMware, Inc.
4bf215546Sopenharmony_ci * All Rights Reserved.
5bf215546Sopenharmony_ci *
6bf215546Sopenharmony_ci * Permission is hereby granted, free of charge, to any person obtaining a
7bf215546Sopenharmony_ci * copy of this software and associated documentation files (the
8bf215546Sopenharmony_ci * "Software"), to deal in the Software without restriction, including
9bf215546Sopenharmony_ci * without limitation the rights to use, copy, modify, merge, publish,
10bf215546Sopenharmony_ci * distribute, sub license, and/or sell copies of the Software, and to
11bf215546Sopenharmony_ci * permit persons to whom the Software is furnished to do so, subject to
12bf215546Sopenharmony_ci * the following conditions:
13bf215546Sopenharmony_ci *
14bf215546Sopenharmony_ci * The above copyright notice and this permission notice (including the
15bf215546Sopenharmony_ci * next paragraph) shall be included in all copies or substantial portions
16bf215546Sopenharmony_ci * of the Software.
17bf215546Sopenharmony_ci *
18bf215546Sopenharmony_ci * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
19bf215546Sopenharmony_ci * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20bf215546Sopenharmony_ci * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
21bf215546Sopenharmony_ci * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS BE LIABLE FOR
22bf215546Sopenharmony_ci * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
23bf215546Sopenharmony_ci * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
24bf215546Sopenharmony_ci * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25bf215546Sopenharmony_ci *
26bf215546Sopenharmony_ci **************************************************************************/
27bf215546Sopenharmony_ci
28bf215546Sopenharmony_ci#include <windows.h>
29bf215546Sopenharmony_ci
30bf215546Sopenharmony_ci#include "pipe/p_screen.h"
31bf215546Sopenharmony_ci#include "pipe/p_state.h"
32bf215546Sopenharmony_ci#include "util/u_memory.h"
33bf215546Sopenharmony_ci#include "hud/hud_context.h"
34bf215546Sopenharmony_ci#include "util/os_time.h"
35bf215546Sopenharmony_ci#include "frontend/api.h"
36bf215546Sopenharmony_ci
37bf215546Sopenharmony_ci#include <GL/gl.h>
38bf215546Sopenharmony_ci#include "gldrv.h"
39bf215546Sopenharmony_ci#include "stw_framebuffer.h"
40bf215546Sopenharmony_ci#include "stw_device.h"
41bf215546Sopenharmony_ci#include "stw_winsys.h"
42bf215546Sopenharmony_ci#include "stw_tls.h"
43bf215546Sopenharmony_ci#include "stw_context.h"
44bf215546Sopenharmony_ci#include "stw_st.h"
45bf215546Sopenharmony_ci
46bf215546Sopenharmony_ci
47bf215546Sopenharmony_ci/**
48bf215546Sopenharmony_ci * Search the framebuffer with the matching HWND while holding the
49bf215546Sopenharmony_ci * stw_dev::fb_mutex global lock.
50bf215546Sopenharmony_ci * If a stw_framebuffer is found, lock it and return the pointer.
51bf215546Sopenharmony_ci * Else, return NULL.
52bf215546Sopenharmony_ci */
53bf215546Sopenharmony_cistatic struct stw_framebuffer *
54bf215546Sopenharmony_cistw_framebuffer_from_hwnd_locked(HWND hwnd)
55bf215546Sopenharmony_ci{
56bf215546Sopenharmony_ci   struct stw_framebuffer *fb;
57bf215546Sopenharmony_ci
58bf215546Sopenharmony_ci   for (fb = stw_dev->fb_head; fb != NULL; fb = fb->next)
59bf215546Sopenharmony_ci      if (fb->hWnd == hwnd) {
60bf215546Sopenharmony_ci         stw_framebuffer_lock(fb);
61bf215546Sopenharmony_ci
62bf215546Sopenharmony_ci         /* When running with Zink, during the Vulkan surface creation
63bf215546Sopenharmony_ci          * it's possible that the underlying Vulkan driver will try to
64bf215546Sopenharmony_ci          * access the HWND/HDC we passed in (see stw_st_fill_private_loader_data()).
65bf215546Sopenharmony_ci          * Because we create the Vulkan surface while holding the framebuffer
66bf215546Sopenharmony_ci          * lock, when the driver starts to look up properties,
67bf215546Sopenharmony_ci          * we'd end up double locking when looking up the framebuffer.
68bf215546Sopenharmony_ci          */
69bf215546Sopenharmony_ci         assert(stw_dev->zink || fb->mutex.RecursionCount == 1);
70bf215546Sopenharmony_ci         return fb;
71bf215546Sopenharmony_ci      }
72bf215546Sopenharmony_ci
73bf215546Sopenharmony_ci   return NULL;
74bf215546Sopenharmony_ci}
75bf215546Sopenharmony_ci
76bf215546Sopenharmony_ci
77bf215546Sopenharmony_ci/**
78bf215546Sopenharmony_ci * Decrement the reference count on the given stw_framebuffer object.
79bf215546Sopenharmony_ci * If the reference count hits zero, destroy the object.
80bf215546Sopenharmony_ci *
81bf215546Sopenharmony_ci * Note: Both stw_dev::fb_mutex and stw_framebuffer::mutex must already be
82bf215546Sopenharmony_ci * locked.  After this function completes, the fb's mutex will be unlocked.
83bf215546Sopenharmony_ci */
84bf215546Sopenharmony_civoid
85bf215546Sopenharmony_cistw_framebuffer_release_locked(struct stw_framebuffer *fb,
86bf215546Sopenharmony_ci                               struct st_context_iface *stctx)
87bf215546Sopenharmony_ci{
88bf215546Sopenharmony_ci   struct stw_framebuffer **link;
89bf215546Sopenharmony_ci
90bf215546Sopenharmony_ci   assert(fb);
91bf215546Sopenharmony_ci   assert(stw_own_mutex(&fb->mutex));
92bf215546Sopenharmony_ci   assert(stw_own_mutex(&stw_dev->fb_mutex) || fb->owner == STW_FRAMEBUFFER_EGL_WINDOW);
93bf215546Sopenharmony_ci
94bf215546Sopenharmony_ci   /* check the reference count */
95bf215546Sopenharmony_ci   fb->refcnt--;
96bf215546Sopenharmony_ci   if (fb->refcnt) {
97bf215546Sopenharmony_ci      stw_framebuffer_unlock(fb);
98bf215546Sopenharmony_ci      return;
99bf215546Sopenharmony_ci   }
100bf215546Sopenharmony_ci
101bf215546Sopenharmony_ci   if (fb->owner != STW_FRAMEBUFFER_EGL_WINDOW) {
102bf215546Sopenharmony_ci      /* remove this stw_framebuffer from the device's linked list */
103bf215546Sopenharmony_ci      link = &stw_dev->fb_head;
104bf215546Sopenharmony_ci      while (*link != fb)
105bf215546Sopenharmony_ci         link = &(*link)->next;
106bf215546Sopenharmony_ci      assert(*link);
107bf215546Sopenharmony_ci      *link = fb->next;
108bf215546Sopenharmony_ci      fb->next = NULL;
109bf215546Sopenharmony_ci   }
110bf215546Sopenharmony_ci
111bf215546Sopenharmony_ci   if (fb->shared_surface)
112bf215546Sopenharmony_ci      stw_dev->stw_winsys->shared_surface_close(stw_dev->screen,
113bf215546Sopenharmony_ci                                                fb->shared_surface);
114bf215546Sopenharmony_ci
115bf215546Sopenharmony_ci   if (fb->winsys_framebuffer)
116bf215546Sopenharmony_ci      fb->winsys_framebuffer->destroy(fb->winsys_framebuffer, stctx ? stctx->pipe : NULL);
117bf215546Sopenharmony_ci
118bf215546Sopenharmony_ci   stw_st_destroy_framebuffer_locked(fb->stfb);
119bf215546Sopenharmony_ci
120bf215546Sopenharmony_ci   stw_framebuffer_unlock(fb);
121bf215546Sopenharmony_ci
122bf215546Sopenharmony_ci   DeleteCriticalSection(&fb->mutex);
123bf215546Sopenharmony_ci
124bf215546Sopenharmony_ci   FREE( fb );
125bf215546Sopenharmony_ci}
126bf215546Sopenharmony_ci
127bf215546Sopenharmony_ci
128bf215546Sopenharmony_ci/**
129bf215546Sopenharmony_ci * Query the size of the given framebuffer's on-screen window and update
130bf215546Sopenharmony_ci * the stw_framebuffer's width/height.
131bf215546Sopenharmony_ci */
132bf215546Sopenharmony_cistatic void
133bf215546Sopenharmony_cistw_framebuffer_get_size(struct stw_framebuffer *fb)
134bf215546Sopenharmony_ci{
135bf215546Sopenharmony_ci   LONG width, height;
136bf215546Sopenharmony_ci   RECT client_rect;
137bf215546Sopenharmony_ci   RECT window_rect;
138bf215546Sopenharmony_ci   POINT client_pos;
139bf215546Sopenharmony_ci
140bf215546Sopenharmony_ci   /*
141bf215546Sopenharmony_ci    * Sanity checking.
142bf215546Sopenharmony_ci    */
143bf215546Sopenharmony_ci   assert(fb->hWnd);
144bf215546Sopenharmony_ci   assert(fb->width && fb->height);
145bf215546Sopenharmony_ci   assert(fb->client_rect.right  == fb->client_rect.left + fb->width);
146bf215546Sopenharmony_ci   assert(fb->client_rect.bottom == fb->client_rect.top  + fb->height);
147bf215546Sopenharmony_ci
148bf215546Sopenharmony_ci   /*
149bf215546Sopenharmony_ci    * Get the client area size.
150bf215546Sopenharmony_ci    */
151bf215546Sopenharmony_ci   if (!GetClientRect(fb->hWnd, &client_rect)) {
152bf215546Sopenharmony_ci      return;
153bf215546Sopenharmony_ci   }
154bf215546Sopenharmony_ci
155bf215546Sopenharmony_ci   assert(client_rect.left == 0);
156bf215546Sopenharmony_ci   assert(client_rect.top == 0);
157bf215546Sopenharmony_ci   width  = client_rect.right  - client_rect.left;
158bf215546Sopenharmony_ci   height = client_rect.bottom - client_rect.top;
159bf215546Sopenharmony_ci
160bf215546Sopenharmony_ci   fb->minimized = width == 0 || height == 0;
161bf215546Sopenharmony_ci
162bf215546Sopenharmony_ci   if (width <= 0 || height <= 0) {
163bf215546Sopenharmony_ci      /*
164bf215546Sopenharmony_ci       * When the window is minimized GetClientRect will return zeros.  Simply
165bf215546Sopenharmony_ci       * preserve the current window size, until the window is restored or
166bf215546Sopenharmony_ci       * maximized again.
167bf215546Sopenharmony_ci       */
168bf215546Sopenharmony_ci      return;
169bf215546Sopenharmony_ci   }
170bf215546Sopenharmony_ci
171bf215546Sopenharmony_ci   if (width != fb->width || height != fb->height) {
172bf215546Sopenharmony_ci      fb->must_resize = TRUE;
173bf215546Sopenharmony_ci      fb->width = width;
174bf215546Sopenharmony_ci      fb->height = height;
175bf215546Sopenharmony_ci   }
176bf215546Sopenharmony_ci
177bf215546Sopenharmony_ci   client_pos.x = 0;
178bf215546Sopenharmony_ci   client_pos.y = 0;
179bf215546Sopenharmony_ci   if (ClientToScreen(fb->hWnd, &client_pos) &&
180bf215546Sopenharmony_ci       GetWindowRect(fb->hWnd, &window_rect)) {
181bf215546Sopenharmony_ci      fb->client_rect.left = client_pos.x - window_rect.left;
182bf215546Sopenharmony_ci      fb->client_rect.top  = client_pos.y - window_rect.top;
183bf215546Sopenharmony_ci   }
184bf215546Sopenharmony_ci
185bf215546Sopenharmony_ci   fb->client_rect.right  = fb->client_rect.left + fb->width;
186bf215546Sopenharmony_ci   fb->client_rect.bottom = fb->client_rect.top  + fb->height;
187bf215546Sopenharmony_ci
188bf215546Sopenharmony_ci#if 0
189bf215546Sopenharmony_ci   debug_printf("\n");
190bf215546Sopenharmony_ci   debug_printf("%s: hwnd = %p\n", __FUNCTION__, fb->hWnd);
191bf215546Sopenharmony_ci   debug_printf("%s: client_position = (%li, %li)\n",
192bf215546Sopenharmony_ci                __FUNCTION__, client_pos.x, client_pos.y);
193bf215546Sopenharmony_ci   debug_printf("%s: window_rect = (%li, %li) - (%li, %li)\n",
194bf215546Sopenharmony_ci                __FUNCTION__,
195bf215546Sopenharmony_ci                window_rect.left, window_rect.top,
196bf215546Sopenharmony_ci                window_rect.right, window_rect.bottom);
197bf215546Sopenharmony_ci   debug_printf("%s: client_rect = (%li, %li) - (%li, %li)\n",
198bf215546Sopenharmony_ci                __FUNCTION__,
199bf215546Sopenharmony_ci                fb->client_rect.left, fb->client_rect.top,
200bf215546Sopenharmony_ci                fb->client_rect.right, fb->client_rect.bottom);
201bf215546Sopenharmony_ci#endif
202bf215546Sopenharmony_ci}
203bf215546Sopenharmony_ci
204bf215546Sopenharmony_ci
205bf215546Sopenharmony_ci/**
206bf215546Sopenharmony_ci * @sa http://msdn.microsoft.com/en-us/library/ms644975(VS.85).aspx
207bf215546Sopenharmony_ci * @sa http://msdn.microsoft.com/en-us/library/ms644960(VS.85).aspx
208bf215546Sopenharmony_ci */
209bf215546Sopenharmony_ciLRESULT CALLBACK
210bf215546Sopenharmony_cistw_call_window_proc(int nCode, WPARAM wParam, LPARAM lParam)
211bf215546Sopenharmony_ci{
212bf215546Sopenharmony_ci   struct stw_tls_data *tls_data;
213bf215546Sopenharmony_ci   PCWPSTRUCT pParams = (PCWPSTRUCT)lParam;
214bf215546Sopenharmony_ci   struct stw_framebuffer *fb;
215bf215546Sopenharmony_ci
216bf215546Sopenharmony_ci   tls_data = stw_tls_get_data();
217bf215546Sopenharmony_ci   if (!tls_data)
218bf215546Sopenharmony_ci      return 0;
219bf215546Sopenharmony_ci
220bf215546Sopenharmony_ci   if (nCode < 0 || !stw_dev)
221bf215546Sopenharmony_ci       return CallNextHookEx(tls_data->hCallWndProcHook, nCode, wParam, lParam);
222bf215546Sopenharmony_ci
223bf215546Sopenharmony_ci   /* We check that the stw_dev object is initialized before we try to do
224bf215546Sopenharmony_ci    * anything with it.  Otherwise, in multi-threaded programs there's a
225bf215546Sopenharmony_ci    * chance of executing this code before the stw_dev object is fully
226bf215546Sopenharmony_ci    * initialized.
227bf215546Sopenharmony_ci    */
228bf215546Sopenharmony_ci   if (stw_dev && stw_dev->initialized) {
229bf215546Sopenharmony_ci      if (pParams->message == WM_WINDOWPOSCHANGED) {
230bf215546Sopenharmony_ci         /* We handle WM_WINDOWPOSCHANGED instead of WM_SIZE because according
231bf215546Sopenharmony_ci          * to http://blogs.msdn.com/oldnewthing/archive/2008/01/15/7113860.aspx
232bf215546Sopenharmony_ci          * WM_SIZE is generated from WM_WINDOWPOSCHANGED by DefWindowProc so it
233bf215546Sopenharmony_ci          * can be masked out by the application.
234bf215546Sopenharmony_ci          */
235bf215546Sopenharmony_ci         LPWINDOWPOS lpWindowPos = (LPWINDOWPOS)pParams->lParam;
236bf215546Sopenharmony_ci         if ((lpWindowPos->flags & SWP_SHOWWINDOW) ||
237bf215546Sopenharmony_ci             !(lpWindowPos->flags & SWP_NOMOVE) ||
238bf215546Sopenharmony_ci             !(lpWindowPos->flags & SWP_NOSIZE)) {
239bf215546Sopenharmony_ci            fb = stw_framebuffer_from_hwnd( pParams->hwnd );
240bf215546Sopenharmony_ci            if (fb) {
241bf215546Sopenharmony_ci               /* Size in WINDOWPOS includes the window frame, so get the size
242bf215546Sopenharmony_ci                * of the client area via GetClientRect.
243bf215546Sopenharmony_ci                */
244bf215546Sopenharmony_ci               stw_framebuffer_get_size(fb);
245bf215546Sopenharmony_ci               stw_framebuffer_unlock(fb);
246bf215546Sopenharmony_ci            }
247bf215546Sopenharmony_ci         }
248bf215546Sopenharmony_ci      }
249bf215546Sopenharmony_ci      else if (pParams->message == WM_DESTROY) {
250bf215546Sopenharmony_ci         stw_lock_framebuffers(stw_dev);
251bf215546Sopenharmony_ci         fb = stw_framebuffer_from_hwnd_locked( pParams->hwnd );
252bf215546Sopenharmony_ci         if (fb) {
253bf215546Sopenharmony_ci            struct stw_context *current_context = stw_current_context();
254bf215546Sopenharmony_ci            struct st_context_iface *ctx_iface = current_context &&
255bf215546Sopenharmony_ci               current_context->current_framebuffer == fb ? current_context->st : NULL;
256bf215546Sopenharmony_ci            stw_framebuffer_release_locked(fb, ctx_iface);
257bf215546Sopenharmony_ci         }
258bf215546Sopenharmony_ci         stw_unlock_framebuffers(stw_dev);
259bf215546Sopenharmony_ci      }
260bf215546Sopenharmony_ci   }
261bf215546Sopenharmony_ci
262bf215546Sopenharmony_ci   return CallNextHookEx(tls_data->hCallWndProcHook, nCode, wParam, lParam);
263bf215546Sopenharmony_ci}
264bf215546Sopenharmony_ci
265bf215546Sopenharmony_ci
266bf215546Sopenharmony_ci/**
267bf215546Sopenharmony_ci * Create a new stw_framebuffer object which corresponds to the given
268bf215546Sopenharmony_ci * HDC/window.  If successful, we return the new stw_framebuffer object
269bf215546Sopenharmony_ci * with its mutex locked.
270bf215546Sopenharmony_ci */
271bf215546Sopenharmony_cistruct stw_framebuffer *
272bf215546Sopenharmony_cistw_framebuffer_create(HWND hWnd, int iPixelFormat, enum stw_framebuffer_owner owner)
273bf215546Sopenharmony_ci{
274bf215546Sopenharmony_ci   struct stw_framebuffer *fb;
275bf215546Sopenharmony_ci   const struct stw_pixelformat_info *pfi;
276bf215546Sopenharmony_ci
277bf215546Sopenharmony_ci   fb = CALLOC_STRUCT( stw_framebuffer );
278bf215546Sopenharmony_ci   if (fb == NULL)
279bf215546Sopenharmony_ci      return NULL;
280bf215546Sopenharmony_ci
281bf215546Sopenharmony_ci   fb->hWnd = hWnd;
282bf215546Sopenharmony_ci   fb->iPixelFormat = iPixelFormat;
283bf215546Sopenharmony_ci
284bf215546Sopenharmony_ci   if (stw_dev->stw_winsys->create_framebuffer)
285bf215546Sopenharmony_ci      fb->winsys_framebuffer =
286bf215546Sopenharmony_ci         stw_dev->stw_winsys->create_framebuffer(stw_dev->screen, hWnd, iPixelFormat);
287bf215546Sopenharmony_ci
288bf215546Sopenharmony_ci   /*
289bf215546Sopenharmony_ci    * We often need a displayable pixel format to make GDI happy. Set it
290bf215546Sopenharmony_ci    * here (always 1, i.e., out first pixel format) where appropriate.
291bf215546Sopenharmony_ci    */
292bf215546Sopenharmony_ci   fb->iDisplayablePixelFormat = iPixelFormat <= stw_dev->pixelformat_count
293bf215546Sopenharmony_ci      ? iPixelFormat : 1;
294bf215546Sopenharmony_ci   fb->owner = owner;
295bf215546Sopenharmony_ci
296bf215546Sopenharmony_ci   fb->pfi = pfi = stw_pixelformat_get_info( iPixelFormat );
297bf215546Sopenharmony_ci   fb->stfb = stw_st_create_framebuffer( fb );
298bf215546Sopenharmony_ci   if (!fb->stfb) {
299bf215546Sopenharmony_ci      FREE( fb );
300bf215546Sopenharmony_ci      return NULL;
301bf215546Sopenharmony_ci   }
302bf215546Sopenharmony_ci
303bf215546Sopenharmony_ci   fb->refcnt = 1;
304bf215546Sopenharmony_ci
305bf215546Sopenharmony_ci   /*
306bf215546Sopenharmony_ci    * Windows can be sometimes have zero width and or height, but we ensure
307bf215546Sopenharmony_ci    * a non-zero framebuffer size at all times.
308bf215546Sopenharmony_ci    */
309bf215546Sopenharmony_ci
310bf215546Sopenharmony_ci   fb->must_resize = TRUE;
311bf215546Sopenharmony_ci   fb->width  = 1;
312bf215546Sopenharmony_ci   fb->height = 1;
313bf215546Sopenharmony_ci   fb->client_rect.left   = 0;
314bf215546Sopenharmony_ci   fb->client_rect.top    = 0;
315bf215546Sopenharmony_ci   fb->client_rect.right  = fb->client_rect.left + fb->width;
316bf215546Sopenharmony_ci   fb->client_rect.bottom = fb->client_rect.top  + fb->height;
317bf215546Sopenharmony_ci
318bf215546Sopenharmony_ci   stw_framebuffer_get_size(fb);
319bf215546Sopenharmony_ci
320bf215546Sopenharmony_ci   InitializeCriticalSection(&fb->mutex);
321bf215546Sopenharmony_ci
322bf215546Sopenharmony_ci   /* This is the only case where we lock the stw_framebuffer::mutex before
323bf215546Sopenharmony_ci    * stw_dev::fb_mutex, since no other thread can know about this framebuffer
324bf215546Sopenharmony_ci    * and we must prevent any other thread from destroying it before we return.
325bf215546Sopenharmony_ci    */
326bf215546Sopenharmony_ci   stw_framebuffer_lock(fb);
327bf215546Sopenharmony_ci
328bf215546Sopenharmony_ci   if (owner != STW_FRAMEBUFFER_EGL_WINDOW) {
329bf215546Sopenharmony_ci      stw_lock_framebuffers(stw_dev);
330bf215546Sopenharmony_ci      fb->next = stw_dev->fb_head;
331bf215546Sopenharmony_ci      stw_dev->fb_head = fb;
332bf215546Sopenharmony_ci      stw_unlock_framebuffers(stw_dev);
333bf215546Sopenharmony_ci   }
334bf215546Sopenharmony_ci
335bf215546Sopenharmony_ci   return fb;
336bf215546Sopenharmony_ci}
337bf215546Sopenharmony_ci
338bf215546Sopenharmony_ci/**
339bf215546Sopenharmony_ci * Increase fb reference count.  The referenced framebuffer should be locked.
340bf215546Sopenharmony_ci *
341bf215546Sopenharmony_ci * It's not necessary to hold stw_dev::fb_mutex global lock.
342bf215546Sopenharmony_ci */
343bf215546Sopenharmony_civoid
344bf215546Sopenharmony_cistw_framebuffer_reference_locked(struct stw_framebuffer *fb)
345bf215546Sopenharmony_ci{
346bf215546Sopenharmony_ci   if (fb) {
347bf215546Sopenharmony_ci      assert(stw_own_mutex(&fb->mutex));
348bf215546Sopenharmony_ci      fb->refcnt++;
349bf215546Sopenharmony_ci   }
350bf215546Sopenharmony_ci}
351bf215546Sopenharmony_ci
352bf215546Sopenharmony_ci/**
353bf215546Sopenharmony_ci * Release stw_framebuffer::mutex lock. This framebuffer must not be accessed
354bf215546Sopenharmony_ci * after calling this function, as it may have been deleted by another thread
355bf215546Sopenharmony_ci * in the meanwhile.
356bf215546Sopenharmony_ci */
357bf215546Sopenharmony_civoid
358bf215546Sopenharmony_cistw_framebuffer_unlock(struct stw_framebuffer *fb)
359bf215546Sopenharmony_ci{
360bf215546Sopenharmony_ci   assert(fb);
361bf215546Sopenharmony_ci   assert(stw_own_mutex(&fb->mutex));
362bf215546Sopenharmony_ci   LeaveCriticalSection(&fb->mutex);
363bf215546Sopenharmony_ci}
364bf215546Sopenharmony_ci
365bf215546Sopenharmony_ci
366bf215546Sopenharmony_ci/**
367bf215546Sopenharmony_ci * Update the framebuffer's size if necessary.
368bf215546Sopenharmony_ci */
369bf215546Sopenharmony_civoid
370bf215546Sopenharmony_cistw_framebuffer_update(struct stw_framebuffer *fb)
371bf215546Sopenharmony_ci{
372bf215546Sopenharmony_ci   assert(fb->stfb);
373bf215546Sopenharmony_ci   assert(fb->height);
374bf215546Sopenharmony_ci   assert(fb->width);
375bf215546Sopenharmony_ci
376bf215546Sopenharmony_ci   /* XXX: It would be nice to avoid checking the size again -- in theory
377bf215546Sopenharmony_ci    * stw_call_window_proc would have cought the resize and stored the right
378bf215546Sopenharmony_ci    * size already, but unfortunately threads created before the DllMain is
379bf215546Sopenharmony_ci    * called don't get a DLL_THREAD_ATTACH notification, and there is no way
380bf215546Sopenharmony_ci    * to know of their existing without using the not very portable PSAPI.
381bf215546Sopenharmony_ci    */
382bf215546Sopenharmony_ci   stw_framebuffer_get_size(fb);
383bf215546Sopenharmony_ci}
384bf215546Sopenharmony_ci
385bf215546Sopenharmony_ci
386bf215546Sopenharmony_ci/**
387bf215546Sopenharmony_ci * Try to free all stw_framebuffer objects associated with the device.
388bf215546Sopenharmony_ci */
389bf215546Sopenharmony_civoid
390bf215546Sopenharmony_cistw_framebuffer_cleanup(void)
391bf215546Sopenharmony_ci{
392bf215546Sopenharmony_ci   struct stw_framebuffer *fb;
393bf215546Sopenharmony_ci   struct stw_framebuffer *next;
394bf215546Sopenharmony_ci
395bf215546Sopenharmony_ci   if (!stw_dev)
396bf215546Sopenharmony_ci      return;
397bf215546Sopenharmony_ci
398bf215546Sopenharmony_ci   stw_lock_framebuffers(stw_dev);
399bf215546Sopenharmony_ci
400bf215546Sopenharmony_ci   fb = stw_dev->fb_head;
401bf215546Sopenharmony_ci   while (fb) {
402bf215546Sopenharmony_ci      next = fb->next;
403bf215546Sopenharmony_ci
404bf215546Sopenharmony_ci      stw_framebuffer_lock(fb);
405bf215546Sopenharmony_ci      stw_framebuffer_release_locked(fb, NULL);
406bf215546Sopenharmony_ci
407bf215546Sopenharmony_ci      fb = next;
408bf215546Sopenharmony_ci   }
409bf215546Sopenharmony_ci   stw_dev->fb_head = NULL;
410bf215546Sopenharmony_ci
411bf215546Sopenharmony_ci   stw_unlock_framebuffers(stw_dev);
412bf215546Sopenharmony_ci}
413bf215546Sopenharmony_ci
414bf215546Sopenharmony_ci
415bf215546Sopenharmony_ci/**
416bf215546Sopenharmony_ci * Given an hdc, return the corresponding stw_framebuffer.
417bf215546Sopenharmony_ci * The returned stw_framebuffer will have its mutex locked.
418bf215546Sopenharmony_ci */
419bf215546Sopenharmony_cistatic struct stw_framebuffer *
420bf215546Sopenharmony_cistw_framebuffer_from_hdc_locked(HDC hdc)
421bf215546Sopenharmony_ci{
422bf215546Sopenharmony_ci   HWND hwnd;
423bf215546Sopenharmony_ci
424bf215546Sopenharmony_ci   hwnd = WindowFromDC(hdc);
425bf215546Sopenharmony_ci   if (!hwnd) {
426bf215546Sopenharmony_ci      return NULL;
427bf215546Sopenharmony_ci   }
428bf215546Sopenharmony_ci
429bf215546Sopenharmony_ci   return stw_framebuffer_from_hwnd_locked(hwnd);
430bf215546Sopenharmony_ci}
431bf215546Sopenharmony_ci
432bf215546Sopenharmony_ci
433bf215546Sopenharmony_ci/**
434bf215546Sopenharmony_ci * Given an HDC, return the corresponding stw_framebuffer.
435bf215546Sopenharmony_ci * The returned stw_framebuffer will have its mutex locked.
436bf215546Sopenharmony_ci */
437bf215546Sopenharmony_cistruct stw_framebuffer *
438bf215546Sopenharmony_cistw_framebuffer_from_hdc(HDC hdc)
439bf215546Sopenharmony_ci{
440bf215546Sopenharmony_ci   struct stw_framebuffer *fb;
441bf215546Sopenharmony_ci
442bf215546Sopenharmony_ci   if (!stw_dev)
443bf215546Sopenharmony_ci      return NULL;
444bf215546Sopenharmony_ci
445bf215546Sopenharmony_ci   stw_lock_framebuffers(stw_dev);
446bf215546Sopenharmony_ci   fb = stw_framebuffer_from_hdc_locked(hdc);
447bf215546Sopenharmony_ci   stw_unlock_framebuffers(stw_dev);
448bf215546Sopenharmony_ci
449bf215546Sopenharmony_ci   return fb;
450bf215546Sopenharmony_ci}
451bf215546Sopenharmony_ci
452bf215546Sopenharmony_ci
453bf215546Sopenharmony_ci/**
454bf215546Sopenharmony_ci * Given an HWND, return the corresponding stw_framebuffer.
455bf215546Sopenharmony_ci * The returned stw_framebuffer will have its mutex locked.
456bf215546Sopenharmony_ci */
457bf215546Sopenharmony_cistruct stw_framebuffer *
458bf215546Sopenharmony_cistw_framebuffer_from_hwnd(HWND hwnd)
459bf215546Sopenharmony_ci{
460bf215546Sopenharmony_ci   struct stw_framebuffer *fb;
461bf215546Sopenharmony_ci
462bf215546Sopenharmony_ci   stw_lock_framebuffers(stw_dev);
463bf215546Sopenharmony_ci   fb = stw_framebuffer_from_hwnd_locked(hwnd);
464bf215546Sopenharmony_ci   stw_unlock_framebuffers(stw_dev);
465bf215546Sopenharmony_ci
466bf215546Sopenharmony_ci   return fb;
467bf215546Sopenharmony_ci}
468bf215546Sopenharmony_ci
469bf215546Sopenharmony_ci
470bf215546Sopenharmony_ciBOOL APIENTRY
471bf215546Sopenharmony_ciDrvSetPixelFormat(HDC hdc, LONG iPixelFormat)
472bf215546Sopenharmony_ci{
473bf215546Sopenharmony_ci   uint count;
474bf215546Sopenharmony_ci   uint index;
475bf215546Sopenharmony_ci   struct stw_framebuffer *fb;
476bf215546Sopenharmony_ci
477bf215546Sopenharmony_ci   if (!stw_dev)
478bf215546Sopenharmony_ci      return FALSE;
479bf215546Sopenharmony_ci
480bf215546Sopenharmony_ci   index = (uint) iPixelFormat - 1;
481bf215546Sopenharmony_ci   count = stw_pixelformat_get_count(hdc);
482bf215546Sopenharmony_ci   if (index >= count)
483bf215546Sopenharmony_ci      return FALSE;
484bf215546Sopenharmony_ci
485bf215546Sopenharmony_ci   fb = stw_framebuffer_from_hdc_locked(hdc);
486bf215546Sopenharmony_ci   if (fb) {
487bf215546Sopenharmony_ci      /*
488bf215546Sopenharmony_ci       * SetPixelFormat must be called only once.  However ignore
489bf215546Sopenharmony_ci       * pbuffers, for which the framebuffer object is created first.
490bf215546Sopenharmony_ci       */
491bf215546Sopenharmony_ci      boolean bPbuffer = fb->owner == STW_FRAMEBUFFER_PBUFFER;
492bf215546Sopenharmony_ci
493bf215546Sopenharmony_ci      stw_framebuffer_unlock( fb );
494bf215546Sopenharmony_ci
495bf215546Sopenharmony_ci      return bPbuffer;
496bf215546Sopenharmony_ci   }
497bf215546Sopenharmony_ci
498bf215546Sopenharmony_ci   fb = stw_framebuffer_create(WindowFromDC(hdc), iPixelFormat, STW_FRAMEBUFFER_WGL_WINDOW);
499bf215546Sopenharmony_ci   if (!fb) {
500bf215546Sopenharmony_ci      return FALSE;
501bf215546Sopenharmony_ci   }
502bf215546Sopenharmony_ci
503bf215546Sopenharmony_ci   stw_framebuffer_unlock( fb );
504bf215546Sopenharmony_ci
505bf215546Sopenharmony_ci   /* Some applications mistakenly use the undocumented wglSetPixelFormat
506bf215546Sopenharmony_ci    * function instead of SetPixelFormat, so we call SetPixelFormat here to
507bf215546Sopenharmony_ci    * avoid opengl32.dll's wglCreateContext to fail */
508bf215546Sopenharmony_ci   if (GetPixelFormat(hdc) == 0) {
509bf215546Sopenharmony_ci      BOOL bRet = SetPixelFormat(hdc, iPixelFormat, NULL);
510bf215546Sopenharmony_ci      if (!bRet) {
511bf215546Sopenharmony_ci	  debug_printf("SetPixelFormat failed\n");
512bf215546Sopenharmony_ci      }
513bf215546Sopenharmony_ci   }
514bf215546Sopenharmony_ci
515bf215546Sopenharmony_ci   return TRUE;
516bf215546Sopenharmony_ci}
517bf215546Sopenharmony_ci
518bf215546Sopenharmony_ci
519bf215546Sopenharmony_ciint
520bf215546Sopenharmony_cistw_pixelformat_get(HDC hdc)
521bf215546Sopenharmony_ci{
522bf215546Sopenharmony_ci   int iPixelFormat = 0;
523bf215546Sopenharmony_ci   struct stw_framebuffer *fb;
524bf215546Sopenharmony_ci
525bf215546Sopenharmony_ci   fb = stw_framebuffer_from_hdc(hdc);
526bf215546Sopenharmony_ci   if (fb) {
527bf215546Sopenharmony_ci      iPixelFormat = fb->iPixelFormat;
528bf215546Sopenharmony_ci      stw_framebuffer_unlock(fb);
529bf215546Sopenharmony_ci   }
530bf215546Sopenharmony_ci
531bf215546Sopenharmony_ci   return iPixelFormat;
532bf215546Sopenharmony_ci}
533bf215546Sopenharmony_ci
534bf215546Sopenharmony_ci
535bf215546Sopenharmony_ciBOOL APIENTRY
536bf215546Sopenharmony_ciDrvPresentBuffers(HDC hdc, LPPRESENTBUFFERS data)
537bf215546Sopenharmony_ci{
538bf215546Sopenharmony_ci   struct stw_framebuffer *fb;
539bf215546Sopenharmony_ci   struct stw_context *ctx;
540bf215546Sopenharmony_ci   struct pipe_screen *screen;
541bf215546Sopenharmony_ci   struct pipe_context *pipe;
542bf215546Sopenharmony_ci   struct pipe_resource *res;
543bf215546Sopenharmony_ci
544bf215546Sopenharmony_ci   if (!stw_dev)
545bf215546Sopenharmony_ci      return FALSE;
546bf215546Sopenharmony_ci
547bf215546Sopenharmony_ci   fb = stw_framebuffer_from_hdc( hdc );
548bf215546Sopenharmony_ci   if (fb == NULL)
549bf215546Sopenharmony_ci      return FALSE;
550bf215546Sopenharmony_ci
551bf215546Sopenharmony_ci   screen = stw_dev->screen;
552bf215546Sopenharmony_ci   ctx = stw_current_context();
553bf215546Sopenharmony_ci   pipe = ctx ? ctx->st->pipe : NULL;
554bf215546Sopenharmony_ci
555bf215546Sopenharmony_ci   res = (struct pipe_resource *)data->pPrivData;
556bf215546Sopenharmony_ci
557bf215546Sopenharmony_ci   if (data->hSurface != fb->hSharedSurface) {
558bf215546Sopenharmony_ci      if (fb->shared_surface) {
559bf215546Sopenharmony_ci         stw_dev->stw_winsys->shared_surface_close(screen, fb->shared_surface);
560bf215546Sopenharmony_ci         fb->shared_surface = NULL;
561bf215546Sopenharmony_ci      }
562bf215546Sopenharmony_ci
563bf215546Sopenharmony_ci      fb->hSharedSurface = data->hSurface;
564bf215546Sopenharmony_ci
565bf215546Sopenharmony_ci      if (data->hSurface &&
566bf215546Sopenharmony_ci         stw_dev->stw_winsys->shared_surface_open) {
567bf215546Sopenharmony_ci         fb->shared_surface =
568bf215546Sopenharmony_ci            stw_dev->stw_winsys->shared_surface_open(screen,
569bf215546Sopenharmony_ci                                                     fb->hSharedSurface);
570bf215546Sopenharmony_ci      }
571bf215546Sopenharmony_ci   }
572bf215546Sopenharmony_ci
573bf215546Sopenharmony_ci   if (!fb->minimized) {
574bf215546Sopenharmony_ci      if (fb->shared_surface) {
575bf215546Sopenharmony_ci         stw_dev->stw_winsys->compose(screen,
576bf215546Sopenharmony_ci                                      res,
577bf215546Sopenharmony_ci                                      fb->shared_surface,
578bf215546Sopenharmony_ci                                      &fb->client_rect,
579bf215546Sopenharmony_ci                                      data->ullPresentToken);
580bf215546Sopenharmony_ci      }
581bf215546Sopenharmony_ci      else {
582bf215546Sopenharmony_ci         stw_dev->stw_winsys->present( screen, pipe, res, hdc );
583bf215546Sopenharmony_ci      }
584bf215546Sopenharmony_ci   }
585bf215546Sopenharmony_ci
586bf215546Sopenharmony_ci   stw_framebuffer_update(fb);
587bf215546Sopenharmony_ci   stw_notify_current_locked(fb);
588bf215546Sopenharmony_ci
589bf215546Sopenharmony_ci   stw_framebuffer_unlock(fb);
590bf215546Sopenharmony_ci
591bf215546Sopenharmony_ci   return TRUE;
592bf215546Sopenharmony_ci}
593bf215546Sopenharmony_ci
594bf215546Sopenharmony_ci
595bf215546Sopenharmony_ci/**
596bf215546Sopenharmony_ci * Queue a composition.
597bf215546Sopenharmony_ci *
598bf215546Sopenharmony_ci * The stw_framebuffer object must have its mutex locked.  The mutex will
599bf215546Sopenharmony_ci * be unlocked here before returning.
600bf215546Sopenharmony_ci */
601bf215546Sopenharmony_ciBOOL
602bf215546Sopenharmony_cistw_framebuffer_present_locked(HDC hdc,
603bf215546Sopenharmony_ci                               struct stw_framebuffer *fb,
604bf215546Sopenharmony_ci                               struct pipe_resource *res)
605bf215546Sopenharmony_ci{
606bf215546Sopenharmony_ci   if (fb->winsys_framebuffer) {
607bf215546Sopenharmony_ci      BOOL result = fb->winsys_framebuffer->present(fb->winsys_framebuffer);
608bf215546Sopenharmony_ci
609bf215546Sopenharmony_ci      stw_framebuffer_update(fb);
610bf215546Sopenharmony_ci      stw_notify_current_locked(fb);
611bf215546Sopenharmony_ci      stw_framebuffer_unlock(fb);
612bf215546Sopenharmony_ci
613bf215546Sopenharmony_ci      return result;
614bf215546Sopenharmony_ci   }
615bf215546Sopenharmony_ci   else if (stw_dev->callbacks.pfnPresentBuffers &&
616bf215546Sopenharmony_ci            stw_dev->stw_winsys->compose) {
617bf215546Sopenharmony_ci      PRESENTBUFFERSCB data;
618bf215546Sopenharmony_ci
619bf215546Sopenharmony_ci      memset(&data, 0, sizeof data);
620bf215546Sopenharmony_ci      data.nVersion = 2;
621bf215546Sopenharmony_ci      data.syncType = PRESCB_SYNCTYPE_NONE;
622bf215546Sopenharmony_ci      data.luidAdapter = stw_dev->AdapterLuid;
623bf215546Sopenharmony_ci      data.updateRect = fb->client_rect;
624bf215546Sopenharmony_ci      data.pPrivData = (void *)res;
625bf215546Sopenharmony_ci
626bf215546Sopenharmony_ci      stw_notify_current_locked(fb);
627bf215546Sopenharmony_ci      stw_framebuffer_unlock(fb);
628bf215546Sopenharmony_ci
629bf215546Sopenharmony_ci      return stw_dev->callbacks.pfnPresentBuffers(hdc, &data);
630bf215546Sopenharmony_ci   }
631bf215546Sopenharmony_ci   else {
632bf215546Sopenharmony_ci      struct pipe_screen *screen = stw_dev->screen;
633bf215546Sopenharmony_ci      struct stw_context *ctx = stw_current_context();
634bf215546Sopenharmony_ci      struct pipe_context *pipe = ctx ? ctx->st->pipe : NULL;
635bf215546Sopenharmony_ci
636bf215546Sopenharmony_ci      stw_dev->stw_winsys->present( screen, pipe, res, hdc );
637bf215546Sopenharmony_ci
638bf215546Sopenharmony_ci      stw_framebuffer_update(fb);
639bf215546Sopenharmony_ci      stw_notify_current_locked(fb);
640bf215546Sopenharmony_ci      stw_framebuffer_unlock(fb);
641bf215546Sopenharmony_ci
642bf215546Sopenharmony_ci      return TRUE;
643bf215546Sopenharmony_ci   }
644bf215546Sopenharmony_ci}
645bf215546Sopenharmony_ci
646bf215546Sopenharmony_ci
647bf215546Sopenharmony_ci/**
648bf215546Sopenharmony_ci * This is called just before issuing the buffer swap/present.
649bf215546Sopenharmony_ci * We query the current time and determine if we should sleep before
650bf215546Sopenharmony_ci * issuing the swap/present.
651bf215546Sopenharmony_ci * This is a bit of a hack and is certainly not very accurate but it
652bf215546Sopenharmony_ci * basically works.
653bf215546Sopenharmony_ci * This is for the WGL_ARB_swap_interval extension.
654bf215546Sopenharmony_ci */
655bf215546Sopenharmony_cistatic void
656bf215546Sopenharmony_ciwait_swap_interval(struct stw_framebuffer *fb)
657bf215546Sopenharmony_ci{
658bf215546Sopenharmony_ci   /* Note: all time variables here are in units of microseconds */
659bf215546Sopenharmony_ci   int64_t cur_time = os_time_get_nano() / 1000;
660bf215546Sopenharmony_ci
661bf215546Sopenharmony_ci   if (fb->prev_swap_time != 0) {
662bf215546Sopenharmony_ci      /* Compute time since previous swap */
663bf215546Sopenharmony_ci      int64_t delta = cur_time - fb->prev_swap_time;
664bf215546Sopenharmony_ci      int64_t min_swap_period =
665bf215546Sopenharmony_ci         1.0e6 / stw_dev->refresh_rate * stw_dev->swap_interval;
666bf215546Sopenharmony_ci
667bf215546Sopenharmony_ci      /* If time since last swap is less than wait period, wait.
668bf215546Sopenharmony_ci       * Note that it's possible for the delta to be negative because of
669bf215546Sopenharmony_ci       * rollover.  See https://bugs.freedesktop.org/show_bug.cgi?id=102241
670bf215546Sopenharmony_ci       */
671bf215546Sopenharmony_ci      if ((delta >= 0) && (delta < min_swap_period)) {
672bf215546Sopenharmony_ci         float fudge = 1.75f;  /* emperical fudge factor */
673bf215546Sopenharmony_ci         int64_t wait = (min_swap_period - delta) * fudge;
674bf215546Sopenharmony_ci         os_time_sleep(wait);
675bf215546Sopenharmony_ci      }
676bf215546Sopenharmony_ci   }
677bf215546Sopenharmony_ci
678bf215546Sopenharmony_ci   fb->prev_swap_time = cur_time;
679bf215546Sopenharmony_ci}
680bf215546Sopenharmony_ci
681bf215546Sopenharmony_ciBOOL
682bf215546Sopenharmony_cistw_framebuffer_swap_locked(HDC hdc, struct stw_framebuffer *fb)
683bf215546Sopenharmony_ci{
684bf215546Sopenharmony_ci   struct stw_context *ctx;
685bf215546Sopenharmony_ci   if (!(fb->pfi->pfd.dwFlags & PFD_DOUBLEBUFFER)) {
686bf215546Sopenharmony_ci      stw_framebuffer_unlock(fb);
687bf215546Sopenharmony_ci      return TRUE;
688bf215546Sopenharmony_ci   }
689bf215546Sopenharmony_ci
690bf215546Sopenharmony_ci   ctx = stw_current_context();
691bf215546Sopenharmony_ci   if (ctx) {
692bf215546Sopenharmony_ci      if (ctx->hud) {
693bf215546Sopenharmony_ci         /* Display the HUD */
694bf215546Sopenharmony_ci         struct pipe_resource *back =
695bf215546Sopenharmony_ci            stw_get_framebuffer_resource(fb->stfb, ST_ATTACHMENT_BACK_LEFT);
696bf215546Sopenharmony_ci         if (back) {
697bf215546Sopenharmony_ci            hud_run(ctx->hud, NULL, back);
698bf215546Sopenharmony_ci         }
699bf215546Sopenharmony_ci      }
700bf215546Sopenharmony_ci
701bf215546Sopenharmony_ci      if (ctx->current_framebuffer == fb) {
702bf215546Sopenharmony_ci         /* flush current context */
703bf215546Sopenharmony_ci         stw_st_flush(ctx->st, fb->stfb, ST_FLUSH_END_OF_FRAME);
704bf215546Sopenharmony_ci      }
705bf215546Sopenharmony_ci   }
706bf215546Sopenharmony_ci
707bf215546Sopenharmony_ci   if (stw_dev->swap_interval != 0 && !fb->winsys_framebuffer) {
708bf215546Sopenharmony_ci      wait_swap_interval(fb);
709bf215546Sopenharmony_ci   }
710bf215546Sopenharmony_ci
711bf215546Sopenharmony_ci   return stw_st_swap_framebuffer_locked(hdc, ctx->st, fb->stfb);
712bf215546Sopenharmony_ci}
713bf215546Sopenharmony_ci
714bf215546Sopenharmony_ciBOOL APIENTRY
715bf215546Sopenharmony_ciDrvSwapBuffers(HDC hdc)
716bf215546Sopenharmony_ci{
717bf215546Sopenharmony_ci   struct stw_framebuffer *fb;
718bf215546Sopenharmony_ci
719bf215546Sopenharmony_ci   if (!stw_dev)
720bf215546Sopenharmony_ci      return FALSE;
721bf215546Sopenharmony_ci
722bf215546Sopenharmony_ci   fb = stw_framebuffer_from_hdc( hdc );
723bf215546Sopenharmony_ci   if (fb == NULL)
724bf215546Sopenharmony_ci      return FALSE;
725bf215546Sopenharmony_ci
726bf215546Sopenharmony_ci   return stw_framebuffer_swap_locked(hdc, fb);
727bf215546Sopenharmony_ci}
728bf215546Sopenharmony_ci
729bf215546Sopenharmony_ci
730bf215546Sopenharmony_ciBOOL APIENTRY
731bf215546Sopenharmony_ciDrvSwapLayerBuffers(HDC hdc, UINT fuPlanes)
732bf215546Sopenharmony_ci{
733bf215546Sopenharmony_ci   if (fuPlanes & WGL_SWAP_MAIN_PLANE)
734bf215546Sopenharmony_ci      return DrvSwapBuffers(hdc);
735bf215546Sopenharmony_ci
736bf215546Sopenharmony_ci   return FALSE;
737bf215546Sopenharmony_ci}
738