1 /*
2 * Copyright © 2014 Keith Packard
3 *
4 * Permission to use, copy, modify, distribute, and sell this software and its
5 * documentation for any purpose is hereby granted without fee, provided that
6 * the above copyright notice appear in all copies and that both that copyright
7 * notice and this permission notice appear in supporting documentation, and
8 * that the name of the copyright holders not be used in advertising or
9 * publicity pertaining to distribution of the software without specific,
10 * written prior permission. The copyright holders make no representations
11 * about the suitability of this software for any purpose. It is provided "as
12 * is" without express or implied warranty.
13 *
14 * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
15 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
16 * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
17 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
18 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
19 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
20 * OF THIS SOFTWARE.
21 */
22 #include <stdlib.h>
23 #include "Xprintf.h"
24
25 #include "glamor_priv.h"
26 #include "glamor_transform.h"
27 #include "glamor_transfer.h"
28
29 #include <mipict.h>
30
31 #define DEFAULT_ATLAS_DIM 1024
32
33 static DevPrivateKeyRec glamor_glyph_private_key;
34
35 struct glamor_glyph_private {
36 int16_t x;
37 int16_t y;
38 uint32_t serial;
39 };
40
41 struct glamor_glyph_atlas {
42 PixmapPtr atlas;
43 PictFormatPtr format;
44 int x, y;
45 int row_height;
46 int nglyph;
47 uint32_t serial;
48 };
49
glamor_get_glyph_private(PixmapPtr pixmap)50 static inline struct glamor_glyph_private *glamor_get_glyph_private(PixmapPtr pixmap) {
51 return dixLookupPrivate(&pixmap->devPrivates, &glamor_glyph_private_key);
52 }
53
54 static inline void
glamor_copy_glyph(PixmapPtr glyph_pixmap,DrawablePtr atlas_draw,int16_t x,int16_t y)55 glamor_copy_glyph(PixmapPtr glyph_pixmap,
56 DrawablePtr atlas_draw,
57 int16_t x,
58 int16_t y)
59 {
60 DrawablePtr glyph_draw = &glyph_pixmap->drawable;
61 BoxRec box = {
62 .x1 = 0,
63 .y1 = 0,
64 .x2 = glyph_draw->width,
65 .y2 = glyph_draw->height,
66 };
67 PixmapPtr upload_pixmap = glyph_pixmap;
68
69 if (glyph_pixmap->drawable.bitsPerPixel != atlas_draw->bitsPerPixel) {
70
71 /* If we're dealing with 1-bit glyphs, we copy them to a
72 * temporary 8-bit pixmap and upload them from there, since
73 * that's what GL can handle.
74 */
75 ScreenPtr screen = atlas_draw->pScreen;
76 GCPtr scratch_gc;
77 ChangeGCVal changes[2];
78
79 upload_pixmap = glamor_create_pixmap(screen,
80 glyph_draw->width,
81 glyph_draw->height,
82 atlas_draw->depth,
83 GLAMOR_CREATE_PIXMAP_CPU);
84 if (!upload_pixmap)
85 return;
86
87 scratch_gc = GetScratchGC(upload_pixmap->drawable.depth, screen);
88 if (!scratch_gc) {
89 glamor_destroy_pixmap(upload_pixmap);
90 return;
91 }
92 changes[0].val = 0xff;
93 changes[1].val = 0x00;
94 if (ChangeGC(NullClient, scratch_gc,
95 GCForeground|GCBackground, changes) != Success) {
96 glamor_destroy_pixmap(upload_pixmap);
97 FreeScratchGC(scratch_gc);
98 return;
99 }
100 ValidateGC(&upload_pixmap->drawable, scratch_gc);
101
102 (*scratch_gc->ops->CopyPlane)(glyph_draw,
103 &upload_pixmap->drawable,
104 scratch_gc,
105 0, 0,
106 glyph_draw->width,
107 glyph_draw->height,
108 0, 0, 0x1);
109 }
110 glamor_upload_boxes((PixmapPtr) atlas_draw,
111 &box, 1,
112 0, 0,
113 x, y,
114 upload_pixmap->devPrivate.ptr,
115 upload_pixmap->devKind);
116
117 if (upload_pixmap != glyph_pixmap)
118 glamor_destroy_pixmap(upload_pixmap);
119 }
120
121 static Bool
glamor_glyph_atlas_init(ScreenPtr screen,struct glamor_glyph_atlas * atlas)122 glamor_glyph_atlas_init(ScreenPtr screen, struct glamor_glyph_atlas *atlas)
123 {
124 glamor_screen_private *glamor_priv = glamor_get_screen_private(screen);
125 PictFormatPtr format = atlas->format;
126
127 atlas->atlas = glamor_create_pixmap(screen, glamor_priv->glyph_atlas_dim,
128 glamor_priv->glyph_atlas_dim, format->depth,
129 GLAMOR_CREATE_FBO_NO_FBO);
130 if (!glamor_pixmap_has_fbo(atlas->atlas)) {
131 glamor_destroy_pixmap(atlas->atlas);
132 atlas->atlas = NULL;
133 }
134 atlas->x = 0;
135 atlas->y = 0;
136 atlas->row_height = 0;
137 atlas->serial++;
138 atlas->nglyph = 0;
139 return TRUE;
140 }
141
142 static Bool
glamor_glyph_can_add(struct glamor_glyph_atlas * atlas,int dim,DrawablePtr glyph_draw)143 glamor_glyph_can_add(struct glamor_glyph_atlas *atlas, int dim, DrawablePtr glyph_draw)
144 {
145 /* Step down */
146 if (atlas->x + glyph_draw->width > dim) {
147 atlas->x = 0;
148 atlas->y += atlas->row_height;
149 atlas->row_height = 0;
150 }
151
152 /* Check for overfull */
153 if (atlas->y + glyph_draw->height > dim)
154 return FALSE;
155
156 return TRUE;
157 }
158
159 static Bool
glamor_glyph_add(struct glamor_glyph_atlas * atlas,DrawablePtr glyph_draw)160 glamor_glyph_add(struct glamor_glyph_atlas *atlas, DrawablePtr glyph_draw)
161 {
162 PixmapPtr glyph_pixmap = (PixmapPtr) glyph_draw;
163 struct glamor_glyph_private *glyph_priv = glamor_get_glyph_private(glyph_pixmap);
164
165 glamor_copy_glyph(glyph_pixmap, &atlas->atlas->drawable, atlas->x, atlas->y);
166
167 glyph_priv->x = atlas->x;
168 glyph_priv->y = atlas->y;
169 glyph_priv->serial = atlas->serial;
170
171 atlas->x += glyph_draw->width;
172 if (atlas->row_height < glyph_draw->height)
173 atlas->row_height = glyph_draw->height;
174
175 atlas->nglyph++;
176
177 return TRUE;
178 }
179
180 static const glamor_facet glamor_facet_composite_glyphs_130 = {
181 .name = "composite_glyphs",
182 .version = 130,
183 .vs_vars = ("attribute vec4 primitive;\n"
184 "attribute vec2 source;\n"
185 "varying vec2 glyph_pos;\n"),
186 .vs_exec = (" vec2 pos = primitive.zw * vec2(gl_VertexID&1, (gl_VertexID&2)>>1);\n"
187 GLAMOR_POS(gl_Position, (primitive.xy + pos))
188 " glyph_pos = (source + pos) * ATLAS_DIM_INV;\n"),
189 .fs_vars = ("varying vec2 glyph_pos;\n"
190 "out vec4 color0;\n"
191 "out vec4 color1;\n"),
192 .fs_exec = (" vec4 mask = texture2D(atlas, glyph_pos);\n"),
193 .source_name = "source",
194 .locations = glamor_program_location_atlas,
195 };
196
197 static const glamor_facet glamor_facet_composite_glyphs_120 = {
198 .name = "composite_glyphs",
199 .vs_vars = ("attribute vec2 primitive;\n"
200 "attribute vec2 source;\n"
201 "varying vec2 glyph_pos;\n"),
202 .vs_exec = (" vec2 pos = vec2(0,0);\n"
203 GLAMOR_POS(gl_Position, primitive.xy)
204 " glyph_pos = source.xy * ATLAS_DIM_INV;\n"),
205 .fs_vars = ("varying vec2 glyph_pos;\n"),
206 .fs_exec = (" vec4 mask = texture2D(atlas, glyph_pos);\n"),
207 .source_name = "source",
208 .locations = glamor_program_location_atlas,
209 };
210
211 static inline Bool
glamor_glyph_use_130(glamor_screen_private * glamor_priv)212 glamor_glyph_use_130(glamor_screen_private *glamor_priv) {
213 return glamor_priv->glsl_version >= 130;
214 }
215
216 static Bool
glamor_glyphs_init_facet(ScreenPtr screen)217 glamor_glyphs_init_facet(ScreenPtr screen)
218 {
219 glamor_screen_private *glamor_priv = glamor_get_screen_private(screen);
220
221 return asprintf(&glamor_priv->glyph_defines, "#define ATLAS_DIM_INV %20.18f\n", 1.0/glamor_priv->glyph_atlas_dim) > 0;
222 }
223
224 static void
glamor_glyphs_fini_facet(ScreenPtr screen)225 glamor_glyphs_fini_facet(ScreenPtr screen)
226 {
227 glamor_screen_private *glamor_priv = glamor_get_screen_private(screen);
228
229 free(glamor_priv->glyph_defines);
230 }
231
232 static void
glamor_glyphs_flush(CARD8 op,PicturePtr src,PicturePtr dst,glamor_program * prog,struct glamor_glyph_atlas * atlas,int nglyph)233 glamor_glyphs_flush(CARD8 op, PicturePtr src, PicturePtr dst,
234 glamor_program *prog,
235 struct glamor_glyph_atlas *atlas, int nglyph)
236 {
237 DrawablePtr drawable = dst->pDrawable;
238 glamor_screen_private *glamor_priv = glamor_get_screen_private(drawable->pScreen);
239 PixmapPtr atlas_pixmap = atlas->atlas;
240 glamor_pixmap_private *atlas_priv = glamor_get_pixmap_private(atlas_pixmap);
241 glamor_pixmap_fbo *atlas_fbo = glamor_pixmap_fbo_at(atlas_priv, 0);
242 PixmapPtr pixmap = glamor_get_drawable_pixmap(drawable);
243 glamor_pixmap_private *pixmap_priv = glamor_get_pixmap_private(pixmap);
244 int box_index;
245 int off_x, off_y;
246
247 glamor_put_vbo_space(drawable->pScreen);
248
249 glEnable(GL_SCISSOR_TEST);
250 glamor_bind_texture(glamor_priv, GL_TEXTURE1, atlas_fbo, FALSE);
251
252 for (;;) {
253 if (!glamor_use_program_render(prog, op, src, dst))
254 break;
255
256 glUniform1i(prog->atlas_uniform, 1);
257
258 glamor_pixmap_loop(pixmap_priv, box_index) {
259 BoxPtr box = RegionRects(dst->pCompositeClip);
260 int nbox = RegionNumRects(dst->pCompositeClip);
261
262 glamor_set_destination_drawable(drawable, box_index, TRUE, FALSE,
263 prog->matrix_uniform,
264 &off_x, &off_y);
265
266 /* Run over the clip list, drawing the glyphs
267 * in each box
268 */
269
270 while (nbox--) {
271 glScissor(box->x1 + off_x,
272 box->y1 + off_y,
273 box->x2 - box->x1,
274 box->y2 - box->y1);
275 box++;
276
277 if (glamor_glyph_use_130(glamor_priv))
278 glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, nglyph);
279 else
280 glamor_glDrawArrays_GL_QUADS(glamor_priv, nglyph);
281 }
282 }
283 if (prog->alpha != glamor_program_alpha_ca_first)
284 break;
285 prog++;
286 }
287
288 glDisable(GL_SCISSOR_TEST);
289
290 if (glamor_glyph_use_130(glamor_priv)) {
291 glVertexAttribDivisor(GLAMOR_VERTEX_SOURCE, 0);
292 glVertexAttribDivisor(GLAMOR_VERTEX_POS, 0);
293 }
294 glDisableVertexAttribArray(GLAMOR_VERTEX_SOURCE);
295 glDisableVertexAttribArray(GLAMOR_VERTEX_POS);
296 glDisable(GL_BLEND);
297
298 glamor_pixmap_invalid(pixmap);
299 }
300
301 static GLshort *
glamor_glyph_start(ScreenPtr screen,int count)302 glamor_glyph_start(ScreenPtr screen, int count)
303 {
304 glamor_screen_private *glamor_priv = glamor_get_screen_private(screen);
305 GLshort *v;
306 char *vbo_offset;
307
308 /* Set up the vertex buffers for the font and destination */
309
310 if (glamor_glyph_use_130(glamor_priv)) {
311 v = glamor_get_vbo_space(screen, count * (6 * sizeof (GLshort)), &vbo_offset);
312
313 glEnableVertexAttribArray(GLAMOR_VERTEX_POS);
314 glVertexAttribDivisor(GLAMOR_VERTEX_POS, 1);
315 glVertexAttribPointer(GLAMOR_VERTEX_POS, 4, GL_SHORT, GL_FALSE,
316 6 * sizeof (GLshort), vbo_offset);
317
318 glEnableVertexAttribArray(GLAMOR_VERTEX_SOURCE);
319 glVertexAttribDivisor(GLAMOR_VERTEX_SOURCE, 1);
320 glVertexAttribPointer(GLAMOR_VERTEX_SOURCE, 2, GL_SHORT, GL_FALSE,
321 6 * sizeof (GLshort), vbo_offset + 4 * sizeof (GLshort));
322 } else {
323 v = glamor_get_vbo_space(screen, count * (16 * sizeof (GLshort)), &vbo_offset);
324
325 glEnableVertexAttribArray(GLAMOR_VERTEX_POS);
326 glVertexAttribPointer(GLAMOR_VERTEX_POS, 2, GL_SHORT, GL_FALSE,
327 4 * sizeof (GLshort), vbo_offset);
328
329 glEnableVertexAttribArray(GLAMOR_VERTEX_SOURCE);
330 glVertexAttribPointer(GLAMOR_VERTEX_SOURCE, 2, GL_SHORT, GL_FALSE,
331 4 * sizeof (GLshort), vbo_offset + 2 * sizeof (GLshort));
332 }
333 return v;
334 }
335
336 static inline struct glamor_glyph_atlas *
glamor_atlas_for_glyph(glamor_screen_private * glamor_priv,DrawablePtr drawable)337 glamor_atlas_for_glyph(glamor_screen_private *glamor_priv, DrawablePtr drawable)
338 {
339 if (drawable->depth == 32)
340 return glamor_priv->glyph_atlas_argb;
341 else
342 return glamor_priv->glyph_atlas_a;
343 }
344
345 void
glamor_composite_glyphs(CARD8 op,PicturePtr src,PicturePtr dst,PictFormatPtr glyph_format,INT16 x_src,INT16 y_src,int nlist,GlyphListPtr list,GlyphPtr * glyphs)346 glamor_composite_glyphs(CARD8 op,
347 PicturePtr src,
348 PicturePtr dst,
349 PictFormatPtr glyph_format,
350 INT16 x_src,
351 INT16 y_src, int nlist, GlyphListPtr list,
352 GlyphPtr *glyphs)
353 {
354 int glyphs_queued;
355 GLshort *v = NULL;
356 DrawablePtr drawable = dst->pDrawable;
357 ScreenPtr screen = drawable->pScreen;
358 glamor_screen_private *glamor_priv = glamor_get_screen_private(screen);
359 glamor_program *prog = NULL;
360 glamor_program_render *glyphs_program = &glamor_priv->glyphs_program;
361 struct glamor_glyph_atlas *glyph_atlas = NULL;
362 int x = 0, y = 0;
363 int n;
364 int glyph_atlas_dim = glamor_priv->glyph_atlas_dim;
365 int glyph_max_dim = glamor_priv->glyph_max_dim;
366 int nglyph = 0;
367 int screen_num = screen->myNum;
368
369 for (n = 0; n < nlist; n++)
370 nglyph += list[n].len;
371
372 glamor_make_current(glamor_priv);
373
374 glyphs_queued = 0;
375
376 while (nlist--) {
377 x += list->xOff;
378 y += list->yOff;
379 n = list->len;
380 list++;
381 while (n--) {
382 GlyphPtr glyph = *glyphs++;
383
384 /* Glyph not empty?
385 */
386 if (glyph->info.width && glyph->info.height) {
387 PicturePtr glyph_pict = GlyphPicture(glyph)[screen_num];
388 DrawablePtr glyph_draw = glyph_pict->pDrawable;
389
390 /* Need to draw with slow path?
391 */
392 if (_X_UNLIKELY(glyph_draw->width > glyph_max_dim ||
393 glyph_draw->height > glyph_max_dim ||
394 !glamor_pixmap_is_memory((PixmapPtr)glyph_draw)))
395 {
396 if (glyphs_queued) {
397 glamor_glyphs_flush(op, src, dst, prog, glyph_atlas, glyphs_queued);
398 glyphs_queued = 0;
399 }
400 bail_one:
401 glamor_composite(op, src, glyph_pict, dst,
402 x_src + (x - glyph->info.x), (y - glyph->info.y),
403 0, 0,
404 x - glyph->info.x, y - glyph->info.y,
405 glyph_draw->width, glyph_draw->height);
406 } else {
407 struct glamor_glyph_private *glyph_priv = glamor_get_glyph_private((PixmapPtr)(glyph_draw));
408 struct glamor_glyph_atlas *next_atlas = glamor_atlas_for_glyph(glamor_priv, glyph_draw);
409
410 /* Switching source glyph format?
411 */
412 if (_X_UNLIKELY(next_atlas != glyph_atlas)) {
413 if (glyphs_queued) {
414 glamor_glyphs_flush(op, src, dst, prog, glyph_atlas, glyphs_queued);
415 glyphs_queued = 0;
416 }
417 glyph_atlas = next_atlas;
418 }
419
420 /* Glyph not cached in current atlas?
421 */
422 if (_X_UNLIKELY(glyph_priv->serial != glyph_atlas->serial)) {
423 if (!glamor_glyph_can_add(glyph_atlas, glyph_atlas_dim, glyph_draw)) {
424 if (glyphs_queued) {
425 glamor_glyphs_flush(op, src, dst, prog, glyph_atlas, glyphs_queued);
426 glyphs_queued = 0;
427 }
428 if (glyph_atlas->atlas) {
429 (*screen->DestroyPixmap)(glyph_atlas->atlas);
430 glyph_atlas->atlas = NULL;
431 }
432 }
433 if (!glyph_atlas->atlas) {
434 glamor_glyph_atlas_init(screen, glyph_atlas);
435 if (!glyph_atlas->atlas)
436 goto bail_one;
437 }
438 glamor_glyph_add(glyph_atlas, glyph_draw);
439 }
440
441 /* First glyph in the current atlas?
442 */
443 if (_X_UNLIKELY(glyphs_queued == 0)) {
444 if (glamor_glyph_use_130(glamor_priv))
445 prog = glamor_setup_program_render(op, src, glyph_pict, dst,
446 glyphs_program,
447 &glamor_facet_composite_glyphs_130,
448 glamor_priv->glyph_defines);
449 else
450 prog = glamor_setup_program_render(op, src, glyph_pict, dst,
451 glyphs_program,
452 &glamor_facet_composite_glyphs_120,
453 glamor_priv->glyph_defines);
454 if (!prog)
455 goto bail_one;
456 v = glamor_glyph_start(screen, nglyph);
457 }
458
459 /* Add the glyph
460 */
461
462 glyphs_queued++;
463 if (_X_LIKELY(glamor_glyph_use_130(glamor_priv))) {
464 v[0] = x - glyph->info.x;
465 v[1] = y - glyph->info.y;
466 v[2] = glyph_draw->width;
467 v[3] = glyph_draw->height;
468 v[4] = glyph_priv->x;
469 v[5] = glyph_priv->y;
470 v += 6;
471 } else {
472 v[0] = x - glyph->info.x;
473 v[1] = y - glyph->info.y;
474 v[2] = glyph_priv->x;
475 v[3] = glyph_priv->y;
476 v += 4;
477
478 v[0] = x - glyph->info.x + glyph_draw->width;
479 v[1] = y - glyph->info.y;
480 v[2] = glyph_priv->x + glyph_draw->width;
481 v[3] = glyph_priv->y;
482 v += 4;
483
484 v[0] = x - glyph->info.x + glyph_draw->width;
485 v[1] = y - glyph->info.y + glyph_draw->height;
486 v[2] = glyph_priv->x + glyph_draw->width;
487 v[3] = glyph_priv->y + glyph_draw->height;
488 v += 4;
489
490 v[0] = x - glyph->info.x;
491 v[1] = y - glyph->info.y + glyph_draw->height;
492 v[2] = glyph_priv->x;
493 v[3] = glyph_priv->y + glyph_draw->height;
494 v += 4;
495 }
496 }
497 }
498 x += glyph->info.xOff;
499 y += glyph->info.yOff;
500 nglyph--;
501 }
502 }
503
504 if (glyphs_queued)
505 glamor_glyphs_flush(op, src, dst, prog, glyph_atlas, glyphs_queued);
506
507 return;
508 }
509
510 static struct glamor_glyph_atlas *
glamor_alloc_glyph_atlas(ScreenPtr screen,int depth,CARD32 f)511 glamor_alloc_glyph_atlas(ScreenPtr screen, int depth, CARD32 f)
512 {
513 PictFormatPtr format;
514 struct glamor_glyph_atlas *glyph_atlas;
515
516 format = PictureMatchFormat(screen, depth, f);
517 if (!format)
518 return NULL;
519 glyph_atlas = calloc (1, sizeof (struct glamor_glyph_atlas));
520 if (!glyph_atlas)
521 return NULL;
522 glyph_atlas->format = format;
523 glyph_atlas->serial = 1;
524
525 return glyph_atlas;
526 }
527
528 Bool
glamor_composite_glyphs_init(ScreenPtr screen)529 glamor_composite_glyphs_init(ScreenPtr screen)
530 {
531 glamor_screen_private *glamor_priv = glamor_get_screen_private(screen);
532
533 if (!dixRegisterPrivateKey(&glamor_glyph_private_key, PRIVATE_PIXMAP, sizeof (struct glamor_glyph_private)))
534 return FALSE;
535
536 /* Make glyph atlases of a reasonable size, but no larger than the maximum
537 * supported by the hardware
538 */
539 glamor_priv->glyph_atlas_dim = MIN(DEFAULT_ATLAS_DIM, glamor_priv->max_fbo_size);
540
541 /* Don't stick huge glyphs in the atlases */
542 glamor_priv->glyph_max_dim = glamor_priv->glyph_atlas_dim / 8;
543
544 glamor_priv->glyph_atlas_a = glamor_alloc_glyph_atlas(screen, 8, PICT_a8);
545 if (!glamor_priv->glyph_atlas_a)
546 return FALSE;
547 glamor_priv->glyph_atlas_argb = glamor_alloc_glyph_atlas(screen, 32, PICT_a8r8g8b8);
548 if (!glamor_priv->glyph_atlas_argb) {
549 free (glamor_priv->glyph_atlas_a);
550 return FALSE;
551 }
552 if (!glamor_glyphs_init_facet(screen))
553 return FALSE;
554 return TRUE;
555 }
556
557 static void
glamor_free_glyph_atlas(struct glamor_glyph_atlas * atlas)558 glamor_free_glyph_atlas(struct glamor_glyph_atlas *atlas)
559 {
560 if (!atlas)
561 return;
562 if (atlas->atlas)
563 (*atlas->atlas->drawable.pScreen->DestroyPixmap)(atlas->atlas);
564 free (atlas);
565 }
566
567 void
glamor_composite_glyphs_fini(ScreenPtr screen)568 glamor_composite_glyphs_fini(ScreenPtr screen)
569 {
570 glamor_screen_private *glamor_priv = glamor_get_screen_private(screen);
571
572 glamor_glyphs_fini_facet(screen);
573 glamor_free_glyph_atlas(glamor_priv->glyph_atlas_a);
574 glamor_free_glyph_atlas(glamor_priv->glyph_atlas_argb);
575 }
576