Mercurial > sdl-ios-xcode
comparison src/video/SDL_renderer_sw.c @ 1895:c121d94672cb
SDL 1.2 is moving to a branch, and SDL 1.3 is becoming the head.
author | Sam Lantinga <slouken@libsdl.org> |
---|---|
date | Mon, 10 Jul 2006 21:04:37 +0000 |
parents | |
children | c2a27da60b18 |
comparison
equal
deleted
inserted
replaced
1894:c69cee13dd76 | 1895:c121d94672cb |
---|---|
1 /* | |
2 SDL - Simple DirectMedia Layer | |
3 Copyright (C) 1997-2006 Sam Lantinga | |
4 | |
5 This library is free software; you can redistribute it and/or | |
6 modify it under the terms of the GNU Lesser General Public | |
7 License as published by the Free Software Foundation; either | |
8 version 2.1 of the License, or (at your option) any later version. | |
9 | |
10 This library is distributed in the hope that it will be useful, | |
11 but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
13 Lesser General Public License for more details. | |
14 | |
15 You should have received a copy of the GNU Lesser General Public | |
16 License along with this library; if not, write to the Free Software | |
17 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | |
18 | |
19 Sam Lantinga | |
20 slouken@libsdl.org | |
21 */ | |
22 #include "SDL_config.h" | |
23 | |
24 #include "SDL_video.h" | |
25 #include "SDL_sysvideo.h" | |
26 #include "SDL_rect_c.h" | |
27 #include "SDL_yuv_sw_c.h" | |
28 | |
29 | |
30 /* SDL surface based renderer implementation */ | |
31 | |
32 static SDL_Renderer *SDL_SW_CreateRenderer(SDL_Window * window, Uint32 flags); | |
33 static int SDL_SW_CreateTexture(SDL_Renderer * renderer, | |
34 SDL_Texture * texture); | |
35 static int SDL_SW_QueryTexturePixels(SDL_Renderer * renderer, | |
36 SDL_Texture * texture, void **pixels, | |
37 int *pitch); | |
38 static int SDL_SW_SetTexturePalette(SDL_Renderer * renderer, | |
39 SDL_Texture * texture, | |
40 const SDL_Color * colors, int firstcolor, | |
41 int ncolors); | |
42 static int SDL_SW_GetTexturePalette(SDL_Renderer * renderer, | |
43 SDL_Texture * texture, SDL_Color * colors, | |
44 int firstcolor, int ncolors); | |
45 static int SDL_SW_UpdateTexture(SDL_Renderer * renderer, | |
46 SDL_Texture * texture, const SDL_Rect * rect, | |
47 const void *pixels, int pitch); | |
48 static int SDL_SW_LockTexture(SDL_Renderer * renderer, SDL_Texture * texture, | |
49 const SDL_Rect * rect, int markDirty, | |
50 void **pixels, int *pitch); | |
51 static void SDL_SW_UnlockTexture(SDL_Renderer * renderer, | |
52 SDL_Texture * texture); | |
53 static void SDL_SW_DirtyTexture(SDL_Renderer * renderer, | |
54 SDL_Texture * texture, int numrects, | |
55 const SDL_Rect * rects); | |
56 static void SDL_SW_SelectRenderTexture(SDL_Renderer * renderer, | |
57 SDL_Texture * texture); | |
58 static int SDL_SW_RenderFill(SDL_Renderer * renderer, const SDL_Rect * rect, | |
59 Uint32 color); | |
60 static int SDL_SW_RenderCopy(SDL_Renderer * renderer, SDL_Texture * texture, | |
61 const SDL_Rect * srcrect, | |
62 const SDL_Rect * dstrect, int blendMode, | |
63 int scaleMode); | |
64 static int SDL_SW_RenderReadPixels(SDL_Renderer * renderer, | |
65 const SDL_Rect * rect, void *pixels, | |
66 int pitch); | |
67 static int SDL_SW_RenderWritePixels(SDL_Renderer * renderer, | |
68 const SDL_Rect * rect, const void *pixels, | |
69 int pitch); | |
70 static void SDL_SW_RenderPresent(SDL_Renderer * renderer); | |
71 static void SDL_SW_DestroyTexture(SDL_Renderer * renderer, | |
72 SDL_Texture * texture); | |
73 static void SDL_SW_DestroyRenderer(SDL_Renderer * renderer); | |
74 | |
75 | |
76 SDL_RenderDriver SDL_SW_RenderDriver = { | |
77 SDL_SW_CreateRenderer, | |
78 { | |
79 "software", | |
80 (SDL_Renderer_PresentDiscard | | |
81 SDL_Renderer_PresentCopy | | |
82 SDL_Renderer_PresentFlip2 | | |
83 SDL_Renderer_PresentFlip3 | SDL_Renderer_RenderTarget), | |
84 (SDL_TextureBlendMode_None | | |
85 SDL_TextureBlendMode_Mask | SDL_TextureBlendMode_Blend), | |
86 (SDL_TextureScaleMode_None | SDL_TextureScaleMode_Fast), | |
87 11, | |
88 { | |
89 SDL_PixelFormat_Index8, | |
90 SDL_PixelFormat_RGB555, | |
91 SDL_PixelFormat_RGB565, | |
92 SDL_PixelFormat_RGB888, | |
93 SDL_PixelFormat_BGR888, | |
94 SDL_PixelFormat_ARGB8888, | |
95 SDL_PixelFormat_RGBA8888, | |
96 SDL_PixelFormat_ABGR8888, | |
97 SDL_PixelFormat_BGRA8888, | |
98 SDL_PixelFormat_YUY2, | |
99 SDL_PixelFormat_UYVY}, | |
100 0, | |
101 0} | |
102 }; | |
103 | |
104 typedef struct | |
105 { | |
106 int current_screen; | |
107 SDL_Surface *screens[3]; | |
108 SDL_Surface *target; | |
109 SDL_Renderer *renderer; | |
110 SDL_DirtyRectList dirty; | |
111 } SDL_SW_RenderData; | |
112 | |
113 SDL_Renderer * | |
114 SDL_SW_CreateRenderer(SDL_Window * window, Uint32 flags) | |
115 { | |
116 SDL_VideoDisplay *display = SDL_GetDisplayFromWindow(window); | |
117 SDL_DisplayMode *displayMode = &display->current_mode; | |
118 SDL_Renderer *renderer; | |
119 SDL_SW_RenderData *data; | |
120 int i, n; | |
121 int bpp; | |
122 Uint32 Rmask, Gmask, Bmask, Amask; | |
123 | |
124 if (!SDL_PixelFormatEnumToMasks | |
125 (displayMode->format, &bpp, &Rmask, &Gmask, &Bmask, &Amask)) { | |
126 SDL_SetError("Unknown display format"); | |
127 return NULL; | |
128 } | |
129 | |
130 renderer = (SDL_Renderer *) SDL_calloc(1, sizeof(*renderer)); | |
131 if (!renderer) { | |
132 SDL_OutOfMemory(); | |
133 return NULL; | |
134 } | |
135 | |
136 data = (SDL_SW_RenderData *) SDL_malloc(sizeof(*data)); | |
137 if (!data) { | |
138 SDL_SW_DestroyRenderer(renderer); | |
139 SDL_OutOfMemory(); | |
140 return NULL; | |
141 } | |
142 SDL_zerop(data); | |
143 | |
144 renderer->CreateTexture = SDL_SW_CreateTexture; | |
145 renderer->QueryTexturePixels = SDL_SW_QueryTexturePixels; | |
146 renderer->SetTexturePalette = SDL_SW_SetTexturePalette; | |
147 renderer->GetTexturePalette = SDL_SW_GetTexturePalette; | |
148 renderer->UpdateTexture = SDL_SW_UpdateTexture; | |
149 renderer->LockTexture = SDL_SW_LockTexture; | |
150 renderer->UnlockTexture = SDL_SW_UnlockTexture; | |
151 renderer->DirtyTexture = SDL_SW_DirtyTexture; | |
152 renderer->SelectRenderTexture = SDL_SW_SelectRenderTexture; | |
153 renderer->RenderFill = SDL_SW_RenderFill; | |
154 renderer->RenderCopy = SDL_SW_RenderCopy; | |
155 renderer->RenderReadPixels = SDL_SW_RenderReadPixels; | |
156 renderer->RenderWritePixels = SDL_SW_RenderWritePixels; | |
157 renderer->RenderPresent = SDL_SW_RenderPresent; | |
158 renderer->DestroyTexture = SDL_SW_DestroyTexture; | |
159 renderer->DestroyRenderer = SDL_SW_DestroyRenderer; | |
160 renderer->info = SDL_SW_RenderDriver.info; | |
161 renderer->window = window->id; | |
162 renderer->driverdata = data; | |
163 | |
164 renderer->info.flags = SDL_Renderer_RenderTarget; | |
165 | |
166 if (flags & SDL_Renderer_PresentFlip2) { | |
167 renderer->info.flags |= SDL_Renderer_PresentFlip2; | |
168 n = 2; | |
169 } else if (flags & SDL_Renderer_PresentFlip3) { | |
170 renderer->info.flags |= SDL_Renderer_PresentFlip3; | |
171 n = 3; | |
172 } else { | |
173 renderer->info.flags |= SDL_Renderer_PresentCopy; | |
174 n = 1; | |
175 } | |
176 for (i = 0; i < n; ++i) { | |
177 data->screens[i] = | |
178 SDL_CreateRGBSurface(0, window->w, window->h, bpp, Rmask, Gmask, | |
179 Bmask, Amask); | |
180 if (!data->screens[i]) { | |
181 SDL_SW_DestroyRenderer(renderer); | |
182 return NULL; | |
183 } | |
184 SDL_SetSurfacePalette(data->screens[i], display->palette); | |
185 } | |
186 data->current_screen = 0; | |
187 data->target = data->screens[0]; | |
188 | |
189 /* Find a render driver that we can use to display data */ | |
190 for (i = 0; i < display->num_render_drivers; ++i) { | |
191 SDL_RenderDriver *driver = &display->render_drivers[i]; | |
192 if (driver->info.name != SDL_SW_RenderDriver.info.name) { | |
193 data->renderer = | |
194 driver->CreateRenderer(window, SDL_Renderer_PresentDiscard); | |
195 if (data->renderer) { | |
196 break; | |
197 } | |
198 } | |
199 } | |
200 if (i == display->num_render_drivers) { | |
201 SDL_SW_DestroyRenderer(renderer); | |
202 SDL_SetError("Couldn't find display render driver"); | |
203 return NULL; | |
204 } | |
205 return renderer; | |
206 } | |
207 | |
208 static int | |
209 SDL_SW_CreateTexture(SDL_Renderer * renderer, SDL_Texture * texture) | |
210 { | |
211 if (SDL_ISPIXELFORMAT_FOURCC(texture->format)) { | |
212 if (texture->access == SDL_TextureAccess_Render) { | |
213 SDL_SetError("Rendering to YUV format textures is not supported"); | |
214 return -1; | |
215 } | |
216 texture->driverdata = SDL_SW_CreateYUVTexture(texture); | |
217 } else { | |
218 int bpp; | |
219 Uint32 Rmask, Gmask, Bmask, Amask; | |
220 | |
221 if (!SDL_PixelFormatEnumToMasks | |
222 (texture->format, &bpp, &Rmask, &Gmask, &Bmask, &Amask)) { | |
223 SDL_SetError("Unknown texture format"); | |
224 return -1; | |
225 } | |
226 | |
227 texture->driverdata = | |
228 SDL_CreateRGBSurface(0, texture->w, texture->h, bpp, Rmask, Gmask, | |
229 Bmask, Amask); | |
230 } | |
231 | |
232 if (!texture->driverdata) { | |
233 return -1; | |
234 } | |
235 return 0; | |
236 } | |
237 | |
238 static int | |
239 SDL_SW_QueryTexturePixels(SDL_Renderer * renderer, SDL_Texture * texture, | |
240 void **pixels, int *pitch) | |
241 { | |
242 if (SDL_ISPIXELFORMAT_FOURCC(texture->format)) { | |
243 return SDL_SW_QueryYUVTexturePixels((SDL_SW_YUVTexture *) texture-> | |
244 driverdata, pixels, pitch); | |
245 } else { | |
246 SDL_Surface *surface = (SDL_Surface *) texture->driverdata; | |
247 | |
248 *pixels = surface->pixels; | |
249 *pitch = surface->pitch; | |
250 return 0; | |
251 } | |
252 } | |
253 | |
254 static int | |
255 SDL_SW_SetTexturePalette(SDL_Renderer * renderer, SDL_Texture * texture, | |
256 const SDL_Color * colors, int firstcolor, | |
257 int ncolors) | |
258 { | |
259 if (SDL_ISPIXELFORMAT_FOURCC(texture->format)) { | |
260 SDL_SetError("YUV textures don't have a palette"); | |
261 return -1; | |
262 } else { | |
263 SDL_Surface *surface = (SDL_Surface *) texture->driverdata; | |
264 | |
265 return SDL_SetPaletteColors(surface->format->palette, colors, | |
266 firstcolor, ncolors); | |
267 } | |
268 } | |
269 | |
270 static int | |
271 SDL_SW_GetTexturePalette(SDL_Renderer * renderer, SDL_Texture * texture, | |
272 SDL_Color * colors, int firstcolor, int ncolors) | |
273 { | |
274 if (SDL_ISPIXELFORMAT_FOURCC(texture->format)) { | |
275 SDL_SetError("YUV textures don't have a palette"); | |
276 return -1; | |
277 } else { | |
278 SDL_Surface *surface = (SDL_Surface *) texture->driverdata; | |
279 | |
280 SDL_memcpy(colors, &surface->format->palette->colors[firstcolor], | |
281 ncolors * sizeof(*colors)); | |
282 return 0; | |
283 } | |
284 } | |
285 | |
286 static int | |
287 SDL_SW_UpdateTexture(SDL_Renderer * renderer, SDL_Texture * texture, | |
288 const SDL_Rect * rect, const void *pixels, int pitch) | |
289 { | |
290 if (SDL_ISPIXELFORMAT_FOURCC(texture->format)) { | |
291 return SDL_SW_UpdateYUVTexture((SDL_SW_YUVTexture *) texture-> | |
292 driverdata, rect, pixels, pitch); | |
293 } else { | |
294 SDL_Surface *surface = (SDL_Surface *) texture->driverdata; | |
295 Uint8 *src, *dst; | |
296 int row; | |
297 size_t length; | |
298 | |
299 src = (Uint8 *) pixels; | |
300 dst = | |
301 (Uint8 *) surface->pixels + rect->y * surface->pitch + | |
302 rect->x * surface->format->BytesPerPixel; | |
303 length = rect->w * surface->format->BytesPerPixel; | |
304 for (row = 0; row < rect->h; ++row) { | |
305 SDL_memcpy(dst, src, length); | |
306 src += pitch; | |
307 dst += surface->pitch; | |
308 } | |
309 return 0; | |
310 } | |
311 } | |
312 | |
313 static int | |
314 SDL_SW_LockTexture(SDL_Renderer * renderer, SDL_Texture * texture, | |
315 const SDL_Rect * rect, int markDirty, void **pixels, | |
316 int *pitch) | |
317 { | |
318 if (SDL_ISPIXELFORMAT_FOURCC(texture->format)) { | |
319 return SDL_SW_LockYUVTexture((SDL_SW_YUVTexture *) texture-> | |
320 driverdata, rect, markDirty, pixels, | |
321 pitch); | |
322 } else { | |
323 SDL_Surface *surface = (SDL_Surface *) texture->driverdata; | |
324 | |
325 *pixels = | |
326 (void *) ((Uint8 *) surface->pixels + rect->y * surface->pitch + | |
327 rect->x * surface->format->BytesPerPixel); | |
328 *pitch = surface->pitch; | |
329 return 0; | |
330 } | |
331 } | |
332 | |
333 static void | |
334 SDL_SW_UnlockTexture(SDL_Renderer * renderer, SDL_Texture * texture) | |
335 { | |
336 if (SDL_ISPIXELFORMAT_FOURCC(texture->format)) { | |
337 SDL_SW_UnlockYUVTexture((SDL_SW_YUVTexture *) texture->driverdata); | |
338 } | |
339 } | |
340 | |
341 static void | |
342 SDL_SW_DirtyTexture(SDL_Renderer * renderer, SDL_Texture * texture, | |
343 int numrects, const SDL_Rect * rects) | |
344 { | |
345 } | |
346 | |
347 static void | |
348 SDL_SW_SelectRenderTexture(SDL_Renderer * renderer, SDL_Texture * texture) | |
349 { | |
350 SDL_SW_RenderData *data = (SDL_SW_RenderData *) renderer->driverdata; | |
351 | |
352 if (texture) { | |
353 data->target = (SDL_Surface *) texture->driverdata; | |
354 } else { | |
355 data->target = data->screens[data->current_screen]; | |
356 } | |
357 } | |
358 | |
359 static int | |
360 SDL_SW_RenderFill(SDL_Renderer * renderer, const SDL_Rect * rect, | |
361 Uint32 color) | |
362 { | |
363 SDL_SW_RenderData *data = (SDL_SW_RenderData *) renderer->driverdata; | |
364 SDL_Rect real_rect = *rect; | |
365 Uint8 r, g, b, a; | |
366 | |
367 SDL_AddDirtyRect(&data->dirty, rect); | |
368 | |
369 a = (Uint8) ((color >> 24) & 0xFF); | |
370 r = (Uint8) ((color >> 16) & 0xFF); | |
371 g = (Uint8) ((color >> 8) & 0xFF); | |
372 b = (Uint8) (color & 0xFF); | |
373 color = SDL_MapRGBA(data->target->format, r, g, b, a); | |
374 | |
375 return SDL_FillRect(data->target, &real_rect, color); | |
376 } | |
377 | |
378 static int | |
379 SDL_SW_RenderCopy(SDL_Renderer * renderer, SDL_Texture * texture, | |
380 const SDL_Rect * srcrect, const SDL_Rect * dstrect, | |
381 int blendMode, int scaleMode) | |
382 { | |
383 SDL_SW_RenderData *data = (SDL_SW_RenderData *) renderer->driverdata; | |
384 SDL_Window *window = SDL_GetWindowFromID(renderer->window); | |
385 SDL_VideoDisplay *display = SDL_GetDisplayFromWindow(window); | |
386 | |
387 SDL_AddDirtyRect(&data->dirty, dstrect); | |
388 | |
389 if (SDL_ISPIXELFORMAT_FOURCC(texture->format)) { | |
390 SDL_Surface *target = data->target; | |
391 void *pixels = | |
392 (Uint8 *) target->pixels + dstrect->y * target->pitch + | |
393 dstrect->x * target->format->BytesPerPixel; | |
394 return SDL_SW_CopyYUVToRGB((SDL_SW_YUVTexture *) texture->driverdata, | |
395 srcrect, display->current_mode.format, | |
396 dstrect->w, dstrect->h, pixels, | |
397 target->pitch); | |
398 } else { | |
399 SDL_Surface *surface = (SDL_Surface *) texture->driverdata; | |
400 SDL_Rect real_srcrect = *srcrect; | |
401 SDL_Rect real_dstrect = *dstrect; | |
402 | |
403 if (blendMode & | |
404 (SDL_TextureBlendMode_Mask | SDL_TextureBlendMode_Blend)) { | |
405 SDL_SetAlpha(surface, SDL_SRCALPHA, 0); | |
406 } else { | |
407 SDL_SetAlpha(surface, 0, 0); | |
408 } | |
409 if (scaleMode != SDL_TextureScaleMode_None && | |
410 (srcrect->w != dstrect->w || srcrect->h != dstrect->h)) { | |
411 return SDL_SoftStretch(surface, &real_srcrect, data->target, | |
412 &real_dstrect); | |
413 } else { | |
414 return SDL_LowerBlit(surface, &real_srcrect, data->target, | |
415 &real_dstrect); | |
416 } | |
417 } | |
418 } | |
419 | |
420 static int | |
421 SDL_SW_RenderReadPixels(SDL_Renderer * renderer, const SDL_Rect * rect, | |
422 void *pixels, int pitch) | |
423 { | |
424 SDL_SW_RenderData *data = (SDL_SW_RenderData *) renderer->driverdata; | |
425 SDL_Surface *surface = data->target; | |
426 Uint8 *src, *dst; | |
427 int row; | |
428 size_t length; | |
429 | |
430 src = | |
431 (Uint8 *) surface->pixels + rect->y * surface->pitch + | |
432 rect->x * surface->format->BytesPerPixel; | |
433 dst = (Uint8 *) pixels; | |
434 length = rect->w * surface->format->BytesPerPixel; | |
435 for (row = 0; row < rect->h; ++row) { | |
436 SDL_memcpy(dst, src, length); | |
437 src += surface->pitch; | |
438 dst += pitch; | |
439 } | |
440 return 0; | |
441 } | |
442 | |
443 static int | |
444 SDL_SW_RenderWritePixels(SDL_Renderer * renderer, const SDL_Rect * rect, | |
445 const void *pixels, int pitch) | |
446 { | |
447 SDL_SW_RenderData *data = (SDL_SW_RenderData *) renderer->driverdata; | |
448 SDL_Surface *surface = data->target; | |
449 Uint8 *src, *dst; | |
450 int row; | |
451 size_t length; | |
452 | |
453 SDL_AddDirtyRect(&data->dirty, rect); | |
454 | |
455 src = (Uint8 *) pixels; | |
456 dst = | |
457 (Uint8 *) surface->pixels + rect->y * surface->pitch + | |
458 rect->x * surface->format->BytesPerPixel; | |
459 length = rect->w * surface->format->BytesPerPixel; | |
460 for (row = 0; row < rect->h; ++row) { | |
461 SDL_memcpy(dst, src, length); | |
462 src += pitch; | |
463 dst += surface->pitch; | |
464 } | |
465 return 0; | |
466 } | |
467 | |
468 static void | |
469 SDL_SW_RenderPresent(SDL_Renderer * renderer) | |
470 { | |
471 SDL_SW_RenderData *data = (SDL_SW_RenderData *) renderer->driverdata; | |
472 SDL_Surface *surface = data->screens[data->current_screen]; | |
473 SDL_DirtyRect *dirty; | |
474 int new_screen; | |
475 | |
476 /* Send the data to the display */ | |
477 for (dirty = data->dirty.list; dirty; dirty = dirty->next) { | |
478 void *pixels = | |
479 (void *) ((Uint8 *) surface->pixels + | |
480 dirty->rect.y * surface->pitch + | |
481 dirty->rect.x * surface->format->BytesPerPixel); | |
482 data->renderer->RenderWritePixels(data->renderer, &dirty->rect, | |
483 pixels, surface->pitch); | |
484 } | |
485 SDL_ClearDirtyRects(&data->dirty); | |
486 data->renderer->RenderPresent(data->renderer); | |
487 | |
488 | |
489 /* Update the flipping chain, if any */ | |
490 if (renderer->info.flags & SDL_Renderer_PresentFlip2) { | |
491 new_screen = (data->current_screen + 1) % 2; | |
492 } else if (renderer->info.flags & SDL_Renderer_PresentFlip3) { | |
493 new_screen = (data->current_screen + 1) % 3; | |
494 } else { | |
495 new_screen = 0; | |
496 } | |
497 if (data->target == data->screens[data->current_screen]) { | |
498 data->target = data->screens[new_screen]; | |
499 } | |
500 data->current_screen = new_screen; | |
501 } | |
502 | |
503 static void | |
504 SDL_SW_DestroyTexture(SDL_Renderer * renderer, SDL_Texture * texture) | |
505 { | |
506 if (SDL_ISPIXELFORMAT_FOURCC(texture->format)) { | |
507 SDL_SW_DestroyYUVTexture((SDL_SW_YUVTexture *) texture->driverdata); | |
508 } else { | |
509 SDL_Surface *surface = (SDL_Surface *) texture->driverdata; | |
510 | |
511 SDL_FreeSurface(surface); | |
512 } | |
513 } | |
514 | |
515 static void | |
516 SDL_SW_DestroyRenderer(SDL_Renderer * renderer) | |
517 { | |
518 SDL_SW_RenderData *data = (SDL_SW_RenderData *) renderer->driverdata; | |
519 int i; | |
520 | |
521 if (data) { | |
522 for (i = 0; i < SDL_arraysize(data->screens); ++i) { | |
523 if (data->screens[i]) { | |
524 SDL_FreeSurface(data->screens[i]); | |
525 } | |
526 } | |
527 SDL_FreeDirtyRects(&data->dirty); | |
528 SDL_free(data); | |
529 } | |
530 SDL_free(renderer); | |
531 } | |
532 | |
533 /* vi: set ts=4 sw=4 expandtab: */ |