/* * Mesa 3-D graphics library * * Copyright (C) 1999-2007 Brian Paul All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "main/glheader.h" #include "main/context.h" #include "main/colormac.h" #include "main/condrender.h" #include "main/macros.h" #include "main/pixeltransfer.h" #include "main/imports.h" #include "s_context.h" #include "s_depth.h" #include "s_span.h" #include "s_stencil.h" #include "s_zoom.h" /** * Determine if there's overlap in an image copy. * This test also compensates for the fact that copies are done from * bottom to top and overlaps can sometimes be handled correctly * without making a temporary image copy. * \return GL_TRUE if the regions overlap, GL_FALSE otherwise. */ static GLboolean regions_overlap(GLint srcx, GLint srcy, GLint dstx, GLint dsty, GLint width, GLint height, GLfloat zoomX, GLfloat zoomY) { if (zoomX == 1.0 && zoomY == 1.0) { /* no zoom */ if (srcx >= dstx + width || (srcx + width <= dstx)) { return GL_FALSE; } else if (srcy < dsty) { /* this is OK */ return GL_FALSE; } else if (srcy > dsty + height) { return GL_FALSE; } else { return GL_TRUE; } } else { /* add one pixel of slop when zooming, just to be safe */ if (srcx > (dstx + ((zoomX > 0.0F) ? (width * zoomX + 1.0F) : 0.0F))) { /* src is completely right of dest */ return GL_FALSE; } else if (srcx + width + 1.0F < dstx + ((zoomX > 0.0F) ? 0.0F : (width * zoomX))) { /* src is completely left of dest */ return GL_FALSE; } else if ((srcy < dsty) && (srcy + height < dsty + (height * zoomY))) { /* src is completely below dest */ return GL_FALSE; } else if ((srcy > dsty) && (srcy + height > dsty + (height * zoomY))) { /* src is completely above dest */ return GL_FALSE; } else { return GL_TRUE; } } } /** * RGBA copypixels */ static void copy_rgba_pixels(struct gl_context *ctx, GLint srcx, GLint srcy, GLint width, GLint height, GLint destx, GLint desty) { GLfloat *tmpImage, *p; GLint sy, dy, stepy, row; const GLboolean zoom = ctx->Pixel.ZoomX != 1.0F || ctx->Pixel.ZoomY != 1.0F; GLint overlapping; GLuint transferOps = ctx->_ImageTransferState; SWspan span; if (!ctx->ReadBuffer->_ColorReadBuffer) { /* no readbuffer - OK */ return; } if (ctx->DrawBuffer == ctx->ReadBuffer) { overlapping = regions_overlap(srcx, srcy, destx, desty, width, height, ctx->Pixel.ZoomX, ctx->Pixel.ZoomY); } else { overlapping = GL_FALSE; } /* Determine if copy should be done bottom-to-top or top-to-bottom */ if (!overlapping && srcy < desty) { /* top-down max-to-min */ sy = srcy + height - 1; dy = desty + height - 1; stepy = -1; } else { /* bottom-up min-to-max */ sy = srcy; dy = desty; stepy = 1; } INIT_SPAN(span, GL_BITMAP); _swrast_span_default_attribs(ctx, &span); span.arrayMask = SPAN_RGBA; span.arrayAttribs = VARYING_BIT_COL0; /* we'll fill in COL0 attrib values */ if (overlapping) { tmpImage = malloc(width * height * sizeof(GLfloat) * 4); if (!tmpImage) { _mesa_error( ctx, GL_OUT_OF_MEMORY, "glCopyPixels" ); return; } /* read the source image as RGBA/float */ p = tmpImage; for (row = 0; row < height; row++) { _swrast_read_rgba_span( ctx, ctx->ReadBuffer->_ColorReadBuffer, width, srcx, sy + row, p ); p += width * 4; } p = tmpImage; } else { tmpImage = NULL; /* silence compiler warnings */ p = NULL; } ASSERT(width < SWRAST_MAX_WIDTH); for (row = 0; row < height; row++, sy += stepy, dy += stepy) { GLvoid *rgba = span.array->attribs[VARYING_SLOT_COL0]; /* Get row/span of source pixels */ if (overlapping) { /* get from buffered image */ memcpy(rgba, p, width * sizeof(GLfloat) * 4); p += width * 4; } else { /* get from framebuffer */ _swrast_read_rgba_span( ctx, ctx->ReadBuffer->_ColorReadBuffer, width, srcx, sy, rgba ); } if (transferOps) { _mesa_apply_rgba_transfer_ops(ctx, transferOps, width, (GLfloat (*)[4]) rgba); } /* Write color span */ span.x = destx; span.y = dy; span.end = width; span.array->ChanType = GL_FLOAT; if (zoom) { _swrast_write_zoomed_rgba_span(ctx, destx, desty, &span, rgba); } else { _swrast_write_rgba_span(ctx, &span); } } span.array->ChanType = CHAN_TYPE; /* restore */ if (overlapping) free(tmpImage); } /** * Convert floating point Z values to integer Z values with pixel transfer's * Z scale and bias. */ static void scale_and_bias_z(struct gl_context *ctx, GLuint width, const GLfloat depth[], GLuint z[]) { const GLuint depthMax = ctx->DrawBuffer->_DepthMax; GLuint i; if (depthMax <= 0xffffff && ctx->Pixel.DepthScale == 1.0 && ctx->Pixel.DepthBias == 0.0) { /* no scale or bias and no clamping and no worry of overflow */ const GLfloat depthMaxF = ctx->DrawBuffer->_DepthMaxF; for (i = 0; i < width; i++) { z[i] = (GLuint) (depth[i] * depthMaxF); } } else { /* need to be careful with overflow */ const GLdouble depthMaxF = ctx->DrawBuffer->_DepthMaxF; for (i = 0; i < width; i++) { GLdouble d = depth[i] * ctx->Pixel.DepthScale + ctx->Pixel.DepthBias; d = CLAMP(d, 0.0, 1.0) * depthMaxF; if (d >= depthMaxF) z[i] = depthMax; else z[i] = (GLuint) d; } } } /* * TODO: Optimize!!!! */ static void copy_depth_pixels( struct gl_context *ctx, GLint srcx, GLint srcy, GLint width, GLint height, GLint destx, GLint desty ) { struct gl_framebuffer *fb = ctx->ReadBuffer; struct gl_renderbuffer *readRb = fb->Attachment[BUFFER_DEPTH].Renderbuffer; GLfloat *p, *tmpImage, *depth; GLint sy, dy, stepy; GLint j; const GLboolean zoom = ctx->Pixel.ZoomX != 1.0F || ctx->Pixel.ZoomY != 1.0F; GLint overlapping; SWspan span; if (!readRb) { /* no readbuffer - OK */ return; } INIT_SPAN(span, GL_BITMAP); _swrast_span_default_attribs(ctx, &span); span.arrayMask = SPAN_Z; if (ctx->DrawBuffer == ctx->ReadBuffer) { overlapping = regions_overlap(srcx, srcy, destx, desty, width, height, ctx->Pixel.ZoomX, ctx->Pixel.ZoomY); } else { overlapping = GL_FALSE; } /* Determine if copy should be bottom-to-top or top-to-bottom */ if (!overlapping && srcy < desty) { /* top-down max-to-min */ sy = srcy + height - 1; dy = desty + height - 1; stepy = -1; } else { /* bottom-up min-to-max */ sy = srcy; dy = desty; stepy = 1; } if (overlapping) { GLint ssy = sy; tmpImage = malloc(width * height * sizeof(GLfloat)); if (!tmpImage) { _mesa_error( ctx, GL_OUT_OF_MEMORY, "glCopyPixels" ); return; } p = tmpImage; for (j = 0; j < height; j++, ssy += stepy) { _swrast_read_depth_span_float(ctx, readRb, width, srcx, ssy, p); p += width; } p = tmpImage; } else { tmpImage = NULL; /* silence compiler warning */ p = NULL; } depth = malloc(width * sizeof(GLfloat)); if (!depth) { _mesa_error(ctx, GL_OUT_OF_MEMORY, "glCopyPixels()"); goto end; } for (j = 0; j < height; j++, sy += stepy, dy += stepy) { /* get depth values */ if (overlapping) { memcpy(depth, p, width * sizeof(GLfloat)); p += width; } else { _swrast_read_depth_span_float(ctx, readRb, width, srcx, sy, depth); } /* apply scale and bias */ scale_and_bias_z(ctx, width, depth, span.array->z); /* write depth values */ span.x = destx; span.y = dy; span.end = width; if (zoom) _swrast_write_zoomed_depth_span(ctx, destx, desty, &span); else _swrast_write_rgba_span(ctx, &span); } free(depth); end: if (overlapping) free(tmpImage); } static void copy_stencil_pixels( struct gl_context *ctx, GLint srcx, GLint srcy, GLint width, GLint height, GLint destx, GLint desty ) { struct gl_framebuffer *fb = ctx->ReadBuffer; struct gl_renderbuffer *rb = fb->Attachment[BUFFER_STENCIL].Renderbuffer; GLint sy, dy, stepy; GLint j; GLubyte *p, *tmpImage, *stencil; const GLboolean zoom = ctx->Pixel.ZoomX != 1.0F || ctx->Pixel.ZoomY != 1.0F; GLint overlapping; if (!rb) { /* no readbuffer - OK */ return; } if (ctx->DrawBuffer == ctx->ReadBuffer) { overlapping = regions_overlap(srcx, srcy, destx, desty, width, height, ctx->Pixel.ZoomX, ctx->Pixel.ZoomY); } else { overlapping = GL_FALSE; } /* Determine if copy should be bottom-to-top or top-to-bottom */ if (!overlapping && srcy < desty) { /* top-down max-to-min */ sy = srcy + height - 1; dy = desty + height - 1; stepy = -1; } else { /* bottom-up min-to-max */ sy = srcy; dy = desty; stepy = 1; } if (overlapping) { GLint ssy = sy; tmpImage = malloc(width * height * sizeof(GLubyte)); if (!tmpImage) { _mesa_error( ctx, GL_OUT_OF_MEMORY, "glCopyPixels" ); return; } p = tmpImage; for (j = 0; j < height; j++, ssy += stepy) { _swrast_read_stencil_span( ctx, rb, width, srcx, ssy, p ); p += width; } p = tmpImage; } else { tmpImage = NULL; /* silence compiler warning */ p = NULL; } stencil = malloc(width * sizeof(GLubyte)); if (!stencil) { _mesa_error(ctx, GL_OUT_OF_MEMORY, "glCopyPixels()"); goto end; } for (j = 0; j < height; j++, sy += stepy, dy += stepy) { /* Get stencil values */ if (overlapping) { memcpy(stencil, p, width * sizeof(GLubyte)); p += width; } else { _swrast_read_stencil_span( ctx, rb, width, srcx, sy, stencil ); } _mesa_apply_stencil_transfer_ops(ctx, width, stencil); /* Write stencil values */ if (zoom) { _swrast_write_zoomed_stencil_span(ctx, destx, desty, width, destx, dy, stencil); } else { _swrast_write_stencil_span( ctx, width, destx, dy, stencil ); } } free(stencil); end: if (overlapping) free(tmpImage); } /** * Try to do a fast 1:1 blit with memcpy. * \return GL_TRUE if successful, GL_FALSE otherwise. */ GLboolean swrast_fast_copy_pixels(struct gl_context *ctx, GLint srcX, GLint srcY, GLsizei width, GLsizei height, GLint dstX, GLint dstY, GLenum type) { struct gl_framebuffer *srcFb = ctx->ReadBuffer; struct gl_framebuffer *dstFb = ctx->DrawBuffer; struct gl_renderbuffer *srcRb, *dstRb; GLint row; GLuint pixelBytes, widthInBytes; GLubyte *srcMap, *dstMap; GLint srcRowStride, dstRowStride; if (type == GL_COLOR) { if (dstFb->_NumColorDrawBuffers != 1) return GL_FALSE; srcRb = srcFb->_ColorReadBuffer; dstRb = dstFb->_ColorDrawBuffers[0]; } else if (type == GL_STENCIL) { srcRb = srcFb->Attachment[BUFFER_STENCIL].Renderbuffer; dstRb = dstFb->Attachment[BUFFER_STENCIL].Renderbuffer; } else if (type == GL_DEPTH) { srcRb = srcFb->Attachment[BUFFER_DEPTH].Renderbuffer; dstRb = dstFb->Attachment[BUFFER_DEPTH].Renderbuffer; } else { ASSERT(type == GL_DEPTH_STENCIL_EXT); /* XXX correct? */ srcRb = srcFb->Attachment[BUFFER_DEPTH].Renderbuffer; dstRb = dstFb->Attachment[BUFFER_DEPTH].Renderbuffer; } /* src and dst renderbuffers must be same format */ if (!srcRb || !dstRb || srcRb->Format != dstRb->Format) { return GL_FALSE; } if (type == GL_STENCIL || type == GL_DEPTH_COMPONENT) { /* can't handle packed depth+stencil here */ if (_mesa_is_format_packed_depth_stencil(srcRb->Format) || _mesa_is_format_packed_depth_stencil(dstRb->Format)) return GL_FALSE; } else if (type == GL_DEPTH_STENCIL) { /* can't handle separate depth/stencil buffers */ if (srcRb != srcFb->Attachment[BUFFER_STENCIL].Renderbuffer || dstRb != dstFb->Attachment[BUFFER_STENCIL].Renderbuffer) return GL_FALSE; } /* clipping not supported */ if (srcX < 0 || srcX + width > (GLint) srcFb->Width || srcY < 0 || srcY + height > (GLint) srcFb->Height || dstX < dstFb->_Xmin || dstX + width > dstFb->_Xmax || dstY < dstFb->_Ymin || dstY + height > dstFb->_Ymax) { return GL_FALSE; } pixelBytes = _mesa_get_format_bytes(srcRb->Format); widthInBytes = width * pixelBytes; if (srcRb == dstRb) { /* map whole buffer for read/write */ /* XXX we could be clever and just map the union region of the * source and dest rects. */ GLubyte *map; GLint rowStride; ctx->Driver.MapRenderbuffer(ctx, srcRb, 0, 0, srcRb->Width, srcRb->Height, GL_MAP_READ_BIT | GL_MAP_WRITE_BIT, &map, &rowStride); if (!map) { _mesa_error(ctx, GL_OUT_OF_MEMORY, "glCopyPixels"); return GL_TRUE; /* don't retry with slow path */ } srcMap = map + srcY * rowStride + srcX * pixelBytes; dstMap = map + dstY * rowStride + dstX * pixelBytes; /* this handles overlapping copies */ if (srcY < dstY) { /* copy in reverse (top->down) order */ srcMap += rowStride * (height - 1); dstMap += rowStride * (height - 1); srcRowStride = -rowStride; dstRowStride = -rowStride; } else { /* copy in normal (bottom->up) order */ srcRowStride = rowStride; dstRowStride = rowStride; } } else { /* different src/dst buffers */ ctx->Driver.MapRenderbuffer(ctx, srcRb, srcX, srcY, width, height, GL_MAP_READ_BIT, &srcMap, &srcRowStride); if (!srcMap) { _mesa_error(ctx, GL_OUT_OF_MEMORY, "glCopyPixels"); return GL_TRUE; /* don't retry with slow path */ } ctx->Driver.MapRenderbuffer(ctx, dstRb, dstX, dstY, width, height, GL_MAP_WRITE_BIT, &dstMap, &dstRowStride); if (!dstMap) { ctx->Driver.UnmapRenderbuffer(ctx, srcRb); _mesa_error(ctx, GL_OUT_OF_MEMORY, "glCopyPixels"); return GL_TRUE; /* don't retry with slow path */ } } for (row = 0; row < height; row++) { /* memmove() in case of overlap */ memmove(dstMap, srcMap, widthInBytes); dstMap += dstRowStride; srcMap += srcRowStride; } ctx->Driver.UnmapRenderbuffer(ctx, srcRb); if (dstRb != srcRb) { ctx->Driver.UnmapRenderbuffer(ctx, dstRb); } return GL_TRUE; } /** * Find/map the renderbuffer that we'll be reading from. * The swrast_render_start() function only maps the drawing buffers, * not the read buffer. */ static struct gl_renderbuffer * map_readbuffer(struct gl_context *ctx, GLenum type) { struct gl_framebuffer *fb = ctx->ReadBuffer; struct gl_renderbuffer *rb; struct swrast_renderbuffer *srb; switch (type) { case GL_COLOR: rb = fb->Attachment[fb->_ColorReadBufferIndex].Renderbuffer; break; case GL_DEPTH: case GL_DEPTH_STENCIL: rb = fb->Attachment[BUFFER_DEPTH].Renderbuffer; break; case GL_STENCIL: rb = fb->Attachment[BUFFER_STENCIL].Renderbuffer; break; default: return NULL; } srb = swrast_renderbuffer(rb); if (!srb || srb->Map) { /* no buffer, or buffer is mapped already, we're done */ return NULL; } ctx->Driver.MapRenderbuffer(ctx, rb, 0, 0, rb->Width, rb->Height, GL_MAP_READ_BIT, &srb->Map, &srb->RowStride); return rb; } /** * Do software-based glCopyPixels. * By time we get here, all parameters will have been error-checked. */ void _swrast_CopyPixels( struct gl_context *ctx, GLint srcx, GLint srcy, GLsizei width, GLsizei height, GLint destx, GLint desty, GLenum type ) { SWcontext *swrast = SWRAST_CONTEXT(ctx); struct gl_renderbuffer *rb; if (!_mesa_check_conditional_render(ctx)) return; /* don't copy */ if (swrast->NewState) _swrast_validate_derived( ctx ); if (!(SWRAST_CONTEXT(ctx)->_RasterMask != 0x0 || ctx->Pixel.ZoomX != 1.0F || ctx->Pixel.ZoomY != 1.0F || ctx->_ImageTransferState) && swrast_fast_copy_pixels(ctx, srcx, srcy, width, height, destx, desty, type)) { /* all done */ return; } swrast_render_start(ctx); rb = map_readbuffer(ctx, type); switch (type) { case GL_COLOR: copy_rgba_pixels( ctx, srcx, srcy, width, height, destx, desty ); break; case GL_DEPTH: copy_depth_pixels( ctx, srcx, srcy, width, height, destx, desty ); break; case GL_STENCIL: copy_stencil_pixels( ctx, srcx, srcy, width, height, destx, desty ); break; case GL_DEPTH_STENCIL_EXT: /* Copy buffers separately (if the fast copy path wasn't taken) */ copy_depth_pixels(ctx, srcx, srcy, width, height, destx, desty); copy_stencil_pixels(ctx, srcx, srcy, width, height, destx, desty); break; default: _mesa_problem(ctx, "unexpected type in _swrast_CopyPixels"); } swrast_render_finish(ctx); if (rb) { struct swrast_renderbuffer *srb = swrast_renderbuffer(rb); ctx->Driver.UnmapRenderbuffer(ctx, rb); srb->Map = NULL; } }