#include "BsGLPixelBuffer.h" #include "BsGLTexture.h" #include "BsGLSupport.h" #include "BsGLPixelFormat.h" #include "BsException.h" #include "BsBitwise.h" #include "BsGLRenderTexture.h" #include "BsRenderStats.h" namespace BansheeEngine { GLPixelBuffer::GLPixelBuffer(UINT32 inWidth, UINT32 inHeight, UINT32 inDepth, PixelFormat inFormat, GpuBufferUsage usage) : PixelBuffer(inWidth, inHeight, inDepth, inFormat, usage, false), mBuffer(inWidth, inHeight, inDepth, inFormat), mGLInternalFormat(GL_NONE) { mCurrentLockOptions = (GpuLockOptions)0; } GLPixelBuffer::~GLPixelBuffer() { mBuffer.freeInternalBuffer(); } void GLPixelBuffer::allocateBuffer() { if(mBuffer.getData()) return; mBuffer.allocateInternalBuffer(); // TODO: use PBO if we're HBU_DYNAMIC } void GLPixelBuffer::freeBuffer() { if(mUsage & GBU_STATIC) { mBuffer.freeInternalBuffer(); } } PixelData GLPixelBuffer::lockImpl(PixelVolume lockBox, GpuLockOptions options) { allocateBuffer(); if(options != GBL_WRITE_ONLY_DISCARD) { // Download the old contents of the texture download(mBuffer); } mCurrentLockOptions = options; mLockedBox = lockBox; return mBuffer.getSubVolume(lockBox); } void GLPixelBuffer::unlockImpl(void) { if (mCurrentLockOptions != GBL_READ_ONLY) { // From buffer to card, only upload if was locked for writing upload(mCurrentLock, mLockedBox); } freeBuffer(); } void GLPixelBuffer::upload(const PixelData& data, const PixelVolume& dest) { BS_EXCEPT(RenderingAPIException, "Upload not possible for this pixel buffer type"); } void GLPixelBuffer::download(const PixelData& data) { BS_EXCEPT(RenderingAPIException, "Download not possible for this pixel buffer type"); } void GLPixelBuffer::blitFromTexture(GLTextureBuffer* src) { blitFromTexture(src, PixelVolume(0, 0, 0, src->getWidth(), src->getHeight(), src->getDepth()), PixelVolume(0, 0, 0, mWidth, mHeight, mDepth) ); } void GLPixelBuffer::blitFromTexture(GLTextureBuffer* src, const PixelVolume& srcBox, const PixelVolume& dstBox) { BS_EXCEPT(RenderingAPIException, "BlitFromTexture not possible for this pixel buffer type"); } void GLPixelBuffer::bindToFramebuffer(GLenum attachment, UINT32 zoffset) { BS_EXCEPT(RenderingAPIException, "Framebuffer bind not possible for this pixel buffer type"); } GLTextureBuffer::GLTextureBuffer(GLenum target, GLuint id, GLint face, GLint level, GpuBufferUsage usage, bool writeGamma, UINT32 multisampleCount): GLPixelBuffer(0, 0, 0, PF_UNKNOWN, usage), mTarget(target), mFaceTarget(0), mTextureID(id), mFace(face), mLevel(level), mMultisampleCount(multisampleCount) { GLint value = 0; glBindTexture(mTarget, mTextureID); // Get face identifier mFaceTarget = mTarget; if(mTarget == GL_TEXTURE_CUBE_MAP) mFaceTarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X + face; // Get width glGetTexLevelParameteriv(mFaceTarget, level, GL_TEXTURE_WIDTH, &value); mWidth = value; // Get height if(target == GL_TEXTURE_1D) value = 1; // Height always 1 for 1D textures else glGetTexLevelParameteriv(mFaceTarget, level, GL_TEXTURE_HEIGHT, &value); mHeight = value; // Get depth if(target != GL_TEXTURE_3D) value = 1; // Depth always 1 for non-3D textures else glGetTexLevelParameteriv(mFaceTarget, level, GL_TEXTURE_DEPTH, &value); mDepth = value; // Get format glGetTexLevelParameteriv(mFaceTarget, level, GL_TEXTURE_INTERNAL_FORMAT, &value); mGLInternalFormat = value; mFormat = GLPixelUtil::getClosestEngineFormat(value); // Default mRowPitch = mWidth; mSlicePitch = mHeight*mWidth; mSizeInBytes = PixelUtil::getMemorySize(mWidth, mHeight, mDepth, mFormat); // Set up pixel box mBuffer = PixelData(mWidth, mHeight, mDepth, mFormat); } GLTextureBuffer::~GLTextureBuffer() { } void GLTextureBuffer::upload(const PixelData &data, const PixelVolume &dest) { if((mUsage & TU_RENDERTARGET) != 0) BS_EXCEPT(NotImplementedException, "Writing to render texture from CPU not supported."); if ((mUsage & TU_DEPTHSTENCIL) != 0) BS_EXCEPT(NotImplementedException, "Writing to depth stencil texture from CPU not supported."); glBindTexture( mTarget, mTextureID ); if(PixelUtil::isCompressed(data.getFormat())) { if(data.getFormat() != mFormat || !data.isConsecutive()) BS_EXCEPT(InvalidParametersException, "Compressed images must be consecutive, in the source format"); GLenum format = GLPixelUtil::getClosestGLInternalFormat(mFormat); switch(mTarget) { case GL_TEXTURE_1D: if (dest.left == 0) { glCompressedTexImage1D(GL_TEXTURE_1D, mLevel, format, dest.getWidth(), 0, data.getConsecutiveSize(), data.getData()); } else { glCompressedTexSubImage1D(GL_TEXTURE_1D, mLevel, dest.left, dest.getWidth(), format, data.getConsecutiveSize(), data.getData()); } break; case GL_TEXTURE_2D: case GL_TEXTURE_CUBE_MAP: if (dest.left == 0 && dest.top == 0) { glCompressedTexImage2D(mFaceTarget, mLevel, format, dest.getWidth(), dest.getHeight(), 0, data.getConsecutiveSize(), data.getData()); } else { glCompressedTexSubImage2D(mFaceTarget, mLevel, dest.left, dest.top, dest.getWidth(), dest.getHeight(), format, data.getConsecutiveSize(), data.getData()); } break; case GL_TEXTURE_3D: if (dest.left == 0 && dest.top == 0 && dest.front == 0) { glCompressedTexImage3D(GL_TEXTURE_3D, mLevel, format, dest.getWidth(), dest.getHeight(), dest.getDepth(), 0, data.getConsecutiveSize(), data.getData()); } else { glCompressedTexSubImage3D(GL_TEXTURE_3D, mLevel, dest.left, dest.top, dest.front, dest.getWidth(), dest.getHeight(), dest.getDepth(), format, data.getConsecutiveSize(), data.getData()); } break; } } else { if(data.getWidth() != data.getRowPitch()) glPixelStorei(GL_UNPACK_ROW_LENGTH, data.getRowPitch()); if(data.getHeight()*data.getWidth() != data.getSlicePitch()) glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, (data.getSlicePitch()/data.getWidth())); if(data.getLeft() > 0 || data.getTop() > 0 || data.getFront() > 0) glPixelStorei(GL_UNPACK_SKIP_PIXELS, data.getLeft() + data.getRowPitch() * data.getTop() + data.getSlicePitch() * data.getFront()); if((data.getWidth()*PixelUtil::getNumElemBytes(data.getFormat())) & 3) glPixelStorei(GL_UNPACK_ALIGNMENT, 1); switch(mTarget) { case GL_TEXTURE_1D: glTexSubImage1D(GL_TEXTURE_1D, mLevel, dest.left, dest.getWidth(), GLPixelUtil::getGLOriginFormat(data.getFormat()), GLPixelUtil::getGLOriginDataType(data.getFormat()), data.getData()); break; case GL_TEXTURE_2D: case GL_TEXTURE_CUBE_MAP: glTexSubImage2D(mFaceTarget, mLevel, dest.left, dest.top, dest.getWidth(), dest.getHeight(), GLPixelUtil::getGLOriginFormat(data.getFormat()), GLPixelUtil::getGLOriginDataType(data.getFormat()), data.getData()); break; case GL_TEXTURE_3D: glTexSubImage3D( GL_TEXTURE_3D, mLevel, dest.left, dest.top, dest.front, dest.getWidth(), dest.getHeight(), dest.getDepth(), GLPixelUtil::getGLOriginFormat(data.getFormat()), GLPixelUtil::getGLOriginDataType(data.getFormat()), data.getData()); break; } } // Restore defaults glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, 0); glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); glPixelStorei(GL_UNPACK_ALIGNMENT, 4); BS_INC_RENDER_STAT_CAT(ResWrite, RenderStatObject_Texture); } void GLTextureBuffer::download(const PixelData &data) { if(data.getWidth() != getWidth() || data.getHeight() != getHeight() || data.getDepth() != getDepth()) BS_EXCEPT(InvalidParametersException, "only download of entire buffer is supported by GL"); glBindTexture( mTarget, mTextureID ); if(PixelUtil::isCompressed(data.getFormat())) { if(data.getFormat() != mFormat || !data.isConsecutive()) BS_EXCEPT(InvalidParametersException, "Compressed images must be consecutive, in the source format"); // Data must be consecutive and at beginning of buffer as PixelStorei not allowed // for compressed formate glGetCompressedTexImage(mFaceTarget, mLevel, data.getData()); } else { if(data.getWidth() != data.getRowPitch()) glPixelStorei(GL_PACK_ROW_LENGTH, data.getRowPitch()); if(data.getHeight()*data.getWidth() != data.getSlicePitch()) glPixelStorei(GL_PACK_IMAGE_HEIGHT, (data.getSlicePitch()/data.getWidth())); if(data.getLeft() > 0 || data.getTop() > 0 || data.getFront() > 0) glPixelStorei(GL_PACK_SKIP_PIXELS, data.getLeft() + data.getRowPitch() * data.getTop() + data.getSlicePitch() * data.getFront()); if((data.getWidth()*PixelUtil::getNumElemBytes(data.getFormat())) & 3) glPixelStorei(GL_PACK_ALIGNMENT, 1); // We can only get the entire texture glGetTexImage(mFaceTarget, mLevel, GLPixelUtil::getGLOriginFormat(data.getFormat()), GLPixelUtil::getGLOriginDataType(data.getFormat()), data.getData()); // Restore defaults glPixelStorei(GL_PACK_ROW_LENGTH, 0); glPixelStorei(GL_PACK_IMAGE_HEIGHT, 0); glPixelStorei(GL_PACK_SKIP_PIXELS, 0); glPixelStorei(GL_PACK_ALIGNMENT, 4); } BS_INC_RENDER_STAT_CAT(ResRead, RenderStatObject_Texture); } void GLTextureBuffer::bindToFramebuffer(GLenum attachment, UINT32 zoffset) { assert(zoffset < mDepth); switch(mTarget) { case GL_TEXTURE_1D: glFramebufferTexture1D(GL_FRAMEBUFFER, attachment, mFaceTarget, mTextureID, mLevel); break; case GL_TEXTURE_2D: case GL_TEXTURE_CUBE_MAP: glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, mFaceTarget, mTextureID, mLevel); break; case GL_TEXTURE_2D_MULTISAMPLE: glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, mFaceTarget, mTextureID, 0); break; case GL_TEXTURE_3D: glFramebufferTexture3D(GL_FRAMEBUFFER, attachment, mFaceTarget, mTextureID, mLevel, zoffset); break; } } void GLTextureBuffer::copyFromFramebuffer(UINT32 zoffset) { glBindTexture(mTarget, mTextureID); switch(mTarget) { case GL_TEXTURE_1D: glCopyTexSubImage1D(mFaceTarget, mLevel, 0, 0, 0, mWidth); break; case GL_TEXTURE_2D: case GL_TEXTURE_CUBE_MAP: glCopyTexSubImage2D(mFaceTarget, mLevel, 0, 0, 0, 0, mWidth, mHeight); break; case GL_TEXTURE_3D: glCopyTexSubImage3D(mFaceTarget, mLevel, 0, 0, zoffset, 0, 0, mWidth, mHeight); break; } } void GLTextureBuffer::blitFromTexture(GLTextureBuffer* src, const PixelVolume& srcBox, const PixelVolume& dstBox) { if (src->mMultisampleCount > 0 && mMultisampleCount == 0) // Resolving MS texture { if (mTarget != GL_TEXTURE_2D || mTarget != GL_TEXTURE_2D_MULTISAMPLE) BS_EXCEPT(InvalidParametersException, "Non-2D multisampled texture not supported."); GLint currentFBO = 0; glGetIntegerv(GL_FRAMEBUFFER_BINDING, ¤tFBO); GLuint readFBO = GLRTTManager::instance().getBlitReadFBO(); GLuint drawFBO = GLRTTManager::instance().getBlitDrawFBO(); // Attach source texture glBindFramebuffer(GL_FRAMEBUFFER, readFBO); src->bindToFramebuffer(0, 0); // Attach destination texture glBindFramebuffer(GL_FRAMEBUFFER, drawFBO); bindToFramebuffer(0, 0); // Perform blit glBindFramebuffer(GL_READ_FRAMEBUFFER, readFBO); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, drawFBO); glReadBuffer(GL_COLOR_ATTACHMENT0); glDrawBuffer(GL_COLOR_ATTACHMENT0); glBlitFramebuffer(srcBox.left, srcBox.top, srcBox.right, srcBox.bottom, dstBox.left, dstBox.top, dstBox.right, dstBox.bottom, GL_COLOR_BUFFER_BIT, GL_NEAREST); // Restore the previously bound FBO glBindFramebuffer(GL_FRAMEBUFFER, currentFBO); } else // Just plain copy { if (mMultisampleCount != src->mMultisampleCount) BS_EXCEPT(InvalidParametersException, "When copying textures their multisample counts must match."); if (mTarget == GL_TEXTURE_3D) // 3D textures can't have arrays so their Z coordinate is handled differently { glCopyImageSubData(src->mTextureID, src->mTarget, src->mLevel, srcBox.left, srcBox.top, srcBox.front, mTextureID, mTarget, mLevel, dstBox.left, dstBox.top, dstBox.front, srcBox.getWidth(), srcBox.getHeight(), srcBox.getDepth()); } else { glCopyImageSubData(src->mTextureID, src->mTarget, src->mLevel, srcBox.left, srcBox.top, src->mFace, mTextureID, mTarget, mLevel, dstBox.left, dstBox.top, mFace, srcBox.getWidth(), srcBox.getHeight(), 1); } } } GLRenderBuffer::GLRenderBuffer(GLenum format, UINT32 width, UINT32 height, GLsizei numSamples): GLPixelBuffer(width, height, 1, GLPixelUtil::getClosestEngineFormat(format), GBU_DYNAMIC), mRenderbufferID(0) { mGLInternalFormat = format; glGenRenderbuffers(1, &mRenderbufferID); glBindRenderbuffer(GL_RENDERBUFFER, mRenderbufferID); /// Allocate storage for depth buffer if (numSamples > 0) { glRenderbufferStorageMultisample(GL_RENDERBUFFER, numSamples, format, width, height); } else { glRenderbufferStorage(GL_RENDERBUFFER, format, width, height); } } GLRenderBuffer::~GLRenderBuffer() { glDeleteRenderbuffers(1, &mRenderbufferID); } void GLRenderBuffer::bindToFramebuffer(GLenum attachment, UINT32 zoffset) { assert(zoffset < mDepth); glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, mRenderbufferID); } };