Home | History | Annotate | Download | only in state_tracker
      1 /**************************************************************************
      2  *
      3  * Copyright 2007 Tungsten Graphics, Inc., Cedar Park, Texas.
      4  * All Rights Reserved.
      5  * Copyright 2009 VMware, Inc.  All Rights Reserved.
      6  *
      7  * Permission is hereby granted, free of charge, to any person obtaining a
      8  * copy of this software and associated documentation files (the
      9  * "Software"), to deal in the Software without restriction, including
     10  * without limitation the rights to use, copy, modify, merge, publish,
     11  * distribute, sub license, and/or sell copies of the Software, and to
     12  * permit persons to whom the Software is furnished to do so, subject to
     13  * the following conditions:
     14  *
     15  * The above copyright notice and this permission notice (including the
     16  * next paragraph) shall be included in all copies or substantial portions
     17  * of the Software.
     18  *
     19  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
     20  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
     21  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
     22  * IN NO EVENT SHALL TUNGSTEN GRAPHICS AND/OR ITS SUPPLIERS BE LIABLE FOR
     23  * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
     24  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
     25  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
     26  *
     27  **************************************************************************/
     28 
     29  /*
     30   * Authors:
     31   *   Keith Whitwell <keith (at) tungstengraphics.com>
     32   *   Brian Paul
     33   *   Michel Dnzer
     34   */
     35 
     36 #include "main/glheader.h"
     37 #include "main/accum.h"
     38 #include "main/formats.h"
     39 #include "main/macros.h"
     40 #include "main/glformats.h"
     41 #include "program/prog_instruction.h"
     42 #include "st_context.h"
     43 #include "st_atom.h"
     44 #include "st_cb_clear.h"
     45 #include "st_cb_fbo.h"
     46 #include "st_format.h"
     47 #include "st_program.h"
     48 
     49 #include "pipe/p_context.h"
     50 #include "pipe/p_shader_tokens.h"
     51 #include "pipe/p_state.h"
     52 #include "pipe/p_defines.h"
     53 #include "util/u_format.h"
     54 #include "util/u_inlines.h"
     55 #include "util/u_simple_shaders.h"
     56 #include "util/u_draw_quad.h"
     57 #include "util/u_upload_mgr.h"
     58 
     59 #include "cso_cache/cso_context.h"
     60 
     61 
     62 /**
     63  * Do per-context initialization for glClear.
     64  */
     65 void
     66 st_init_clear(struct st_context *st)
     67 {
     68    struct pipe_screen *pscreen = st->pipe->screen;
     69 
     70    memset(&st->clear, 0, sizeof(st->clear));
     71 
     72    st->clear.raster.gl_rasterization_rules = 1;
     73    st->clear.raster.depth_clip = 1;
     74    st->clear.enable_ds_separate = pscreen->get_param(pscreen, PIPE_CAP_DEPTHSTENCIL_CLEAR_SEPARATE);
     75 }
     76 
     77 
     78 /**
     79  * Free per-context state for glClear.
     80  */
     81 void
     82 st_destroy_clear(struct st_context *st)
     83 {
     84    if (st->clear.fs) {
     85       cso_delete_fragment_shader(st->cso_context, st->clear.fs);
     86       st->clear.fs = NULL;
     87    }
     88    if (st->clear.vs) {
     89       cso_delete_vertex_shader(st->cso_context, st->clear.vs);
     90       st->clear.vs = NULL;
     91    }
     92 }
     93 
     94 
     95 /**
     96  * Helper function to set the fragment shaders.
     97  */
     98 static INLINE void
     99 set_fragment_shader(struct st_context *st)
    100 {
    101    if (!st->clear.fs)
    102       st->clear.fs = util_make_fragment_passthrough_shader(st->pipe);
    103 
    104    cso_set_fragment_shader_handle(st->cso_context, st->clear.fs);
    105 }
    106 
    107 
    108 /**
    109  * Helper function to set the vertex shader.
    110  */
    111 static INLINE void
    112 set_vertex_shader(struct st_context *st)
    113 {
    114    /* vertex shader - still required to provide the linkage between
    115     * fragment shader input semantics and vertex_element/buffers.
    116     */
    117    if (!st->clear.vs)
    118    {
    119       const uint semantic_names[] = { TGSI_SEMANTIC_POSITION,
    120                                       TGSI_SEMANTIC_COLOR };
    121       const uint semantic_indexes[] = { 0, 0 };
    122       st->clear.vs = util_make_vertex_passthrough_shader(st->pipe, 2,
    123                                                          semantic_names,
    124                                                          semantic_indexes);
    125    }
    126 
    127    cso_set_vertex_shader_handle(st->cso_context, st->clear.vs);
    128 }
    129 
    130 
    131 /**
    132  * Draw a screen-aligned quadrilateral.
    133  * Coords are clip coords with y=0=bottom.
    134  */
    135 static void
    136 draw_quad(struct st_context *st,
    137           float x0, float y0, float x1, float y1, GLfloat z,
    138           const union pipe_color_union *color)
    139 {
    140    struct pipe_context *pipe = st->pipe;
    141    struct pipe_resource *vbuf = NULL;
    142    GLuint i, offset;
    143    float (*vertices)[2][4];  /**< vertex pos + color */
    144 
    145    if (u_upload_alloc(st->uploader, 0, 4 * sizeof(vertices[0]),
    146                       &offset, &vbuf, (void **) &vertices) != PIPE_OK) {
    147       return;
    148    }
    149 
    150    /* positions */
    151    vertices[0][0][0] = x0;
    152    vertices[0][0][1] = y0;
    153 
    154    vertices[1][0][0] = x1;
    155    vertices[1][0][1] = y0;
    156 
    157    vertices[2][0][0] = x1;
    158    vertices[2][0][1] = y1;
    159 
    160    vertices[3][0][0] = x0;
    161    vertices[3][0][1] = y1;
    162 
    163    /* same for all verts: */
    164    for (i = 0; i < 4; i++) {
    165       vertices[i][0][2] = z;
    166       vertices[i][0][3] = 1.0;
    167       vertices[i][1][0] = color->f[0];
    168       vertices[i][1][1] = color->f[1];
    169       vertices[i][1][2] = color->f[2];
    170       vertices[i][1][3] = color->f[3];
    171    }
    172 
    173    u_upload_unmap(st->uploader);
    174 
    175    /* draw */
    176    util_draw_vertex_buffer(pipe,
    177                            st->cso_context,
    178                            vbuf, offset,
    179                            PIPE_PRIM_TRIANGLE_FAN,
    180                            4,  /* verts */
    181                            2); /* attribs/vert */
    182 
    183    pipe_resource_reference(&vbuf, NULL);
    184 }
    185 
    186 
    187 
    188 /**
    189  * Do glClear by drawing a quadrilateral.
    190  * The vertices of the quad will be computed from the
    191  * ctx->DrawBuffer->_X/Ymin/max fields.
    192  */
    193 static void
    194 clear_with_quad(struct gl_context *ctx,
    195                 GLboolean color, GLboolean depth, GLboolean stencil)
    196 {
    197    struct st_context *st = st_context(ctx);
    198    const struct gl_framebuffer *fb = ctx->DrawBuffer;
    199    const GLfloat fb_width = (GLfloat) fb->Width;
    200    const GLfloat fb_height = (GLfloat) fb->Height;
    201    const GLfloat x0 = (GLfloat) ctx->DrawBuffer->_Xmin / fb_width * 2.0f - 1.0f;
    202    const GLfloat x1 = (GLfloat) ctx->DrawBuffer->_Xmax / fb_width * 2.0f - 1.0f;
    203    const GLfloat y0 = (GLfloat) ctx->DrawBuffer->_Ymin / fb_height * 2.0f - 1.0f;
    204    const GLfloat y1 = (GLfloat) ctx->DrawBuffer->_Ymax / fb_height * 2.0f - 1.0f;
    205    union pipe_color_union clearColor;
    206 
    207    /*
    208    printf("%s %s%s%s %f,%f %f,%f\n", __FUNCTION__,
    209 	  color ? "color, " : "",
    210 	  depth ? "depth, " : "",
    211 	  stencil ? "stencil" : "",
    212 	  x0, y0,
    213 	  x1, y1);
    214    */
    215 
    216    cso_save_blend(st->cso_context);
    217    cso_save_stencil_ref(st->cso_context);
    218    cso_save_depth_stencil_alpha(st->cso_context);
    219    cso_save_rasterizer(st->cso_context);
    220    cso_save_sample_mask(st->cso_context);
    221    cso_save_viewport(st->cso_context);
    222    cso_save_fragment_shader(st->cso_context);
    223    cso_save_stream_outputs(st->cso_context);
    224    cso_save_vertex_shader(st->cso_context);
    225    cso_save_geometry_shader(st->cso_context);
    226    cso_save_vertex_elements(st->cso_context);
    227    cso_save_vertex_buffers(st->cso_context);
    228 
    229    /* blend state: RGBA masking */
    230    {
    231       struct pipe_blend_state blend;
    232       memset(&blend, 0, sizeof(blend));
    233       blend.rt[0].rgb_src_factor = PIPE_BLENDFACTOR_ONE;
    234       blend.rt[0].alpha_src_factor = PIPE_BLENDFACTOR_ONE;
    235       blend.rt[0].rgb_dst_factor = PIPE_BLENDFACTOR_ZERO;
    236       blend.rt[0].alpha_dst_factor = PIPE_BLENDFACTOR_ZERO;
    237       if (color) {
    238          if (ctx->Color.ColorMask[0][0])
    239             blend.rt[0].colormask |= PIPE_MASK_R;
    240          if (ctx->Color.ColorMask[0][1])
    241             blend.rt[0].colormask |= PIPE_MASK_G;
    242          if (ctx->Color.ColorMask[0][2])
    243             blend.rt[0].colormask |= PIPE_MASK_B;
    244          if (ctx->Color.ColorMask[0][3])
    245             blend.rt[0].colormask |= PIPE_MASK_A;
    246          if (st->ctx->Color.DitherFlag)
    247             blend.dither = 1;
    248       }
    249       cso_set_blend(st->cso_context, &blend);
    250    }
    251 
    252    /* depth_stencil state: always pass/set to ref value */
    253    {
    254       struct pipe_depth_stencil_alpha_state depth_stencil;
    255       memset(&depth_stencil, 0, sizeof(depth_stencil));
    256       if (depth) {
    257          depth_stencil.depth.enabled = 1;
    258          depth_stencil.depth.writemask = 1;
    259          depth_stencil.depth.func = PIPE_FUNC_ALWAYS;
    260       }
    261 
    262       if (stencil) {
    263          struct pipe_stencil_ref stencil_ref;
    264          memset(&stencil_ref, 0, sizeof(stencil_ref));
    265          depth_stencil.stencil[0].enabled = 1;
    266          depth_stencil.stencil[0].func = PIPE_FUNC_ALWAYS;
    267          depth_stencil.stencil[0].fail_op = PIPE_STENCIL_OP_REPLACE;
    268          depth_stencil.stencil[0].zpass_op = PIPE_STENCIL_OP_REPLACE;
    269          depth_stencil.stencil[0].zfail_op = PIPE_STENCIL_OP_REPLACE;
    270          depth_stencil.stencil[0].valuemask = 0xff;
    271          depth_stencil.stencil[0].writemask = ctx->Stencil.WriteMask[0] & 0xff;
    272          stencil_ref.ref_value[0] = ctx->Stencil.Clear;
    273          cso_set_stencil_ref(st->cso_context, &stencil_ref);
    274       }
    275 
    276       cso_set_depth_stencil_alpha(st->cso_context, &depth_stencil);
    277    }
    278 
    279    cso_set_vertex_elements(st->cso_context, 2, st->velems_util_draw);
    280    cso_set_stream_outputs(st->cso_context, 0, NULL, 0);
    281    cso_set_sample_mask(st->cso_context, ~0);
    282    cso_set_rasterizer(st->cso_context, &st->clear.raster);
    283 
    284    /* viewport state: viewport matching window dims */
    285    {
    286       const GLboolean invert = (st_fb_orientation(fb) == Y_0_TOP);
    287       struct pipe_viewport_state vp;
    288       vp.scale[0] = 0.5f * fb_width;
    289       vp.scale[1] = fb_height * (invert ? -0.5f : 0.5f);
    290       vp.scale[2] = 1.0f;
    291       vp.scale[3] = 1.0f;
    292       vp.translate[0] = 0.5f * fb_width;
    293       vp.translate[1] = 0.5f * fb_height;
    294       vp.translate[2] = 0.0f;
    295       vp.translate[3] = 0.0f;
    296       cso_set_viewport(st->cso_context, &vp);
    297    }
    298 
    299    set_fragment_shader(st);
    300    set_vertex_shader(st);
    301    cso_set_geometry_shader_handle(st->cso_context, NULL);
    302 
    303    if (ctx->DrawBuffer->_ColorDrawBuffers[0]) {
    304       struct gl_renderbuffer *rb = ctx->DrawBuffer->_ColorDrawBuffers[0];
    305       GLboolean is_integer = _mesa_is_enum_format_integer(rb->InternalFormat);
    306 
    307       st_translate_color(&ctx->Color.ClearColor,
    308                          &clearColor,
    309                          ctx->DrawBuffer->_ColorDrawBuffers[0]->_BaseFormat,
    310                          is_integer);
    311    }
    312 
    313    /* draw quad matching scissor rect */
    314    draw_quad(st, x0, y0, x1, y1, (GLfloat) ctx->Depth.Clear, &clearColor);
    315 
    316    /* Restore pipe state */
    317    cso_restore_blend(st->cso_context);
    318    cso_restore_stencil_ref(st->cso_context);
    319    cso_restore_depth_stencil_alpha(st->cso_context);
    320    cso_restore_rasterizer(st->cso_context);
    321    cso_restore_sample_mask(st->cso_context);
    322    cso_restore_viewport(st->cso_context);
    323    cso_restore_fragment_shader(st->cso_context);
    324    cso_restore_vertex_shader(st->cso_context);
    325    cso_restore_geometry_shader(st->cso_context);
    326    cso_restore_vertex_elements(st->cso_context);
    327    cso_restore_vertex_buffers(st->cso_context);
    328    cso_restore_stream_outputs(st->cso_context);
    329 }
    330 
    331 
    332 /**
    333  * Determine if we need to clear the depth buffer by drawing a quad.
    334  */
    335 static INLINE GLboolean
    336 check_clear_color_with_quad(struct gl_context *ctx, struct gl_renderbuffer *rb)
    337 {
    338    if (ctx->Scissor.Enabled &&
    339        (ctx->Scissor.X != 0 ||
    340         ctx->Scissor.Y != 0 ||
    341         ctx->Scissor.Width < rb->Width ||
    342         ctx->Scissor.Height < rb->Height))
    343       return GL_TRUE;
    344 
    345    if (!ctx->Color.ColorMask[0][0] ||
    346        !ctx->Color.ColorMask[0][1] ||
    347        !ctx->Color.ColorMask[0][2] ||
    348        !ctx->Color.ColorMask[0][3])
    349       return GL_TRUE;
    350 
    351    return GL_FALSE;
    352 }
    353 
    354 
    355 /**
    356  * Determine if we need to clear the combiend depth/stencil buffer by
    357  * drawing a quad.
    358  */
    359 static INLINE GLboolean
    360 check_clear_depth_stencil_with_quad(struct gl_context *ctx, struct gl_renderbuffer *rb)
    361 {
    362    const GLuint stencilMax = 0xff;
    363    GLboolean maskStencil
    364       = (ctx->Stencil.WriteMask[0] & stencilMax) != stencilMax;
    365 
    366    assert(_mesa_get_format_bits(rb->Format, GL_STENCIL_BITS) > 0);
    367 
    368    if (ctx->Scissor.Enabled &&
    369        (ctx->Scissor.X != 0 ||
    370         ctx->Scissor.Y != 0 ||
    371         ctx->Scissor.Width < rb->Width ||
    372         ctx->Scissor.Height < rb->Height))
    373       return GL_TRUE;
    374 
    375    if (maskStencil)
    376       return GL_TRUE;
    377 
    378    return GL_FALSE;
    379 }
    380 
    381 
    382 /**
    383  * Determine if we need to clear the depth buffer by drawing a quad.
    384  */
    385 static INLINE GLboolean
    386 check_clear_depth_with_quad(struct gl_context *ctx, struct gl_renderbuffer *rb,
    387                             boolean ds_separate)
    388 {
    389    const struct st_renderbuffer *strb = st_renderbuffer(rb);
    390    const GLboolean isDS = util_format_is_depth_and_stencil(strb->surface->format);
    391 
    392    if (ctx->Scissor.Enabled &&
    393        (ctx->Scissor.X != 0 ||
    394         ctx->Scissor.Y != 0 ||
    395         ctx->Scissor.Width < rb->Width ||
    396         ctx->Scissor.Height < rb->Height))
    397       return GL_TRUE;
    398 
    399    if (!ds_separate && isDS && ctx->DrawBuffer->Visual.stencilBits > 0)
    400       return GL_TRUE;
    401 
    402    return GL_FALSE;
    403 }
    404 
    405 
    406 /**
    407  * Determine if we need to clear the stencil buffer by drawing a quad.
    408  */
    409 static INLINE GLboolean
    410 check_clear_stencil_with_quad(struct gl_context *ctx, struct gl_renderbuffer *rb,
    411                               boolean ds_separate)
    412 {
    413    const struct st_renderbuffer *strb = st_renderbuffer(rb);
    414    const GLboolean isDS = util_format_is_depth_and_stencil(strb->surface->format);
    415    const GLuint stencilMax = 0xff;
    416    const GLboolean maskStencil
    417       = (ctx->Stencil.WriteMask[0] & stencilMax) != stencilMax;
    418 
    419    assert(_mesa_get_format_bits(rb->Format, GL_STENCIL_BITS) > 0);
    420 
    421    if (maskStencil)
    422       return GL_TRUE;
    423 
    424    if (ctx->Scissor.Enabled &&
    425        (ctx->Scissor.X != 0 ||
    426         ctx->Scissor.Y != 0 ||
    427         ctx->Scissor.Width < rb->Width ||
    428         ctx->Scissor.Height < rb->Height))
    429       return GL_TRUE;
    430 
    431    /* This is correct, but it is necessary to look at the depth clear
    432     * value held in the surface when it comes time to issue the clear,
    433     * rather than taking depth and stencil clear values from the
    434     * current state.
    435     */
    436    if (!ds_separate && isDS && ctx->DrawBuffer->Visual.depthBits > 0)
    437       return GL_TRUE;
    438 
    439    return GL_FALSE;
    440 }
    441 
    442 
    443 /**
    444  * Called via ctx->Driver.Clear()
    445  */
    446 static void
    447 st_Clear(struct gl_context *ctx, GLbitfield mask)
    448 {
    449    static const GLbitfield BUFFER_BITS_DS
    450       = (BUFFER_BIT_DEPTH | BUFFER_BIT_STENCIL);
    451    struct st_context *st = st_context(ctx);
    452    struct gl_renderbuffer *depthRb
    453       = ctx->DrawBuffer->Attachment[BUFFER_DEPTH].Renderbuffer;
    454    struct gl_renderbuffer *stencilRb
    455       = ctx->DrawBuffer->Attachment[BUFFER_STENCIL].Renderbuffer;
    456    GLbitfield quad_buffers = 0x0;
    457    GLbitfield clear_buffers = 0x0;
    458    GLuint i;
    459 
    460    /* This makes sure the pipe has the latest scissor, etc values */
    461    st_validate_state( st );
    462 
    463    if (mask & BUFFER_BITS_COLOR) {
    464       for (i = 0; i < ctx->DrawBuffer->_NumColorDrawBuffers; i++) {
    465          GLuint b = ctx->DrawBuffer->_ColorDrawBufferIndexes[i];
    466 
    467          if (mask & (1 << b)) {
    468             struct gl_renderbuffer *rb
    469                = ctx->DrawBuffer->Attachment[b].Renderbuffer;
    470             struct st_renderbuffer *strb = st_renderbuffer(rb);
    471 
    472             if (!strb || !strb->surface)
    473                continue;
    474 
    475             if (check_clear_color_with_quad( ctx, rb ))
    476                quad_buffers |= PIPE_CLEAR_COLOR;
    477             else
    478                clear_buffers |= PIPE_CLEAR_COLOR;
    479          }
    480       }
    481    }
    482 
    483    if ((mask & BUFFER_BITS_DS) == BUFFER_BITS_DS && depthRb == stencilRb) {
    484       /* clearing combined depth + stencil */
    485       struct st_renderbuffer *strb = st_renderbuffer(depthRb);
    486 
    487       if (strb->surface) {
    488          if (check_clear_depth_stencil_with_quad(ctx, depthRb))
    489             quad_buffers |= PIPE_CLEAR_DEPTHSTENCIL;
    490          else
    491             clear_buffers |= PIPE_CLEAR_DEPTHSTENCIL;
    492       }
    493    }
    494    else {
    495       /* separate depth/stencil clears */
    496       /* I don't think truly separate buffers are actually possible in gallium or hw? */
    497       if (mask & BUFFER_BIT_DEPTH) {
    498          struct st_renderbuffer *strb = st_renderbuffer(depthRb);
    499 
    500          if (strb->surface) {
    501             if (check_clear_depth_with_quad(ctx, depthRb,
    502                                             st->clear.enable_ds_separate))
    503                quad_buffers |= PIPE_CLEAR_DEPTH;
    504             else
    505                clear_buffers |= PIPE_CLEAR_DEPTH;
    506          }
    507       }
    508       if (mask & BUFFER_BIT_STENCIL) {
    509          struct st_renderbuffer *strb = st_renderbuffer(stencilRb);
    510 
    511          if (strb->surface) {
    512             if (check_clear_stencil_with_quad(ctx, stencilRb,
    513                                               st->clear.enable_ds_separate))
    514                quad_buffers |= PIPE_CLEAR_STENCIL;
    515             else
    516                clear_buffers |= PIPE_CLEAR_STENCIL;
    517          }
    518       }
    519    }
    520 
    521    /*
    522     * If we're going to use clear_with_quad() for any reason, use it for
    523     * everything possible.
    524     */
    525    if (quad_buffers) {
    526       quad_buffers |= clear_buffers;
    527       clear_with_quad(ctx,
    528                       quad_buffers & PIPE_CLEAR_COLOR,
    529                       quad_buffers & PIPE_CLEAR_DEPTH,
    530                       quad_buffers & PIPE_CLEAR_STENCIL);
    531    } else if (clear_buffers) {
    532       /* driver cannot know it can clear everything if the buffer
    533        * is a combined depth/stencil buffer but this wasn't actually
    534        * required from the visual. Hence fix this up to avoid potential
    535        * read-modify-write in the driver.
    536        */
    537       union pipe_color_union clearColor;
    538 
    539       if ((clear_buffers & PIPE_CLEAR_DEPTHSTENCIL) &&
    540           ((clear_buffers & PIPE_CLEAR_DEPTHSTENCIL) != PIPE_CLEAR_DEPTHSTENCIL) &&
    541           (depthRb == stencilRb) &&
    542           (ctx->DrawBuffer->Visual.depthBits == 0 ||
    543            ctx->DrawBuffer->Visual.stencilBits == 0))
    544          clear_buffers |= PIPE_CLEAR_DEPTHSTENCIL;
    545 
    546       if (ctx->DrawBuffer->_ColorDrawBuffers[0]) {
    547          struct gl_renderbuffer *rb = ctx->DrawBuffer->_ColorDrawBuffers[0];
    548          GLboolean is_integer = _mesa_is_enum_format_integer(rb->InternalFormat);
    549 
    550          st_translate_color(&ctx->Color.ClearColor,
    551                             &clearColor,
    552 			    ctx->DrawBuffer->_ColorDrawBuffers[0]->_BaseFormat,
    553 			    is_integer);
    554       }
    555 
    556       st->pipe->clear(st->pipe, clear_buffers, &clearColor,
    557                       ctx->Depth.Clear, ctx->Stencil.Clear);
    558    }
    559    if (mask & BUFFER_BIT_ACCUM)
    560       _mesa_clear_accum_buffer(ctx);
    561 }
    562 
    563 
    564 void
    565 st_init_clear_functions(struct dd_function_table *functions)
    566 {
    567    functions->Clear = st_Clear;
    568 }
    569