You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
998 lines
30 KiB
998 lines
30 KiB
/*------------------------------------------------------------------------- |
|
* 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 tcu::TestLog; |
|
using tcu::TextureFormat; |
|
using tcu::NotSupportedError; |
|
using glu::TransferFormat; |
|
using glu::mapGLInternalFormat; |
|
using glu::mapGLTransferFormat; |
|
using glu::getTextureFormatName; |
|
using glu::getTypeName; |
|
using glu::getFramebufferTargetName; |
|
using glu::getFramebufferAttachmentName; |
|
using glu::getFramebufferAttachmentTypeName; |
|
using glu::getTextureTargetName; |
|
using glu::getTransferFormat; |
|
using glu::ContextInfo; |
|
using glu::ContextType; |
|
using glu::RenderContext; |
|
using de::UniquePtr; |
|
using de::toString; |
|
using std::set; |
|
using std::vector; |
|
using std::string; |
|
using std::istringstream; |
|
using std::istream_iterator; |
|
|
|
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) |
|
: m_renderCtx(ctx) |
|
{ |
|
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)); |
|
|
|
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); |
|
} |
|
|
|
} // FboUtil |
|
} // gls |
|
} // deqp
|
|
|