/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "GLcommon/FramebufferData.h" #include "aemu/base/files/StreamSerializing.h" #include "GLcommon/GLEScontext.h" #include "GLcommon/GLutils.h" #include "GLcommon/TextureData.h" #include #include RenderbufferData::RenderbufferData(android::base::Stream* stream) : ObjectData(stream) { attachedFB = stream->getBe32(); attachedPoint = stream->getBe32(); // TODO: load eglImageGlobalTexObject width = stream->getBe32(); height = stream->getBe32(); internalformat = stream->getBe32(); hostInternalFormat = stream->getBe32(); everBound = stream->getBe32(); } void RenderbufferData::onSave(android::base::Stream* stream, unsigned int globalName) const { ObjectData::onSave(stream, globalName); stream->putBe32(attachedFB); stream->putBe32(attachedPoint); // TODO: snapshot eglImageGlobalTexObject if (eglImageGlobalTexObject) { fprintf(stderr, "RenderbufferData::onSave: warning:" " EglImage snapshot unimplemented. \n"); } stream->putBe32(width); stream->putBe32(height); stream->putBe32(internalformat); stream->putBe32(hostInternalFormat); stream->putBe32(everBound); } void RenderbufferData::restore(ObjectLocalName localName, const getGlobalName_t& getGlobalName) { ObjectData::restore(localName, getGlobalName); int globalName = getGlobalName(NamedObjectType::RENDERBUFFER, localName); GLDispatch& dispatcher = GLEScontext::dispatcher(); dispatcher.glBindRenderbuffer(GL_RENDERBUFFER, globalName); if (hostInternalFormat != GL_NONE) { dispatcher.glRenderbufferStorage(GL_RENDERBUFFER, hostInternalFormat, width, height); } } void RenderbufferData::makeTextureDirty() { if (saveableTexture) { saveableTexture->makeDirty(); } } static GLenum s_index2Attachment(int idx); FramebufferData::FramebufferData(GLuint name, GLuint globalName) : ObjectData(FRAMEBUFFER_DATA) , m_fbName(name), m_fbGlobalName(globalName) { } FramebufferData::FramebufferData(android::base::Stream* stream) : ObjectData(stream) { m_fbName = stream->getBe32(); int attachNum = stream->getBe32(); (void)attachNum; assert(attachNum == MAX_ATTACH_POINTS); for (auto& attachPoint : m_attachPoints) { attachPoint.target = stream->getBe32(); attachPoint.name = stream->getBe32(); attachPoint.objType = (NamedObjectType)stream->getBe32(); // attachPoint.obj will be set up in postLoad attachPoint.owned = stream->getByte(); } m_dirty = stream->getByte(); m_hasBeenBound = stream->getByte(); m_hasDrawBuffers = stream->getByte(); android::base::loadBuffer(stream, &m_drawBuffers); m_readBuffer = stream->getBe32(); } FramebufferData::~FramebufferData() { for (int i=0; iputBe32(m_fbName); stream->putBe32(MAX_ATTACH_POINTS); for (auto& attachPoint : m_attachPoints) { stream->putBe32(attachPoint.target); stream->putBe32(attachPoint.name); // do not save attachPoint.obj if (attachPoint.obj) { stream->putBe32((uint32_t)ObjectDataType2NamedObjectType( attachPoint.obj->getDataType())); } else { stream->putBe32((uint32_t)NamedObjectType::NULLTYPE); } stream->putByte(attachPoint.owned); } stream->putByte(m_dirty); stream->putByte(m_hasBeenBound); stream->putByte(m_hasDrawBuffers); android::base::saveBuffer(stream, m_drawBuffers); stream->putBe32(m_readBuffer); } void FramebufferData::postLoad(const getObjDataPtr_t& getObjDataPtr) { for (auto& attachPoint : m_attachPoints) { if (NamedObjectType::NULLTYPE != attachPoint.objType) { attachPoint.obj = getObjDataPtr(attachPoint.objType, attachPoint.name); if (!attachPoint.obj) { fprintf(stderr, "FramebufferData::postLoad: warning: " "bound render buffer restore failed.\n"); attachPoint.obj.reset(new RenderbufferData); } } else { attachPoint.obj = {}; } } } void FramebufferData::restore(ObjectLocalName localName, const getGlobalName_t& getGlobalName) { ObjectData::restore(localName, getGlobalName); if (!hasBeenBoundAtLeastOnce()) return; int globalName = getGlobalName(NamedObjectType::FRAMEBUFFER, localName); GLDispatch& dispatcher = GLEScontext::dispatcher(); dispatcher.glBindFramebuffer(GL_FRAMEBUFFER, globalName); for (int i = 0; i < MAX_ATTACH_POINTS; i++) { auto& attachPoint = m_attachPoints[i]; if (!attachPoint.name) continue; // bound to nothing // attachPoint.owned is true only when color buffer 0 is // not bound. In such situation, it will generate its own object when // calling validate() if (attachPoint.owned) { attachPoint.name = 0; continue; } if (attachPoint.obj) { // binding a render buffer assert(attachPoint.obj->getDataType() == RENDERBUFFER_DATA); attachPoint.globalName = getGlobalName(NamedObjectType::RENDERBUFFER, attachPoint.name); RenderbufferData *rbData = (RenderbufferData*)attachPoint.obj.get(); if (rbData->eglImageGlobalTexObject) { fprintf(stderr, "FramebufferData::restore: warning: " "binding egl image unsupported\n"); } else { assert(attachPoint.target == GL_RENDERBUFFER); dispatcher.glFramebufferRenderbuffer( GL_FRAMEBUFFER, s_index2Attachment(i), attachPoint.target, attachPoint.globalName); } } else { // binding a texture int texGlobalName = getGlobalName(NamedObjectType::TEXTURE, attachPoint.name); attachPoint.globalName = texGlobalName; if (!texGlobalName) { fprintf(stderr, "FramebufferData::restore: warning: " "a texture is deleted without unbinding FBO\n"); } dispatcher.glFramebufferTexture2D(GL_FRAMEBUFFER, s_index2Attachment(i), attachPoint.target, texGlobalName, 0); } } m_dirty = true; if (m_hasDrawBuffers) { dispatcher.glDrawBuffers(m_drawBuffers.size(), m_drawBuffers.data()); } if (dispatcher.glReadBuffer) { dispatcher.glReadBuffer(m_readBuffer); } } void FramebufferData::makeTextureDirty(const getObjDataPtr_t& getObjDataPtr) { if (!hasBeenBoundAtLeastOnce()) return; for (int i = 0; i < MAX_ATTACH_POINTS; i++) { auto& attachPoint = m_attachPoints[i]; if (!attachPoint.name || attachPoint.owned || attachPoint.obj) { // If not bound to a texture, do nothing continue; } TextureData* texData = (TextureData*)getObjDataPtr( NamedObjectType::TEXTURE, attachPoint.name).get(); if (texData) { texData->makeDirty(); } } } void FramebufferData::setAttachment( class GLEScontext* ctx, GLenum attachment, GLenum target, GLuint name, ObjectDataPtr obj, bool takeOwnership) { int idx = attachmentPointIndex(attachment); if (!name) { detachObject(idx); return; } if (m_attachPoints[idx].target != target || m_attachPoints[idx].name != name || m_attachPoints[idx].obj.get() != obj.get() || m_attachPoints[idx].owned != takeOwnership) { detachObject(idx); m_attachPoints[idx].target = target; m_attachPoints[idx].name = name; NamedObjectType namedObjectType = target == GL_RENDERBUFFER ? NamedObjectType::RENDERBUFFER : NamedObjectType::TEXTURE; m_attachPoints[idx].globalName = name ? ctx->shareGroup()->getGlobalName(namedObjectType, name) : 0; m_attachPoints[idx].obj = obj; m_attachPoints[idx].owned = takeOwnership; if (target == GL_RENDERBUFFER_OES && obj.get() != NULL) { RenderbufferData *rbData = (RenderbufferData *)obj.get(); rbData->attachedFB = m_fbName; rbData->attachedPoint = attachment; } m_dirty = true; refreshSeparateDepthStencilAttachmentState(); } } GLuint FramebufferData::getAttachment(GLenum attachment, GLenum *outTarget, ObjectDataPtr *outObj) { int idx = attachmentPointIndex(attachment); if (outTarget) *outTarget = m_attachPoints[idx].target; if (outObj) *outObj = m_attachPoints[idx].obj; return m_attachPoints[idx].name; } GLint FramebufferData::getAttachmentSamples(GLEScontext* ctx, GLenum attachment) { int idx = attachmentPointIndex(attachment); // Don't expose own attachments. if (m_attachPoints[idx].owned) return 0; GLenum target = m_attachPoints[idx].target; GLuint name = m_attachPoints[idx].name; if (target == GL_RENDERBUFFER) { RenderbufferData* rbData = (RenderbufferData*) ctx->shareGroup()->getObjectData(NamedObjectType::RENDERBUFFER, name); return rbData ? rbData->samples : 0; } else { TextureData* texData = (TextureData*) ctx->shareGroup()->getObjectData(NamedObjectType::TEXTURE, name); return texData ? texData->samples : 0; } } void FramebufferData::getAttachmentDimensions(GLEScontext* ctx, GLenum attachment, GLint* width, GLint* height) { int idx = attachmentPointIndex(attachment); // Don't expose own attachments. if (m_attachPoints[idx].owned) return; GLenum target = m_attachPoints[idx].target; GLuint name = m_attachPoints[idx].name; if (target == GL_RENDERBUFFER) { RenderbufferData* rbData = (RenderbufferData*) ctx->shareGroup()->getObjectData(NamedObjectType::RENDERBUFFER, name); if (rbData) { *width = rbData->width; *height = rbData->height; } } else { TextureData* texData = (TextureData*) ctx->shareGroup()->getObjectData(NamedObjectType::TEXTURE, name); if (texData) { *width = texData->width; *height = texData->height; } } } GLint FramebufferData::getAttachmentInternalFormat(GLEScontext* ctx, GLenum attachment) { int idx = attachmentPointIndex(attachment); // Don't expose own attachments. if (m_attachPoints[idx].owned) return 0; GLenum target = m_attachPoints[idx].target; GLuint name = m_attachPoints[idx].name; if (target == GL_RENDERBUFFER) { RenderbufferData* rbData = (RenderbufferData*) ctx->shareGroup()->getObjectData(NamedObjectType::RENDERBUFFER, name); return rbData ? rbData->internalformat : 0; } else { TextureData* texData = (TextureData*) ctx->shareGroup()->getObjectData(NamedObjectType::TEXTURE, name); return texData? texData->internalFormat : 0; } } void FramebufferData::separateDepthStencilWorkaround(GLEScontext* ctx) { // Swiftshader does not need the workaround as it allows separate depth/stencil. if (isGles2Gles()) return; // bug: 78083376 // // Some apps rely on using separate depth/stencil attachments with separate // backing images. This affects macOS OpenGL because it does not allow // separate depth/stencil attachments with separate backing images. // // Emulate them here with a single combined backing image. #ifdef __APPLE__ if (!m_hasSeparateDepthStencil || m_separateDSEmulationRbo) return; GLuint prevRboBinding; GLuint prevFboBinding; auto& gl = ctx->dispatcher(); // Use the depth stencil's dimensions. GLint widthDepth; GLint heightDepth; getAttachmentDimensions(ctx, GL_DEPTH_ATTACHMENT, &widthDepth, &heightDepth); gl.glGetIntegerv(GL_RENDERBUFFER_BINDING, (GLint*)&prevRboBinding); gl.glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, (GLint*)&prevFboBinding); gl.glGenRenderbuffers(1, &m_separateDSEmulationRbo); gl.glBindRenderbuffer(GL_RENDERBUFFER, m_separateDSEmulationRbo); gl.glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, widthDepth, heightDepth); gl.glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_fbGlobalName); gl.glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, m_separateDSEmulationRbo); gl.glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, m_separateDSEmulationRbo); gl.glBindFramebuffer(GL_DRAW_FRAMEBUFFER, prevFboBinding); gl.glBindRenderbuffer(GL_RENDERBUFFER, prevRboBinding); #endif } int FramebufferData::attachmentPointIndex(GLenum attachment) { switch(attachment) { case GL_COLOR_ATTACHMENT0_OES: return 0; case GL_DEPTH_ATTACHMENT_OES: return 1; case GL_STENCIL_ATTACHMENT_OES: return 2; case GL_DEPTH_STENCIL_ATTACHMENT: return 3; default: { // for colorbuffer 1 ~ 15, they are continuous int idx = attachment - GL_COLOR_ATTACHMENT1 + 4; // in case for some new attachment extensions if (idx < 4 || idx > MAX_ATTACH_POINTS) { idx = MAX_ATTACH_POINTS; } return idx; } } } static GLenum s_index2Attachment(int idx) { switch (idx) { case 0: return GL_COLOR_ATTACHMENT0_OES; case 1: return GL_DEPTH_ATTACHMENT_OES; case 2: return GL_STENCIL_ATTACHMENT_OES; case 3: return GL_DEPTH_STENCIL_ATTACHMENT; default: return idx - 4 + GL_COLOR_ATTACHMENT1; } } void FramebufferData::detachObject(int idx) { if (m_attachPoints[idx].target == GL_RENDERBUFFER_OES && m_attachPoints[idx].obj.get() != NULL) { RenderbufferData *rbData = (RenderbufferData *)m_attachPoints[idx].obj.get(); rbData->attachedFB = 0; rbData->attachedPoint = 0; } if(m_attachPoints[idx].owned) { switch(m_attachPoints[idx].target) { case GL_RENDERBUFFER_OES: GLEScontext::dispatcher().glDeleteRenderbuffers(1, &(m_attachPoints[idx].name)); break; case GL_TEXTURE_2D: GLEScontext::dispatcher().glDeleteTextures(1, &(m_attachPoints[idx].name)); break; } } m_attachPoints[idx] = {}; refreshSeparateDepthStencilAttachmentState(); } // bug: 78083376 // // Check attachment state and delete / recreate original depth/stencil // attachments if necessary. // void FramebufferData::refreshSeparateDepthStencilAttachmentState() { m_hasSeparateDepthStencil = false; ObjectDataPtr depthObject = m_attachPoints[attachmentPointIndex(GL_DEPTH_ATTACHMENT)].obj; ObjectDataPtr stencilObject = m_attachPoints[attachmentPointIndex(GL_STENCIL_ATTACHMENT)].obj; m_hasSeparateDepthStencil = depthObject && stencilObject && (depthObject != stencilObject); if (m_hasSeparateDepthStencil) return; // Delete the emulated RBO and restore the original // if we don't have separate depth/stencil anymore. auto& gl = GLEScontext::dispatcher(); if (!m_separateDSEmulationRbo) return; gl.glDeleteRenderbuffers(1, &m_separateDSEmulationRbo); m_separateDSEmulationRbo = 0; // Now that we don't have separate depth/stencil attachments, // we might need to restore one of the original attachments, // because we were using a nonzero m_separateDSEmulationRbo. GLenum attachmentToRestore = m_attachPoints[attachmentPointIndex(GL_DEPTH_ATTACHMENT)].name ? GL_DEPTH_ATTACHMENT : ( m_attachPoints[attachmentPointIndex(GL_STENCIL_ATTACHMENT)].name ? GL_STENCIL_ATTACHMENT : 0); if (!attachmentToRestore) return; GLuint objectToRestore = m_attachPoints[attachmentPointIndex(attachmentToRestore)].globalName; GLenum objectTypeToRestore = m_attachPoints[attachmentPointIndex(attachmentToRestore)].target; GLuint prevFboBinding; gl.glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, (GLint*)&prevFboBinding); gl.glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_fbGlobalName); switch (objectTypeToRestore) { case GL_RENDERBUFFER: gl.glFramebufferRenderbuffer( GL_DRAW_FRAMEBUFFER, attachmentToRestore, GL_RENDERBUFFER, objectToRestore); break; case GL_TEXTURE_2D: gl.glFramebufferTexture2D( GL_DRAW_FRAMEBUFFER, attachmentToRestore, GL_TEXTURE_2D, objectToRestore, 0); break; } gl.glBindFramebuffer(GL_DRAW_FRAMEBUFFER, prevFboBinding); } void FramebufferData::validate(GLEScontext* ctx) { // Do not validate if on another GLES2 backend if (isGles2Gles()) return; if(!getAttachment(GL_COLOR_ATTACHMENT0_OES, NULL, NULL)) { // GLES does not require the framebuffer to have a color attachment. // OpenGL does. Therefore, if no color is attached, create a dummy // color texture and attach it. // This dummy color texture will is owned by the FramebufferObject, // and will be released by it when its object is detached. GLint type = GL_NONE; GLint name = 0; ctx->dispatcher().glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT_OES, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &type); if(type != GL_NONE) { ctx->dispatcher().glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT_OES, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &name); } else { ctx->dispatcher().glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT_OES, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &type); if(type != GL_NONE) { ctx->dispatcher().glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT_OES, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &name); } else { // No color, depth or stencil attachments - do nothing return; } } // Find the existing attachment(s) dimensions GLint width = 0; GLint height = 0; if(type == GL_RENDERBUFFER) { GLint prev; ctx->dispatcher().glGetIntegerv(GL_RENDERBUFFER_BINDING, &prev); ctx->dispatcher().glBindRenderbuffer(GL_RENDERBUFFER, name); ctx->dispatcher().glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &width); ctx->dispatcher().glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &height); ctx->dispatcher().glBindRenderbuffer(GL_RENDERBUFFER, prev); } else if(type == GL_TEXTURE) { GLint prev; ctx->dispatcher().glGetIntegerv(GL_TEXTURE_BINDING_2D, &prev); ctx->dispatcher().glBindTexture(GL_TEXTURE_2D, name); ctx->dispatcher().glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width); ctx->dispatcher().glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height); ctx->dispatcher().glBindTexture(GL_TEXTURE_2D, prev); } // Create the color attachment and attch it unsigned int tex = 0; ctx->dispatcher().glGenTextures(1, &tex); GLint prev; ctx->dispatcher().glGetIntegerv(GL_TEXTURE_BINDING_2D, &prev); ctx->dispatcher().glBindTexture(GL_TEXTURE_2D, tex); ctx->dispatcher().glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); ctx->dispatcher().glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); ctx->dispatcher().glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); ctx->dispatcher().glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); ctx->dispatcher().glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); ctx->dispatcher().glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0_OES, GL_TEXTURE_2D, tex, 0); setAttachment(ctx, GL_COLOR_ATTACHMENT0_OES, GL_TEXTURE_2D, tex, ObjectDataPtr(), true); ctx->dispatcher().glBindTexture(GL_TEXTURE_2D, prev); } if(m_dirty) { // This is a workaround for a bug found in several OpenGL // drivers (e.g. ATI's) - after the framebuffer attachments // have changed, and before the next draw, unbind and rebind // the framebuffer to sort things out. ctx->dispatcher().glBindFramebuffer(GL_FRAMEBUFFER,0); ctx->dispatcher().glBindFramebuffer( GL_FRAMEBUFFER, m_fbGlobalName); m_dirty = false; } } void FramebufferData::setDrawBuffers(GLsizei n, const GLenum * bufs) { m_drawBuffers.resize(n); memcpy(m_drawBuffers.data(), bufs, n * sizeof(GLenum)); m_hasDrawBuffers = true; } void FramebufferData::setReadBuffers(GLenum src) { m_readBuffer = src; }