Home | History | Annotate | Download | only in softpipe
      1 /**************************************************************************
      2  *
      3  * Copyright 2007 Tungsten Graphics, Inc., Cedar Park, Texas.
      4  * All Rights Reserved.
      5  *
      6  * Permission is hereby granted, free of charge, to any person obtaining a
      7  * copy of this software and associated documentation files (the
      8  * "Software"), to deal in the Software without restriction, including
      9  * without limitation the rights to use, copy, modify, merge, publish,
     10  * distribute, sub license, and/or sell copies of the Software, and to
     11  * permit persons to whom the Software is furnished to do so, subject to
     12  * the following conditions:
     13  *
     14  * The above copyright notice and this permission notice (including the
     15  * next paragraph) shall be included in all copies or substantial portions
     16  * of the Software.
     17  *
     18  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
     19  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
     20  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
     21  * IN NO EVENT SHALL TUNGSTEN GRAPHICS AND/OR ITS SUPPLIERS BE LIABLE FOR
     22  * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
     23  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
     24  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
     25  *
     26  **************************************************************************/
     27 
     28 /**
     29  * Render target tile caching.
     30  *
     31  * Author:
     32  *    Brian Paul
     33  */
     34 
     35 #include "util/u_inlines.h"
     36 #include "util/u_format.h"
     37 #include "util/u_memory.h"
     38 #include "util/u_tile.h"
     39 #include "sp_tile_cache.h"
     40 
     41 static struct softpipe_cached_tile *
     42 sp_alloc_tile(struct softpipe_tile_cache *tc);
     43 
     44 
     45 /**
     46  * Return the position in the cache for the tile that contains win pos (x,y).
     47  * We currently use a direct mapped cache so this is like a hack key.
     48  * At some point we should investige something more sophisticated, like
     49  * a LRU replacement policy.
     50  */
     51 #define CACHE_POS(x, y) \
     52    (((x) + (y) * 5) % NUM_ENTRIES)
     53 
     54 
     55 
     56 /**
     57  * Is the tile at (x,y) in cleared state?
     58  */
     59 static INLINE uint
     60 is_clear_flag_set(const uint *bitvec, union tile_address addr)
     61 {
     62    int pos, bit;
     63    pos = addr.bits.y * (MAX_WIDTH / TILE_SIZE) + addr.bits.x;
     64    assert(pos / 32 < (MAX_WIDTH / TILE_SIZE) * (MAX_HEIGHT / TILE_SIZE) / 32);
     65    bit = bitvec[pos / 32] & (1 << (pos & 31));
     66    return bit;
     67 }
     68 
     69 
     70 /**
     71  * Mark the tile at (x,y) as not cleared.
     72  */
     73 static INLINE void
     74 clear_clear_flag(uint *bitvec, union tile_address addr)
     75 {
     76    int pos;
     77    pos = addr.bits.y * (MAX_WIDTH / TILE_SIZE) + addr.bits.x;
     78    assert(pos / 32 < (MAX_WIDTH / TILE_SIZE) * (MAX_HEIGHT / TILE_SIZE) / 32);
     79    bitvec[pos / 32] &= ~(1 << (pos & 31));
     80 }
     81 
     82 
     83 struct softpipe_tile_cache *
     84 sp_create_tile_cache( struct pipe_context *pipe )
     85 {
     86    struct softpipe_tile_cache *tc;
     87    uint pos;
     88    int maxLevels, maxTexSize;
     89 
     90    /* sanity checking: max sure MAX_WIDTH/HEIGHT >= largest texture image */
     91    maxLevels = pipe->screen->get_param(pipe->screen, PIPE_CAP_MAX_TEXTURE_2D_LEVELS);
     92    maxTexSize = 1 << (maxLevels - 1);
     93    assert(MAX_WIDTH >= maxTexSize);
     94 
     95    assert(sizeof(union tile_address) == 4);
     96 
     97    assert((TILE_SIZE << TILE_ADDR_BITS) >= MAX_WIDTH);
     98 
     99    tc = CALLOC_STRUCT( softpipe_tile_cache );
    100    if (tc) {
    101       tc->pipe = pipe;
    102       for (pos = 0; pos < NUM_ENTRIES; pos++) {
    103          tc->tile_addrs[pos].bits.invalid = 1;
    104       }
    105       tc->last_tile_addr.bits.invalid = 1;
    106 
    107       /* this allocation allows us to guarantee that allocation
    108        * failures are never fatal later
    109        */
    110       tc->tile = MALLOC_STRUCT( softpipe_cached_tile );
    111       if (!tc->tile)
    112       {
    113          FREE(tc);
    114          return NULL;
    115       }
    116 
    117       /* XXX this code prevents valgrind warnings about use of uninitialized
    118        * memory in programs that don't clear the surface before rendering.
    119        * However, it breaks clearing in other situations (such as in
    120        * progs/tests/drawbuffers, see bug 24402).
    121        */
    122 #if 0
    123       /* set flags to indicate all the tiles are cleared */
    124       memset(tc->clear_flags, 255, sizeof(tc->clear_flags));
    125 #endif
    126    }
    127    return tc;
    128 }
    129 
    130 
    131 void
    132 sp_destroy_tile_cache(struct softpipe_tile_cache *tc)
    133 {
    134    if (tc) {
    135       uint pos;
    136 
    137       for (pos = 0; pos < NUM_ENTRIES; pos++) {
    138          /*assert(tc->entries[pos].x < 0);*/
    139          FREE( tc->entries[pos] );
    140       }
    141       FREE( tc->tile );
    142 
    143       if (tc->transfer) {
    144          tc->pipe->transfer_destroy(tc->pipe, tc->transfer);
    145       }
    146 
    147       FREE( tc );
    148    }
    149 }
    150 
    151 
    152 /**
    153  * Specify the surface to cache.
    154  */
    155 void
    156 sp_tile_cache_set_surface(struct softpipe_tile_cache *tc,
    157                           struct pipe_surface *ps)
    158 {
    159    struct pipe_context *pipe = tc->pipe;
    160 
    161    if (tc->transfer) {
    162       if (ps == tc->surface)
    163          return;
    164 
    165       if (tc->transfer_map) {
    166          pipe->transfer_unmap(pipe, tc->transfer);
    167          tc->transfer_map = NULL;
    168       }
    169 
    170       pipe->transfer_destroy(pipe, tc->transfer);
    171       tc->transfer = NULL;
    172    }
    173 
    174    tc->surface = ps;
    175 
    176    if (ps) {
    177       tc->transfer = pipe_get_transfer(pipe, ps->texture,
    178                                        ps->u.tex.level, ps->u.tex.first_layer,
    179                                        PIPE_TRANSFER_READ_WRITE |
    180                                        PIPE_TRANSFER_UNSYNCHRONIZED,
    181                                        0, 0, ps->width, ps->height);
    182 
    183       tc->depth_stencil = util_format_is_depth_or_stencil(ps->format);
    184    }
    185 }
    186 
    187 
    188 /**
    189  * Return the transfer being cached.
    190  */
    191 struct pipe_surface *
    192 sp_tile_cache_get_surface(struct softpipe_tile_cache *tc)
    193 {
    194    return tc->surface;
    195 }
    196 
    197 
    198 void
    199 sp_tile_cache_map_transfers(struct softpipe_tile_cache *tc)
    200 {
    201    if (tc->transfer && !tc->transfer_map)
    202       tc->transfer_map = tc->pipe->transfer_map(tc->pipe, tc->transfer);
    203 }
    204 
    205 
    206 void
    207 sp_tile_cache_unmap_transfers(struct softpipe_tile_cache *tc)
    208 {
    209    if (tc->transfer_map) {
    210       tc->pipe->transfer_unmap(tc->pipe, tc->transfer);
    211       tc->transfer_map = NULL;
    212    }
    213 }
    214 
    215 
    216 /**
    217  * Set pixels in a tile to the given clear color/value, float.
    218  */
    219 static void
    220 clear_tile_rgba(struct softpipe_cached_tile *tile,
    221                 enum pipe_format format,
    222                 const union pipe_color_union *clear_value)
    223 {
    224    if (clear_value->f[0] == 0.0 &&
    225        clear_value->f[1] == 0.0 &&
    226        clear_value->f[2] == 0.0 &&
    227        clear_value->f[3] == 0.0) {
    228       memset(tile->data.color, 0, sizeof(tile->data.color));
    229    }
    230    else {
    231       uint i, j;
    232 
    233       if (util_format_is_pure_uint(format)) {
    234          for (i = 0; i < TILE_SIZE; i++) {
    235             for (j = 0; j < TILE_SIZE; j++) {
    236                tile->data.colorui128[i][j][0] = clear_value->ui[0];
    237                tile->data.colorui128[i][j][1] = clear_value->ui[1];
    238                tile->data.colorui128[i][j][2] = clear_value->ui[2];
    239                tile->data.colorui128[i][j][3] = clear_value->ui[3];
    240             }
    241          }
    242       } else if (util_format_is_pure_sint(format)) {
    243          for (i = 0; i < TILE_SIZE; i++) {
    244             for (j = 0; j < TILE_SIZE; j++) {
    245                tile->data.colori128[i][j][0] = clear_value->i[0];
    246                tile->data.colori128[i][j][1] = clear_value->i[1];
    247                tile->data.colori128[i][j][2] = clear_value->i[2];
    248                tile->data.colori128[i][j][3] = clear_value->i[3];
    249             }
    250          }
    251       } else {
    252          for (i = 0; i < TILE_SIZE; i++) {
    253             for (j = 0; j < TILE_SIZE; j++) {
    254                tile->data.color[i][j][0] = clear_value->f[0];
    255                tile->data.color[i][j][1] = clear_value->f[1];
    256                tile->data.color[i][j][2] = clear_value->f[2];
    257                tile->data.color[i][j][3] = clear_value->f[3];
    258             }
    259          }
    260       }
    261    }
    262 }
    263 
    264 
    265 /**
    266  * Set a tile to a solid value/color.
    267  */
    268 static void
    269 clear_tile(struct softpipe_cached_tile *tile,
    270            enum pipe_format format,
    271            uint64_t clear_value)
    272 {
    273    uint i, j;
    274 
    275    switch (util_format_get_blocksize(format)) {
    276    case 1:
    277       memset(tile->data.any, clear_value, TILE_SIZE * TILE_SIZE);
    278       break;
    279    case 2:
    280       if (clear_value == 0) {
    281          memset(tile->data.any, 0, 2 * TILE_SIZE * TILE_SIZE);
    282       }
    283       else {
    284          for (i = 0; i < TILE_SIZE; i++) {
    285             for (j = 0; j < TILE_SIZE; j++) {
    286                tile->data.depth16[i][j] = (ushort) clear_value;
    287             }
    288          }
    289       }
    290       break;
    291    case 4:
    292       if (clear_value == 0) {
    293          memset(tile->data.any, 0, 4 * TILE_SIZE * TILE_SIZE);
    294       }
    295       else {
    296          for (i = 0; i < TILE_SIZE; i++) {
    297             for (j = 0; j < TILE_SIZE; j++) {
    298                tile->data.depth32[i][j] = clear_value;
    299             }
    300          }
    301       }
    302       break;
    303    case 8:
    304       if (clear_value == 0) {
    305          memset(tile->data.any, 0, 8 * TILE_SIZE * TILE_SIZE);
    306       }
    307       else {
    308          for (i = 0; i < TILE_SIZE; i++) {
    309             for (j = 0; j < TILE_SIZE; j++) {
    310                tile->data.depth64[i][j] = clear_value;
    311             }
    312          }
    313       }
    314       break;
    315    default:
    316       assert(0);
    317    }
    318 }
    319 
    320 
    321 /**
    322  * Actually clear the tiles which were flagged as being in a clear state.
    323  */
    324 static void
    325 sp_tile_cache_flush_clear(struct softpipe_tile_cache *tc)
    326 {
    327    struct pipe_transfer *pt = tc->transfer;
    328    const uint w = tc->transfer->box.width;
    329    const uint h = tc->transfer->box.height;
    330    uint x, y;
    331    uint numCleared = 0;
    332 
    333    assert(pt->resource);
    334    if (!tc->tile)
    335       tc->tile = sp_alloc_tile(tc);
    336 
    337    /* clear the scratch tile to the clear value */
    338    if (tc->depth_stencil) {
    339       clear_tile(tc->tile, pt->resource->format, tc->clear_val);
    340    } else {
    341       clear_tile_rgba(tc->tile, pt->resource->format, &tc->clear_color);
    342    }
    343 
    344    /* push the tile to all positions marked as clear */
    345    for (y = 0; y < h; y += TILE_SIZE) {
    346       for (x = 0; x < w; x += TILE_SIZE) {
    347          union tile_address addr = tile_address(x, y);
    348 
    349          if (is_clear_flag_set(tc->clear_flags, addr)) {
    350             /* write the scratch tile to the surface */
    351             if (tc->depth_stencil) {
    352                pipe_put_tile_raw(tc->pipe,
    353                                  pt,
    354                                  x, y, TILE_SIZE, TILE_SIZE,
    355                                  tc->tile->data.any, 0/*STRIDE*/);
    356             }
    357             else {
    358                if (util_format_is_pure_uint(tc->surface->format)) {
    359                   pipe_put_tile_ui_format(tc->pipe, pt,
    360                                           x, y, TILE_SIZE, TILE_SIZE,
    361                                           pt->resource->format,
    362                                           (unsigned *) tc->tile->data.colorui128);
    363                } else if (util_format_is_pure_sint(tc->surface->format)) {
    364                   pipe_put_tile_i_format(tc->pipe, pt,
    365                                          x, y, TILE_SIZE, TILE_SIZE,
    366                                          pt->resource->format,
    367                                          (int *) tc->tile->data.colori128);
    368                } else {
    369                   pipe_put_tile_rgba(tc->pipe, pt,
    370                                      x, y, TILE_SIZE, TILE_SIZE,
    371                                      (float *) tc->tile->data.color);
    372                }
    373             }
    374             numCleared++;
    375          }
    376       }
    377    }
    378 
    379    /* reset all clear flags to zero */
    380    memset(tc->clear_flags, 0, sizeof(tc->clear_flags));
    381 
    382 #if 0
    383    debug_printf("num cleared: %u\n", numCleared);
    384 #endif
    385 }
    386 
    387 static void
    388 sp_flush_tile(struct softpipe_tile_cache* tc, unsigned pos)
    389 {
    390    if (!tc->tile_addrs[pos].bits.invalid) {
    391       if (tc->depth_stencil) {
    392          pipe_put_tile_raw(tc->pipe, tc->transfer,
    393                            tc->tile_addrs[pos].bits.x * TILE_SIZE,
    394                            tc->tile_addrs[pos].bits.y * TILE_SIZE,
    395                            TILE_SIZE, TILE_SIZE,
    396                            tc->entries[pos]->data.depth32, 0/*STRIDE*/);
    397       }
    398       else {
    399          if (util_format_is_pure_uint(tc->surface->format)) {
    400             pipe_put_tile_ui_format(tc->pipe, tc->transfer,
    401                                     tc->tile_addrs[pos].bits.x * TILE_SIZE,
    402                                     tc->tile_addrs[pos].bits.y * TILE_SIZE,
    403                                     TILE_SIZE, TILE_SIZE,
    404                                     tc->surface->format,
    405                                     (unsigned *) tc->entries[pos]->data.colorui128);
    406          } else if (util_format_is_pure_sint(tc->surface->format)) {
    407             pipe_put_tile_i_format(tc->pipe, tc->transfer,
    408                                    tc->tile_addrs[pos].bits.x * TILE_SIZE,
    409                                    tc->tile_addrs[pos].bits.y * TILE_SIZE,
    410                                    TILE_SIZE, TILE_SIZE,
    411                                    tc->surface->format,
    412                                    (int *) tc->entries[pos]->data.colori128);
    413          } else {
    414             pipe_put_tile_rgba_format(tc->pipe, tc->transfer,
    415                                       tc->tile_addrs[pos].bits.x * TILE_SIZE,
    416                                       tc->tile_addrs[pos].bits.y * TILE_SIZE,
    417                                       TILE_SIZE, TILE_SIZE,
    418                                       tc->surface->format,
    419                                       (float *) tc->entries[pos]->data.color);
    420          }
    421       }
    422       tc->tile_addrs[pos].bits.invalid = 1;  /* mark as empty */
    423    }
    424 }
    425 
    426 /**
    427  * Flush the tile cache: write all dirty tiles back to the transfer.
    428  * any tiles "flagged" as cleared will be "really" cleared.
    429  */
    430 void
    431 sp_flush_tile_cache(struct softpipe_tile_cache *tc)
    432 {
    433    struct pipe_transfer *pt = tc->transfer;
    434    int inuse = 0, pos;
    435 
    436    if (pt) {
    437       /* caching a drawing transfer */
    438       for (pos = 0; pos < NUM_ENTRIES; pos++) {
    439          struct softpipe_cached_tile *tile = tc->entries[pos];
    440          if (!tile)
    441          {
    442             assert(tc->tile_addrs[pos].bits.invalid);
    443             continue;
    444          }
    445 
    446          sp_flush_tile(tc, pos);
    447          ++inuse;
    448       }
    449 
    450       sp_tile_cache_flush_clear(tc);
    451 
    452 
    453       tc->last_tile_addr.bits.invalid = 1;
    454    }
    455 
    456 #if 0
    457    debug_printf("flushed tiles in use: %d\n", inuse);
    458 #endif
    459 }
    460 
    461 static struct softpipe_cached_tile *
    462 sp_alloc_tile(struct softpipe_tile_cache *tc)
    463 {
    464    struct softpipe_cached_tile * tile = MALLOC_STRUCT(softpipe_cached_tile);
    465    if (!tile)
    466    {
    467       /* in this case, steal an existing tile */
    468       if (!tc->tile)
    469       {
    470          unsigned pos;
    471          for (pos = 0; pos < NUM_ENTRIES; ++pos) {
    472             if (!tc->entries[pos])
    473                continue;
    474 
    475             sp_flush_tile(tc, pos);
    476             tc->tile = tc->entries[pos];
    477             tc->entries[pos] = NULL;
    478             break;
    479          }
    480 
    481          /* this should never happen */
    482          if (!tc->tile)
    483             abort();
    484       }
    485 
    486       tile = tc->tile;
    487       tc->tile = NULL;
    488 
    489       tc->last_tile_addr.bits.invalid = 1;
    490    }
    491    return tile;
    492 }
    493 
    494 /**
    495  * Get a tile from the cache.
    496  * \param x, y  position of tile, in pixels
    497  */
    498 struct softpipe_cached_tile *
    499 sp_find_cached_tile(struct softpipe_tile_cache *tc,
    500                     union tile_address addr )
    501 {
    502    struct pipe_transfer *pt = tc->transfer;
    503    /* cache pos/entry: */
    504    const int pos = CACHE_POS(addr.bits.x,
    505                              addr.bits.y);
    506    struct softpipe_cached_tile *tile = tc->entries[pos];
    507 
    508    if (!tile) {
    509       tile = sp_alloc_tile(tc);
    510       tc->entries[pos] = tile;
    511    }
    512 
    513    if (addr.value != tc->tile_addrs[pos].value) {
    514 
    515       assert(pt->resource);
    516       if (tc->tile_addrs[pos].bits.invalid == 0) {
    517          /* put dirty tile back in framebuffer */
    518          if (tc->depth_stencil) {
    519             pipe_put_tile_raw(tc->pipe, pt,
    520                               tc->tile_addrs[pos].bits.x * TILE_SIZE,
    521                               tc->tile_addrs[pos].bits.y * TILE_SIZE,
    522                               TILE_SIZE, TILE_SIZE,
    523                               tile->data.depth32, 0/*STRIDE*/);
    524          }
    525          else {
    526             if (util_format_is_pure_uint(tc->surface->format)) {
    527                pipe_put_tile_ui_format(tc->pipe, pt,
    528                                       tc->tile_addrs[pos].bits.x * TILE_SIZE,
    529                                       tc->tile_addrs[pos].bits.y * TILE_SIZE,
    530                                       TILE_SIZE, TILE_SIZE,
    531                                       tc->surface->format,
    532                                       (unsigned *) tile->data.colorui128);
    533             } else if (util_format_is_pure_sint(tc->surface->format)) {
    534                pipe_put_tile_i_format(tc->pipe, pt,
    535                                       tc->tile_addrs[pos].bits.x * TILE_SIZE,
    536                                       tc->tile_addrs[pos].bits.y * TILE_SIZE,
    537                                       TILE_SIZE, TILE_SIZE,
    538                                       tc->surface->format,
    539                                       (int *) tile->data.colori128);
    540             } else {
    541                pipe_put_tile_rgba_format(tc->pipe, pt,
    542                                          tc->tile_addrs[pos].bits.x * TILE_SIZE,
    543                                          tc->tile_addrs[pos].bits.y * TILE_SIZE,
    544                                          TILE_SIZE, TILE_SIZE,
    545                                          tc->surface->format,
    546                                          (float *) tile->data.color);
    547             }
    548          }
    549       }
    550 
    551       tc->tile_addrs[pos] = addr;
    552 
    553       if (is_clear_flag_set(tc->clear_flags, addr)) {
    554          /* don't get tile from framebuffer, just clear it */
    555          if (tc->depth_stencil) {
    556             clear_tile(tile, pt->resource->format, tc->clear_val);
    557          }
    558          else {
    559             clear_tile_rgba(tile, pt->resource->format, &tc->clear_color);
    560          }
    561          clear_clear_flag(tc->clear_flags, addr);
    562       }
    563       else {
    564          /* get new tile data from transfer */
    565          if (tc->depth_stencil) {
    566             pipe_get_tile_raw(tc->pipe, pt,
    567                               tc->tile_addrs[pos].bits.x * TILE_SIZE,
    568                               tc->tile_addrs[pos].bits.y * TILE_SIZE,
    569                               TILE_SIZE, TILE_SIZE,
    570                               tile->data.depth32, 0/*STRIDE*/);
    571          }
    572          else {
    573             if (util_format_is_pure_uint(tc->surface->format)) {
    574                pipe_get_tile_ui_format(tc->pipe, pt,
    575                                          tc->tile_addrs[pos].bits.x * TILE_SIZE,
    576                                          tc->tile_addrs[pos].bits.y * TILE_SIZE,
    577                                          TILE_SIZE, TILE_SIZE,
    578                                          tc->surface->format,
    579                                          (unsigned *) tile->data.colorui128);
    580             } else if (util_format_is_pure_sint(tc->surface->format)) {
    581                pipe_get_tile_i_format(tc->pipe, pt,
    582                                          tc->tile_addrs[pos].bits.x * TILE_SIZE,
    583                                          tc->tile_addrs[pos].bits.y * TILE_SIZE,
    584                                          TILE_SIZE, TILE_SIZE,
    585                                          tc->surface->format,
    586                                          (int *) tile->data.colori128);
    587             } else {
    588                pipe_get_tile_rgba_format(tc->pipe, pt,
    589                                          tc->tile_addrs[pos].bits.x * TILE_SIZE,
    590                                          tc->tile_addrs[pos].bits.y * TILE_SIZE,
    591                                          TILE_SIZE, TILE_SIZE,
    592                                          tc->surface->format,
    593                                          (float *) tile->data.color);
    594             }
    595          }
    596       }
    597    }
    598 
    599    tc->last_tile = tile;
    600    tc->last_tile_addr = addr;
    601    return tile;
    602 }
    603 
    604 
    605 
    606 
    607 
    608 /**
    609  * When a whole surface is being cleared to a value we can avoid
    610  * fetching tiles above.
    611  * Save the color and set a 'clearflag' for each tile of the screen.
    612  */
    613 void
    614 sp_tile_cache_clear(struct softpipe_tile_cache *tc,
    615                     const union pipe_color_union *color,
    616                     uint64_t clearValue)
    617 {
    618    uint pos;
    619 
    620    tc->clear_color = *color;
    621 
    622    tc->clear_val = clearValue;
    623 
    624    /* set flags to indicate all the tiles are cleared */
    625    memset(tc->clear_flags, 255, sizeof(tc->clear_flags));
    626 
    627    for (pos = 0; pos < NUM_ENTRIES; pos++) {
    628       tc->tile_addrs[pos].bits.invalid = 1;
    629    }
    630    tc->last_tile_addr.bits.invalid = 1;
    631 }
    632