#include "BsGLTexture.h" #include "BsGLSupport.h" #include "BsGLPixelFormat.h" #include "BsGLPixelBuffer.h" #include "BsException.h" #include "BsBitwise.h" #include "BsCoreThread.h" #include "BsTextureManager.h" #include "BsGLRenderTexture.h" #include "BsRenderStats.h" namespace BansheeEngine { GLTextureCore::GLTextureCore(GLSupport& support, TextureType textureType, UINT32 width, UINT32 height, UINT32 depth, UINT32 numMipmaps, PixelFormat format, int usage, bool hwGamma, UINT32 multisampleCount) : TextureCore(textureType, width, height, depth, numMipmaps, format, usage, hwGamma, multisampleCount), mTextureID(0), mGLSupport(support), mGLFormat(0) { } GLTextureCore::~GLTextureCore() { } void GLTextureCore::initialize() { UINT32 width = mProperties.getWidth(); UINT32 height = mProperties.getHeight(); UINT32 depth = mProperties.getDepth(); TextureType texType = mProperties.getTextureType(); PixelFormat pixFormat = mProperties.getFormat(); int usage = mProperties.getUsage(); UINT32 numMips = mProperties.getNumMipmaps(); // Check requested number of mipmaps UINT32 maxMips = PixelUtil::getMaxMipmaps(width, height, depth, mProperties.getFormat()); if (numMips > maxMips) BS_EXCEPT(InvalidParametersException, "Invalid number of mipmaps. Maximum allowed is: " + toString(maxMips)); if ((usage & TU_RENDERTARGET) != 0) { if (texType != TEX_TYPE_2D) BS_EXCEPT(NotImplementedException, "Only 2D render targets are supported at the moment"); } if ((usage & TU_DEPTHSTENCIL) != 0) { if (texType != TEX_TYPE_2D) BS_EXCEPT(NotImplementedException, "Only 2D depth stencil targets are supported at the moment"); if (!PixelUtil::isDepth(pixFormat)) BS_EXCEPT(NotImplementedException, "Supplied format is not a depth stencil format. Format: " + toString(pixFormat)); } // Generate texture handle glGenTextures(1, &mTextureID); // Set texture type glBindTexture(getGLTextureTarget(), mTextureID); // This needs to be set otherwise the texture doesn't get rendered glTexParameteri(getGLTextureTarget(), GL_TEXTURE_MAX_LEVEL, numMips); // Allocate internal buffer so that glTexSubImageXD can be used mGLFormat = GLPixelUtil::getClosestGLInternalFormat(pixFormat, mProperties.isHardwareGammaEnabled()); if (PixelUtil::isCompressed(pixFormat)) { if((usage & TU_RENDERTARGET) != 0) BS_EXCEPT(InvalidParametersException, "Cannot use a compressed format for a render target."); if ((usage & TU_DEPTHSTENCIL) != 0) BS_EXCEPT(InvalidParametersException, "Cannot use a compressed format for a depth stencil target."); } UINT32 sampleCount = mProperties.getMultisampleCount(); if ((usage & TU_RENDERTARGET) != 0 && mProperties.getTextureType() == TEX_TYPE_2D && sampleCount > 0) { glTexImage2DMultisample(GL_TEXTURE_2D, sampleCount, mGLFormat, width, height, GL_FALSE); } else if ((usage & TU_DEPTHSTENCIL) != 0) { if (sampleCount > 0) { glTexImage2DMultisample(GL_TEXTURE_2D, sampleCount, mGLFormat, width, height, GL_FALSE); } else { GLenum depthStencilFormat = GLPixelUtil::getDepthStencilTypeFromFormat(pixFormat); glTexImage2D(GL_TEXTURE_2D, 0, mGLFormat, width, height, 0, GL_DEPTH_STENCIL, depthStencilFormat, nullptr); } } else { GLenum baseFormat = GLPixelUtil::getGLOriginFormat(pixFormat); GLenum baseDataType = GLPixelUtil::getGLOriginDataType(pixFormat); // Run through this process to pre-generate mipmap pyramid for (UINT32 mip = 0; mip <= numMips; mip++) { switch (texType) { case TEX_TYPE_1D: glTexImage1D(GL_TEXTURE_1D, mip, mGLFormat, width, 0, baseFormat, baseDataType, nullptr); break; case TEX_TYPE_2D: glTexImage2D(GL_TEXTURE_2D, mip, mGLFormat, width, height, 0, baseFormat, baseDataType, nullptr); break; case TEX_TYPE_3D: glTexImage3D(GL_TEXTURE_3D, mip, mGLFormat, width, height, depth, 0, baseFormat, baseDataType, nullptr); break; case TEX_TYPE_CUBE_MAP: for(UINT32 face = 0; face < 6; face++) { glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, mip, mGLFormat, width, height, 0, baseFormat, baseDataType, nullptr); } break; }; if(width > 1) width = width/2; if(height > 1) height = height/2; if(depth > 1) depth = depth/2; } } createSurfaceList(); PixelBufferPtr buffer = getBuffer(0, 0); #if BS_DEBUG_MODE if(buffer != nullptr) { if(pixFormat != buffer->getFormat()) { BS_EXCEPT(InternalErrorException, "Could not create a texture buffer with wanted format: " + toString(pixFormat)); } } #endif BS_INC_RENDER_STAT_CAT(ResCreated, RenderStatObject_Texture); TextureCore::initialize(); } void GLTextureCore::destroy() { mSurfaceList.clear(); glDeleteTextures(1, &mTextureID); clearBufferViews(); BS_INC_RENDER_STAT_CAT(ResDestroyed, RenderStatObject_Texture); TextureCore::destroy(); } GLenum GLTextureCore::getGLTextureTarget() const { switch (mProperties.getTextureType()) { case TEX_TYPE_1D: return GL_TEXTURE_1D; case TEX_TYPE_2D: if (mProperties.getMultisampleCount() > 0) return GL_TEXTURE_2D_MULTISAMPLE; else return GL_TEXTURE_2D; case TEX_TYPE_3D: return GL_TEXTURE_3D; case TEX_TYPE_CUBE_MAP: return GL_TEXTURE_CUBE_MAP; default: return 0; }; } GLuint GLTextureCore::getGLID() const { THROW_IF_NOT_CORE_THREAD; return mTextureID; } PixelData GLTextureCore::lockImpl(GpuLockOptions options, UINT32 mipLevel, UINT32 face) { if (mProperties.getMultisampleCount() > 0) BS_EXCEPT(InvalidStateException, "Multisampled textures cannot be accessed from the CPU directly."); if(mLockedBuffer != nullptr) BS_EXCEPT(InternalErrorException, "Trying to lock a buffer that's already locked."); UINT32 mipWidth = mProperties.getWidth() >> mipLevel; UINT32 mipHeight = mProperties.getHeight() >> mipLevel; UINT32 mipDepth = mProperties.getDepth() >> mipLevel; PixelData lockedArea(mipWidth, mipHeight, mipDepth, mProperties.getFormat()); mLockedBuffer = getBuffer(face, mipLevel); lockedArea.setExternalBuffer((UINT8*)mLockedBuffer->lock(options)); return lockedArea; } void GLTextureCore::unlockImpl() { if(mLockedBuffer == nullptr) BS_EXCEPT(InternalErrorException, "Trying to unlock a buffer that's not locked."); mLockedBuffer->unlock(); mLockedBuffer = nullptr; } void GLTextureCore::readData(PixelData& dest, UINT32 mipLevel, UINT32 face) { if (mProperties.getMultisampleCount() > 0) BS_EXCEPT(InvalidStateException, "Multisampled textures cannot be accessed from the CPU directly."); getBuffer(face, mipLevel)->download(dest); } void GLTextureCore::writeData(const PixelData& src, UINT32 mipLevel, UINT32 face, bool discardWholeBuffer) { if (mProperties.getMultisampleCount() > 0) BS_EXCEPT(InvalidStateException, "Multisampled textures cannot be accessed from the CPU directly."); getBuffer(face, mipLevel)->upload(src, src.getExtents()); } void GLTextureCore::copyImpl(UINT32 srcFace, UINT32 srcMipLevel, UINT32 destFace, UINT32 destMipLevel, const SPtr& target) { size_t numMips = std::min(mProperties.getNumMipmaps(), target->getProperties().getNumMipmaps()); GLTextureCore* destTex = static_cast(target.get()); GLTextureBuffer *src = static_cast(getBuffer(srcFace, srcMipLevel).get()); destTex->getBuffer(destFace, destMipLevel)->blitFromTexture(src); } void GLTextureCore::createSurfaceList() { mSurfaceList.clear(); for (UINT32 face = 0; face < mProperties.getNumFaces(); face++) { for (UINT32 mip = 0; mip <= mProperties.getNumMipmaps(); mip++) { GLPixelBuffer *buf = bs_new(getGLTextureTarget(), mTextureID, face, mip, static_cast(mProperties.getUsage()), mProperties.isHardwareGammaEnabled(), mProperties.getMultisampleCount()); mSurfaceList.push_back(bs_shared_ptr(buf)); if(buf->getWidth() == 0 || buf->getHeight() == 0 || buf->getDepth() == 0) { BS_EXCEPT(RenderingAPIException, "Zero sized texture surface on texture face " + toString(face) + " mipmap "+toString(mip) + ". Probably, the GL driver refused to create the texture."); } } } } std::shared_ptr GLTextureCore::getBuffer(UINT32 face, UINT32 mipmap) { THROW_IF_NOT_CORE_THREAD; if(face >= mProperties.getNumFaces()) BS_EXCEPT(InvalidParametersException, "Face index out of range"); if (mipmap > mProperties.getNumMipmaps()) BS_EXCEPT(InvalidParametersException, "Mipmap index out of range"); unsigned int idx = face * (mProperties.getNumMipmaps() + 1) + mipmap; assert(idx < mSurfaceList.size()); return mSurfaceList[idx]; } }