/* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of NVIDIA CORPORATION nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef _RENDERCHECK_GLES_H_ #define _RENDERCHECK_GLES_H_ #include #include #include #include #include #include #include #include #include using std::vector; using std::map; using std::string; #define BUFFER_OFFSET(i) ((char *)NULL + (i)) #if _DEBUG #define CHECK_FBO checkStatus(__FILE__, __LINE__, true) #else #define CHECK_FBO true #endif class CheckRender { public: CheckRender(unsigned int width, unsigned int height, unsigned int Bpp, bool bQAReadback, bool bUseFBO, bool bUsePBO) : m_Width(width), m_Height(height), m_Bpp(Bpp), m_bQAReadback(bQAReadback), m_bUseFBO(bUseFBO), m_bUsePBO(bUsePBO), m_PixelFormat(GL_RGBA), m_fThresholdCompare(0.0f) { allocateMemory(width, height, Bpp, bUseFBO, bUsePBO); } virtual ~CheckRender() { // Release PBO resources if (m_bUsePBO) { glDeleteBuffers(1, &m_pboReadback); m_pboReadback = 0; } free(m_pImageData); } virtual void allocateMemory(unsigned int width, unsigned int height, unsigned int Bpp, bool bUseFBO, bool bUsePBO) { // Create the PBO for readbacks if (bUsePBO) { glGenBuffers(1, &m_pboReadback); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, m_pboReadback); glBufferData(GL_PIXEL_UNPACK_BUFFER, width * height * Bpp, NULL, GL_STREAM_READ); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); } m_pImageData = (unsigned char *)malloc( width * height * Bpp); // This is the image data stored in system memory } virtual void setExecPath(char *path) { m_ExecPath = path; } virtual void EnableQAReadback(bool bStatus) { m_bQAReadback = bStatus; } virtual bool IsQAReadback() { return m_bQAReadback; } virtual bool IsFBO() { return m_bUseFBO; } virtual bool IsPBO() { return m_bUsePBO; } virtual void *imageData() { return m_pImageData; } // Interface to this class functions virtual void setPixelFormat(GLenum format) { m_PixelFormat = format; } virtual int getPixelFormat() { return m_PixelFormat; } virtual bool checkStatus(const char *zfile, int line, bool silent) = 0; virtual bool readback(GLuint width, GLuint height) = 0; virtual bool readback(GLuint width, GLuint height, GLuint bufObject) = 0; virtual bool readback(GLuint width, GLuint height, unsigned char *membuf) = 0; virtual void bindReadback() { if (!m_bQAReadback) { printf("CheckRender::bindReadback() uninitialized!\n"); return; } if (m_bUsePBO) { glBindBuffer(GL_PIXEL_PACK_BUFFER, m_pboReadback); // Bind the PBO } } virtual void unbindReadback() { if (!m_bQAReadback) { printf("CheckRender::unbindReadback() uninitialized!\n"); return; } if (m_bUsePBO) { glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); // Release the bind on the PBO } } virtual void savePGM(const char *zfilename, bool bInvert, void **ppReadBuf) { if (zfilename != NULL) { if (bInvert) { unsigned char *readBuf; unsigned char *writeBuf = (unsigned char *)malloc(m_Width * m_Height); for (unsigned int y = 0; y < m_Height; y++) { if (ppReadBuf) { readBuf = *(unsigned char **)ppReadBuf; } else { readBuf = (unsigned char *)m_pImageData; } memcpy(&writeBuf[m_Width * m_Bpp * y], (readBuf + m_Width * (m_Height - 1 - y)), m_Width); } // we copy the results back to original system buffer if (ppReadBuf) { memcpy(*ppReadBuf, writeBuf, m_Width * m_Height); } else { memcpy(m_pImageData, writeBuf, m_Width * m_Height); } free(writeBuf); } printf("> Saving PGM: <%s>\n", zfilename); if (ppReadBuf) { sdkSavePGM(zfilename, *(unsigned char **)ppReadBuf, m_Width, m_Height); } else { sdkSavePGM(zfilename, (unsigned char *)m_pImageData, m_Width, m_Height); } } } virtual void savePPM(const char *zfilename, bool bInvert, void **ppReadBuf) { if (zfilename != NULL) { if (bInvert) { unsigned char *readBuf; unsigned char *writeBuf = (unsigned char *)malloc(m_Width * m_Height * m_Bpp); for (unsigned int y = 0; y < m_Height; y++) { if (ppReadBuf) { readBuf = *(unsigned char **)ppReadBuf; } else { readBuf = (unsigned char *)m_pImageData; } memcpy(&writeBuf[m_Width * m_Bpp * y], (readBuf + m_Width * m_Bpp * (m_Height - 1 - y)), m_Width * m_Bpp); } // we copy the results back to original system buffer if (ppReadBuf) { memcpy(*ppReadBuf, writeBuf, m_Width * m_Height * m_Bpp); } else { memcpy(m_pImageData, writeBuf, m_Width * m_Height * m_Bpp); } free(writeBuf); } printf("> Saving PPM: <%s>\n", zfilename); if (ppReadBuf) { sdkSavePPM4ub(zfilename, *(unsigned char **)ppReadBuf, m_Width, m_Height); } else { sdkSavePPM4ub(zfilename, (unsigned char *)m_pImageData, m_Width, m_Height); } } } virtual bool PGMvsPGM(const char *src_file, const char *ref_file, const float epsilon, const float threshold = 0.0f) { unsigned char *src_data = NULL, *ref_data = NULL; unsigned long error_count = 0; unsigned int width, height; char *ref_file_path = sdkFindFilePath(ref_file, m_ExecPath.c_str()); if (ref_file_path == NULL) { printf( "CheckRender::PGMvsPGM unable to find <%s> in <%s> Aborting " "comparison!\n", ref_file, m_ExecPath.c_str()); printf(">>> Check info.xml and [project//data] folder <%s> <<<\n", ref_file); printf("Aborting comparison!\n"); printf(" FAILED\n"); error_count++; } else { if (src_file == NULL || ref_file_path == NULL) { printf("PGMvsPGM: Aborting comparison\n"); return false; } printf(" src_file <%s>\n", src_file); printf(" ref_file <%s>\n", ref_file_path); if (sdkLoadPPMub(ref_file_path, &ref_data, &width, &height) != true) { printf("PGMvsPGM: unable to load ref image file: %s\n", ref_file_path); return false; } if (sdkLoadPPMub(src_file, &src_data, &width, &height) != true) { printf("PGMvsPGM: unable to load src image file: %s\n", src_file); return false; } printf( "PGMvsPGM: comparing images size (%d,%d) epsilon(%2.4f), " "threshold(%4.2f%%)\n", m_Height, m_Width, epsilon, threshold * 100); if (compareDataAsFloatThreshold( ref_data, src_data, m_Height * m_Width, epsilon, threshold) == false) { error_count = 1; } } if (error_count == 0) { printf(" OK\n"); } else { printf(" FAILURE: %d errors...\n", (unsigned int)error_count); } return (error_count == 0); // returns true if all pixels pass } virtual bool PPMvsPPM(const char *src_file, const char *ref_file, const float epsilon, const float threshold = 0.0f) { unsigned long error_count = 0; char *ref_file_path = sdkFindFilePath(ref_file, m_ExecPath.c_str()); if (ref_file_path == NULL) { printf( "CheckRender::PPMvsPPM unable to find <%s> in <%s> Aborting " "comparison!\n", ref_file, m_ExecPath.c_str()); printf(">>> Check info.xml and [project//data] folder <%s> <<<\n", ref_file); printf("Aborting comparison!\n"); printf(" FAILED\n"); error_count++; } if (src_file == NULL || ref_file_path == NULL) { printf("PPMvsPPM: Aborting comparison\n"); return false; } printf(" src_file <%s>\n", src_file); printf(" ref_file <%s>\n", ref_file_path); return (sdkComparePPM(src_file, ref_file_path, epsilon, threshold, true) == true ? true : false); } void setThresholdCompare(float value) { m_fThresholdCompare = value; } virtual void dumpBin(void *data, unsigned int bytes, const char *filename) { FILE *fp; printf("CheckRender::dumpBin: <%s>\n", filename); FOPEN(fp, filename, "wb"); fwrite(data, bytes, 1, fp); fflush(fp); fclose(fp); } virtual bool compareBin2BinUint(const char *src_file, const char *ref_file, unsigned int nelements, const float epsilon, const float threshold) { unsigned int *src_buffer, *ref_buffer; FILE *src_fp = NULL, *ref_fp = NULL; unsigned long error_count = 0; size_t fsize = 0; FOPEN(src_fp, src_file, "rb"); if (src_fp == NULL) { printf("compareBin2Bin unable to open src_file: %s\n", src_file); error_count++; } char *ref_file_path = sdkFindFilePath(ref_file, m_ExecPath.c_str()); if (ref_file_path == NULL) { printf("compareBin2Bin unable to find <%s> in <%s>\n", ref_file, m_ExecPath.c_str()); printf(">>> Check info.xml and [project//data] folder <%s> <<<\n", ref_file); printf("Aborting comparison!\n"); printf(" FAILED\n"); error_count++; if (src_fp) { fclose(src_fp); } if (ref_fp) { fclose(ref_fp); } } else { FOPEN(ref_fp, ref_file_path, "rb"); if (ref_fp == NULL) { printf("compareBin2Bin unable to open ref_file: %s\n", ref_file_path); error_count++; } if (src_fp && ref_fp) { src_buffer = (unsigned int *)malloc(nelements * sizeof(unsigned int)); ref_buffer = (unsigned int *)malloc(nelements * sizeof(unsigned int)); fsize = fread(src_buffer, sizeof(unsigned int), nelements, src_fp); if (fsize != nelements) { printf( "compareBin2Bin failed to read %u elements from " "%s\n", nelements, src_file); error_count++; } fsize = fread(ref_buffer, sizeof(unsigned int), nelements, ref_fp); if (fsize == 0) { printf( "compareBin2Bin failed to read %u elements from " "%s\n", nelements, ref_file_path); error_count++; } printf( "> compareBin2Bin nelements=%d, epsilon=%4.2f, " "threshold=%4.2f\n", nelements, epsilon, threshold); printf(" src_file <%s>\n", src_file); printf(" ref_file <%s>\n", ref_file_path); if (!compareData(ref_buffer, src_buffer, nelements, epsilon, threshold)) { error_count++; } fclose(src_fp); fclose(ref_fp); free(src_buffer); free(ref_buffer); } else { if (src_fp) { fclose(src_fp); } if (ref_fp) { fclose(ref_fp); } } } if (error_count == 0) { printf(" OK\n"); } else { printf(" FAILURE: %d errors...\n", (unsigned int)error_count); } return (error_count == 0); // returns true if all pixels pass } virtual bool compareBin2BinFloat(const char *src_file, const char *ref_file, unsigned int nelements, const float epsilon, const float threshold) { float *src_buffer, *ref_buffer; FILE *src_fp = NULL, *ref_fp = NULL; size_t fsize = 0; unsigned long error_count = 0; FOPEN(src_fp, src_file, "rb"); if (src_fp == NULL) { printf("compareBin2Bin unable to open src_file: %s\n", src_file); error_count = 1; } char *ref_file_path = sdkFindFilePath(ref_file, m_ExecPath.c_str()); if (ref_file_path == NULL) { printf("compareBin2Bin unable to find <%s> in <%s>\n", ref_file, m_ExecPath.c_str()); printf(">>> Check info.xml and [project//data] folder <%s> <<<\n", m_ExecPath.c_str()); printf("Aborting comparison!\n"); printf(" FAILED\n"); error_count++; if (src_fp) { fclose(src_fp); } if (ref_fp) { fclose(ref_fp); } } else { FOPEN(ref_fp, ref_file_path, "rb"); if (ref_fp == NULL) { printf("compareBin2Bin unable to open ref_file: %s\n", ref_file_path); error_count = 1; } if (src_fp && ref_fp) { src_buffer = (float *)malloc(nelements * sizeof(float)); ref_buffer = (float *)malloc(nelements * sizeof(float)); fsize = fread(src_buffer, sizeof(float), nelements, src_fp); if (fsize != nelements) { printf("compareBin2Bin failed to read %u elements from %s\n", nelements, src_file); error_count++; } fsize = fread(ref_buffer, sizeof(float), nelements, ref_fp); if (fsize == 0) { printf("compareBin2Bin failed to read %u elements from %s\n", nelements, ref_file_path); error_count++; } printf( "> compareBin2Bin nelements=%d, epsilon=%4.2f, " "threshold=%4.2f\n", nelements, epsilon, threshold); printf(" src_file <%s>\n", src_file); printf(" ref_file <%s>\n", ref_file_path); if (!compareDataAsFloatThreshold( ref_buffer, src_buffer, nelements, epsilon, threshold)) { error_count++; } fclose(src_fp); fclose(ref_fp); free(src_buffer); free(ref_buffer); } else { if (src_fp) { fclose(src_fp); } if (ref_fp) { fclose(ref_fp); } } } if (error_count == 0) { printf(" OK\n"); } else { printf(" FAILURE: %d errors...\n", (unsigned int)error_count); } return (error_count == 0); // returns true if all pixels pass } protected: unsigned int m_Width, m_Height, m_Bpp; unsigned char *m_pImageData; // This is the image data stored in system memory bool m_bQAReadback, m_bUseFBO, m_bUsePBO; GLuint m_pboReadback; GLenum m_PixelFormat; float m_fThresholdCompare; string m_ExecPath; }; class CheckBackBuffer : public CheckRender { public: CheckBackBuffer(unsigned int width, unsigned int height, unsigned int Bpp, bool bUseOpenGL = true) : CheckRender(width, height, Bpp, false, false, bUseOpenGL) {} virtual ~CheckBackBuffer() {} virtual bool checkStatus(const char *zfile, int line, bool silent) { GLenum nErrorCode = glGetError(); if (nErrorCode != GL_NO_ERROR) { if (!silent) { // printf("Assertion failed(%s,%d): %s\n", zfile, line, // gluErrorString(nErrorCode)); } } return true; } virtual bool readback(GLuint width, GLuint height) { bool ret = false; if (m_bUsePBO) { // binds the PBO for readback bindReadback(); // Initiate the readback BLT from BackBuffer->PBO->membuf glReadPixels(0, 0, width, height, getPixelFormat(), GL_UNSIGNED_BYTE, BUFFER_OFFSET(0)); ret = checkStatus(__FILE__, __LINE__, true); if (!ret) { printf("CheckBackBuffer::glReadPixels() checkStatus = %d\n", ret); } // map - unmap simulates readback without the copy void *ioMem = glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, width * height * m_Bpp, GL_READ_ONLY); memcpy(m_pImageData, ioMem, width * height * m_Bpp); glUnmapBuffer(GL_PIXEL_PACK_BUFFER); // release the PBO unbindReadback(); } else { // reading direct from the backbuffer glReadBuffer(GL_FRONT); glReadPixels(0, 0, width, height, getPixelFormat(), GL_UNSIGNED_BYTE, m_pImageData); } return ret; } virtual bool readback(GLuint width, GLuint height, GLuint bufObject) { bool ret = false; if (m_bUseFBO) { if (m_bUsePBO) { printf("CheckBackBuffer::readback() FBO->PBO->m_pImageData\n"); // binds the PBO for readback bindReadback(); // bind FBO buffer (we want to transfer FBO -> PBO) glBindFramebuffer(GL_FRAMEBUFFER, bufObject); // Now initiate the readback to PBO glReadPixels(0, 0, width, height, getPixelFormat(), GL_UNSIGNED_BYTE, BUFFER_OFFSET(0)); ret = checkStatus(__FILE__, __LINE__, true); if (!ret) { printf("CheckBackBuffer::readback() FBO->PBO checkStatus = %d\n", ret); } // map - unmap simulates readback without the copy void *ioMem = glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, width * height * m_Bpp, GL_MAP_READ_BIT); memcpy(m_pImageData, ioMem, width * height * m_Bpp); glUnmapBuffer(GL_PIXEL_PACK_BUFFER); // release the FBO glBindFramebuffer(GL_FRAMEBUFFER, 0); // release the PBO unbindReadback(); } else { printf("CheckBackBuffer::readback() FBO->m_pImageData\n"); // Reading direct to FBO using glReadPixels glBindFramebuffer(GL_FRAMEBUFFER, bufObject); ret = checkStatus(__FILE__, __LINE__, true); if (!ret) { printf( "CheckBackBuffer::readback::glBindFramebufferEXT() fbo=%d " "checkStatus = %d\n", bufObject, ret); } glReadBuffer(static_cast(GL_COLOR_ATTACHMENT0)); ret &= checkStatus(__FILE__, __LINE__, true); if (!ret) { printf( "CheckBackBuffer::readback::glReadBuffer() fbo=%d checkStatus = " "%d\n", bufObject, ret); } glReadPixels(0, 0, width, height, getPixelFormat(), GL_UNSIGNED_BYTE, m_pImageData); glBindFramebuffer(GL_FRAMEBUFFER, 0); } } else { printf("CheckBackBuffer::readback() PBO->m_pImageData\n"); // read from bufObject (PBO) to system memorys image glBindBuffer(GL_PIXEL_PACK_BUFFER, bufObject); // Bind the PBO // map - unmap simulates readback without the copy void *ioMem = glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, width * height * m_Bpp, GL_MAP_READ_BIT); // allocate a buffer so we can flip the image unsigned char *temp_buf = (unsigned char *)malloc(width * height * m_Bpp); memcpy(temp_buf, ioMem, width * height * m_Bpp); // let's flip the image as we copy for (unsigned int y = 0; y < height; y++) { memcpy((void *)&(m_pImageData[(height - y) * width * m_Bpp]), (void *)&(temp_buf[y * width * m_Bpp]), width * m_Bpp); } free(temp_buf); glUnmapBuffer(GL_PIXEL_PACK_BUFFER); // read from bufObject (PBO) to system memory image glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); // unBind the PBO } return CHECK_FBO; } virtual bool readback(GLuint width, GLuint height, unsigned char *memBuf) { // let's flip the image as we copy for (unsigned int y = 0; y < height; y++) { memcpy((void *)&(m_pImageData[(height - y) * width * m_Bpp]), (void *)&(memBuf[y * width * m_Bpp]), width * m_Bpp); } return true; } private: virtual void bindFragmentProgram(){}; virtual void bindRenderPath(){}; virtual void unbindRenderPath(){}; // bind to the BackBuffer to Texture virtual void bindTexture(){}; // release this bind virtual void unbindTexture(){}; }; // structure defining the properties of a single buffer struct bufferConfig { string name; GLenum format; int bits; }; // structures defining properties of an FBO struct fboConfig { string name; GLenum colorFormat; GLenum depthFormat; int redbits; int depthBits; int depthSamples; int coverageSamples; }; struct fboData { GLuint colorTex; // color texture GLuint depthTex; // depth texture GLuint fb; // render framebuffer GLuint resolveFB; // multisample resolve target GLuint colorRB; // color render buffer GLuint depthRB; // depth render buffer }; class CFrameBufferObject { public: CFrameBufferObject(unsigned int width, unsigned int height, unsigned int Bpp, bool bUseFloat, GLenum eTarget) : m_Width(width), m_Height(height), m_bUseFloat(bUseFloat), m_eGLTarget(eTarget) { glGenFramebuffers(1, &m_fboData.fb); m_fboData.colorTex = createTexture(m_eGLTarget, width, height, GL_RGBA, GL_RGBA); m_fboData.depthTex = createTexture( m_eGLTarget, width, height, GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT); attachTexture(m_eGLTarget, m_fboData.depthTex, GL_DEPTH_ATTACHMENT); attachTexture(m_eGLTarget, m_fboData.colorTex, GL_COLOR_ATTACHMENT0); bool ret = checkStatus(__FILE__, __LINE__, false); } void check_gl_error(const char *file, int line) { GLenum err(glGetError()); while (err != GL_NO_ERROR) { char error[64]; switch (err) { case GL_INVALID_OPERATION: strcpy(error, "INVALID_OPERATION"); break; case GL_INVALID_ENUM: strcpy(error, "INVALID_ENUM"); break; case GL_INVALID_VALUE: strcpy(error, "INVALID_VALUE"); break; case GL_OUT_OF_MEMORY: strcpy(error, "OUT_OF_MEMORY"); break; case GL_INVALID_FRAMEBUFFER_OPERATION: strcpy(error, "INVALID_FRAMEBUFFER_OPERATION"); break; } printf("GL_%s - %s : %d\n", error, file, line); err = glGetError(); } } virtual ~CFrameBufferObject() { freeResources(); } GLuint createTexture(GLenum target, int w, int h, GLint internalformat, GLenum format) { GLuint texid; glGenTextures(1, &texid); glBindTexture(target, texid); if (format != GL_DEPTH_COMPONENT) { glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); } else { glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); } glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); if (internalformat == GL_DEPTH_COMPONENT24) { glTexImage2D(target, 0, internalformat, w, h, 0, format, GL_UNSIGNED_INT, 0); } else { glTexImage2D(target, 0, internalformat, w, h, 0, format, GL_UNSIGNED_BYTE, 0); } check_gl_error(__FILE__, __LINE__); glBindTexture(target, 0); return texid; } void attachTexture(GLenum texTarget, GLuint texId, GLenum attachment = GL_COLOR_ATTACHMENT0, int mipLevel = 0, int zSlice = 0) { bindRenderPath(); check_gl_error(__FILE__, __LINE__); glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, texTarget, texId, mipLevel); checkStatus(__FILE__, __LINE__, false); unbindRenderPath(); } bool initialize(unsigned width, unsigned height, fboConfig &rConfigFBO, fboData &rActiveFBO) { // Framebuffer config options vector colorConfigs; vector depthConfigs; bufferConfig temp; // add default color configs temp.name = (m_bUseFloat ? "RGBA32F" : "RGBA8"); temp.bits = (m_bUseFloat ? 32 : 8); temp.format = (m_bUseFloat ? GL_RGBA32F : GL_RGBA8); colorConfigs.push_back(temp); // add default depth configs temp.name = "D24"; temp.bits = 24; temp.format = GL_DEPTH_COMPONENT24; depthConfigs.push_back(temp); // If the FBO can be created, add it to the list of available configs, and // make a menu entry string root = colorConfigs[0].name + " " + depthConfigs[0].name; rConfigFBO.colorFormat = colorConfigs[0].format; rConfigFBO.depthFormat = depthConfigs[0].format; rConfigFBO.redbits = colorConfigs[0].bits; rConfigFBO.depthBits = depthConfigs[0].bits; // single sample rConfigFBO.name = root; rConfigFBO.coverageSamples = 0; rConfigFBO.depthSamples = 0; create(width, height, rConfigFBO, rActiveFBO); glBindFramebuffer(GL_FRAMEBUFFER, 0); return CHECK_FBO; } bool create(GLuint width, GLuint height, fboConfig &config, fboData &data) { bool multisample = config.depthSamples > 0; bool ret = true; GLint query; printf("\nCreating FBO <%s> (%dx%d) Float:%s\n", config.name.c_str(), (int)width, (int)height, (m_bUseFloat ? "Y" : "N")); glGenFramebuffers(1, &data.fb); glGenTextures(1, &data.colorTex); // init texture glBindTexture(m_eGLTarget, data.colorTex); glTexImage2D(m_eGLTarget, 0, config.colorFormat, width, height, 0, GL_RGBA, (m_bUseFloat ? GL_FLOAT : GL_UNSIGNED_BYTE), NULL); glGenerateMipmap(m_eGLTarget); glTexParameterf(m_eGLTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameterf(m_eGLTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameterf(m_eGLTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // GL_LINEAR_MIPMAP_LINEAR); glTexParameterf(m_eGLTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // GL_LINEAR); { glGenTextures(1, &data.depthTex); data.depthRB = 0; data.colorRB = 0; data.resolveFB = 0; // non-multisample, so bind things directly to the FBO glBindFramebuffer(GL_FRAMEBUFFER, data.fb); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_eGLTarget, data.colorTex, 0); glBindTexture(m_eGLTarget, data.depthTex); glTexImage2D(m_eGLTarget, 0, config.depthFormat, width, height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL); glTexParameterf(m_eGLTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // GL_LINEAR); glTexParameterf(m_eGLTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // GL_LINEAR); glTexParameterf(m_eGLTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameterf(m_eGLTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // glTexParameterf(m_eGLTarget, GL_DEPTH_TEXTURE_MODE, // GL_LUMINANCE); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, m_eGLTarget, data.depthTex, 0); ret &= checkStatus(__FILE__, __LINE__, false); } glBindFramebuffer(GL_FRAMEBUFFER, data.fb); glGetIntegerv(GL_RED_BITS, &query); if (query != config.redbits) { ret = false; } glGetIntegerv(GL_DEPTH_BITS, &query); if (query != config.depthBits) { ret = false; } if (multisample) { glBindFramebuffer(GL_FRAMEBUFFER, data.resolveFB); glGetIntegerv(GL_RED_BITS, &query); if (query != config.redbits) { ret = false; } } glBindFramebuffer(GL_FRAMEBUFFER, 0); ret &= checkStatus(__FILE__, __LINE__, true); return ret; } virtual void freeResources() { if (m_fboData.fb) { glDeleteFramebuffers(1, &m_fboData.fb); } if (m_fboData.resolveFB) { glDeleteFramebuffers(1, &m_fboData.resolveFB); } if (m_fboData.colorRB) { glDeleteRenderbuffers(1, &m_fboData.colorRB); } if (m_fboData.depthRB) { glDeleteRenderbuffers(1, &m_fboData.depthRB); } if (m_fboData.colorTex) { glDeleteTextures(1, &m_fboData.colorTex); } if (m_fboData.depthTex) { glDeleteTextures(1, &m_fboData.depthTex); } glDeleteProgram(m_textureProgram); glDeleteProgram(m_overlayProgram); } virtual bool checkStatus(const char *zfile, int line, bool silent) { GLenum status; status = (GLenum)glCheckFramebufferStatus(GL_FRAMEBUFFER); if (status != GL_FRAMEBUFFER_COMPLETE) { printf("<%s : %d> - this one ", zfile, line); } switch (status) { case GL_FRAMEBUFFER_COMPLETE: break; case GL_FRAMEBUFFER_UNSUPPORTED: if (!silent) { printf("Unsupported framebuffer format\n"); } return false; case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: if (!silent) { printf("Framebuffer incomplete, missing attachment\n"); } return false; case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: if (!silent) { printf("Framebuffer incomplete, duplicate attachment\n"); } return false; default: assert(0); return false; } return true; } // bind to the FrameBuffer Object void bindRenderPath() { glBindFramebuffer(GL_FRAMEBUFFER, m_fboData.fb); } // release current FrameBuffer Object void unbindRenderPath() { glBindFramebuffer(GL_FRAMEBUFFER, 0); } // bind to the FBO to Texture void bindTexture() { glBindTexture(m_eGLTarget, m_fboData.colorTex); } // release this bind void unbindTexture() { glBindTexture(m_eGLTarget, 0); } GLuint getFbo() { return m_fboData.fb; } GLuint getTex() { return m_fboData.colorTex; } GLuint getDepthTex() { return m_fboData.depthTex; } private: GLuint m_Width, m_Height; fboData m_fboData; fboConfig m_fboConfig; GLuint m_textureProgram; GLuint m_overlayProgram; bool m_bUseFloat; GLenum m_eGLTarget; }; // CheckFBO - render and verify contents of the FBO class CheckFBO : public CheckRender { public: CheckFBO(unsigned int width, unsigned int height, unsigned int Bpp) : CheckRender(width, height, Bpp, false, false, true), m_pFrameBufferObject(NULL) {} CheckFBO(unsigned int width, unsigned int height, unsigned int Bpp, CFrameBufferObject *pFrameBufferObject) : CheckRender(width, height, Bpp, false, true, true), m_pFrameBufferObject(pFrameBufferObject) {} void check_gl_error(const char *file, int line) { GLenum err(glGetError()); while (err != GL_NO_ERROR) { char error[64]; switch (err) { case GL_INVALID_OPERATION: strcpy(error, "INVALID_OPERATION"); break; case GL_INVALID_ENUM: strcpy(error, "INVALID_ENUM"); break; case GL_INVALID_VALUE: strcpy(error, "INVALID_VALUE"); break; case GL_OUT_OF_MEMORY: strcpy(error, "OUT_OF_MEMORY"); break; case GL_INVALID_FRAMEBUFFER_OPERATION: strcpy(error, "INVALID_FRAMEBUFFER_OPERATION"); break; } printf("GL_%s - %s : %d\n", error, file, line); err = glGetError(); } } virtual ~CheckFBO() {} virtual bool checkStatus(const char *zfile, int line, bool silent) { GLenum status; status = (GLenum)glCheckFramebufferStatus(GL_FRAMEBUFFER); if (status != GL_FRAMEBUFFER_COMPLETE) { printf("<%s : %d> - here ", zfile, line); } switch (status) { case GL_FRAMEBUFFER_COMPLETE: break; case GL_FRAMEBUFFER_UNSUPPORTED: if (!silent) { printf("Unsupported framebuffer format\n"); } return false; case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: if (!silent) { printf("Framebuffer incomplete, missing attachment\n"); } return false; case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: if (!silent) { printf("Framebuffer incomplete, duplicate attachment\n"); } return false; case GL_FRAMEBUFFER_UNDEFINED: if (!silent) { printf("Framebuffer undefined\n"); } return false; default: if (!silent) { printf("Framebuffer incomplete, default state\n"); } assert(0); return false; } return true; } virtual bool readback(GLuint width, GLuint height) { bool ret = false; if (m_bUsePBO) { // binds the PBO for readback bindReadback(); // bind FBO buffer (we want to transfer FBO -> PBO) glBindFramebuffer(GL_FRAMEBUFFER, m_pFrameBufferObject->getFbo()); ret = checkStatus(__FILE__, __LINE__, false); if (!ret) { printf("CheckFBO::readback() glBindFramebuffer checkStatus = %d\n", ret); } // Now initiate the readback to PBO glReadPixels(0, 0, width, height, getPixelFormat(), GL_UNSIGNED_BYTE, BUFFER_OFFSET(0)); ret = checkStatus(__FILE__, __LINE__, false); check_gl_error(__FILE__, __LINE__); if (!ret) { printf("CheckFBO::readback() FBO->PBO checkStatus = %d\n", ret); } int nBufferSize = 0; glGetBufferParameteriv(GL_PIXEL_PACK_BUFFER, GL_BUFFER_SIZE, &nBufferSize); if (nBufferSize != width * height * m_Bpp) { printf("Buffer size incorrect, exiting..\n"); exit(EXIT_FAILURE); } // map - unmap simulates readback without the copy void *ioMem = glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, width * height * m_Bpp, GL_MAP_READ_BIT); check_gl_error(__FILE__, __LINE__); if (ioMem != NULL) { memcpy(m_pImageData, ioMem, width * height * m_Bpp); } else { printf("\nError: Unable to map the PBO\n"); exit(EXIT_FAILURE); } glUnmapBuffer(GL_PIXEL_PACK_BUFFER); // release the FBO glBindFramebuffer(GL_FRAMEBUFFER, 0); // release the PBO unbindReadback(); } else { // Reading back from FBO using glReadPixels glBindFramebuffer(GL_FRAMEBUFFER, m_pFrameBufferObject->getFbo()); ret = checkStatus(__FILE__, __LINE__, true); if (!ret) { printf("CheckFBO::readback::glBindFramebufferEXT() checkStatus = %d\n", ret); } glReadBuffer(static_cast(GL_COLOR_ATTACHMENT0)); ret &= checkStatus(__FILE__, __LINE__, true); if (!ret) { printf("CheckFBO::readback::glReadBuffer() checkStatus = %d\n", ret); } glReadPixels(0, 0, width, height, getPixelFormat(), GL_UNSIGNED_BYTE, m_pImageData); glBindFramebuffer(GL_FRAMEBUFFER, 0); } return CHECK_FBO; } virtual bool readback(GLuint width, GLuint height, GLuint bufObject) { bool ret = false; if (m_bUseFBO) { if (m_bUsePBO) { printf("CheckFBO::readback() FBO->PBO->m_pImageData\n"); // binds the PBO for readback bindReadback(); // bind FBO buffer (we want to transfer FBO -> PBO) glBindFramebuffer(GL_FRAMEBUFFER, bufObject); // Now initiate the readback to PBO glReadPixels(0, 0, width, height, getPixelFormat(), GL_UNSIGNED_BYTE, BUFFER_OFFSET(0)); ret = checkStatus(__FILE__, __LINE__, true); if (!ret) { printf("CheckFBO::readback() FBO->PBO checkStatus = %d\n", ret); } // map - unmap simulates readback without the copy void *ioMem = glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, width * height * m_Bpp, GL_MAP_READ_BIT); memcpy(m_pImageData, ioMem, width * height * m_Bpp); glUnmapBuffer(GL_PIXEL_PACK_BUFFER); // release the FBO glBindFramebuffer(GL_FRAMEBUFFER, 0); // release the PBO unbindReadback(); } else { printf("CheckFBO::readback() FBO->m_pImageData\n"); // Reading direct to FBO using glReadPixels glBindFramebuffer(GL_FRAMEBUFFER, bufObject); ret = checkStatus(__FILE__, __LINE__, true); if (!ret) { printf( "CheckFBO::readback::glBindFramebufferEXT() fbo=%d checkStatus = " "%d\n", (int)bufObject, (int)ret); } glReadBuffer(static_cast(GL_COLOR_ATTACHMENT0)); ret &= checkStatus(__FILE__, __LINE__, true); if (!ret) { printf("CheckFBO::readback::glReadBuffer() fbo=%d checkStatus = %d\n", (int)bufObject, (int)ret); } glReadPixels(0, 0, width, height, getPixelFormat(), GL_UNSIGNED_BYTE, m_pImageData); glBindFramebuffer(GL_FRAMEBUFFER, 0); } } else { printf("CheckFBO::readback() PBO->m_pImageData\n"); // read from bufObject (PBO) to system memorys image glBindBuffer(GL_PIXEL_PACK_BUFFER, bufObject); // Bind the PBO // map - unmap simulates readback without the copy void *ioMem = glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, width * height * m_Bpp, GL_MAP_READ_BIT); memcpy(m_pImageData, ioMem, width * height * m_Bpp); glUnmapBuffer(GL_PIXEL_PACK_BUFFER); // read from bufObject (PBO) to system memory image glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); // unBind the PBO } return CHECK_FBO; } virtual bool readback(GLuint width, GLuint height, unsigned char *memBuf) { // let's flip the image as we copy for (unsigned int y = 0; y < height; y++) { memcpy((void *)&(m_pImageData[(height - y) * width * m_Bpp]), (void *)&(memBuf[y * width * m_Bpp]), width * m_Bpp); } return true; } private: CFrameBufferObject *m_pFrameBufferObject; }; #endif // _RENDERCHECK_GLES_H_