intel_fbo.c revision 8d68a90e225d831a395ba788e425cb717eec1f9a
1/************************************************************************** 2 * 3 * Copyright 2006 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#include "main/imports.h" 30#include "main/macros.h" 31#include "main/mfeatures.h" 32#include "main/mtypes.h" 33#include "main/fbobject.h" 34#include "main/framebuffer.h" 35#include "main/renderbuffer.h" 36#include "main/context.h" 37#include "main/texrender.h" 38#include "drivers/common/meta.h" 39 40#include "intel_context.h" 41#include "intel_batchbuffer.h" 42#include "intel_buffers.h" 43#include "intel_fbo.h" 44#include "intel_mipmap_tree.h" 45#include "intel_regions.h" 46#include "intel_tex.h" 47#include "intel_span.h" 48#ifndef I915 49#include "brw_context.h" 50#endif 51 52#define FILE_DEBUG_FLAG DEBUG_FBO 53 54 55/** 56 * Create a new framebuffer object. 57 */ 58static struct gl_framebuffer * 59intel_new_framebuffer(struct gl_context * ctx, GLuint name) 60{ 61 /* Only drawable state in intel_framebuffer at this time, just use Mesa's 62 * class 63 */ 64 return _mesa_new_framebuffer(ctx, name); 65} 66 67 68/** Called by gl_renderbuffer::Delete() */ 69static void 70intel_delete_renderbuffer(struct gl_renderbuffer *rb) 71{ 72 GET_CURRENT_CONTEXT(ctx); 73 struct intel_context *intel = intel_context(ctx); 74 struct intel_renderbuffer *irb = intel_renderbuffer(rb); 75 76 ASSERT(irb); 77 78 if (intel && irb->region) { 79 intel_region_release(&irb->region); 80 } 81 82 free(irb); 83} 84 85 86/** 87 * Return a pointer to a specific pixel in a renderbuffer. 88 */ 89static void * 90intel_get_pointer(struct gl_context * ctx, struct gl_renderbuffer *rb, 91 GLint x, GLint y) 92{ 93 /* By returning NULL we force all software rendering to go through 94 * the span routines. 95 */ 96 return NULL; 97} 98 99 100/** 101 * Called via glRenderbufferStorageEXT() to set the format and allocate 102 * storage for a user-created renderbuffer. 103 */ 104static GLboolean 105intel_alloc_renderbuffer_storage(struct gl_context * ctx, struct gl_renderbuffer *rb, 106 GLenum internalFormat, 107 GLuint width, GLuint height) 108{ 109 struct intel_context *intel = intel_context(ctx); 110 struct intel_renderbuffer *irb = intel_renderbuffer(rb); 111 int cpp, tiling; 112 113 ASSERT(rb->Name != 0); 114 115 switch (internalFormat) { 116 default: 117 /* Use the same format-choice logic as for textures. 118 * Renderbuffers aren't any different from textures for us, 119 * except they're less useful because you can't texture with 120 * them. 121 */ 122 rb->Format = intel->ctx.Driver.ChooseTextureFormat(ctx, internalFormat, 123 GL_NONE, GL_NONE); 124 break; 125 case GL_STENCIL_INDEX: 126 case GL_STENCIL_INDEX1_EXT: 127 case GL_STENCIL_INDEX4_EXT: 128 case GL_STENCIL_INDEX8_EXT: 129 case GL_STENCIL_INDEX16_EXT: 130 /* These aren't actual texture formats, so force them here. */ 131 rb->Format = MESA_FORMAT_S8_Z24; 132 break; 133 } 134 135 rb->_BaseFormat = _mesa_base_fbo_format(ctx, internalFormat); 136 rb->DataType = intel_mesa_format_to_rb_datatype(rb->Format); 137 cpp = _mesa_get_format_bytes(rb->Format); 138 139 intel_flush(ctx); 140 141 /* free old region */ 142 if (irb->region) { 143 intel_region_release(&irb->region); 144 } 145 146 /* allocate new memory region/renderbuffer */ 147 148 /* alloc hardware renderbuffer */ 149 DBG("Allocating %d x %d Intel RBO\n", width, height); 150 151 tiling = I915_TILING_NONE; 152 if (intel->use_texture_tiling) { 153 GLenum base_format = _mesa_get_format_base_format(rb->Format); 154 155 if (intel->gen >= 4 && (base_format == GL_DEPTH_COMPONENT || 156 base_format == GL_DEPTH_STENCIL)) 157 tiling = I915_TILING_Y; 158 else 159 tiling = I915_TILING_X; 160 } 161 162 irb->region = intel_region_alloc(intel->intelScreen, tiling, cpp, 163 width, height, GL_TRUE); 164 if (!irb->region) 165 return GL_FALSE; /* out of memory? */ 166 167 ASSERT(irb->region->buffer); 168 169 rb->Width = width; 170 rb->Height = height; 171 172 return GL_TRUE; 173} 174 175 176#if FEATURE_OES_EGL_image 177static void 178intel_image_target_renderbuffer_storage(struct gl_context *ctx, 179 struct gl_renderbuffer *rb, 180 void *image_handle) 181{ 182 struct intel_context *intel = intel_context(ctx); 183 struct intel_renderbuffer *irb; 184 __DRIscreen *screen; 185 __DRIimage *image; 186 187 screen = intel->intelScreen->driScrnPriv; 188 image = screen->dri2.image->lookupEGLImage(screen, image_handle, 189 screen->loaderPrivate); 190 if (image == NULL) 191 return; 192 193 irb = intel_renderbuffer(rb); 194 if (irb->region) 195 intel_region_release(&irb->region); 196 intel_region_reference(&irb->region, image->region); 197 198 rb->InternalFormat = image->internal_format; 199 rb->Width = image->region->width; 200 rb->Height = image->region->height; 201 rb->Format = image->format; 202 rb->DataType = image->data_type; 203 rb->_BaseFormat = _mesa_base_fbo_format(&intel->ctx, 204 image->internal_format); 205} 206#endif 207 208/** 209 * Called for each hardware renderbuffer when a _window_ is resized. 210 * Just update fields. 211 * Not used for user-created renderbuffers! 212 */ 213static GLboolean 214intel_alloc_window_storage(struct gl_context * ctx, struct gl_renderbuffer *rb, 215 GLenum internalFormat, GLuint width, GLuint height) 216{ 217 ASSERT(rb->Name == 0); 218 rb->Width = width; 219 rb->Height = height; 220 rb->InternalFormat = internalFormat; 221 222 return GL_TRUE; 223} 224 225 226static void 227intel_resize_buffers(struct gl_context *ctx, struct gl_framebuffer *fb, 228 GLuint width, GLuint height) 229{ 230 int i; 231 232 _mesa_resize_framebuffer(ctx, fb, width, height); 233 234 fb->Initialized = GL_TRUE; /* XXX remove someday */ 235 236 if (fb->Name != 0) { 237 return; 238 } 239 240 241 /* Make sure all window system renderbuffers are up to date */ 242 for (i = BUFFER_FRONT_LEFT; i <= BUFFER_BACK_RIGHT; i++) { 243 struct gl_renderbuffer *rb = fb->Attachment[i].Renderbuffer; 244 245 /* only resize if size is changing */ 246 if (rb && (rb->Width != width || rb->Height != height)) { 247 rb->AllocStorage(ctx, rb, rb->InternalFormat, width, height); 248 } 249 } 250} 251 252 253/** Dummy function for gl_renderbuffer::AllocStorage() */ 254static GLboolean 255intel_nop_alloc_storage(struct gl_context * ctx, struct gl_renderbuffer *rb, 256 GLenum internalFormat, GLuint width, GLuint height) 257{ 258 _mesa_problem(ctx, "intel_op_alloc_storage should never be called."); 259 return GL_FALSE; 260} 261 262 263void 264intel_renderbuffer_set_region(struct intel_context *intel, 265 struct intel_renderbuffer *rb, 266 struct intel_region *region) 267{ 268 struct intel_region *old; 269 270 old = rb->region; 271 rb->region = NULL; 272 intel_region_reference(&rb->region, region); 273 intel_region_release(&old); 274} 275 276 277/** 278 * Create a new intel_renderbuffer which corresponds to an on-screen window, 279 * not a user-created renderbuffer. 280 */ 281struct intel_renderbuffer * 282intel_create_renderbuffer(gl_format format) 283{ 284 GET_CURRENT_CONTEXT(ctx); 285 286 struct intel_renderbuffer *irb; 287 288 irb = CALLOC_STRUCT(intel_renderbuffer); 289 if (!irb) { 290 _mesa_error(ctx, GL_OUT_OF_MEMORY, "creating renderbuffer"); 291 return NULL; 292 } 293 294 _mesa_init_renderbuffer(&irb->Base, 0); 295 irb->Base.ClassID = INTEL_RB_CLASS; 296 irb->Base._BaseFormat = _mesa_get_format_base_format(format); 297 irb->Base.Format = format; 298 irb->Base.InternalFormat = irb->Base._BaseFormat; 299 irb->Base.DataType = intel_mesa_format_to_rb_datatype(format); 300 301 /* intel-specific methods */ 302 irb->Base.Delete = intel_delete_renderbuffer; 303 irb->Base.AllocStorage = intel_alloc_window_storage; 304 irb->Base.GetPointer = intel_get_pointer; 305 306 return irb; 307} 308 309 310/** 311 * Create a new renderbuffer object. 312 * Typically called via glBindRenderbufferEXT(). 313 */ 314static struct gl_renderbuffer * 315intel_new_renderbuffer(struct gl_context * ctx, GLuint name) 316{ 317 /*struct intel_context *intel = intel_context(ctx); */ 318 struct intel_renderbuffer *irb; 319 320 irb = CALLOC_STRUCT(intel_renderbuffer); 321 if (!irb) { 322 _mesa_error(ctx, GL_OUT_OF_MEMORY, "creating renderbuffer"); 323 return NULL; 324 } 325 326 _mesa_init_renderbuffer(&irb->Base, name); 327 irb->Base.ClassID = INTEL_RB_CLASS; 328 329 /* intel-specific methods */ 330 irb->Base.Delete = intel_delete_renderbuffer; 331 irb->Base.AllocStorage = intel_alloc_renderbuffer_storage; 332 irb->Base.GetPointer = intel_get_pointer; 333 /* span routines set in alloc_storage function */ 334 335 return &irb->Base; 336} 337 338 339/** 340 * Called via glBindFramebufferEXT(). 341 */ 342static void 343intel_bind_framebuffer(struct gl_context * ctx, GLenum target, 344 struct gl_framebuffer *fb, struct gl_framebuffer *fbread) 345{ 346 if (target == GL_FRAMEBUFFER_EXT || target == GL_DRAW_FRAMEBUFFER_EXT) { 347 intel_draw_buffer(ctx, fb); 348 } 349 else { 350 /* don't need to do anything if target == GL_READ_FRAMEBUFFER_EXT */ 351 } 352} 353 354 355/** 356 * Called via glFramebufferRenderbufferEXT(). 357 */ 358static void 359intel_framebuffer_renderbuffer(struct gl_context * ctx, 360 struct gl_framebuffer *fb, 361 GLenum attachment, struct gl_renderbuffer *rb) 362{ 363 DBG("Intel FramebufferRenderbuffer %u %u\n", fb->Name, rb ? rb->Name : 0); 364 365 intel_flush(ctx); 366 367 _mesa_framebuffer_renderbuffer(ctx, fb, attachment, rb); 368 intel_draw_buffer(ctx, fb); 369} 370 371 372static GLboolean 373intel_update_wrapper(struct gl_context *ctx, struct intel_renderbuffer *irb, 374 struct gl_texture_image *texImage) 375{ 376 if (!intel_span_supports_format(texImage->TexFormat)) { 377 DBG("Render to texture BAD FORMAT %s\n", 378 _mesa_get_format_name(texImage->TexFormat)); 379 return GL_FALSE; 380 } else { 381 DBG("Render to texture %s\n", _mesa_get_format_name(texImage->TexFormat)); 382 } 383 384 irb->Base.Format = texImage->TexFormat; 385 irb->Base.DataType = intel_mesa_format_to_rb_datatype(texImage->TexFormat); 386 irb->Base.InternalFormat = texImage->InternalFormat; 387 irb->Base._BaseFormat = _mesa_base_fbo_format(ctx, irb->Base.InternalFormat); 388 irb->Base.Width = texImage->Width; 389 irb->Base.Height = texImage->Height; 390 391 irb->Base.Delete = intel_delete_renderbuffer; 392 irb->Base.AllocStorage = intel_nop_alloc_storage; 393 394 return GL_TRUE; 395} 396 397 398/** 399 * When glFramebufferTexture[123]D is called this function sets up the 400 * gl_renderbuffer wrapper around the texture image. 401 * This will have the region info needed for hardware rendering. 402 */ 403static struct intel_renderbuffer * 404intel_wrap_texture(struct gl_context * ctx, struct gl_texture_image *texImage) 405{ 406 const GLuint name = ~0; /* not significant, but distinct for debugging */ 407 struct intel_renderbuffer *irb; 408 409 /* make an intel_renderbuffer to wrap the texture image */ 410 irb = CALLOC_STRUCT(intel_renderbuffer); 411 if (!irb) { 412 _mesa_error(ctx, GL_OUT_OF_MEMORY, "glFramebufferTexture"); 413 return NULL; 414 } 415 416 _mesa_init_renderbuffer(&irb->Base, name); 417 irb->Base.ClassID = INTEL_RB_CLASS; 418 419 if (!intel_update_wrapper(ctx, irb, texImage)) { 420 free(irb); 421 return NULL; 422 } 423 424 return irb; 425} 426 427static void 428intel_set_draw_offset_for_image(struct intel_texture_image *intel_image, 429 int zoffset) 430{ 431 struct intel_mipmap_tree *mt = intel_image->mt; 432 unsigned int dst_x, dst_y; 433 434 /* compute offset of the particular 2D image within the texture region */ 435 intel_miptree_get_image_offset(intel_image->mt, 436 intel_image->level, 437 intel_image->face, 438 zoffset, 439 &dst_x, &dst_y); 440 441 mt->region->draw_offset = (dst_y * mt->region->pitch + dst_x) * mt->cpp; 442 mt->region->draw_x = dst_x; 443 mt->region->draw_y = dst_y; 444} 445 446/** 447 * Called by glFramebufferTexture[123]DEXT() (and other places) to 448 * prepare for rendering into texture memory. This might be called 449 * many times to choose different texture levels, cube faces, etc 450 * before intel_finish_render_texture() is ever called. 451 */ 452static void 453intel_render_texture(struct gl_context * ctx, 454 struct gl_framebuffer *fb, 455 struct gl_renderbuffer_attachment *att) 456{ 457 struct gl_texture_image *newImage 458 = att->Texture->Image[att->CubeMapFace][att->TextureLevel]; 459 struct intel_renderbuffer *irb = intel_renderbuffer(att->Renderbuffer); 460 struct intel_texture_image *intel_image; 461 462 (void) fb; 463 464 ASSERT(newImage); 465 466 intel_image = intel_texture_image(newImage); 467 if (!intel_image->mt) { 468 /* Fallback on drawing to a texture that doesn't have a miptree 469 * (has a border, width/height 0, etc.) 470 */ 471 _mesa_reference_renderbuffer(&att->Renderbuffer, NULL); 472 _mesa_render_texture(ctx, fb, att); 473 return; 474 } 475 else if (!irb) { 476 irb = intel_wrap_texture(ctx, newImage); 477 if (irb) { 478 /* bind the wrapper to the attachment point */ 479 _mesa_reference_renderbuffer(&att->Renderbuffer, &irb->Base); 480 } 481 else { 482 /* fallback to software rendering */ 483 _mesa_render_texture(ctx, fb, att); 484 return; 485 } 486 } 487 488 if (!intel_update_wrapper(ctx, irb, newImage)) { 489 _mesa_reference_renderbuffer(&att->Renderbuffer, NULL); 490 _mesa_render_texture(ctx, fb, att); 491 return; 492 } 493 494 DBG("Begin render texture tid %lx tex=%u w=%d h=%d refcount=%d\n", 495 _glthread_GetID(), 496 att->Texture->Name, newImage->Width, newImage->Height, 497 irb->Base.RefCount); 498 499 /* point the renderbufer's region to the texture image region */ 500 if (irb->region != intel_image->mt->region) { 501 if (irb->region) 502 intel_region_release(&irb->region); 503 intel_region_reference(&irb->region, intel_image->mt->region); 504 } 505 506 intel_set_draw_offset_for_image(intel_image, att->Zoffset); 507 intel_image->used_as_render_target = GL_TRUE; 508 509#ifndef I915 510 if (!brw_context(ctx)->has_surface_tile_offset && 511 (intel_image->mt->region->draw_offset & 4095) != 0) { 512 /* Original gen4 hardware couldn't draw to a non-tile-aligned 513 * destination in a miptree unless you actually setup your 514 * renderbuffer as a miptree and used the fragile 515 * lod/array_index/etc. controls to select the image. So, 516 * instead, we just make a new single-level miptree and render 517 * into that. 518 */ 519 struct intel_context *intel = intel_context(ctx); 520 struct intel_mipmap_tree *old_mt = intel_image->mt; 521 struct intel_mipmap_tree *new_mt; 522 int comp_byte = 0, texel_bytes; 523 524 if (_mesa_is_format_compressed(intel_image->base.TexFormat)) 525 comp_byte = intel_compressed_num_bytes(intel_image->base.TexFormat); 526 527 texel_bytes = _mesa_get_format_bytes(intel_image->base.TexFormat); 528 529 new_mt = intel_miptree_create(intel, newImage->TexObject->Target, 530 intel_image->base._BaseFormat, 531 intel_image->base.InternalFormat, 532 intel_image->level, 533 intel_image->level, 534 intel_image->base.Width, 535 intel_image->base.Height, 536 intel_image->base.Depth, 537 texel_bytes, comp_byte, GL_TRUE); 538 539 intel_miptree_image_copy(intel, 540 new_mt, 541 intel_image->face, 542 intel_image->level, 543 old_mt); 544 545 intel_miptree_release(intel, &intel_image->mt); 546 intel_image->mt = new_mt; 547 intel_set_draw_offset_for_image(intel_image, att->Zoffset); 548 549 intel_region_release(&irb->region); 550 intel_region_reference(&irb->region, intel_image->mt->region); 551 } 552#endif 553 /* update drawing region, etc */ 554 intel_draw_buffer(ctx, fb); 555} 556 557 558/** 559 * Called by Mesa when rendering to a texture is done. 560 */ 561static void 562intel_finish_render_texture(struct gl_context * ctx, 563 struct gl_renderbuffer_attachment *att) 564{ 565 struct intel_context *intel = intel_context(ctx); 566 struct gl_texture_object *tex_obj = att->Texture; 567 struct gl_texture_image *image = 568 tex_obj->Image[att->CubeMapFace][att->TextureLevel]; 569 struct intel_texture_image *intel_image = intel_texture_image(image); 570 571 DBG("Finish render texture tid %lx tex=%u\n", 572 _glthread_GetID(), att->Texture->Name); 573 574 /* Flag that this image may now be validated into the object's miptree. */ 575 if (intel_image) 576 intel_image->used_as_render_target = GL_FALSE; 577 578 /* Since we've (probably) rendered to the texture and will (likely) use 579 * it in the texture domain later on in this batchbuffer, flush the 580 * batch. Once again, we wish for a domain tracker in libdrm to cover 581 * usage inside of a batchbuffer like GEM does in the kernel. 582 */ 583 intel_batchbuffer_emit_mi_flush(intel); 584} 585 586/** 587 * Do additional "completeness" testing of a framebuffer object. 588 */ 589static void 590intel_validate_framebuffer(struct gl_context *ctx, struct gl_framebuffer *fb) 591{ 592 struct intel_context *intel = intel_context(ctx); 593 const struct intel_renderbuffer *depthRb = 594 intel_get_renderbuffer(fb, BUFFER_DEPTH); 595 const struct intel_renderbuffer *stencilRb = 596 intel_get_renderbuffer(fb, BUFFER_STENCIL); 597 int i; 598 599 if (depthRb && stencilRb && stencilRb != depthRb) { 600 if (fb->Attachment[BUFFER_DEPTH].Type == GL_TEXTURE && 601 fb->Attachment[BUFFER_STENCIL].Type == GL_TEXTURE && 602 (fb->Attachment[BUFFER_DEPTH].Texture->Name == 603 fb->Attachment[BUFFER_STENCIL].Texture->Name)) { 604 /* OK */ 605 } else { 606 /* we only support combined depth/stencil buffers, not separate 607 * stencil buffers. 608 */ 609 DBG("Only supports combined depth/stencil (found %s, %s)\n", 610 depthRb ? _mesa_get_format_name(depthRb->Base.Format): "NULL", 611 stencilRb ? _mesa_get_format_name(stencilRb->Base.Format): "NULL"); 612 fb->_Status = GL_FRAMEBUFFER_UNSUPPORTED_EXT; 613 } 614 } 615 616 for (i = 0; i < Elements(fb->Attachment); i++) { 617 struct gl_renderbuffer *rb; 618 struct intel_renderbuffer *irb; 619 620 if (fb->Attachment[i].Type == GL_NONE) 621 continue; 622 623 /* A supported attachment will have a Renderbuffer set either 624 * from being a Renderbuffer or being a texture that got the 625 * intel_wrap_texture() treatment. 626 */ 627 rb = fb->Attachment[i].Renderbuffer; 628 if (rb == NULL) { 629 DBG("attachment without renderbuffer\n"); 630 fb->_Status = GL_FRAMEBUFFER_UNSUPPORTED_EXT; 631 continue; 632 } 633 634 irb = intel_renderbuffer(rb); 635 if (irb == NULL) { 636 DBG("software rendering renderbuffer\n"); 637 fb->_Status = GL_FRAMEBUFFER_UNSUPPORTED_EXT; 638 continue; 639 } 640 641 if (!intel_span_supports_format(irb->Base.Format) || 642 !intel->vtbl.render_target_supported(irb->Base.Format)) { 643 DBG("Unsupported texture/renderbuffer format attached: %s\n", 644 _mesa_get_format_name(irb->Base.Format)); 645 fb->_Status = GL_FRAMEBUFFER_UNSUPPORTED_EXT; 646 } 647 } 648} 649 650 651/** 652 * Do one-time context initializations related to GL_EXT_framebuffer_object. 653 * Hook in device driver functions. 654 */ 655void 656intel_fbo_init(struct intel_context *intel) 657{ 658 intel->ctx.Driver.NewFramebuffer = intel_new_framebuffer; 659 intel->ctx.Driver.NewRenderbuffer = intel_new_renderbuffer; 660 intel->ctx.Driver.BindFramebuffer = intel_bind_framebuffer; 661 intel->ctx.Driver.FramebufferRenderbuffer = intel_framebuffer_renderbuffer; 662 intel->ctx.Driver.RenderTexture = intel_render_texture; 663 intel->ctx.Driver.FinishRenderTexture = intel_finish_render_texture; 664 intel->ctx.Driver.ResizeBuffers = intel_resize_buffers; 665 intel->ctx.Driver.ValidateFramebuffer = intel_validate_framebuffer; 666 intel->ctx.Driver.BlitFramebuffer = _mesa_meta_BlitFramebuffer; 667 668#if FEATURE_OES_EGL_image 669 intel->ctx.Driver.EGLImageTargetRenderbufferStorage = 670 intel_image_target_renderbuffer_storage; 671#endif 672} 673