dri2.c revision 02448f2241d90fdbf97b0197e857a8f5acf5d4c7
1/* 2 * Mesa 3-D graphics library 3 * Version: 7.9 4 * 5 * Copyright 2009, VMware, Inc. 6 * All Rights Reserved. 7 * Copyright (C) 2010 LunarG Inc. 8 * 9 * Permission is hereby granted, free of charge, to any person obtaining a 10 * copy of this software and associated documentation files (the "Software"), 11 * to deal in the Software without restriction, including without limitation 12 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 13 * and/or sell copies of the Software, and to permit persons to whom the 14 * Software is furnished to do so, subject to the following conditions: 15 * 16 * The above copyright notice and this permission notice shall be included 17 * in all copies or substantial portions 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 MERCHANTABILITY, 21 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 22 * BRIAN PAUL BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 23 * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 24 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 * 26 * Authors: 27 * Keith Whitwell <keithw@vmware.com> 28 * Jakob Bornecrantz <wallbraker@gmail.com> 29 * Chia-I Wu <olv@lunarg.com> 30 */ 31 32#include "util/u_memory.h" 33#include "util/u_inlines.h" 34#include "util/u_format.h" 35#include "util/u_debug.h" 36#include "state_tracker/drm_driver.h" 37 38#include "dri_screen.h" 39#include "dri_context.h" 40#include "dri_drawable.h" 41#include "dri2_buffer.h" 42 43/** 44 * DRI2 flush extension. 45 */ 46static void 47dri2_flush_drawable(__DRIdrawable *draw) 48{ 49} 50 51static void 52dri2_invalidate_drawable(__DRIdrawable *dPriv) 53{ 54 struct dri_drawable *drawable = dri_drawable(dPriv); 55 struct dri_context *ctx = drawable->context; 56 57 dri2InvalidateDrawable(dPriv); 58 drawable->dPriv->lastStamp = *drawable->dPriv->pStamp; 59 60 if (ctx) 61 ctx->st->notify_invalid_framebuffer(ctx->st, &drawable->base); 62} 63 64static const __DRI2flushExtension dri2FlushExtension = { 65 { __DRI2_FLUSH, __DRI2_FLUSH_VERSION }, 66 dri2_flush_drawable, 67 dri2_invalidate_drawable, 68}; 69 70/** 71 * Retrieve __DRIbuffer from the DRI loader. 72 */ 73static __DRIbuffer * 74dri2_drawable_get_buffers(struct dri_drawable *drawable, 75 const enum st_attachment_type *statts, 76 unsigned *count) 77{ 78 __DRIdrawable *dri_drawable = drawable->dPriv; 79 struct __DRIdri2LoaderExtensionRec *loader = drawable->sPriv->dri2.loader; 80 boolean with_format; 81 __DRIbuffer *buffers; 82 int num_buffers; 83 unsigned attachments[10]; 84 unsigned num_attachments, i; 85 86 assert(loader); 87 with_format = dri_with_format(drawable->sPriv); 88 89 num_attachments = 0; 90 91 /* for Xserver 1.6.0 (DRI2 version 1) we always need to ask for the front */ 92 if (!with_format) 93 attachments[num_attachments++] = __DRI_BUFFER_FRONT_LEFT; 94 95 for (i = 0; i < *count; i++) { 96 enum pipe_format format; 97 unsigned bind; 98 int att, bpp; 99 100 dri_drawable_get_format(drawable, statts[i], &format, &bind); 101 if (format == PIPE_FORMAT_NONE) 102 continue; 103 104 switch (statts[i]) { 105 case ST_ATTACHMENT_FRONT_LEFT: 106 /* already added */ 107 if (!with_format) 108 continue; 109 att = __DRI_BUFFER_FRONT_LEFT; 110 break; 111 case ST_ATTACHMENT_BACK_LEFT: 112 att = __DRI_BUFFER_BACK_LEFT; 113 break; 114 case ST_ATTACHMENT_FRONT_RIGHT: 115 att = __DRI_BUFFER_FRONT_RIGHT; 116 break; 117 case ST_ATTACHMENT_BACK_RIGHT: 118 att = __DRI_BUFFER_BACK_RIGHT; 119 break; 120 case ST_ATTACHMENT_DEPTH_STENCIL: 121 att = __DRI_BUFFER_DEPTH_STENCIL; 122 break; 123 default: 124 att = -1; 125 break; 126 } 127 128 bpp = util_format_get_blocksizebits(format); 129 130 if (att >= 0) { 131 attachments[num_attachments++] = att; 132 if (with_format) { 133 attachments[num_attachments++] = bpp; 134 } 135 } 136 } 137 138 if (with_format) { 139 num_attachments /= 2; 140 buffers = loader->getBuffersWithFormat(dri_drawable, 141 &dri_drawable->w, &dri_drawable->h, 142 attachments, num_attachments, 143 &num_buffers, dri_drawable->loaderPrivate); 144 } 145 else { 146 buffers = loader->getBuffers(dri_drawable, 147 &dri_drawable->w, &dri_drawable->h, 148 attachments, num_attachments, 149 &num_buffers, dri_drawable->loaderPrivate); 150 } 151 152 if (buffers) { 153 /* set one cliprect to cover the whole dri_drawable */ 154 dri_drawable->x = 0; 155 dri_drawable->y = 0; 156 dri_drawable->backX = 0; 157 dri_drawable->backY = 0; 158 dri_drawable->numClipRects = 1; 159 dri_drawable->pClipRects[0].x1 = 0; 160 dri_drawable->pClipRects[0].y1 = 0; 161 dri_drawable->pClipRects[0].x2 = dri_drawable->w; 162 dri_drawable->pClipRects[0].y2 = dri_drawable->h; 163 dri_drawable->numBackClipRects = 1; 164 dri_drawable->pBackClipRects[0].x1 = 0; 165 dri_drawable->pBackClipRects[0].y1 = 0; 166 dri_drawable->pBackClipRects[0].x2 = dri_drawable->w; 167 dri_drawable->pBackClipRects[0].y2 = dri_drawable->h; 168 169 *count = num_buffers; 170 } 171 172 return buffers; 173} 174 175/** 176 * Process __DRIbuffer and convert them into pipe_resources. 177 */ 178static void 179dri2_drawable_process_buffers(struct dri_drawable *drawable, 180 __DRIbuffer *buffers, unsigned count) 181{ 182 struct dri_screen *screen = dri_screen(drawable->sPriv); 183 __DRIdrawable *dri_drawable = drawable->dPriv; 184 struct pipe_resource templ; 185 struct winsys_handle whandle; 186 boolean have_depth = FALSE; 187 unsigned i, bind; 188 189 if (drawable->old_num == count && 190 drawable->old_w == dri_drawable->w && 191 drawable->old_h == dri_drawable->h && 192 memcmp(drawable->old, buffers, sizeof(__DRIbuffer) * count) == 0) 193 return; 194 195 for (i = 0; i < ST_ATTACHMENT_COUNT; i++) 196 pipe_resource_reference(&drawable->textures[i], NULL); 197 198 memset(&templ, 0, sizeof(templ)); 199 templ.target = screen->target; 200 templ.last_level = 0; 201 templ.width0 = dri_drawable->w; 202 templ.height0 = dri_drawable->h; 203 templ.depth0 = 1; 204 templ.array_size = 1; 205 206 memset(&whandle, 0, sizeof(whandle)); 207 208 for (i = 0; i < count; i++) { 209 __DRIbuffer *buf = &buffers[i]; 210 enum st_attachment_type statt; 211 enum pipe_format format; 212 213 switch (buf->attachment) { 214 case __DRI_BUFFER_FRONT_LEFT: 215 if (!screen->auto_fake_front) { 216 statt = ST_ATTACHMENT_INVALID; 217 break; 218 } 219 /* fallthrough */ 220 case __DRI_BUFFER_FAKE_FRONT_LEFT: 221 statt = ST_ATTACHMENT_FRONT_LEFT; 222 break; 223 case __DRI_BUFFER_BACK_LEFT: 224 statt = ST_ATTACHMENT_BACK_LEFT; 225 break; 226 case __DRI_BUFFER_DEPTH: 227 case __DRI_BUFFER_DEPTH_STENCIL: 228 case __DRI_BUFFER_STENCIL: 229 /* use only the first depth/stencil buffer */ 230 if (!have_depth) { 231 have_depth = TRUE; 232 statt = ST_ATTACHMENT_DEPTH_STENCIL; 233 } 234 else { 235 statt = ST_ATTACHMENT_INVALID; 236 } 237 break; 238 default: 239 statt = ST_ATTACHMENT_INVALID; 240 break; 241 } 242 243 dri_drawable_get_format(drawable, statt, &format, &bind); 244 if (statt == ST_ATTACHMENT_INVALID || format == PIPE_FORMAT_NONE) 245 continue; 246 247 templ.format = format; 248 templ.bind = bind; 249 whandle.handle = buf->name; 250 whandle.stride = buf->pitch; 251 252 drawable->textures[statt] = 253 screen->base.screen->resource_from_handle(screen->base.screen, 254 &templ, &whandle); 255 } 256 257 drawable->old_num = count; 258 drawable->old_w = dri_drawable->w; 259 drawable->old_h = dri_drawable->h; 260 memcpy(drawable->old, buffers, sizeof(__DRIbuffer) * count); 261} 262 263static __DRIbuffer * 264dri2_allocate_buffer(__DRIscreen *sPriv, 265 unsigned attachment, unsigned format, 266 int width, int height) 267{ 268 struct dri_screen *screen = dri_screen(sPriv); 269 struct dri2_buffer *buffer; 270 struct pipe_resource templ; 271 enum st_attachment_type statt; 272 enum pipe_format pf; 273 unsigned bind; 274 struct winsys_handle whandle; 275 276 switch (attachment) { 277 case __DRI_BUFFER_FRONT_LEFT: 278 case __DRI_BUFFER_FAKE_FRONT_LEFT: 279 statt = ST_ATTACHMENT_FRONT_LEFT; 280 bind = PIPE_BIND_RENDER_TARGET | PIPE_BIND_SAMPLER_VIEW; 281 break; 282 case __DRI_BUFFER_BACK_LEFT: 283 statt = ST_ATTACHMENT_BACK_LEFT; 284 bind = PIPE_BIND_RENDER_TARGET | PIPE_BIND_SAMPLER_VIEW; 285 break; 286 case __DRI_BUFFER_DEPTH: 287 case __DRI_BUFFER_DEPTH_STENCIL: 288 case __DRI_BUFFER_STENCIL: 289 statt = ST_ATTACHMENT_DEPTH_STENCIL; 290 bind = PIPE_BIND_DEPTH_STENCIL; /* XXX sampler? */ 291 break; 292 default: 293 statt = ST_ATTACHMENT_INVALID; 294 break; 295 } 296 297 switch (format) { 298 case 32: 299 pf = PIPE_FORMAT_B8G8R8X8_UNORM; 300 break; 301 case 16: 302 pf = PIPE_FORMAT_Z16_UNORM; 303 break; 304 default: 305 return NULL; 306 } 307 308 buffer = CALLOC_STRUCT(dri2_buffer); 309 if (!buffer) 310 return NULL; 311 312 memset(&templ, 0, sizeof(templ)); 313 templ.bind = bind; 314 templ.format = pf; 315 templ.target = PIPE_TEXTURE_2D; 316 templ.last_level = 0; 317 templ.width0 = width; 318 templ.height0 = height; 319 templ.depth0 = 1; 320 templ.array_size = 1; 321 322 buffer->resource = 323 screen->base.screen->resource_create(screen->base.screen, &templ); 324 if (!buffer->resource) 325 return NULL; 326 327 memset(&whandle, 0, sizeof(whandle)); 328 whandle.type = DRM_API_HANDLE_TYPE_SHARED; 329 screen->base.screen->resource_get_handle(screen->base.screen, 330 buffer->resource, &whandle); 331 332 buffer->base.attachment = attachment; 333 buffer->base.name = whandle.handle; 334 buffer->base.cpp = util_format_get_blocksize(pf); 335 buffer->base.pitch = whandle.stride; 336 337 return &buffer->base; 338} 339 340static void 341dri2_release_buffer(__DRIscreen *sPriv, __DRIbuffer *bPriv) 342{ 343 struct dri2_buffer *buffer = dri2_buffer(bPriv); 344 345 pipe_resource_reference(&buffer->resource, NULL); 346 FREE(buffer); 347} 348 349/* 350 * Backend functions for st_framebuffer interface. 351 */ 352 353static void 354dri2_allocate_textures(struct dri_drawable *drawable, 355 const enum st_attachment_type *statts, 356 unsigned count) 357{ 358 __DRIbuffer *buffers; 359 unsigned num_buffers = count; 360 361 buffers = dri2_drawable_get_buffers(drawable, statts, &num_buffers); 362 if (buffers) 363 dri2_drawable_process_buffers(drawable, buffers, num_buffers); 364} 365 366static void 367dri2_flush_frontbuffer(struct dri_drawable *drawable, 368 enum st_attachment_type statt) 369{ 370 __DRIdrawable *dri_drawable = drawable->dPriv; 371 struct __DRIdri2LoaderExtensionRec *loader = drawable->sPriv->dri2.loader; 372 373 if (loader->flushFrontBuffer == NULL) 374 return; 375 376 if (statt == ST_ATTACHMENT_FRONT_LEFT) { 377 loader->flushFrontBuffer(dri_drawable, dri_drawable->loaderPrivate); 378 } 379} 380 381static __DRIimage * 382dri2_lookup_egl_image(struct dri_screen *screen, void *handle) 383{ 384 __DRIimageLookupExtension *loader = screen->sPriv->dri2.image; 385 __DRIimage *img; 386 387 if (!loader->lookupEGLImage) 388 return NULL; 389 390 img = loader->lookupEGLImage(screen->sPriv, 391 handle, screen->sPriv->loaderPrivate); 392 393 return img; 394} 395 396static __DRIimage * 397dri2_create_image_from_name(__DRIscreen *_screen, 398 int width, int height, int format, 399 int name, int pitch, void *loaderPrivate) 400{ 401 struct dri_screen *screen = dri_screen(_screen); 402 __DRIimage *img; 403 struct pipe_resource templ; 404 struct winsys_handle whandle; 405 unsigned tex_usage; 406 enum pipe_format pf; 407 408 tex_usage = PIPE_BIND_RENDER_TARGET | PIPE_BIND_SAMPLER_VIEW; 409 410 switch (format) { 411 case __DRI_IMAGE_FORMAT_RGB565: 412 pf = PIPE_FORMAT_B5G6R5_UNORM; 413 break; 414 case __DRI_IMAGE_FORMAT_XRGB8888: 415 pf = PIPE_FORMAT_B8G8R8X8_UNORM; 416 break; 417 case __DRI_IMAGE_FORMAT_ARGB8888: 418 pf = PIPE_FORMAT_B8G8R8A8_UNORM; 419 break; 420 default: 421 pf = PIPE_FORMAT_NONE; 422 break; 423 } 424 if (pf == PIPE_FORMAT_NONE) 425 return NULL; 426 427 img = CALLOC_STRUCT(__DRIimageRec); 428 if (!img) 429 return NULL; 430 431 memset(&templ, 0, sizeof(templ)); 432 templ.bind = tex_usage; 433 templ.format = pf; 434 templ.target = screen->target; 435 templ.last_level = 0; 436 templ.width0 = width; 437 templ.height0 = height; 438 templ.depth0 = 1; 439 templ.array_size = 1; 440 441 memset(&whandle, 0, sizeof(whandle)); 442 whandle.handle = name; 443 whandle.stride = pitch * util_format_get_blocksize(pf); 444 445 img->texture = screen->base.screen->resource_from_handle(screen->base.screen, 446 &templ, &whandle); 447 if (!img->texture) { 448 FREE(img); 449 return NULL; 450 } 451 452 img->level = 0; 453 img->layer = 0; 454 img->loader_private = loaderPrivate; 455 456 return img; 457} 458 459static __DRIimage * 460dri2_create_image_from_renderbuffer(__DRIcontext *context, 461 int renderbuffer, void *loaderPrivate) 462{ 463 struct dri_context *ctx = dri_context(context); 464 465 if (!ctx->st->get_resource_for_egl_image) 466 return NULL; 467 468 /* TODO */ 469 return NULL; 470} 471 472static __DRIimage * 473dri2_create_image(__DRIscreen *_screen, 474 int width, int height, int format, 475 unsigned int use, void *loaderPrivate) 476{ 477 struct dri_screen *screen = dri_screen(_screen); 478 __DRIimage *img; 479 struct pipe_resource templ; 480 unsigned tex_usage; 481 enum pipe_format pf; 482 483 tex_usage = PIPE_BIND_RENDER_TARGET | PIPE_BIND_SAMPLER_VIEW; 484 485 switch (format) { 486 case __DRI_IMAGE_FORMAT_RGB565: 487 pf = PIPE_FORMAT_B5G6R5_UNORM; 488 break; 489 case __DRI_IMAGE_FORMAT_XRGB8888: 490 pf = PIPE_FORMAT_B8G8R8X8_UNORM; 491 break; 492 case __DRI_IMAGE_FORMAT_ARGB8888: 493 pf = PIPE_FORMAT_B8G8R8A8_UNORM; 494 break; 495 default: 496 pf = PIPE_FORMAT_NONE; 497 break; 498 } 499 if (pf == PIPE_FORMAT_NONE) 500 return NULL; 501 502 img = CALLOC_STRUCT(__DRIimageRec); 503 if (!img) 504 return NULL; 505 506 memset(&templ, 0, sizeof(templ)); 507 templ.bind = tex_usage; 508 templ.format = pf; 509 templ.target = PIPE_TEXTURE_2D; 510 templ.last_level = 0; 511 templ.width0 = width; 512 templ.height0 = height; 513 templ.depth0 = 1; 514 515 img->texture = screen->base.screen->resource_create(screen->base.screen, &templ); 516 if (!img->texture) { 517 FREE(img); 518 return NULL; 519 } 520 521 img->level = 0; 522 img->layer = 0; 523 524 img->loader_private = loaderPrivate; 525 return img; 526} 527 528static GLboolean 529dri2_query_image(__DRIimage *image, int attrib, int *value) 530{ 531 struct winsys_handle whandle; 532 memset(&whandle, 0, sizeof(whandle)); 533 534 switch (attrib) { 535 case __DRI_IMAGE_ATTRIB_STRIDE: 536 image->texture->screen->resource_get_handle(image->texture->screen, 537 image->texture, &whandle); 538 *value = whandle.stride; 539 return GL_TRUE; 540 case __DRI_IMAGE_ATTRIB_HANDLE: 541 whandle.type = DRM_API_HANDLE_TYPE_KMS; 542 image->texture->screen->resource_get_handle(image->texture->screen, 543 image->texture, &whandle); 544 *value = whandle.handle; 545 return GL_TRUE; 546 case __DRI_IMAGE_ATTRIB_NAME: 547 whandle.type = DRM_API_HANDLE_TYPE_SHARED; 548 image->texture->screen->resource_get_handle(image->texture->screen, 549 image->texture, &whandle); 550 *value = whandle.handle; 551 return GL_TRUE; 552 default: 553 return GL_FALSE; 554 } 555} 556 557static void 558dri2_destroy_image(__DRIimage *img) 559{ 560 pipe_resource_reference(&img->texture, NULL); 561 FREE(img); 562} 563 564static struct __DRIimageExtensionRec dri2ImageExtension = { 565 { __DRI_IMAGE, __DRI_IMAGE_VERSION }, 566 dri2_create_image_from_name, 567 dri2_create_image_from_renderbuffer, 568 dri2_destroy_image, 569 dri2_create_image, 570 dri2_query_image, 571}; 572 573/* 574 * Backend function init_screen. 575 */ 576 577static const __DRIextension *dri_screen_extensions[] = { 578 &driReadDrawableExtension, 579 &driCopySubBufferExtension.base, 580 &driSwapControlExtension.base, 581 &driMediaStreamCounterExtension.base, 582 &driTexBufferExtension.base, 583 &dri2FlushExtension.base, 584 &dri2ImageExtension.base, 585 &dri2ConfigQueryExtension.base, 586 NULL 587}; 588 589/** 590 * This is the driver specific part of the createNewScreen entry point. 591 * 592 * Returns the struct gl_config supported by this driver. 593 */ 594static const __DRIconfig ** 595dri2_init_screen(__DRIscreen * sPriv) 596{ 597 const __DRIconfig **configs; 598 struct dri_screen *screen; 599 struct pipe_screen *pscreen; 600 601 screen = CALLOC_STRUCT(dri_screen); 602 if (!screen) 603 return NULL; 604 605 screen->sPriv = sPriv; 606 screen->fd = sPriv->fd; 607 608 sPriv->private = (void *)screen; 609 sPriv->extensions = dri_screen_extensions; 610 611 pscreen = driver_descriptor.create_screen(screen->fd); 612 /* dri_init_screen_helper checks pscreen for us */ 613 614 configs = dri_init_screen_helper(screen, pscreen, 32); 615 if (!configs) 616 goto fail; 617 618 sPriv->api_mask = 0; 619 if (screen->st_api->profile_mask & ST_PROFILE_DEFAULT_MASK) 620 sPriv->api_mask |= 1 << __DRI_API_OPENGL; 621 if (screen->st_api->profile_mask & ST_PROFILE_OPENGL_ES1_MASK) 622 sPriv->api_mask |= 1 << __DRI_API_GLES; 623 if (screen->st_api->profile_mask & ST_PROFILE_OPENGL_ES2_MASK) 624 sPriv->api_mask |= 1 << __DRI_API_GLES2; 625 626 screen->auto_fake_front = dri_with_format(sPriv); 627 screen->broken_invalidate = !sPriv->dri2.useInvalidate; 628 screen->lookup_egl_image = dri2_lookup_egl_image; 629 630 return configs; 631fail: 632 dri_destroy_screen_helper(screen); 633 FREE(screen); 634 return NULL; 635} 636 637static boolean 638dri2_create_context(gl_api api, const struct gl_config * visual, 639 __DRIcontext * cPriv, void *sharedContextPrivate) 640{ 641 struct dri_context *ctx = NULL; 642 643 if (!dri_create_context(api, visual, cPriv, sharedContextPrivate)) 644 return FALSE; 645 646 ctx = cPriv->driverPrivate; 647 648 return TRUE; 649} 650 651static boolean 652dri2_create_buffer(__DRIscreen * sPriv, 653 __DRIdrawable * dPriv, 654 const struct gl_config * visual, boolean isPixmap) 655{ 656 struct dri_drawable *drawable = NULL; 657 658 if (!dri_create_buffer(sPriv, dPriv, visual, isPixmap)) 659 return FALSE; 660 661 drawable = dPriv->driverPrivate; 662 663 drawable->allocate_textures = dri2_allocate_textures; 664 drawable->flush_frontbuffer = dri2_flush_frontbuffer; 665 666 return TRUE; 667} 668 669/** 670 * DRI driver virtual function table. 671 * 672 * DRI versions differ in their implementation of init_screen and swap_buffers. 673 */ 674const struct __DriverAPIRec driDriverAPI = { 675 .InitScreen = NULL, 676 .InitScreen2 = dri2_init_screen, 677 .DestroyScreen = dri_destroy_screen, 678 .CreateContext = dri2_create_context, 679 .DestroyContext = dri_destroy_context, 680 .CreateBuffer = dri2_create_buffer, 681 .DestroyBuffer = dri_destroy_buffer, 682 .MakeCurrent = dri_make_current, 683 .UnbindContext = dri_unbind_context, 684 685 .GetSwapInfo = NULL, 686 .GetDrawableMSC = NULL, 687 .WaitForMSC = NULL, 688 689 .SwapBuffers = NULL, 690 .CopySubBuffer = NULL, 691 692 .AllocateBuffer = dri2_allocate_buffer, 693 .ReleaseBuffer = dri2_release_buffer, 694}; 695 696/* This is the table of extensions that the loader will dlsym() for. */ 697PUBLIC const __DRIextension *__driDriverExtensions[] = { 698 &driCoreExtension.base, 699 &driLegacyExtension.base, 700 &driDRI2Extension.base, 701 NULL 702}; 703 704/* vim: set sw=3 ts=8 sts=3 expandtab: */ 705