/* 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_GL_H_ #define _RENDERCHECK_GL_H_ #include #include #include #include #include #include #include #if defined(__APPLE__) || defined(MACOSX) #include #else #include #endif #include #include using std::map; using std::string; using std::vector; #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_BGRA), 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_ARB, m_pboReadback); glBufferData(GL_PIXEL_UNPACK_BUFFER_ARB, width * height * Bpp, NULL, GL_STREAM_READ); glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, 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_ARB, 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_ARB, 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 = glMapBuffer(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY); memcpy(m_pImageData, ioMem, width * height * m_Bpp); glUnmapBuffer(GL_PIXEL_PACK_BUFFER_ARB); // 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) glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 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 = glMapBuffer(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY); memcpy(m_pImageData, ioMem, width * height * m_Bpp); glUnmapBuffer(GL_PIXEL_PACK_BUFFER_ARB); // release the FBO glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); // release the PBO unbindReadback(); } else { printf("CheckBackBuffer::readback() FBO->m_pImageData\n"); // Reading direct to FBO using glReadPixels glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 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_EXT)); 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); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); } } else { printf("CheckBackBuffer::readback() PBO->m_pImageData\n"); // read from bufObject (PBO) to system memorys image glBindBuffer(GL_PIXEL_PACK_BUFFER_ARB, bufObject); // Bind the PBO // map - unmap simulates readback without the copy void *ioMem = glMapBuffer(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY); // 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_ARB); // read from bufObject (PBO) to system memory image glBindBuffer(GL_PIXEL_PACK_BUFFER_ARB, 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) { glGenFramebuffersEXT(1, &m_fboData.fb); m_fboData.colorTex = createTexture(m_eGLTarget, width, height, (bUseFloat ? GL_RGBA32F_ARB : GL_RGBA8), GL_RGBA); m_fboData.depthTex = createTexture( m_eGLTarget, width, height, GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT); attachTexture(m_eGLTarget, m_fboData.colorTex, GL_COLOR_ATTACHMENT0_EXT); attachTexture(m_eGLTarget, m_fboData.depthTex, GL_DEPTH_ATTACHMENT_EXT); // bool ret = checkStatus(__FILE__, __LINE__, true); } virtual ~CFrameBufferObject() { // freeResources(); } GLuint createTexture(GLenum target, int w, int h, GLint internalformat, GLenum format) { GLuint texid; glGenTextures(1, &texid); glBindTexture(target, texid); glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexImage2D(target, 0, internalformat, w, h, 0, format, GL_FLOAT, 0); return texid; } void attachTexture(GLenum texTarget, GLuint texId, GLenum attachment = GL_COLOR_ATTACHMENT0_EXT, int mipLevel = 0, int zSlice = 0) { bindRenderPath(); switch (texTarget) { case GL_TEXTURE_1D: glFramebufferTexture1DEXT(GL_FRAMEBUFFER_EXT, attachment, GL_TEXTURE_1D, texId, mipLevel); break; case GL_TEXTURE_3D: glFramebufferTexture3DEXT(GL_FRAMEBUFFER_EXT, attachment, GL_TEXTURE_3D, texId, mipLevel, zSlice); break; default: // Default is GL_TEXTURE_2D, GL_TEXTURE_RECTANGLE_ARB, or cube faces glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, attachment, texTarget, texId, mipLevel); break; } 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_ARB : 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); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); if (m_bUseFloat) { // load fragment programs const char *strTextureProgram2D = "!!ARBfp1.0\n" "TEX result.color, fragment.texcoord[0], texture[0], 2D;\n" "END\n"; m_textureProgram = nv::CompileASMShader(GL_FRAGMENT_PROGRAM_ARB, strTextureProgram2D); const char *strOverlayProgram = "!!ARBfp1.0\n" "TEMP t;\n" "TEX t, fragment.texcoord[0], texture[0], 2D;\n" "MOV result.color, t;\n" "END\n"; m_overlayProgram = nv::CompileASMShader(GL_FRAGMENT_PROGRAM_ARB, strOverlayProgram); } 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")); glGenFramebuffersEXT(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); glGenerateMipmapEXT(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 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, data.fb); glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, 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); glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, m_eGLTarget, data.depthTex, 0); ret &= checkStatus(__FILE__, __LINE__, true); } glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 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) { glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, data.resolveFB); glGetIntegerv(GL_RED_BITS, &query); if (query != config.redbits) { ret = false; } } glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); ret &= checkStatus(__FILE__, __LINE__, true); return ret; } virtual void freeResources() { if (m_fboData.fb) { glDeleteFramebuffersEXT(1, &m_fboData.fb); } if (m_fboData.resolveFB) { glDeleteFramebuffersEXT(1, &m_fboData.resolveFB); } if (m_fboData.colorRB) { glDeleteRenderbuffersEXT(1, &m_fboData.colorRB); } if (m_fboData.depthRB) { glDeleteRenderbuffersEXT(1, &m_fboData.depthRB); } if (m_fboData.colorTex) { glDeleteTextures(1, &m_fboData.colorTex); } if (m_fboData.depthTex) { glDeleteTextures(1, &m_fboData.depthTex); } glDeleteProgramsARB(1, &m_textureProgram); glDeleteProgramsARB(1, &m_overlayProgram); } virtual bool checkStatus(const char *zfile, int line, bool silent) { GLenum status; status = (GLenum)glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); if (status != GL_FRAMEBUFFER_COMPLETE_EXT) { printf("<%s : %d> - ", zfile, line); } switch (status) { case GL_FRAMEBUFFER_COMPLETE_EXT: break; case GL_FRAMEBUFFER_UNSUPPORTED_EXT: if (!silent) { printf("Unsupported framebuffer format\n"); } return false; case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT: if (!silent) { printf("Framebuffer incomplete, missing attachment\n"); } return false; case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT: if (!silent) { printf("Framebuffer incomplete, duplicate attachment\n"); } return false; case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT: if (!silent) { printf( "Framebuffer incomplete, attached images must have same " "dimensions\n"); } return false; case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT: if (!silent) { printf( "Framebuffer incomplete, attached images must have same " "format\n"); } return false; case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT: if (!silent) { printf("Framebuffer incomplete, missing draw buffer\n"); } return false; case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT: if (!silent) { printf("Framebuffer incomplete, missing read buffer\n"); } return false; default: assert(0); return false; } return true; } virtual void renderQuad(int width, int height, GLenum eTarget) { float width_norm = (float)width / (float)m_Width, height_norm = (float)height / (float)m_Height; // Bind the FBO texture for the display path glBindTexture(eTarget, m_fboData.colorTex); glGenerateMipmapEXT(GL_TEXTURE_2D); glBindTexture(eTarget, 0); // now render to the full screen using this texture glClearColor(0.2f, 0.2f, 0.2f, 0.0f); glClear(GL_COLOR_BUFFER_BIT); glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, m_textureProgram); glEnable(GL_FRAGMENT_PROGRAM_ARB); glDisable(GL_DEPTH_TEST); glBegin(GL_QUADS); { glVertex2f(0.0f, 0.0f); glTexCoord2f(0.0f, 0.0f); glVertex2f(0.0f, height_norm); glTexCoord2f(width_norm, 0.0f); glVertex2f(width_norm, height_norm); glTexCoord2f(width_norm, height_norm); glVertex2f(width_norm, 0.0f); glTexCoord2f(0.0f, height_norm); } glEnd(); // Release the FBO texture (finished rendering) glBindTexture(eTarget, 0); } // bind to the Fragment Program void bindFragmentProgram() { glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, m_textureProgram); glEnable(GL_FRAGMENT_PROGRAM_ARB); } // bind to the FrameBuffer Object void bindRenderPath() { glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_fboData.fb); } // release current FrameBuffer Object void unbindRenderPath() { glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 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) {} virtual ~CheckFBO() {} virtual bool checkStatus(const char *zfile, int line, bool silent) { GLenum status; status = (GLenum)glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); if (status != GL_FRAMEBUFFER_COMPLETE_EXT) { printf("<%s : %d> - ", zfile, line); } switch (status) { case GL_FRAMEBUFFER_COMPLETE_EXT: break; case GL_FRAMEBUFFER_UNSUPPORTED_EXT: if (!silent) { printf("Unsupported framebuffer format\n"); } return false; case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT: if (!silent) { printf("Framebuffer incomplete, missing attachment\n"); } return false; case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT: if (!silent) { printf("Framebuffer incomplete, duplicate attachment\n"); } return false; case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT: if (!silent) { printf( "Framebuffer incomplete, attached images must have same " "dimensions\n"); } return false; case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT: if (!silent) { printf( "Framebuffer incomplete, attached images must have same " "format\n"); } return false; case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT: if (!silent) { printf("Framebuffer incomplete, missing draw buffer\n"); } return false; case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT: if (!silent) { printf("Framebuffer incomplete, missing read buffer\n"); } return false; default: 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) glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_pFrameBufferObject->getFbo()); // 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 = glMapBuffer(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY); memcpy(m_pImageData, ioMem, width * height * m_Bpp); glUnmapBuffer(GL_PIXEL_PACK_BUFFER_ARB); // release the FBO glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); // release the PBO unbindReadback(); } else { // Reading back from FBO using glReadPixels glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_pFrameBufferObject->getFbo()); ret = checkStatus(__FILE__, __LINE__, true); if (!ret) { printf("CheckFBO::readback::glBindFramebufferEXT() checkStatus = %d\n", ret); } glReadBuffer(static_cast(GL_COLOR_ATTACHMENT0_EXT)); 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); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 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) glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 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 = glMapBuffer(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY); memcpy(m_pImageData, ioMem, width * height * m_Bpp); glUnmapBuffer(GL_PIXEL_PACK_BUFFER_ARB); // release the FBO glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); // release the PBO unbindReadback(); } else { printf("CheckFBO::readback() FBO->m_pImageData\n"); // Reading direct to FBO using glReadPixels glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 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_EXT)); 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); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); } } else { printf("CheckFBO::readback() PBO->m_pImageData\n"); // read from bufObject (PBO) to system memorys image glBindBuffer(GL_PIXEL_PACK_BUFFER_ARB, bufObject); // Bind the PBO // map - unmap simulates readback without the copy void *ioMem = glMapBuffer(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY); memcpy(m_pImageData, ioMem, width * height * m_Bpp); glUnmapBuffer(GL_PIXEL_PACK_BUFFER_ARB); // read from bufObject (PBO) to system memory image glBindBuffer(GL_PIXEL_PACK_BUFFER_ARB, 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_GL_H_