/* -*- tab-iWidth: 4; -*- */ /* vi: set sw=2 ts=4 expandtab: */ /* * Copyright 2018-2020 Mark Callow. * SPDX-License-Identifier: Apache-2.0 */ /** * @file * @brief Tests texture loading by using glDrawTexture to draw the texture. * * @author Mark Callow */ #if defined(_WIN32) #if _MSC_VER < 1900 #define snprintf _snprintf #endif #define _CRT_SECURE_NO_WARNINGS #endif #include #include #include #include "disable_glm_warnings.h" #include #include #include "reenable_warnings.h" #include "DrawTexture.h" #include "GLTextureTranscoder.hpp" #include "TranscodeTargetStrToFmt.h" #include "frame.h" #include "quad.h" #include "argparser.h" #include "ltexceptions.h" #if !defined(GL_TEXTURE_1D) #define GL_TEXTURE_1D 0x0DE0 #endif /* ------------------------------------------------------------------------- */ extern const GLchar* pszVs; extern const GLchar *pszDecalFs, *pszDecalSrgbEncodeFs; extern const GLchar *pszColorFs, *pszColorSrgbEncodeFs; /* ------------------------------------------------------------------------- */ LoadTestSample* DrawTexture::create(uint32_t width, uint32_t height, const char* const szArgs, const std::string sBasePath) { return new DrawTexture(width, height, szArgs, sBasePath); } DrawTexture::DrawTexture(uint32_t width, uint32_t height, const char* const szArgs, const std::string sBasePath) : GL3LoadTestSample(width, height, szArgs, sBasePath) { GLfloat* pfQuadTexCoords = quad_texture; GLfloat fTmpTexCoords[sizeof(quad_texture)/sizeof(GLfloat)]; GLenum target; GLenum glerror; GLint sign_s = 1, sign_t = 1; GLint i; GLuint gnColorFs, gnDecalFs, gnVs; GLsizeiptr offset; ktxTexture* kTexture; KTX_error_code ktxresult; bInitialized = false; transcodeTarget = KTX_TTF_NOSELECTION; gnTexture = 0; processArgs(szArgs); std::string ktxfilepath = externalFile ? ktxfilename : getAssetPath() + ktxfilename; ktxresult = ktxTexture_CreateFromNamedFile(ktxfilepath.c_str(), preloadImages ? KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT : KTX_TEXTURE_CREATE_NO_FLAGS, &kTexture); if (KTX_SUCCESS != ktxresult) { std::stringstream message; message << "Creation of ktxTexture from \"" << ktxfilepath << "\" failed: " << ktxErrorString(ktxresult); throw std::runtime_error(message.str()); } if (ktxTexture_NeedsTranscoding(kTexture)) { TextureTranscoder tc; tc.transcode((ktxTexture2*)kTexture, transcodeTarget); } ktxresult = ktxTexture_GLUpload(kTexture, &gnTexture, &target, &glerror); if (KTX_SUCCESS == ktxresult) { // GLUpload won't set target to GL_TEXTURE_1D not supported by context. if (target != GL_TEXTURE_1D && target != GL_TEXTURE_2D) { /* Can only draw 1D & 2D textures */ std::stringstream message; glDeleteTextures(1, &gnTexture); message << "DrawTexture supports only 1D & 2D textures. \"" << ktxfilename << "\" is not one of these."; throw std::runtime_error(message.str()); } if (kTexture->orientation.x == KTX_ORIENT_X_LEFT) sign_s = -1; if (kTexture->orientation.y == KTX_ORIENT_Y_DOWN) sign_t = -1; if (sign_s < 0 || sign_t < 0) { // Transform the texture coordinates to get correct image // orientation. int iNumCoords = sizeof(quad_texture) / sizeof(float); for (i = 0; i < iNumCoords; i++) { fTmpTexCoords[i] = quad_texture[i]; if (i & 1) { // odd, i.e. a y coordinate if (sign_t < 1) { fTmpTexCoords[i] = fTmpTexCoords[i] * -1 + 1; } } else { // an x coordinate if (sign_s < 1) { fTmpTexCoords[i] = fTmpTexCoords[i] * -1 + 1; } } } pfQuadTexCoords = fTmpTexCoords; } uTexWidth = kTexture->baseWidth; uTexHeight = kTexture->baseHeight; if (kTexture->numLevels > 1) // Enable bilinear mipmapping. // TO DO: application can consider inserting a key,value pair in // the KTX file that indicates what type of filtering to use. glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); else glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); ktx_uint32_t swizzleLen; char* swizzleStr; ktxresult = ktxHashList_FindValue(&kTexture->kvDataHead, KTX_SWIZZLE_KEY, &swizzleLen, (void**)&swizzleStr); if (ktxresult == KTX_SUCCESS && swizzleLen == 5) { if (contextSupportsSwizzle()) { GLint swizzle[4]; swizzle[0] = swizzleStr[0] == 'r' ? GL_RED : swizzleStr[0] == 'g' ? GL_GREEN : swizzleStr[0] == 'b' ? GL_BLUE : swizzleStr[0] == 'a' ? GL_ALPHA : swizzleStr[0] == '0' ? GL_ZERO : GL_ONE; swizzle[1] = swizzleStr[1] == 'r' ? GL_RED : swizzleStr[1] == 'g' ? GL_GREEN : swizzleStr[1] == 'b' ? GL_BLUE : swizzleStr[1] == 'a' ? GL_ALPHA : swizzleStr[1] == '0' ? GL_ZERO : GL_ONE; swizzle[2] = swizzleStr[2] == 'r' ? GL_RED : swizzleStr[2] == 'g' ? GL_GREEN : swizzleStr[2] == 'b' ? GL_BLUE : swizzleStr[2] == 'a' ? GL_ALPHA : swizzleStr[2] == '2' ? GL_ZERO : GL_ONE; swizzle[3] = swizzleStr[3] == 'r' ? GL_RED : swizzleStr[3] == 'g' ? GL_GREEN : swizzleStr[3] == 'b' ? GL_BLUE : swizzleStr[3] == 'a' ? GL_ALPHA : swizzleStr[3] == '3' ? GL_ZERO : GL_ONE; glTexParameteri(target, GL_TEXTURE_SWIZZLE_R, swizzle[0]); glTexParameteri(target, GL_TEXTURE_SWIZZLE_G, swizzle[1]); glTexParameteri(target, GL_TEXTURE_SWIZZLE_B, swizzle[2]); glTexParameteri(target, GL_TEXTURE_SWIZZLE_A, swizzle[3]); } else { std::stringstream message; message << "Input file has swizzle metadata but the " << "GL context does not support swizzling."; // I have absolutely no idea why the following line makes clang // raise an error about no matching conversion from // std::__1::basic_stringstream to unsupported_ttype // so I've resorted to using a temporary variable. //throw(unsupported_ttype(message.str()); std::string msg = message.str(); throw(unsupported_ttype(msg)); } } assert(GL_NO_ERROR == glGetError()); ktxTexture_Destroy(kTexture); } else { std::stringstream message; message << "ktxTexture_GLUpload failed: "; if (ktxresult != KTX_GL_ERROR) { message << ktxErrorString(ktxresult); throw std::runtime_error(message.str()); } else if (kTexture->isCompressed // Emscripten/WebGL returns INVALID_VALUE for unsupported // ETC formats. && (glerror == GL_INVALID_ENUM || glerror == GL_INVALID_VALUE)) { throw unsupported_ctype(); } else { message << std::showbase << "GL error " << std::hex << glerror << " occurred."; throw std::runtime_error(message.str()); } } glClearColor(0.4f, 0.4f, 0.5f, 1.0f); // Must have vertex data in buffer objects to use VAO's on ES3/GL Core glGenBuffers(1, &gnVbo); glBindBuffer(GL_ARRAY_BUFFER, gnVbo); // Create the buffer data store glBufferData(GL_ARRAY_BUFFER, sizeof(frame_position) + sizeof(frame_color) + sizeof(quad_position) + sizeof(quad_color) + sizeof(quad_texture), NULL, GL_STATIC_DRAW); glGenVertexArrays(2, gnVaos); // Interleave data copying and attrib pointer setup so offset is only // computed once. // Setup VAO and buffer the data for frame glBindVertexArray(gnVaos[FRAME]); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); offset = 0; glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(frame_position), frame_position); glVertexAttribPointer(0, 3, GL_BYTE, GL_FALSE, 0, (GLvoid*)offset); offset += sizeof(frame_position); glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(frame_color), frame_color); glVertexAttribPointer(1, 3, GL_BYTE, GL_FALSE, 0, (GLvoid*)offset); offset += sizeof(frame_color); // Setup VAO for quad glBindVertexArray(gnVaos[QUAD]); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); glEnableVertexAttribArray(2); glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(quad_position), quad_position); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (GLvoid*)offset); offset += sizeof(quad_position); glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(quad_color), quad_color); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, (GLvoid*)offset); offset += sizeof(quad_color); glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(quad_texture), pfQuadTexCoords); glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, (GLvoid*)offset); glBindVertexArray(0); const GLchar *actualColorFs, *actualDecalFs; if (framebufferColorEncoding() == GL_LINEAR) { actualColorFs = pszColorSrgbEncodeFs; actualDecalFs = pszDecalSrgbEncodeFs; } else { actualColorFs = pszColorFs; actualDecalFs = pszDecalFs; } try { makeShader(GL_VERTEX_SHADER, pszVs, &gnVs); makeShader(GL_FRAGMENT_SHADER, actualColorFs, &gnColorFs); makeProgram(gnVs, gnColorFs, &gnColProg); gulMvMatrixLocCP = glGetUniformLocation(gnColProg, "mvmatrix"); gulPMatrixLocCP = glGetUniformLocation(gnColProg, "pmatrix"); makeShader(GL_FRAGMENT_SHADER, actualDecalFs, &gnDecalFs); makeProgram(gnVs, gnDecalFs, &gnTexProg); } catch (std::exception& e) { (void)e; // To quiet unused variable warnings from some compilers. throw; } gulMvMatrixLocTP = glGetUniformLocation(gnTexProg, "mvmatrix"); gulPMatrixLocTP = glGetUniformLocation(gnTexProg, "pmatrix"); gulSamplerLocTP = glGetUniformLocation(gnTexProg, "sampler"); glUseProgram(gnTexProg); // We're using the default texture unit 0 glUniform1i(gulSamplerLocTP, 0); glDeleteShader(gnVs); glDeleteShader(gnColorFs); glDeleteShader(gnDecalFs); // Set the quad's mv matrix to scale by the texture size. // With the pixel-mapping ortho projection set below, the texture will // be rendered at actual size just like DrawTex*OES. quadMvMatrix = glm::scale(glm::mat4(), glm::vec3((float)uTexWidth / 2, (float)uTexHeight / 2, 1)); assert(GL_NO_ERROR == glGetError()); bInitialized = true; } /* ------------------------------------------------------------------------- */ DrawTexture::~DrawTexture() { if (bInitialized) { // A bug in the PVR SDK 3.1 emulator causes the // glDeleteProgram(gnColProg) below to raise an INVALID_VALUE error // if the following glUseProgram(0) has been executed. Strangely the // equivalent line in TexturedCube.cpp, where only 1 program is used, // does not raise an error. glUseProgram(0); glDeleteTextures(1, &gnTexture); glDeleteProgram(gnTexProg); glDeleteProgram(gnColProg); glDeleteBuffers(1, &gnVbo); glDeleteVertexArrays(2, gnVaos); } assert(GL_NO_ERROR == glGetError()); } /* ------------------------------------------------------------------------- */ void DrawTexture::processArgs(std::string sArgs) { // Options descriptor struct argparser::option longopts[] = { {"external", argparser::option::no_argument, &externalFile, 1}, {"preload", argparser::option::no_argument, &preloadImages, 1}, {"transcode-target", argparser::option::required_argument, nullptr, 2}, {NULL, argparser::option::no_argument, nullptr, 0} }; argvector argv(sArgs); argparser ap(argv); int ch; while ((ch = ap.getopt(nullptr, longopts, nullptr)) != -1) { switch (ch) { case 0: break; case 2: transcodeTarget = TranscodeTargetStrToFmt(ap.optarg); break; default: assert(false); // Error in args in sample table. } } assert(ap.optind < argv.size()); ktxfilename = argv[ap.optind]; } /* ------------------------------------------------------------------------- */ void DrawTexture::resize(uint32_t uNewWidth, uint32_t uNewHeight) { glViewport(0, 0, uNewWidth, uNewHeight); this->uWidth = uNewWidth; this->uHeight = uNewHeight; // Set up an orthographic projection where 1 = 1 pixel, and 0,0,0 // is at the center of the window. //atSetOrthoZeroAtCenterMatrix(fPMatrix, // 0.0f, (float)iWidth, // 0.0f, (float)iHeight, // -1.0f, 1.0f); // Set up an orthographic projection where 1 = 1 pixel pMatrix = glm::ortho(0.f, (float)uWidth, 0.f, (float)uHeight); // Move (0,0,0) to the center of the window. pMatrix *= glm::translate(glm::mat4(), glm::vec3((float)uWidth/2, (float)uHeight/2, 0)); // Scale the frame to fill the viewport. To guarantee its lines // appear we need to inset them by half-a-pixel hence the -1. // [Lines at the edges of the clip volume may or may not appear // depending on the OpenGL ES implementation. This is because // (a) the edges are on the points of the diamonds of the diamond // exit rule and slight precision errors can easily push the // lines outside the diamonds. // (b) the specification allows lines to be up to 1 pixel either // side of the exact position.] frameMvMatrix = glm::scale(glm::mat4(), glm::vec3((float)(uWidth - 1)/2, (float)(uHeight - 1)/2, 1)); } /* ------------------------------------------------------------------------- */ void DrawTexture::run(uint32_t /*msTicks*/) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glBindVertexArray(gnVaos[FRAME]); glUseProgram(gnColProg); glUniformMatrix4fv(gulMvMatrixLocCP, 1, GL_FALSE, glm::value_ptr(frameMvMatrix)); glUniformMatrix4fv(gulPMatrixLocCP, 1, GL_FALSE, glm::value_ptr(pMatrix)); glDrawArrays(GL_LINE_LOOP, 0, 4); glBindVertexArray(gnVaos[QUAD]); glUseProgram(gnTexProg); glUniformMatrix4fv(gulMvMatrixLocTP, 1, GL_FALSE, glm::value_ptr(quadMvMatrix)); glUniformMatrix4fv(gulPMatrixLocTP, 1, GL_FALSE, glm::value_ptr(pMatrix)); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); assert(GL_NO_ERROR == glGetError()); } /* ------------------------------------------------------------------------- */