mask.c revision 37ec090ac9025529325209b2b616a2d6ece4c367
1/************************************************************************** 2 * 3 * Copyright 2009 VMware, Inc. All Rights Reserved. 4 * 5 * Permission is hereby granted, free of charge, to any person obtaining a 6 * copy of this software and associated documentation files (the 7 * "Software"), to deal in the Software without restriction, including 8 * without limitation the rights to use, copy, modify, merge, publish, 9 * distribute, sub license, and/or sell copies of the Software, and to 10 * permit persons to whom the Software is furnished to do so, subject to 11 * the following conditions: 12 * 13 * The above copyright notice and this permission notice (including the 14 * next paragraph) shall be included in all copies or substantial portions 15 * of the Software. 16 * 17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 18 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. 20 * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS BE LIABLE FOR 21 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 22 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 * 25 **************************************************************************/ 26 27#include "mask.h" 28 29#include "path.h" 30#include "image.h" 31#include "shaders_cache.h" 32#include "renderer.h" 33#include "asm_util.h" 34#include "st_inlines.h" 35 36#include "pipe/p_context.h" 37#include "pipe/p_screen.h" 38#include "util/u_inlines.h" 39#include "util/u_format.h" 40#include "util/u_memory.h" 41 42struct vg_mask_layer { 43 struct vg_object base; 44 45 VGint width; 46 VGint height; 47 48 struct pipe_sampler_view *sampler_view; 49}; 50 51static INLINE struct pipe_surface * 52alpha_mask_surface(struct vg_context *ctx, int usage) 53{ 54 struct pipe_screen *screen = ctx->pipe->screen; 55 struct st_framebuffer *stfb = ctx->draw_buffer; 56 return screen->get_tex_surface(screen, 57 stfb->alpha_mask_view->texture, 58 0, 0, 0, 59 usage); 60} 61 62static INLINE VGboolean 63intersect_rectangles(VGint dwidth, VGint dheight, 64 VGint swidth, VGint sheight, 65 VGint tx, VGint ty, 66 VGint twidth, VGint theight, 67 VGint *offsets, 68 VGint *location) 69{ 70 if (tx + twidth <= 0 || tx >= dwidth) 71 return VG_FALSE; 72 if (ty + theight <= 0 || ty >= dheight) 73 return VG_FALSE; 74 75 offsets[0] = 0; 76 offsets[1] = 0; 77 location[0] = tx; 78 location[1] = ty; 79 80 if (tx < 0) { 81 offsets[0] -= tx; 82 location[0] = 0; 83 84 location[2] = MIN2(tx + swidth, MIN2(dwidth, tx + twidth)); 85 offsets[2] = location[2]; 86 } else { 87 offsets[2] = MIN2(twidth, MIN2(dwidth - tx, swidth )); 88 location[2] = offsets[2]; 89 } 90 91 if (ty < 0) { 92 offsets[1] -= ty; 93 location[1] = 0; 94 95 location[3] = MIN2(ty + sheight, MIN2(dheight, ty + theight)); 96 offsets[3] = location[3]; 97 } else { 98 offsets[3] = MIN2(theight, MIN2(dheight - ty, sheight)); 99 location[3] = offsets[3]; 100 } 101 102 return VG_TRUE; 103} 104 105#if DEBUG_MASKS 106static void read_alpha_mask(void * data, VGint dataStride, 107 VGImageFormat dataFormat, 108 VGint sx, VGint sy, 109 VGint width, VGint height) 110{ 111 struct vg_context *ctx = vg_current_context(); 112 struct pipe_context *pipe = ctx->pipe; 113 struct pipe_screen *screen = pipe->screen; 114 115 struct st_framebuffer *stfb = ctx->draw_buffer; 116 struct st_renderbuffer *strb = stfb->alpha_mask; 117 struct pipe_framebuffer_state *fb = &ctx->state.g3d.fb; 118 119 VGfloat temp[VEGA_MAX_IMAGE_WIDTH][4]; 120 VGfloat *df = (VGfloat*)temp; 121 VGint y = (fb->height - sy) - 1, yStep = -1; 122 VGint i; 123 VGubyte *dst = (VGubyte *)data; 124 VGint xoffset = 0, yoffset = 0; 125 126 /* make sure rendering has completed */ 127 pipe->flush(pipe, PIPE_FLUSH_RENDER_CACHE, NULL); 128 if (sx < 0) { 129 xoffset = -sx; 130 xoffset *= _vega_size_for_format(dataFormat); 131 width += sx; 132 sx = 0; 133 } 134 if (sy < 0) { 135 yoffset = -sy; 136 height += sy; 137 sy = 0; 138 y = (fb->height - sy) - 1; 139 yoffset *= dataStride; 140 } 141 142 { 143 struct pipe_surface *surf; 144 145 surf = screen->get_tex_surface(screen, strb->texture, 0, 0, 0, 146 PIPE_BIND_TRANSFER_READ); 147 148 /* Do a row at a time to flip image data vertically */ 149 for (i = 0; i < height; i++) { 150#if 0 151 debug_printf("%d-%d == %d\n", sy, height, y); 152#endif 153 pipe_get_tile_rgba(surf, sx, y, width, 1, df); 154 y += yStep; 155 _vega_pack_rgba_span_float(ctx, width, temp, dataFormat, 156 dst + yoffset + xoffset); 157 dst += dataStride; 158 } 159 160 pipe_surface_reference(&surf, NULL); 161 } 162} 163 164void save_alpha_to_file(const char *filename) 165{ 166 struct vg_context *ctx = vg_current_context(); 167 struct pipe_framebuffer_state *fb = &ctx->state.g3d.fb; 168 VGint *data; 169 int i, j; 170 171 data = malloc(sizeof(int) * fb->width * fb->height); 172 read_alpha_mask(data, fb->width * sizeof(int), 173 VG_sRGBA_8888, 174 0, 0, fb->width, fb->height); 175 fprintf(stderr, "/*---------- start */\n"); 176 fprintf(stderr, "const int image_width = %d;\n", 177 fb->width); 178 fprintf(stderr, "const int image_height = %d;\n", 179 fb->height); 180 fprintf(stderr, "const int image_data = {\n"); 181 for (i = 0; i < fb->height; ++i) { 182 for (j = 0; j < fb->width; ++j) { 183 int rgba = data[i * fb->height + j]; 184 int argb = 0; 185 argb = (rgba >> 8); 186 argb |= ((rgba & 0xff) << 24); 187 fprintf(stderr, "0x%x, ", argb); 188 } 189 fprintf(stderr, "\n"); 190 } 191 fprintf(stderr, "};\n"); 192 fprintf(stderr, "/*---------- end */\n"); 193} 194#endif 195 196static void setup_mask_framebuffer(struct pipe_surface *surf, 197 VGint surf_width, VGint surf_height) 198{ 199 struct vg_context *ctx = vg_current_context(); 200 struct pipe_framebuffer_state fb; 201 202 memset(&fb, 0, sizeof(fb)); 203 fb.width = surf_width; 204 fb.height = surf_height; 205 fb.nr_cbufs = 1; 206 fb.cbufs[0] = surf; 207 { 208 VGint i; 209 for (i = 1; i < PIPE_MAX_COLOR_BUFS; ++i) 210 fb.cbufs[i] = 0; 211 } 212 cso_set_framebuffer(ctx->cso_context, &fb); 213} 214 215 216/* setup shader constants */ 217static void setup_mask_operation(VGMaskOperation operation) 218{ 219 struct vg_context *ctx = vg_current_context(); 220 struct pipe_resource **cbuf = &ctx->mask.cbuf; 221 const VGint param_bytes = 4 * sizeof(VGfloat); 222 const VGfloat ones[4] = {1.f, 1.f, 1.f, 1.f}; 223 void *shader = 0; 224 225 /* We always need to get a new buffer, to keep the drivers simple and 226 * avoid gratuitous rendering synchronization. 227 */ 228 pipe_resource_reference(cbuf, NULL); 229 230 *cbuf = pipe_buffer_create(ctx->pipe->screen, 231 PIPE_BIND_CONSTANT_BUFFER, 232 param_bytes); 233 if (*cbuf) { 234 st_no_flush_pipe_buffer_write(ctx, *cbuf, 235 0, param_bytes, ones); 236 } 237 238 ctx->pipe->set_constant_buffer(ctx->pipe, PIPE_SHADER_FRAGMENT, 0, *cbuf); 239 switch (operation) { 240 case VG_UNION_MASK: { 241 if (!ctx->mask.union_fs) { 242 ctx->mask.union_fs = shader_create_from_text(ctx->pipe, 243 union_mask_asm, 244 200, 245 PIPE_SHADER_FRAGMENT); 246 } 247 shader = ctx->mask.union_fs->driver; 248 } 249 break; 250 case VG_INTERSECT_MASK: { 251 if (!ctx->mask.intersect_fs) { 252 ctx->mask.intersect_fs = shader_create_from_text(ctx->pipe, 253 intersect_mask_asm, 254 200, 255 PIPE_SHADER_FRAGMENT); 256 } 257 shader = ctx->mask.intersect_fs->driver; 258 } 259 break; 260 case VG_SUBTRACT_MASK: { 261 if (!ctx->mask.subtract_fs) { 262 ctx->mask.subtract_fs = shader_create_from_text(ctx->pipe, 263 subtract_mask_asm, 264 200, 265 PIPE_SHADER_FRAGMENT); 266 } 267 shader = ctx->mask.subtract_fs->driver; 268 } 269 break; 270 case VG_SET_MASK: { 271 if (!ctx->mask.set_fs) { 272 ctx->mask.set_fs = shader_create_from_text(ctx->pipe, 273 set_mask_asm, 274 200, 275 PIPE_SHADER_FRAGMENT); 276 } 277 shader = ctx->mask.set_fs->driver; 278 } 279 break; 280 default: 281 assert(0); 282 break; 283 } 284 cso_set_fragment_shader_handle(ctx->cso_context, shader); 285} 286 287static void setup_mask_samplers(struct pipe_sampler_view *umask) 288{ 289 struct vg_context *ctx = vg_current_context(); 290 struct pipe_sampler_state *samplers[PIPE_MAX_SAMPLERS]; 291 struct pipe_sampler_view *sampler_views[PIPE_MAX_SAMPLERS]; 292 struct st_framebuffer *fb_buffers = ctx->draw_buffer; 293 struct pipe_sampler_view *uprev = NULL; 294 struct pipe_sampler_state sampler; 295 296 uprev = fb_buffers->blend_texture_view; 297 sampler = ctx->mask.sampler; 298 sampler.normalized_coords = 1; 299 300 samplers[0] = NULL; 301 samplers[1] = NULL; 302 sampler_views[0] = NULL; 303 sampler_views[1] = NULL; 304 305 samplers[0] = &sampler; 306 samplers[1] = &ctx->mask.sampler; 307 308 sampler_views[0] = umask; 309 sampler_views[1] = uprev; 310 311 cso_set_samplers(ctx->cso_context, 2, 312 (const struct pipe_sampler_state **)samplers); 313 cso_set_fragment_sampler_views(ctx->cso_context, 2, sampler_views); 314} 315 316 317/* setup shader constants */ 318static void setup_mask_fill(const VGfloat color[4]) 319{ 320 struct vg_context *ctx = vg_current_context(); 321 struct pipe_resource **cbuf = &ctx->mask.cbuf; 322 const VGint param_bytes = 4 * sizeof(VGfloat); 323 324 /* We always need to get a new buffer, to keep the drivers simple and 325 * avoid gratuitous rendering synchronization. 326 */ 327 pipe_resource_reference(cbuf, NULL); 328 329 *cbuf = pipe_buffer_create(ctx->pipe->screen, 330 PIPE_BIND_CONSTANT_BUFFER, 331 param_bytes); 332 if (*cbuf) { 333 st_no_flush_pipe_buffer_write(ctx, *cbuf, 0, param_bytes, color); 334 } 335 336 ctx->pipe->set_constant_buffer(ctx->pipe, PIPE_SHADER_FRAGMENT, 0, *cbuf); 337 cso_set_fragment_shader_handle(ctx->cso_context, 338 shaders_cache_fill(ctx->sc, 339 VEGA_SOLID_FILL_SHADER)); 340} 341 342static void setup_mask_blend() 343{ 344 struct vg_context *ctx = vg_current_context(); 345 346 struct pipe_blend_state blend; 347 348 memset(&blend, 0, sizeof(struct pipe_blend_state)); 349 blend.rt[0].blend_enable = 0; 350 blend.rt[0].colormask = PIPE_MASK_RGBA; 351 blend.rt[0].rgb_src_factor = PIPE_BLENDFACTOR_ONE; 352 blend.rt[0].alpha_src_factor = PIPE_BLENDFACTOR_ONE; 353 blend.rt[0].rgb_dst_factor = PIPE_BLENDFACTOR_ZERO; 354 blend.rt[0].alpha_dst_factor = PIPE_BLENDFACTOR_ZERO; 355 356 cso_set_blend(ctx->cso_context, &blend); 357} 358 359 360static void surface_fill(struct pipe_surface *surf, 361 int surf_width, int surf_height, 362 int x, int y, int width, int height, 363 const VGfloat color[4]) 364{ 365 struct vg_context *ctx = vg_current_context(); 366 367 if (x < 0) { 368 width += x; 369 x = 0; 370 } 371 if (y < 0) { 372 height += y; 373 y = 0; 374 } 375 376 cso_save_framebuffer(ctx->cso_context); 377 cso_save_blend(ctx->cso_context); 378 cso_save_fragment_shader(ctx->cso_context); 379 380 setup_mask_blend(); 381 setup_mask_fill(color); 382 setup_mask_framebuffer(surf, surf_width, surf_height); 383 384 renderer_draw_quad(ctx->renderer, x, y, 385 x + width, y + height, 0.0f/*depth should be disabled*/); 386 387 388 /* make sure rendering has completed */ 389 ctx->pipe->flush(ctx->pipe, 390 PIPE_FLUSH_RENDER_CACHE | PIPE_FLUSH_FRAME, 391 NULL); 392 393#if DEBUG_MASKS 394 save_alpha_to_file(0); 395#endif 396 397 cso_restore_blend(ctx->cso_context); 398 cso_restore_framebuffer(ctx->cso_context); 399 cso_restore_fragment_shader(ctx->cso_context); 400} 401 402 403static void mask_using_texture(struct pipe_sampler_view *sampler_view, 404 VGMaskOperation operation, 405 VGint x, VGint y, 406 VGint width, VGint height) 407{ 408 struct vg_context *ctx = vg_current_context(); 409 struct pipe_resource *texture = sampler_view->texture; 410 struct pipe_surface *surface = 411 alpha_mask_surface(ctx, PIPE_BIND_RENDER_TARGET); 412 VGint offsets[4], loc[4]; 413 414 if (!surface) 415 return; 416 if (!intersect_rectangles(surface->width, surface->height, 417 texture->width0, texture->height0, 418 x, y, width, height, 419 offsets, loc)) 420 return; 421#if 0 422 debug_printf("Offset = [%d, %d, %d, %d]\n", offsets[0], 423 offsets[1], offsets[2], offsets[3]); 424 debug_printf("Locati = [%d, %d, %d, %d]\n", loc[0], 425 loc[1], loc[2], loc[3]); 426#endif 427 428 /* prepare our blend surface */ 429 vg_prepare_blend_surface_from_mask(ctx); 430 431 cso_save_samplers(ctx->cso_context); 432 cso_save_fragment_sampler_views(ctx->cso_context); 433 cso_save_framebuffer(ctx->cso_context); 434 cso_save_blend(ctx->cso_context); 435 cso_save_fragment_shader(ctx->cso_context); 436 437 setup_mask_samplers(sampler_view); 438 setup_mask_blend(); 439 setup_mask_operation(operation); 440 setup_mask_framebuffer(surface, surface->width, surface->height); 441 442 /* render the quad to propagate the rendering from stencil */ 443 renderer_draw_texture(ctx->renderer, texture, 444 offsets[0], offsets[1], 445 offsets[0] + offsets[2], offsets[1] + offsets[3], 446 loc[0], loc[1], loc[0] + loc[2], loc[1] + loc[3]); 447 448 /* make sure rendering has completed */ 449 ctx->pipe->flush(ctx->pipe, PIPE_FLUSH_RENDER_CACHE, NULL); 450 cso_restore_blend(ctx->cso_context); 451 cso_restore_framebuffer(ctx->cso_context); 452 cso_restore_fragment_shader(ctx->cso_context); 453 cso_restore_samplers(ctx->cso_context); 454 cso_restore_fragment_sampler_views(ctx->cso_context); 455 456 pipe_surface_reference(&surface, NULL); 457} 458 459 460#ifdef OPENVG_VERSION_1_1 461 462struct vg_mask_layer * mask_layer_create(VGint width, VGint height) 463{ 464 struct vg_context *ctx = vg_current_context(); 465 struct vg_mask_layer *mask = 0; 466 467 mask = CALLOC_STRUCT(vg_mask_layer); 468 vg_init_object(&mask->base, ctx, VG_OBJECT_MASK); 469 mask->width = width; 470 mask->height = height; 471 472 { 473 struct pipe_resource pt; 474 struct pipe_context *pipe = ctx->pipe; 475 struct pipe_screen *screen = ctx->pipe->screen; 476 struct pipe_sampler_view view_templ; 477 struct pipe_sampler_view *view = NULL; 478 struct pipe_resource *texture; 479 480 memset(&pt, 0, sizeof(pt)); 481 pt.target = PIPE_TEXTURE_2D; 482 pt.format = PIPE_FORMAT_B8G8R8A8_UNORM; 483 pt.last_level = 0; 484 pt.width0 = width; 485 pt.height0 = height; 486 pt.depth0 = 1; 487 pt.bind = PIPE_BIND_SAMPLER_VIEW; 488 pt.compressed = 0; 489 490 texture = screen->resource_create(screen, &pt); 491 492 if (texture) { 493 u_sampler_view_default_template(&view_templ, texture, texture->format); 494 view = pipe->create_sampler_view(pipe, texture, &view_templ); 495 } 496 pipe_resource_reference(&texture, NULL); 497 mask->sampler_view = view; 498 } 499 500 vg_context_add_object(ctx, VG_OBJECT_MASK, mask); 501 502 return mask; 503} 504 505void mask_layer_destroy(struct vg_mask_layer *layer) 506{ 507 struct vg_context *ctx = vg_current_context(); 508 509 vg_context_remove_object(ctx, VG_OBJECT_MASK, layer); 510 pipe_resource_release(&layer->texture); 511 FREE(layer); 512} 513 514void mask_layer_fill(struct vg_mask_layer *layer, 515 VGint x, VGint y, 516 VGint width, VGint height, 517 VGfloat value) 518{ 519 struct vg_context *ctx = vg_current_context(); 520 VGfloat alpha_color[4] = {0, 0, 0, 0}; 521 struct pipe_surface *surface; 522 523 alpha_color[3] = value; 524 525 surface = ctx->pipe->screen->get_tex_surface( 526 ctx->pipe->screen, layer->sampler_view->texture, 527 0, 0, 0, 528 PIPE_BIND_RENDER_TARGET); 529 530 surface_fill(surface, 531 layer->width, layer->height, 532 x, y, width, height, alpha_color); 533 534 ctx->pipe->screen->tex_surface_release(ctx->pipe->screen, &surface); 535} 536 537void mask_copy(struct vg_mask_layer *layer, 538 VGint sx, VGint sy, 539 VGint dx, VGint dy, 540 VGint width, VGint height) 541{ 542 struct vg_context *ctx = vg_current_context(); 543 struct st_framebuffer *fb_buffers = ctx->draw_buffer; 544 545 renderer_copy_texture(ctx->renderer, 546 layer->sampler_view, 547 sx, sy, 548 sx + width, sy + height, 549 fb_buffers->alpha_mask_view->texture, 550 dx, dy, 551 dx + width, dy + height); 552} 553 554static void mask_layer_render_to(struct vg_mask_layer *layer, 555 struct path *path, 556 VGbitfield paint_modes) 557{ 558 struct vg_context *ctx = vg_current_context(); 559 const VGfloat fill_color[4] = {1.f, 1.f, 1.f, 1.f}; 560 struct pipe_screen *screen = ctx->pipe->screen; 561 struct pipe_surface *surface; 562 563 surface = screen->get_tex_surface(screen, layer->sampler_view->texture, 0, 0, 0, 564 PIPE_BIND_RENDER_TARGET); 565 566 cso_save_framebuffer(ctx->cso_context); 567 cso_save_fragment_shader(ctx->cso_context); 568 569 setup_mask_blend(); 570 setup_mask_fill(fill_color); 571 setup_mask_framebuffer(surface, layer->width, layer->height); 572 573 if (paint_modes & VG_FILL_PATH) { 574 struct matrix *mat = &ctx->state.vg.path_user_to_surface_matrix; 575 path_fill(path, mat); 576 } 577 578 if (paint_modes & VG_STROKE_PATH){ 579 path_stroke(path); 580 } 581 582 583 /* make sure rendering has completed */ 584 ctx->pipe->flush(ctx->pipe, PIPE_FLUSH_RENDER_CACHE, NULL); 585 586 cso_restore_framebuffer(ctx->cso_context); 587 cso_restore_fragment_shader(ctx->cso_context); 588 ctx->state.dirty |= BLEND_DIRTY; 589 590 screen->tex_surface_release(ctx->pipe->screen, &surface); 591} 592 593void mask_render_to(struct path *path, 594 VGbitfield paint_modes, 595 VGMaskOperation operation) 596{ 597 struct vg_context *ctx = vg_current_context(); 598 struct st_framebuffer *fb_buffers = ctx->draw_buffer; 599 struct vg_mask_layer *temp_layer; 600 VGint width, height; 601 602 width = fb_buffers->alpha_mask_view->texture->width0; 603 height = fb_buffers->alpha_mask_view->texture->width0; 604 605 temp_layer = mask_layer_create(width, height); 606 607 mask_layer_render_to(temp_layer, path, paint_modes); 608 609 mask_using_layer(temp_layer, 0, 0, width, height, 610 operation); 611 612 mask_layer_destroy(temp_layer); 613} 614 615void mask_using_layer(struct vg_mask_layer *layer, 616 VGMaskOperation operation, 617 VGint x, VGint y, 618 VGint width, VGint height) 619{ 620 mask_using_texture(layer->sampler_view, operation, 621 x, y, width, height); 622} 623 624VGint mask_layer_width(struct vg_mask_layer *layer) 625{ 626 return layer->width; 627} 628 629VGint mask_layer_height(struct vg_mask_layer *layer) 630{ 631 return layer->height; 632} 633 634 635#endif 636 637void mask_using_image(struct vg_image *image, 638 VGMaskOperation operation, 639 VGint x, VGint y, 640 VGint width, VGint height) 641{ 642 mask_using_texture(image->sampler_view, operation, 643 x, y, width, height); 644} 645 646void mask_fill(VGint x, VGint y, VGint width, VGint height, 647 VGfloat value) 648{ 649 struct vg_context *ctx = vg_current_context(); 650 VGfloat alpha_color[4] = {.0f, .0f, .0f, value}; 651 struct pipe_surface *surf = alpha_mask_surface( 652 ctx, PIPE_BIND_RENDER_TARGET); 653 654#if DEBUG_MASKS 655 debug_printf("mask_fill(%d, %d, %d, %d) with rgba(%f, %f, %f, %f)\n", 656 x, y, width, height, 657 alpha_color[0], alpha_color[1], 658 alpha_color[2], alpha_color[3]); 659 debug_printf("XXX %f === %f \n", 660 alpha_color[3], value); 661#endif 662 663 surface_fill(surf, surf->width, surf->height, 664 x, y, width, height, alpha_color); 665 666 pipe_surface_reference(&surf, NULL); 667} 668 669VGint mask_bind_samplers(struct pipe_sampler_state **samplers, 670 struct pipe_sampler_view **sampler_views) 671{ 672 struct vg_context *ctx = vg_current_context(); 673 674 if (ctx->state.vg.masking) { 675 struct st_framebuffer *fb_buffers = ctx->draw_buffer; 676 677 samplers[1] = &ctx->mask.sampler; 678 sampler_views[1] = fb_buffers->alpha_mask_view; 679 return 1; 680 } else 681 return 0; 682} 683