425 lines
16 KiB
C++
425 lines
16 KiB
C++
/* -*- 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 <assert.h>
|
|
#include <sstream>
|
|
#include <ktx.h>
|
|
#include "disable_glm_warnings.h"
|
|
#include <glm/gtc/matrix_transform.hpp>
|
|
#include <glm/gtc/type_ptr.hpp>
|
|
#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());
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- */
|