1/* 2 * Copyright 2006-2012, Haiku, Inc. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Jérôme Duval, korli@users.berlios.de 7 * Philippe Houdoin, philippe.houdoin@free.fr 8 * Artur Wyszynski, harakash@gmail.com 9 * Alexander von Gluck, kallisti5@unixzen.com 10 */ 11 12 13#include "MesaSoftwareRenderer.h" 14 15#include <Autolock.h> 16#include <DirectWindowPrivate.h> 17#include <GraphicsDefs.h> 18#include <Screen.h> 19#include <stdio.h> 20#include <string.h> 21 22extern "C" { 23#include "extensions.h" 24#include "drivers/common/driverfuncs.h" 25#include "drivers/common/meta.h" 26#include "main/colormac.h" 27#include "main/cpuinfo.h" 28#include "main/buffers.h" 29#include "main/formats.h" 30#include "main/framebuffer.h" 31#include "main/renderbuffer.h" 32#include "swrast/swrast.h" 33#include "swrast/s_renderbuffer.h" 34#include "swrast_setup/swrast_setup.h" 35#include "tnl/tnl.h" 36#include "tnl/t_context.h" 37#include "tnl/t_pipeline.h" 38#include "vbo/vbo.h" 39 40 41//#define TRACE_SOFTGL 42#ifdef TRACE_SOFTGL 43# define TRACE(x...) printf("MesaSoftwareRenderer: " x) 44# define CALLED() printf("MesaSoftwareRenderer: %s\n", __PRETTY_FUNCTION__) 45#else 46# define TRACE(x...) 47# define CALLED() 48#endif 49 50#define ERROR(x...) printf("MesaSoftwareRenderer: " x) 51} 52 53 54extern const char* color_space_name(color_space space); 55 56 57extern "C" _EXPORT BGLRenderer* 58instantiate_gl_renderer(BGLView* view, ulong options, 59 BGLDispatcher* dispatcher) 60{ 61 return new MesaSoftwareRenderer(view, options, dispatcher); 62} 63 64 65MesaSoftwareRenderer::MesaSoftwareRenderer(BGLView* view, ulong options, 66 BGLDispatcher* dispatcher) 67 : BGLRenderer(view, options, dispatcher), 68 fBitmap(NULL), 69 fDirectModeEnabled(false), 70 fInfo(NULL), 71 fInfoLocker("info locker"), 72 fContext(NULL), 73 fVisual(NULL), 74 fFrameBuffer(NULL), 75 fFrontRenderBuffer(NULL), 76 fBackRenderBuffer(NULL), 77 fColorSpace(B_NO_COLOR_SPACE) 78{ 79 CALLED(); 80 81 fColorSpace = BScreen(GLView()->Window()).ColorSpace(); 82 83 // We force single buffering for the time being 84 options &= ~BGL_DOUBLE; 85 86 const GLboolean rgbFlag = ((options & BGL_INDEX) == 0); 87 const GLboolean alphaFlag = ((options & BGL_ALPHA) == BGL_ALPHA); 88 const GLboolean dblFlag = ((options & BGL_DOUBLE) == BGL_DOUBLE); 89 const GLboolean stereoFlag = false; 90 const GLint depth = (options & BGL_DEPTH) ? 16 : 0; 91 const GLint stencil = (options & BGL_STENCIL) ? 8 : 0; 92 const GLint accum = (options & BGL_ACCUM) ? 16 : 0; 93 const GLint red = rgbFlag ? 8 : 0; 94 const GLint green = rgbFlag ? 8 : 0; 95 const GLint blue = rgbFlag ? 8 : 0; 96 const GLint alpha = alphaFlag ? 8 : 0; 97 98 fOptions = options; // | BGL_INDIRECT; 99 struct dd_function_table functions; 100 101 fVisual = _mesa_create_visual(dblFlag, stereoFlag, red, green, 102 blue, alpha, depth, stencil, accum, accum, accum, 103 alpha ? accum : 0, 1); 104 105 // Initialize device driver function table 106 _mesa_init_driver_functions(&functions); 107 108 functions.GetString = _GetString; 109 functions.UpdateState = _UpdateState; 110 functions.MapRenderbuffer = _RenderBufferMap; 111 functions.GetBufferSize = NULL; 112 functions.Error = _Error; 113 functions.Flush = _Flush; 114 115 // create core context 116 fContext = _mesa_create_context(API_OPENGL, fVisual, NULL, 117 &functions, this); 118 119 if (!fContext) { 120 ERROR("%s: Failed to create Mesa context!\n", __func__); 121 _mesa_destroy_visual(fVisual); 122 return; 123 } 124 125 /* Initialize the software rasterizer and helper modules. */ 126 _swrast_CreateContext(fContext); 127 _vbo_CreateContext(fContext); 128 _tnl_CreateContext(fContext); 129 _swsetup_CreateContext(fContext); 130 _swsetup_Wakeup(fContext); 131 132 // Use default TCL pipeline 133 TNL_CONTEXT(fContext)->Driver.RunPipeline = _tnl_run_pipeline; 134 135 _mesa_meta_init(fContext); 136 _mesa_enable_sw_extensions(fContext); 137 _mesa_enable_1_3_extensions(fContext); 138 _mesa_enable_1_4_extensions(fContext); 139 _mesa_enable_1_5_extensions(fContext); 140 _mesa_enable_2_0_extensions(fContext); 141 _mesa_enable_2_1_extensions(fContext); 142 143 // create core framebuffer 144 fFrameBuffer = _mesa_create_framebuffer(fVisual); 145 if (fFrameBuffer == NULL) { 146 ERROR("%s: Unable to calloc GL FrameBuffer!\n", __func__); 147 _mesa_destroy_visual(fVisual); 148 return; 149 } 150 151 // Setup front render buffer 152 fFrontRenderBuffer = _NewRenderBuffer(true); 153 if (fFrontRenderBuffer == NULL) { 154 ERROR("%s: FrontRenderBuffer is requested but unallocated!\n", 155 __func__); 156 _mesa_destroy_visual(fVisual); 157 free(fFrameBuffer); 158 return; 159 } 160 _mesa_add_renderbuffer(fFrameBuffer, BUFFER_FRONT_LEFT, 161 &fFrontRenderBuffer->Base); 162 163 // Setup back render buffer (if requested) 164 if (fVisual->doubleBufferMode) { 165 fBackRenderBuffer = _NewRenderBuffer(false); 166 if (fBackRenderBuffer == NULL) { 167 ERROR("%s: BackRenderBuffer is requested but unallocated!\n", 168 __func__); 169 _mesa_destroy_visual(fVisual); 170 free(fFrameBuffer); 171 return; 172 } 173 _mesa_add_renderbuffer(fFrameBuffer, BUFFER_BACK_LEFT, 174 &fBackRenderBuffer->Base); 175 } 176 177 _swrast_add_soft_renderbuffers(fFrameBuffer, GL_FALSE, 178 fVisual->haveDepthBuffer, fVisual->haveStencilBuffer, 179 fVisual->haveAccumBuffer, alphaFlag, GL_FALSE); 180 181 BRect bounds = view->Bounds(); 182 fWidth = (GLint)bounds.Width(); 183 fHeight = (GLint)bounds.Height(); 184 185 // some stupid applications (Quake2) don't even think about calling LockGL() 186 // before using glGetString and its glGet*() friends... 187 // so make sure there is at least a valid context. 188 189 if (!_mesa_get_current_context()) { 190 LockGL(); 191 // not needed, we don't have a looper yet: UnlockLooper(); 192 } 193} 194 195 196MesaSoftwareRenderer::~MesaSoftwareRenderer() 197{ 198 CALLED(); 199 _swsetup_DestroyContext(fContext); 200 _swrast_DestroyContext(fContext); 201 _tnl_DestroyContext(fContext); 202 _vbo_DestroyContext(fContext); 203 _mesa_destroy_visual(fVisual); 204 _mesa_destroy_framebuffer(fFrameBuffer); 205 _mesa_destroy_context(fContext); 206 207 free(fInfo); 208 free(fFrameBuffer); 209 210 delete fBitmap; 211} 212 213 214void 215MesaSoftwareRenderer::LockGL() 216{ 217 CALLED(); 218 BGLRenderer::LockGL(); 219 220 _mesa_make_current(fContext, fFrameBuffer, fFrameBuffer); 221 222 color_space colorSpace = BScreen(GLView()->Window()).ColorSpace(); 223 224 GLuint width = fWidth; 225 GLuint height = fHeight; 226 227 BAutolock lock(fInfoLocker); 228 if (fDirectModeEnabled && fInfo != NULL) { 229 width = fInfo->window_bounds.right 230 - fInfo->window_bounds.left + 1; 231 height = fInfo->window_bounds.bottom 232 - fInfo->window_bounds.top + 1; 233 } 234 235 if (fColorSpace != colorSpace) { 236 fColorSpace = colorSpace; 237 _SetupRenderBuffer(&fFrontRenderBuffer->Base, fColorSpace); 238 if (fVisual->doubleBufferMode) 239 _SetupRenderBuffer(&fBackRenderBuffer->Base, fColorSpace); 240 } 241 242 _CheckResize(width, height); 243} 244 245 246void 247MesaSoftwareRenderer::UnlockGL() 248{ 249 CALLED(); 250 _mesa_make_current(fContext, NULL, NULL); 251 BGLRenderer::UnlockGL(); 252} 253 254 255void 256MesaSoftwareRenderer::SwapBuffers(bool VSync) 257{ 258 CALLED(); 259 260 if (!fBitmap) 261 return; 262 263 if (fVisual->doubleBufferMode) 264 _mesa_notifySwapBuffers(fContext); 265 266 if (!fDirectModeEnabled || fInfo == NULL) { 267 if (GLView()->LockLooperWithTimeout(1000) == B_OK) { 268 GLView()->DrawBitmap(fBitmap, B_ORIGIN); 269 GLView()->UnlockLooper(); 270 } 271 } else { 272 // TODO: Here the BGLView needs to be drawlocked. 273 _CopyToDirect(); 274 } 275 276 if (VSync) { 277 BScreen screen(GLView()->Window()); 278 screen.WaitForRetrace(); 279 } 280} 281 282 283void 284MesaSoftwareRenderer::Draw(BRect updateRect) 285{ 286 CALLED(); 287 if (fBitmap && (!fDirectModeEnabled || (fInfo == NULL))) 288 GLView()->DrawBitmap(fBitmap, updateRect, updateRect); 289} 290 291 292status_t 293MesaSoftwareRenderer::CopyPixelsOut(BPoint location, BBitmap* bitmap) 294{ 295 CALLED(); 296 color_space scs = fBitmap->ColorSpace(); 297 color_space dcs = bitmap->ColorSpace(); 298 299 if (scs != dcs && (scs != B_RGBA32 || dcs != B_RGB32)) { 300 fprintf(stderr, "CopyPixelsOut(): incompatible color space: %s != %s\n", 301 color_space_name(scs), 302 color_space_name(dcs)); 303 return B_BAD_TYPE; 304 } 305 306 BRect sr = fBitmap->Bounds(); 307 BRect dr = bitmap->Bounds(); 308 309 sr = sr & dr.OffsetBySelf(location); 310 dr = sr.OffsetByCopy(-location.x, -location.y); 311 312 uint8* ps = (uint8*)fBitmap->Bits(); 313 uint8* pd = (uint8*)bitmap->Bits(); 314 uint32* s; 315 uint32* d; 316 uint32 y; 317 for (y = (uint32)sr.top; y <= (uint32)sr.bottom; y++) { 318 s = (uint32*)(ps + y * fBitmap->BytesPerRow()); 319 s += (uint32)sr.left; 320 321 d = (uint32*)(pd + (y + (uint32)(dr.top - sr.top)) 322 * bitmap->BytesPerRow()); 323 d += (uint32)dr.left; 324 325 memcpy(d, s, dr.IntegerWidth() * 4); 326 } 327 return B_OK; 328} 329 330 331status_t 332MesaSoftwareRenderer::CopyPixelsIn(BBitmap* bitmap, BPoint location) 333{ 334 CALLED(); 335 color_space scs = bitmap->ColorSpace(); 336 color_space dcs = fBitmap->ColorSpace(); 337 338 if (scs != dcs && (dcs != B_RGBA32 || scs != B_RGB32)) { 339 fprintf(stderr, "CopyPixelsIn(): incompatible color space: %s != %s\n", 340 color_space_name(scs), 341 color_space_name(dcs)); 342 return B_BAD_TYPE; 343 } 344 345 BRect sr = bitmap->Bounds(); 346 BRect dr = fBitmap->Bounds(); 347 348 sr = sr & dr.OffsetBySelf(location); 349 dr = sr.OffsetByCopy(-location.x, -location.y); 350 351 uint8* ps = (uint8*)bitmap->Bits(); 352 uint8* pd = (uint8*)fBitmap->Bits(); 353 uint32* s; 354 uint32* d; 355 uint32 y; 356 for (y = (uint32)sr.top; y <= (uint32)sr.bottom; y++) { 357 s = (uint32*)(ps + y * bitmap->BytesPerRow()); 358 s += (uint32)sr.left; 359 360 d = (uint32*)(pd + (y + (uint32)(dr.top - sr.top)) 361 * fBitmap->BytesPerRow()); 362 d += (uint32)dr.left; 363 364 memcpy(d, s, dr.IntegerWidth() * 4); 365 } 366 return B_OK; 367} 368 369 370void 371MesaSoftwareRenderer::EnableDirectMode(bool enabled) 372{ 373 fDirectModeEnabled = enabled; 374} 375 376 377void 378MesaSoftwareRenderer::DirectConnected(direct_buffer_info* info) 379{ 380 // TODO: I'm not sure we need to do this: BGLView already 381 // keeps a local copy of the direct_buffer_info passed by 382 // BDirectWindow::DirectConnected(). 383 BAutolock lock(fInfoLocker); 384 if (info) { 385 if (!fInfo) { 386 fInfo = (direct_buffer_info*)malloc(DIRECT_BUFFER_INFO_AREA_SIZE); 387 if (!fInfo) 388 return; 389 } 390 memcpy(fInfo, info, DIRECT_BUFFER_INFO_AREA_SIZE); 391 } else if (fInfo) { 392 free(fInfo); 393 fInfo = NULL; 394 } 395} 396 397 398void 399MesaSoftwareRenderer::FrameResized(float width, float height) 400{ 401 BAutolock lock(fInfoLocker); 402 _CheckResize((GLuint)width, (GLuint)height); 403} 404 405 406void 407MesaSoftwareRenderer::_CheckResize(GLuint newWidth, GLuint newHeight) 408{ 409 CALLED(); 410 411 if (fBitmap && newWidth == fWidth 412 && newHeight == fHeight) { 413 return; 414 } 415 416 _mesa_resize_framebuffer(fContext, fFrameBuffer, newWidth, newHeight); 417 fHeight = newHeight; 418 fWidth = newWidth; 419 420 _AllocateBitmap(); 421} 422 423 424void 425MesaSoftwareRenderer::_AllocateBitmap() 426{ 427 CALLED(); 428 429 // allocate new size of back buffer bitmap 430 delete fBitmap; 431 fBitmap = NULL; 432 433 if (fWidth < 1 || fHeight < 1) { 434 TRACE("%s: Cannot allocate bitmap < 1x1!\n", __func__); 435 return; 436 } 437 438 BRect rect(0.0, 0.0, fWidth - 1, fHeight - 1); 439 fBitmap = new BBitmap(rect, fColorSpace); 440 441 #if 0 442 // Used for platform optimized drawing 443 for (uint i = 0; i < fHeight; i++) { 444 fRowAddr[fHeight - i - 1] = (GLvoid *)((GLubyte *)fBitmap->Bits() 445 + i * fBitmap->BytesPerRow()); 446 } 447 #endif 448 449 fFrameBuffer->Width = fWidth; 450 fFrameBuffer->Height = fHeight; 451 TRACE("%s: Bitmap Size: %" B_PRIu32 "\n", __func__, fBitmap->BitsLength()); 452 453 fFrontRenderBuffer->Buffer = (GLubyte*)fBitmap->Bits(); 454} 455 456 457// #pragma mark - static 458 459 460void 461MesaSoftwareRenderer::_Error(gl_context* ctx) 462{ 463 MesaSoftwareRenderer* mr = (MesaSoftwareRenderer*)ctx->DriverCtx; 464 if (mr && mr->GLView()) 465 mr->GLView()->ErrorCallback((unsigned long)ctx->ErrorValue); 466} 467 468 469const GLubyte* 470MesaSoftwareRenderer::_GetString(gl_context* ctx, GLenum name) 471{ 472 switch (name) { 473 case GL_VENDOR: 474 return (const GLubyte*) "Mesa Project"; 475 case GL_RENDERER: 476 return (const GLubyte*) "Software Rasterizer"; 477 default: 478 // Let core library handle all other cases 479 return NULL; 480 } 481} 482 483 484void 485MesaSoftwareRenderer::_UpdateState(gl_context* ctx, GLuint new_state) 486{ 487 if (!ctx) 488 return; 489 490 CALLED(); 491 _swrast_InvalidateState(ctx, new_state); 492 _swsetup_InvalidateState(ctx, new_state); 493 _vbo_InvalidateState(ctx, new_state); 494 _tnl_InvalidateState(ctx, new_state); 495} 496 497 498GLboolean 499MesaSoftwareRenderer::_RenderBufferStorage(gl_context* ctx, 500 struct gl_renderbuffer* render, GLenum internalFormat, 501 GLuint width, GLuint height) 502{ 503 CALLED(); 504 505 render->Width = width; 506 render->Height = height; 507 508 struct swrast_renderbuffer *swRenderBuffer = swrast_renderbuffer(render); 509 510 swRenderBuffer->RowStride = width * _mesa_get_format_bytes(render->Format); 511 512 return GL_TRUE; 513} 514 515 516GLboolean 517MesaSoftwareRenderer::_RenderBufferStorageMalloc(gl_context* ctx, 518 struct gl_renderbuffer* render, GLenum internalFormat, 519 GLuint width, GLuint height) 520{ 521 CALLED(); 522 523 render->Width = width; 524 render->Height = height; 525 526 struct swrast_renderbuffer *swRenderBuffer = swrast_renderbuffer(render); 527 528 if (swRenderBuffer != NULL) { 529 free(swRenderBuffer->Buffer); 530 swRenderBuffer->RowStride 531 = width * _mesa_get_format_bytes(render->Format); 532 533 uint32 size = swRenderBuffer->RowStride * height; 534 TRACE("%s: Allocate %" B_PRIu32 " bytes for RenderBuffer\n", 535 __func__, size); 536 swRenderBuffer->Buffer = (GLubyte*)malloc(size); 537 if (!swRenderBuffer->Buffer) { 538 ERROR("%s: Memory allocation failure!\n", __func__); 539 return GL_FALSE; 540 } 541 } else { 542 ERROR("%s: Couldn't obtain software renderbuffer!\n", 543 __func__); 544 return GL_FALSE; 545 } 546 547 return GL_TRUE; 548} 549 550 551void 552MesaSoftwareRenderer::_Flush(gl_context* ctx) 553{ 554 CALLED(); 555 MesaSoftwareRenderer* mr = (MesaSoftwareRenderer*)ctx->DriverCtx; 556 if ((mr->fOptions & BGL_DOUBLE) == 0) { 557 // TODO: SwapBuffers() can call _CopyToDirect(), which should 558 // be always called with with the BGLView drawlocked. 559 // This is not always the case if called from here. 560 mr->SwapBuffers(); 561 } 562} 563 564 565struct swrast_renderbuffer* 566MesaSoftwareRenderer::_NewRenderBuffer(bool front) 567{ 568 CALLED(); 569 struct swrast_renderbuffer *swRenderBuffer 570 = (struct swrast_renderbuffer*)calloc(1, sizeof *swRenderBuffer); 571 572 if (!swRenderBuffer) { 573 ERROR("%s: Failed calloc RenderBuffer\n", __func__); 574 return NULL; 575 } 576 577 _mesa_init_renderbuffer(&swRenderBuffer->Base, 0); 578 579 swRenderBuffer->Base.ClassID = HAIKU_SWRAST_RENDERBUFFER_CLASS; 580 swRenderBuffer->Base.RefCount = 1; 581 swRenderBuffer->Base.Delete = _RenderBufferDelete; 582 583 if (!front) 584 swRenderBuffer->Base.AllocStorage = _RenderBufferStorageMalloc; 585 else 586 swRenderBuffer->Base.AllocStorage = _RenderBufferStorage; 587 588 if (_SetupRenderBuffer(&swRenderBuffer->Base, fColorSpace) != B_OK) { 589 free(swRenderBuffer); 590 return NULL; 591 } 592 593 return swRenderBuffer; 594} 595 596 597status_t 598MesaSoftwareRenderer::_SetupRenderBuffer(struct gl_renderbuffer* rb, 599 color_space colorSpace) 600{ 601 CALLED(); 602 603 rb->InternalFormat = GL_RGBA; 604 605 switch (colorSpace) { 606 case B_RGBA32: 607 rb->_BaseFormat = GL_RGBA; 608 rb->Format = MESA_FORMAT_ARGB8888; 609 break; 610 case B_RGB32: 611 rb->_BaseFormat = GL_RGB; 612 rb->Format = MESA_FORMAT_XRGB8888; 613 break; 614 case B_RGB24: 615 rb->_BaseFormat = GL_RGB; 616 rb->Format = MESA_FORMAT_RGB888; 617 break; 618 case B_RGB16: 619 rb->_BaseFormat = GL_RGB; 620 rb->Format = MESA_FORMAT_RGB565; 621 break; 622 case B_RGB15: 623 rb->_BaseFormat = GL_RGB; 624 rb->Format = MESA_FORMAT_ARGB1555; 625 break; 626 default: 627 fprintf(stderr, "Unsupported screen color space %s\n", 628 color_space_name(fColorSpace)); 629 debugger("Unsupported OpenGL color space"); 630 return B_ERROR; 631 } 632 return B_OK; 633} 634 635 636/*! Y inverted Map RenderBuffer function 637 We use a BBitmap for storage which has Y inverted. 638 If the Mesa provided Map function ever allows external 639 control of this we can omit this function. 640*/ 641void 642MesaSoftwareRenderer::_RenderBufferMap(gl_context *ctx, 643 struct gl_renderbuffer *rb, GLuint x, GLuint y, GLuint w, GLuint h, 644 GLbitfield mode, GLubyte **mapOut, GLint *rowStrideOut) 645{ 646 if (rb->ClassID == HAIKU_SWRAST_RENDERBUFFER_CLASS) { 647 struct swrast_renderbuffer *srb = swrast_renderbuffer(rb); 648 const GLuint bpp = _mesa_get_format_bytes(rb->Format); 649 GLint rowStride = rb->Width * bpp; // in Bytes 650 651 y = rb->Height - y - 1; 652 653 *rowStrideOut = -rowStride; 654 *mapOut = (GLubyte *) srb->Buffer + y * rowStride + x * bpp; 655 } else { 656 _swrast_map_soft_renderbuffer(ctx, rb, x, y, w, h, mode, 657 mapOut, rowStrideOut); 658 } 659} 660 661 662void 663MesaSoftwareRenderer::_RenderBufferDelete(struct gl_renderbuffer* rb) 664{ 665 CALLED(); 666 if (rb != NULL) { 667 struct swrast_renderbuffer *swRenderBuffer 668 = swrast_renderbuffer(rb); 669 if (swRenderBuffer != NULL) 670 free(swRenderBuffer->Buffer); 671 } 672 free(rb); 673} 674 675 676void 677MesaSoftwareRenderer::_CopyToDirect() 678{ 679 BAutolock lock(fInfoLocker); 680 681 // check the bitmap size still matches the size 682 if (fInfo->window_bounds.bottom - fInfo->window_bounds.top 683 != fBitmap->Bounds().IntegerHeight() 684 || fInfo->window_bounds.right - fInfo->window_bounds.left 685 != fBitmap->Bounds().IntegerWidth()) 686 return; 687 688 uint8 bytesPerPixel = fInfo->bits_per_pixel / 8; 689 uint32 bytesPerRow = fBitmap->BytesPerRow(); 690 for (uint32 i = 0; i < fInfo->clip_list_count; i++) { 691 clipping_rect *clip = &fInfo->clip_list[i]; 692 int32 height = clip->bottom - clip->top + 1; 693 int32 bytesWidth 694 = (clip->right - clip->left + 1) * bytesPerPixel; 695 uint8* p = (uint8*)fInfo->bits + clip->top 696 * fInfo->bytes_per_row + clip->left * bytesPerPixel; 697 uint8* b = (uint8*)fBitmap->Bits() 698 + (clip->top - fInfo->window_bounds.top) * bytesPerRow 699 + (clip->left - fInfo->window_bounds.left) 700 * bytesPerPixel; 701 702 for (int y = 0; y < height; y++) { 703 memcpy(p, b, bytesWidth); 704 p += fInfo->bytes_per_row; 705 b += bytesPerRow; 706 } 707 } 708} 709