| /*------------------------------------------------------------------------- |
| * drawElements Quality Program OpenGL (ES) Module |
| * ----------------------------------------------- |
| * |
| * Copyright 2014 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. |
| * |
| *//*! |
| * \file |
| * \brief Utilities for framebuffer objects. |
| *//*--------------------------------------------------------------------*/ |
| |
| #include "glsFboUtil.hpp" |
| |
| #include "glwEnums.hpp" |
| #include "deUniquePtr.hpp" |
| #include "gluTextureUtil.hpp" |
| #include "gluStrUtil.hpp" |
| #include "deStringUtil.hpp" |
| #include "deSTLUtil.hpp" |
| #include <sstream> |
| |
| using namespace glw; |
| using de::toString; |
| using de::UniquePtr; |
| using glu::ContextInfo; |
| using glu::ContextType; |
| using glu::getFramebufferAttachmentName; |
| using glu::getFramebufferAttachmentTypeName; |
| using glu::getFramebufferTargetName; |
| using glu::getTextureFormatName; |
| using glu::getTextureTargetName; |
| using glu::getTransferFormat; |
| using glu::getTypeName; |
| using glu::mapGLInternalFormat; |
| using glu::mapGLTransferFormat; |
| using glu::RenderContext; |
| using glu::TransferFormat; |
| using std::istream_iterator; |
| using std::istringstream; |
| using std::set; |
| using std::string; |
| using std::vector; |
| using tcu::NotSupportedError; |
| using tcu::TestLog; |
| using tcu::TextureFormat; |
| |
| namespace deqp |
| { |
| namespace gls |
| { |
| |
| namespace FboUtil |
| { |
| |
| #if defined(DE_DEBUG) |
| static bool isFramebufferStatus(glw::GLenum fboStatus) |
| { |
| return glu::getFramebufferStatusName(fboStatus) != DE_NULL; |
| } |
| |
| static bool isErrorCode(glw::GLenum errorCode) |
| { |
| return glu::getErrorName(errorCode) != DE_NULL; |
| } |
| #endif |
| |
| std::ostream &operator<<(std::ostream &stream, const ImageFormat &format) |
| { |
| if (format.unsizedType == GL_NONE) |
| { |
| // sized format |
| return stream << glu::getTextureFormatStr(format.format); |
| } |
| else |
| { |
| // unsized format |
| return stream << "(format = " << glu::getTextureFormatStr(format.format) |
| << ", type = " << glu::getTypeStr(format.unsizedType) << ")"; |
| } |
| } |
| |
| void FormatDB::addCoreFormat(ImageFormat format, FormatFlags newFlags) |
| { |
| FormatFlags &flags = m_formatFlags[format]; |
| flags = FormatFlags(flags | newFlags); |
| } |
| |
| void FormatDB::addExtensionFormat(ImageFormat format, FormatFlags newFlags, |
| const std::set<std::string> &requiredExtensions) |
| { |
| DE_ASSERT(!requiredExtensions.empty()); |
| |
| { |
| FormatFlags &flags = m_formatFlags[format]; |
| flags = FormatFlags(flags | newFlags); |
| } |
| |
| { |
| std::set<ExtensionInfo> &extensionInfo = m_formatExtensions[format]; |
| ExtensionInfo extensionRecord; |
| |
| extensionRecord.flags = newFlags; |
| extensionRecord.requiredExtensions = requiredExtensions; |
| |
| DE_ASSERT(!de::contains(extensionInfo, extensionRecord)); // extensions specified only once |
| extensionInfo.insert(extensionRecord); |
| } |
| } |
| |
| // Not too fast at the moment, might consider indexing? |
| Formats FormatDB::getFormats(FormatFlags requirements) const |
| { |
| Formats ret; |
| for (FormatMap::const_iterator it = m_formatFlags.begin(); it != m_formatFlags.end(); it++) |
| { |
| if ((it->second & requirements) == requirements) |
| ret.insert(it->first); |
| } |
| return ret; |
| } |
| |
| bool FormatDB::isKnownFormat(ImageFormat format) const |
| { |
| return de::contains(m_formatFlags, format); |
| } |
| |
| FormatFlags FormatDB::getFormatInfo(ImageFormat format) const |
| { |
| DE_ASSERT(de::contains(m_formatFlags, format)); |
| return de::lookup(m_formatFlags, format); |
| } |
| |
| std::set<std::set<std::string>> FormatDB::getFormatFeatureExtensions(ImageFormat format, FormatFlags requirements) const |
| { |
| DE_ASSERT(de::contains(m_formatExtensions, format)); |
| |
| const std::set<ExtensionInfo> &extensionInfo = de::lookup(m_formatExtensions, format); |
| std::set<std::set<std::string>> ret; |
| |
| for (std::set<ExtensionInfo>::const_iterator it = extensionInfo.begin(); it != extensionInfo.end(); ++it) |
| { |
| if ((it->flags & requirements) == requirements) |
| ret.insert(it->requiredExtensions); |
| } |
| |
| return ret; |
| } |
| |
| bool FormatDB::ExtensionInfo::operator<(const ExtensionInfo &other) const |
| { |
| return (requiredExtensions < other.requiredExtensions) || |
| ((requiredExtensions == other.requiredExtensions) && (flags < other.flags)); |
| } |
| |
| static bool detectGLESCompatibleContext(const RenderContext &ctx, int requiredMajor, int requiredMinor) |
| { |
| const glw::Functions &gl = ctx.getFunctions(); |
| glw::GLint majorVersion = 0; |
| glw::GLint minorVersion = 0; |
| |
| // Detect compatible GLES context by querying GL_MAJOR_VERSION. |
| // This query does not exist on GLES2 so a failing query implies |
| // GLES2 context. |
| |
| gl.getIntegerv(GL_MAJOR_VERSION, &majorVersion); |
| if (gl.getError() != GL_NO_ERROR) |
| majorVersion = 2; |
| |
| gl.getIntegerv(GL_MINOR_VERSION, &minorVersion); |
| if (gl.getError() != GL_NO_ERROR) |
| minorVersion = 0; |
| |
| return (majorVersion > requiredMajor) || (majorVersion == requiredMajor && minorVersion >= requiredMinor); |
| } |
| |
| static bool checkExtensionSupport(const ContextInfo &ctxInfo, const RenderContext &ctx, const std::string &extension) |
| { |
| if (de::beginsWith(extension, "GL_")) |
| return ctxInfo.isExtensionSupported(extension.c_str()); |
| else if (extension == "DEQP_gles3_core_compatible") |
| return detectGLESCompatibleContext(ctx, 3, 0); |
| else if (extension == "DEQP_gles31_core_compatible") |
| return detectGLESCompatibleContext(ctx, 3, 1); |
| else |
| { |
| DE_ASSERT(false); |
| return false; |
| } |
| } |
| |
| bool checkExtensionSupport(const RenderContext &ctx, const std::string &extension) |
| { |
| const de::UniquePtr<ContextInfo> info(ContextInfo::create(ctx)); |
| return checkExtensionSupport(*info, ctx, extension); |
| } |
| |
| std::string getExtensionDescription(const std::string &extension) |
| { |
| if (de::beginsWith(extension, "GL_")) |
| return extension; |
| else if (extension == "DEQP_gles3_core_compatible") |
| return "GLES3 compatible context"; |
| else if (extension == "DEQP_gles31_core_compatible") |
| return "GLES3.1 compatible context"; |
| else |
| { |
| DE_ASSERT(false); |
| return ""; |
| } |
| } |
| |
| void addFormats(FormatDB &db, FormatEntries stdFmts) |
| { |
| for (const FormatEntry *it = stdFmts.begin(); it != stdFmts.end(); it++) |
| { |
| for (const FormatKey *it2 = it->second.begin(); it2 != it->second.end(); it2++) |
| db.addCoreFormat(formatKeyInfo(*it2), it->first); |
| } |
| } |
| |
| void addExtFormats(FormatDB &db, FormatExtEntries extFmts, const RenderContext *ctx) |
| { |
| const UniquePtr<ContextInfo> ctxInfo(ctx != DE_NULL ? ContextInfo::create(*ctx) : DE_NULL); |
| for (const FormatExtEntry *entryIt = extFmts.begin(); entryIt != extFmts.end(); entryIt++) |
| { |
| bool supported = true; |
| std::set<std::string> requiredExtensions; |
| |
| // parse required extensions |
| { |
| istringstream tokenStream(string(entryIt->extensions)); |
| istream_iterator<string> tokens((tokenStream)), end; |
| |
| while (tokens != end) |
| { |
| requiredExtensions.insert(*tokens); |
| ++tokens; |
| } |
| } |
| |
| // check support |
| if (ctxInfo) |
| { |
| for (std::set<std::string>::const_iterator extIt = requiredExtensions.begin(); |
| extIt != requiredExtensions.end(); ++extIt) |
| { |
| if (!checkExtensionSupport(*ctxInfo, *ctx, *extIt)) |
| { |
| supported = false; |
| break; |
| } |
| } |
| } |
| |
| if (supported) |
| for (const FormatKey *i2 = entryIt->formats.begin(); i2 != entryIt->formats.end(); i2++) |
| db.addExtensionFormat(formatKeyInfo(*i2), FormatFlags(entryIt->flags), requiredExtensions); |
| } |
| } |
| |
| FormatFlags formatFlag(GLenum context) |
| { |
| switch (context) |
| { |
| case GL_NONE: |
| return FormatFlags(0); |
| case GL_RENDERBUFFER: |
| return RENDERBUFFER_VALID; |
| case GL_TEXTURE: |
| return TEXTURE_VALID; |
| case GL_STENCIL_ATTACHMENT: |
| return STENCIL_RENDERABLE; |
| case GL_DEPTH_ATTACHMENT: |
| return DEPTH_RENDERABLE; |
| default: |
| DE_ASSERT(context >= GL_COLOR_ATTACHMENT0 && context <= GL_COLOR_ATTACHMENT15); |
| return COLOR_RENDERABLE; |
| } |
| } |
| |
| static FormatFlags getAttachmentRenderabilityFlag(GLenum attachment) |
| { |
| switch (attachment) |
| { |
| case GL_STENCIL_ATTACHMENT: |
| return STENCIL_RENDERABLE; |
| case GL_DEPTH_ATTACHMENT: |
| return DEPTH_RENDERABLE; |
| |
| default: |
| DE_ASSERT(attachment >= GL_COLOR_ATTACHMENT0 && attachment <= GL_COLOR_ATTACHMENT15); |
| return COLOR_RENDERABLE; |
| } |
| } |
| |
| namespace config |
| { |
| |
| GLsizei imageNumSamples(const Image &img) |
| { |
| if (const Renderbuffer *rbo = dynamic_cast<const Renderbuffer *>(&img)) |
| return rbo->numSamples; |
| return 0; |
| } |
| |
| static GLenum glTarget(const Image &img) |
| { |
| if (dynamic_cast<const Renderbuffer *>(&img) != DE_NULL) |
| return GL_RENDERBUFFER; |
| if (dynamic_cast<const Texture2D *>(&img) != DE_NULL) |
| return GL_TEXTURE_2D; |
| if (dynamic_cast<const TextureCubeMap *>(&img) != DE_NULL) |
| return GL_TEXTURE_CUBE_MAP; |
| if (dynamic_cast<const Texture3D *>(&img) != DE_NULL) |
| return GL_TEXTURE_3D; |
| if (dynamic_cast<const Texture2DArray *>(&img) != DE_NULL) |
| return GL_TEXTURE_2D_ARRAY; |
| |
| DE_FATAL("Impossible image type"); |
| return GL_NONE; |
| } |
| |
| static void glInitFlat(const TextureFlat &cfg, GLenum target, const glw::Functions &gl) |
| { |
| const TransferFormat format = transferImageFormat(cfg.internalFormat); |
| GLint w = cfg.width; |
| GLint h = cfg.height; |
| for (GLint level = 0; level < cfg.numLevels; level++) |
| { |
| gl.texImage2D(target, level, cfg.internalFormat.format, w, h, 0, format.format, format.dataType, DE_NULL); |
| w = de::max(1, w / 2); |
| h = de::max(1, h / 2); |
| } |
| } |
| |
| static void glInitLayered(const TextureLayered &cfg, GLint depth_divider, const glw::Functions &gl) |
| { |
| const TransferFormat format = transferImageFormat(cfg.internalFormat); |
| GLint w = cfg.width; |
| GLint h = cfg.height; |
| GLint depth = cfg.numLayers; |
| for (GLint level = 0; level < cfg.numLevels; level++) |
| { |
| gl.texImage3D(glTarget(cfg), level, cfg.internalFormat.format, w, h, depth, 0, format.format, format.dataType, |
| DE_NULL); |
| w = de::max(1, w / 2); |
| h = de::max(1, h / 2); |
| depth = de::max(1, depth / depth_divider); |
| } |
| } |
| |
| static void glInit(const Texture &cfg, const glw::Functions &gl) |
| { |
| if (const Texture2D *t2d = dynamic_cast<const Texture2D *>(&cfg)) |
| glInitFlat(*t2d, glTarget(*t2d), gl); |
| else if (const TextureCubeMap *tcm = dynamic_cast<const TextureCubeMap *>(&cfg)) |
| { |
| // \todo [2013-12-05 lauri] |
| // move this to glu or someplace sensible (this array is already |
| // present in duplicates) |
| static const GLenum s_cubeMapFaces[] = { |
| GL_TEXTURE_CUBE_MAP_NEGATIVE_X, GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, |
| GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, GL_TEXTURE_CUBE_MAP_POSITIVE_Z, |
| }; |
| const Range<GLenum> range = GLS_ARRAY_RANGE(s_cubeMapFaces); |
| for (const GLenum *it = range.begin(); it != range.end(); it++) |
| glInitFlat(*tcm, *it, gl); |
| } |
| else if (const Texture3D *t3d = dynamic_cast<const Texture3D *>(&cfg)) |
| glInitLayered(*t3d, 2, gl); |
| else if (const Texture2DArray *t2a = dynamic_cast<const Texture2DArray *>(&cfg)) |
| glInitLayered(*t2a, 1, gl); |
| } |
| |
| static GLuint glCreate(const Image &cfg, const glw::Functions &gl) |
| { |
| GLuint ret = 0; |
| if (const Renderbuffer *const rbo = dynamic_cast<const Renderbuffer *>(&cfg)) |
| { |
| gl.genRenderbuffers(1, &ret); |
| gl.bindRenderbuffer(GL_RENDERBUFFER, ret); |
| |
| if (rbo->numSamples == 0) |
| gl.renderbufferStorage(GL_RENDERBUFFER, rbo->internalFormat.format, rbo->width, rbo->height); |
| else |
| gl.renderbufferStorageMultisample(GL_RENDERBUFFER, rbo->numSamples, rbo->internalFormat.format, rbo->width, |
| rbo->height); |
| |
| gl.bindRenderbuffer(GL_RENDERBUFFER, 0); |
| } |
| else if (const Texture *const tex = dynamic_cast<const Texture *>(&cfg)) |
| { |
| gl.genTextures(1, &ret); |
| gl.bindTexture(glTarget(*tex), ret); |
| glInit(*tex, gl); |
| gl.bindTexture(glTarget(*tex), 0); |
| } |
| else |
| DE_FATAL("Impossible image type"); |
| return ret; |
| } |
| |
| static void glDelete(const Image &cfg, GLuint img, const glw::Functions &gl) |
| { |
| if (dynamic_cast<const Renderbuffer *>(&cfg) != DE_NULL) |
| gl.deleteRenderbuffers(1, &img); |
| else if (dynamic_cast<const Texture *>(&cfg) != DE_NULL) |
| gl.deleteTextures(1, &img); |
| else |
| DE_FATAL("Impossible image type"); |
| } |
| |
| static void attachAttachment(const Attachment &att, GLenum attPoint, const glw::Functions &gl) |
| { |
| if (const RenderbufferAttachment *const rAtt = dynamic_cast<const RenderbufferAttachment *>(&att)) |
| gl.framebufferRenderbuffer(rAtt->target, attPoint, rAtt->renderbufferTarget, rAtt->imageName); |
| else if (const TextureFlatAttachment *const fAtt = dynamic_cast<const TextureFlatAttachment *>(&att)) |
| gl.framebufferTexture2D(fAtt->target, attPoint, fAtt->texTarget, fAtt->imageName, fAtt->level); |
| else if (const TextureLayerAttachment *const lAtt = dynamic_cast<const TextureLayerAttachment *>(&att)) |
| gl.framebufferTextureLayer(lAtt->target, attPoint, lAtt->imageName, lAtt->level, lAtt->layer); |
| else |
| DE_FATAL("Impossible attachment type"); |
| } |
| |
| GLenum attachmentType(const Attachment &att) |
| { |
| if (dynamic_cast<const RenderbufferAttachment *>(&att) != DE_NULL) |
| return GL_RENDERBUFFER; |
| else if (dynamic_cast<const TextureAttachment *>(&att) != DE_NULL) |
| return GL_TEXTURE; |
| |
| DE_FATAL("Impossible attachment type"); |
| return GL_NONE; |
| } |
| |
| static GLsizei textureLayer(const TextureAttachment &tAtt) |
| { |
| if (dynamic_cast<const TextureFlatAttachment *>(&tAtt) != DE_NULL) |
| return 0; |
| else if (const TextureLayerAttachment *const lAtt = dynamic_cast<const TextureLayerAttachment *>(&tAtt)) |
| return lAtt->layer; |
| |
| DE_FATAL("Impossible attachment type"); |
| return 0; |
| } |
| |
| static void checkAttachmentCompleteness(Checker &cctx, const Attachment &attachment, GLenum attPoint, |
| const Image *image, const FormatDB &db) |
| { |
| // GLES2 4.4.5 / GLES3 4.4.4, "Framebuffer attachment completeness" |
| |
| if (const TextureAttachment *const texAtt = dynamic_cast<const TextureAttachment *>(&attachment)) |
| if (const TextureLayered *const ltex = dynamic_cast<const TextureLayered *>(image)) |
| { |
| // GLES3: "If the value of FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is |
| // TEXTURE and the value of FRAMEBUFFER_ATTACHMENT_OBJECT_NAME names a |
| // three-dimensional texture, then the value of |
| // FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER must be smaller than the depth |
| // of the texture. |
| // |
| // GLES3: "If the value of FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is |
| // TEXTURE and the value of FRAMEBUFFER_ATTACHMENT_OBJECT_NAME names a |
| // two-dimensional array texture, then the value of |
| // FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER must be smaller than the |
| // number of layers in the texture. |
| |
| if (textureLayer(*texAtt) >= ltex->numLayers) |
| cctx.addFBOStatus(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT, "Attached layer index is larger than present"); |
| } |
| |
| // "The width and height of image are non-zero." |
| if (image->width == 0 || image->height == 0) |
| cctx.addFBOStatus(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT, "Width and height of an image are not non-zero"); |
| |
| // Check for renderability |
| if (db.isKnownFormat(image->internalFormat)) |
| { |
| const FormatFlags flags = db.getFormatInfo(image->internalFormat); |
| |
| // If the format does not have the proper renderability flag, the |
| // completeness check _must_ fail. |
| if ((flags & getAttachmentRenderabilityFlag(attPoint)) == 0) |
| cctx.addFBOStatus(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT, |
| "Attachment format is not renderable in this attachment"); |
| // If the format is only optionally renderable, the completeness check _can_ fail. |
| else if ((flags & REQUIRED_RENDERABLE) == 0) |
| cctx.addPotentialFBOStatus(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT, |
| "Attachment format is not required renderable"); |
| } |
| else |
| cctx.addFBOStatus(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT, "Attachment format is not legal"); |
| } |
| |
| } // namespace config |
| |
| using namespace config; |
| |
| Checker::Checker(const glu::RenderContext &ctx, const FormatDB &formats) : m_renderCtx(ctx), m_formats(formats) |
| { |
| m_statusCodes.setAllowComplete(true); |
| } |
| |
| void Checker::addGLError(glw::GLenum error, const char *description) |
| { |
| m_statusCodes.addErrorCode(error, description); |
| m_statusCodes.setAllowComplete(false); |
| } |
| |
| void Checker::addPotentialGLError(glw::GLenum error, const char *description) |
| { |
| m_statusCodes.addErrorCode(error, description); |
| } |
| |
| void Checker::addFBOStatus(GLenum status, const char *description) |
| { |
| m_statusCodes.addFBOErrorStatus(status, description); |
| m_statusCodes.setAllowComplete(false); |
| } |
| |
| void Checker::addPotentialFBOStatus(GLenum status, const char *description) |
| { |
| m_statusCodes.addFBOErrorStatus(status, description); |
| } |
| |
| FboVerifier::FboVerifier(const FormatDB &formats, CheckerFactory &factory, const glu::RenderContext &renderCtx) |
| : m_formats(formats) |
| , m_factory(factory) |
| , m_renderCtx(renderCtx) |
| { |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Return acceptable framebuffer status codes. |
| * |
| * This function examines the framebuffer configuration descriptor `fboConfig` |
| * and returns the set of status codes that `glCheckFramebufferStatus` is |
| * allowed to return on a conforming implementation when given a framebuffer |
| * whose configuration adheres to `fboConfig`. |
| * |
| * The returned set is guaranteed to be non-empty, but it may contain multiple |
| * INCOMPLETE statuses (if there are multiple errors in the spec), or or a mix |
| * of COMPLETE and INCOMPLETE statuses (if supporting a FBO with this spec is |
| * optional). Furthermore, the statuses may contain GL error codes, which |
| * indicate that trying to create a framebuffer configuration like this could |
| * have failed with an error (if one was checked for) even before |
| * `glCheckFramebufferStatus` was ever called. |
| * |
| *//*--------------------------------------------------------------------*/ |
| ValidStatusCodes FboVerifier::validStatusCodes(const Framebuffer &fboConfig) const |
| { |
| const AttachmentMap &atts = fboConfig.attachments; |
| const UniquePtr<Checker> cctx(m_factory.createChecker(m_renderCtx, m_formats)); |
| |
| for (TextureMap::const_iterator it = fboConfig.textures.begin(); it != fboConfig.textures.end(); it++) |
| { |
| std::string errorDescription; |
| |
| if (m_formats.isKnownFormat(it->second->internalFormat)) |
| { |
| const FormatFlags flags = m_formats.getFormatInfo(it->second->internalFormat); |
| |
| if ((flags & TEXTURE_VALID) == 0) |
| errorDescription = |
| "Format " + de::toString(it->second->internalFormat) + " is not a valid format for a texture"; |
| } |
| else if (it->second->internalFormat.unsizedType == GL_NONE) |
| { |
| // sized format |
| errorDescription = "Format " + de::toString(it->second->internalFormat) + " does not exist"; |
| } |
| else |
| { |
| // unsized type-format pair |
| errorDescription = "Format " + de::toString(it->second->internalFormat) + " is not a legal format"; |
| } |
| |
| if (!errorDescription.empty()) |
| { |
| cctx->addGLError(GL_INVALID_ENUM, errorDescription.c_str()); |
| cctx->addGLError(GL_INVALID_OPERATION, errorDescription.c_str()); |
| cctx->addGLError(GL_INVALID_VALUE, errorDescription.c_str()); |
| } |
| } |
| |
| for (RboMap::const_iterator it = fboConfig.rbos.begin(); it != fboConfig.rbos.end(); it++) |
| { |
| if (m_formats.isKnownFormat(it->second->internalFormat)) |
| { |
| const FormatFlags flags = m_formats.getFormatInfo(it->second->internalFormat); |
| if ((flags & RENDERBUFFER_VALID) == 0) |
| { |
| const std::string reason = |
| "Format " + de::toString(it->second->internalFormat) + " is not a valid format for a renderbuffer"; |
| cctx->addGLError(GL_INVALID_ENUM, reason.c_str()); |
| } |
| } |
| else |
| { |
| const std::string reason = |
| "Internal format " + de::toString(it->second->internalFormat) + " does not exist"; |
| cctx->addGLError(GL_INVALID_ENUM, reason.c_str()); |
| } |
| } |
| |
| // "There is at least one image attached to the framebuffer." |
| // \todo support XXX_framebuffer_no_attachments |
| if (atts.empty()) |
| cctx->addFBOStatus(GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT, "No images attached to the framebuffer"); |
| |
| for (AttachmentMap::const_iterator it = atts.begin(); it != atts.end(); it++) |
| { |
| const GLenum attPoint = it->first; |
| const Attachment &att = *it->second; |
| const Image *const image = fboConfig.getImage(attachmentType(att), att.imageName); |
| |
| checkAttachmentCompleteness(*cctx, att, attPoint, image, m_formats); |
| cctx->check(it->first, *it->second, image); |
| } |
| |
| return cctx->getStatusCodes(); |
| } |
| |
| void Framebuffer::attach(glw::GLenum attPoint, const Attachment *att) |
| { |
| if (att == DE_NULL) |
| attachments.erase(attPoint); |
| else |
| attachments[attPoint] = att; |
| } |
| |
| const Image *Framebuffer::getImage(GLenum type, glw::GLuint imgName) const |
| { |
| switch (type) |
| { |
| case GL_TEXTURE: |
| return de::lookupDefault(textures, imgName, DE_NULL); |
| case GL_RENDERBUFFER: |
| return de::lookupDefault(rbos, imgName, DE_NULL); |
| default: |
| DE_FATAL("Bad image type"); |
| } |
| return DE_NULL; // shut up compiler warning |
| } |
| |
| void Framebuffer::setTexture(glw::GLuint texName, const Texture &texCfg) |
| { |
| textures[texName] = &texCfg; |
| } |
| |
| void Framebuffer::setRbo(glw::GLuint rbName, const Renderbuffer &rbCfg) |
| { |
| rbos[rbName] = &rbCfg; |
| } |
| |
| static void logField(TestLog &log, const string &field, const string &value) |
| { |
| log << TestLog::Message << field << ": " << value << TestLog::EndMessage; |
| } |
| |
| static void logImage(const Image &img, TestLog &log, bool useType) |
| { |
| const GLenum type = img.internalFormat.unsizedType; |
| logField(log, "Internal format", getTextureFormatName(img.internalFormat.format)); |
| if (useType && type != GL_NONE) |
| logField(log, "Format type", getTypeName(type)); |
| logField(log, "Width", toString(img.width)); |
| logField(log, "Height", toString(img.height)); |
| } |
| |
| static void logRenderbuffer(const Renderbuffer &rbo, TestLog &log) |
| { |
| logImage(rbo, log, false); |
| logField(log, "Samples", toString(rbo.numSamples)); |
| } |
| |
| static void logTexture(const Texture &tex, TestLog &log) |
| { |
| logField(log, "Type", glu::getTextureTargetName(glTarget(tex))); |
| logImage(tex, log, true); |
| logField(log, "Levels", toString(tex.numLevels)); |
| if (const TextureLayered *const lTex = dynamic_cast<const TextureLayered *>(&tex)) |
| logField(log, "Layers", toString(lTex->numLayers)); |
| } |
| |
| static void logAttachment(const Attachment &att, TestLog &log) |
| { |
| logField(log, "Target", getFramebufferTargetName(att.target)); |
| logField(log, "Type", getFramebufferAttachmentTypeName(attachmentType(att))); |
| logField(log, "Image Name", toString(att.imageName)); |
| if (const RenderbufferAttachment *const rAtt = dynamic_cast<const RenderbufferAttachment *>(&att)) |
| { |
| DE_UNREF(rAtt); // To shut up compiler during optimized builds. |
| DE_ASSERT(rAtt->renderbufferTarget == GL_RENDERBUFFER); |
| logField(log, "Renderbuffer Target", "GL_RENDERBUFFER"); |
| } |
| else if (const TextureAttachment *const tAtt = dynamic_cast<const TextureAttachment *>(&att)) |
| { |
| logField(log, "Mipmap Level", toString(tAtt->level)); |
| if (const TextureFlatAttachment *const fAtt = dynamic_cast<const TextureFlatAttachment *>(tAtt)) |
| logField(log, "Texture Target", getTextureTargetName(fAtt->texTarget)); |
| else if (const TextureLayerAttachment *const lAtt = dynamic_cast<const TextureLayerAttachment *>(tAtt)) |
| logField(log, "Layer", toString(lAtt->level)); |
| } |
| } |
| |
| void logFramebufferConfig(const Framebuffer &cfg, TestLog &log) |
| { |
| log << TestLog::Section("Framebuffer", "Framebuffer configuration"); |
| |
| for (RboMap::const_iterator it = cfg.rbos.begin(); it != cfg.rbos.end(); ++it) |
| { |
| const string num = toString(it->first); |
| const tcu::ScopedLogSection subsection(log, num, "Renderbuffer " + num); |
| |
| logRenderbuffer(*it->second, log); |
| } |
| |
| for (TextureMap::const_iterator it = cfg.textures.begin(); it != cfg.textures.end(); ++it) |
| { |
| const string num = toString(it->first); |
| const tcu::ScopedLogSection subsection(log, num, "Texture " + num); |
| |
| logTexture(*it->second, log); |
| } |
| |
| const string attDesc = cfg.attachments.empty() ? "Framebuffer has no attachments" : "Framebuffer attachments"; |
| log << TestLog::Section("Attachments", attDesc); |
| for (AttachmentMap::const_iterator it = cfg.attachments.begin(); it != cfg.attachments.end(); it++) |
| { |
| const string attPointName = getFramebufferAttachmentName(it->first); |
| log << TestLog::Section(attPointName, "Attachment point " + attPointName); |
| logAttachment(*it->second, log); |
| log << TestLog::EndSection; |
| } |
| log << TestLog::EndSection; // Attachments |
| |
| log << TestLog::EndSection; // Framebuffer |
| } |
| |
| ValidStatusCodes::ValidStatusCodes(void) : m_allowComplete(false) |
| { |
| } |
| |
| bool ValidStatusCodes::isFBOStatusValid(glw::GLenum fboStatus) const |
| { |
| if (fboStatus == GL_FRAMEBUFFER_COMPLETE) |
| return m_allowComplete; |
| else |
| { |
| // rule violation exists? |
| for (int ndx = 0; ndx < (int)m_errorStatuses.size(); ++ndx) |
| { |
| if (m_errorStatuses[ndx].errorCode == fboStatus) |
| return true; |
| } |
| return false; |
| } |
| } |
| |
| bool ValidStatusCodes::isFBOStatusRequired(glw::GLenum fboStatus) const |
| { |
| if (fboStatus == GL_FRAMEBUFFER_COMPLETE) |
| return m_allowComplete && m_errorStatuses.empty(); |
| else |
| // fboStatus is the only allowed error status and succeeding is forbidden |
| return !m_allowComplete && m_errorStatuses.size() == 1 && m_errorStatuses.front().errorCode == fboStatus; |
| } |
| |
| bool ValidStatusCodes::isErrorCodeValid(glw::GLenum errorCode) const |
| { |
| if (errorCode == GL_NO_ERROR) |
| return m_errorCodes.empty(); |
| else |
| { |
| // rule violation exists? |
| for (int ndx = 0; ndx < (int)m_errorCodes.size(); ++ndx) |
| { |
| if (m_errorCodes[ndx].errorCode == errorCode) |
| return true; |
| } |
| return false; |
| } |
| } |
| |
| bool ValidStatusCodes::isErrorCodeRequired(glw::GLenum errorCode) const |
| { |
| if (m_errorCodes.empty() && errorCode == GL_NO_ERROR) |
| return true; |
| else |
| // only this error code listed |
| return m_errorCodes.size() == 1 && m_errorCodes.front().errorCode == errorCode; |
| } |
| |
| void ValidStatusCodes::addErrorCode(glw::GLenum error, const char *description) |
| { |
| DE_ASSERT(isErrorCode(error)); |
| DE_ASSERT(error != GL_NO_ERROR); |
| addViolation(m_errorCodes, error, description); |
| } |
| |
| void ValidStatusCodes::addFBOErrorStatus(glw::GLenum status, const char *description) |
| { |
| DE_ASSERT(isFramebufferStatus(status)); |
| DE_ASSERT(status != GL_FRAMEBUFFER_COMPLETE); |
| addViolation(m_errorStatuses, status, description); |
| } |
| |
| void ValidStatusCodes::setAllowComplete(bool b) |
| { |
| m_allowComplete = b; |
| } |
| |
| void ValidStatusCodes::logLegalResults(tcu::TestLog &log) const |
| { |
| tcu::MessageBuilder msg(&log); |
| std::vector<std::string> validResults; |
| |
| for (int ndx = 0; ndx < (int)m_errorCodes.size(); ++ndx) |
| validResults.push_back(std::string(glu::getErrorName(m_errorCodes[ndx].errorCode)) + |
| " (during FBO initialization)"); |
| |
| for (int ndx = 0; ndx < (int)m_errorStatuses.size(); ++ndx) |
| validResults.push_back(glu::getFramebufferStatusName(m_errorStatuses[ndx].errorCode)); |
| |
| if (m_allowComplete) |
| validResults.push_back("GL_FRAMEBUFFER_COMPLETE"); |
| |
| msg << "Expected "; |
| if (validResults.size() > 1) |
| msg << "one of "; |
| |
| for (int ndx = 0; ndx < (int)validResults.size(); ++ndx) |
| { |
| const bool last = ((ndx + 1) == (int)validResults.size()); |
| const bool secondToLast = ((ndx + 2) == (int)validResults.size()); |
| |
| msg << validResults[ndx]; |
| if (!last) |
| msg << ((secondToLast) ? (" or ") : (", ")); |
| } |
| |
| msg << "." << TestLog::EndMessage; |
| } |
| |
| void ValidStatusCodes::logRules(tcu::TestLog &log) const |
| { |
| const tcu::ScopedLogSection section(log, "Rules", "Active rules"); |
| |
| for (int ndx = 0; ndx < (int)m_errorCodes.size(); ++ndx) |
| logRule(log, glu::getErrorName(m_errorCodes[ndx].errorCode), m_errorCodes[ndx].rules); |
| |
| for (int ndx = 0; ndx < (int)m_errorStatuses.size(); ++ndx) |
| logRule(log, glu::getFramebufferStatusName(m_errorStatuses[ndx].errorCode), m_errorStatuses[ndx].rules); |
| |
| if (m_allowComplete) |
| { |
| std::set<std::string> defaultRule; |
| defaultRule.insert("FBO is complete"); |
| logRule(log, "GL_FRAMEBUFFER_COMPLETE", defaultRule); |
| } |
| } |
| |
| void ValidStatusCodes::logRule(tcu::TestLog &log, const std::string &ruleName, const std::set<std::string> &rules) const |
| { |
| if (!rules.empty()) |
| { |
| const tcu::ScopedLogSection section(log, ruleName, ruleName); |
| tcu::MessageBuilder msg(&log); |
| |
| msg << "Rules:\n"; |
| for (std::set<std::string>::const_iterator it = rules.begin(); it != rules.end(); ++it) |
| msg << "\t * " << *it << "\n"; |
| msg << TestLog::EndMessage; |
| } |
| } |
| |
| void ValidStatusCodes::addViolation(std::vector<RuleViolation> &dst, glw::GLenum code, const char *description) const |
| { |
| // rule violation already exists? |
| for (int ndx = 0; ndx < (int)dst.size(); ++ndx) |
| { |
| if (dst[ndx].errorCode == code) |
| { |
| dst[ndx].rules.insert(std::string(description)); |
| return; |
| } |
| } |
| |
| // new violation |
| { |
| RuleViolation violation; |
| |
| violation.errorCode = code; |
| violation.rules.insert(std::string(description)); |
| |
| dst.push_back(violation); |
| } |
| } |
| |
| FboBuilder::FboBuilder(GLuint fbo, GLenum target, const glw::Functions &gl) |
| : m_error(GL_NO_ERROR) |
| , m_target(target) |
| , m_gl(gl) |
| { |
| m_gl.bindFramebuffer(m_target, fbo); |
| } |
| |
| FboBuilder::~FboBuilder(void) |
| { |
| for (TextureMap::const_iterator it = textures.begin(); it != textures.end(); it++) |
| { |
| glDelete(*it->second, it->first, m_gl); |
| } |
| for (RboMap::const_iterator it = rbos.begin(); it != rbos.end(); it++) |
| { |
| glDelete(*it->second, it->first, m_gl); |
| } |
| m_gl.bindFramebuffer(m_target, 0); |
| for (Configs::const_iterator it = m_configs.begin(); it != m_configs.end(); it++) |
| { |
| delete *it; |
| } |
| } |
| |
| void FboBuilder::checkError(void) |
| { |
| const GLenum error = m_gl.getError(); |
| if (error != GL_NO_ERROR && m_error == GL_NO_ERROR) |
| m_error = error; |
| } |
| |
| void FboBuilder::glAttach(GLenum attPoint, const Attachment *att) |
| { |
| if (att == NULL) |
| m_gl.framebufferRenderbuffer(m_target, attPoint, GL_RENDERBUFFER, 0); |
| else |
| attachAttachment(*att, attPoint, m_gl); |
| checkError(); |
| attach(attPoint, att); |
| } |
| |
| GLuint FboBuilder::glCreateTexture(const Texture &texCfg) |
| { |
| const GLuint texName = glCreate(texCfg, m_gl); |
| checkError(); |
| setTexture(texName, texCfg); |
| return texName; |
| } |
| |
| GLuint FboBuilder::glCreateRbo(const Renderbuffer &rbCfg) |
| { |
| const GLuint rbName = glCreate(rbCfg, m_gl); |
| checkError(); |
| setRbo(rbName, rbCfg); |
| return rbName; |
| } |
| |
| TransferFormat transferImageFormat(const ImageFormat &imgFormat) |
| { |
| if (imgFormat.unsizedType == GL_NONE) |
| return getTransferFormat(mapGLInternalFormat(imgFormat.format)); |
| else |
| return TransferFormat(imgFormat.format, imgFormat.unsizedType); |
| } |
| |
| } // namespace FboUtil |
| } // namespace gls |
| } // namespace deqp |