This commit is contained in:
2026-06-14 19:09:18 +01:00
parent 14bd1a9271
commit 13fa90a0e9
3958 changed files with 999286 additions and 4 deletions
@@ -0,0 +1,313 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2015-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @file
* @~English
*
* @brief Implementation of app for running a set of OpenGL load tests.
*/
#include <exception>
#include <sstream>
#include <ktx.h>
#include "GLLoadTests.h"
#include "ltexceptions.h"
GLLoadTests::GLLoadTests(const sampleInvocation samples[],
const uint32_t numSamples,
const char* const name,
const SDL_GLProfile profile,
const int majorVersion,
const int minorVersion)
: GLAppSDL(name, 640, 480, profile, majorVersion, minorVersion),
siSamples(samples), sampleIndex(numSamples)
{
pCurSample = nullptr;
}
GLLoadTests::~GLLoadTests()
{
delete pCurSample;
}
bool
GLLoadTests::initialize(Args& args)
{
bool explicitlyLoadOpenGL = false;
for (uint32_t i = 1; i < args.size(); i++) {
if (args[i].compare("--load-gl") == 0) {
explicitlyLoadOpenGL = true;
args.erase(args.begin() + i);
break;
}
}
if (!GLAppSDL::initialize(args))
return false;
// --load-gl allows testing of explicitly loading the OpenGL pointers.
// Default is for ktxTexture_GLUpload to implicitly load them.
if (explicitlyLoadOpenGL)
ktxLoadOpenGL(reinterpret_cast<PFNGLGETPROCADDRESS>(SDL_GL_GetProcAddress));
for (auto it = args.begin() + 1; it != args.end(); it++) {
infiles.push_back(*it);
}
if (infiles.size() > 0) {
sampleIndex.setNumSamples((uint32_t)infiles.size());
}
// Launch the first sample.
invokeSample(Direction::eForward);
return true;
}
void
GLLoadTests::finalize()
{
delete pCurSample;
GLAppSDL::finalize();
}
bool
GLLoadTests::doEvent(SDL_Event* event)
{
bool result = false;
switch (event->type) {
case SDL_EVENT_KEY_UP:
switch (event->key.key) {
case 'q':
quit = true;
break;
case 'n':
++sampleIndex;
invokeSample(Direction::eForward);
break;
case 'p':
--sampleIndex;
invokeSample(Direction::eBack);
break;
default:
result = true;
}
break;
case SDL_EVENT_MOUSE_BUTTON_DOWN:
// Forward to sample in case this is the start of motion.
result = true;
switch (event->button.button) {
case SDL_BUTTON_LEFT:
buttonDown.x = event->button.x;
buttonDown.y = event->button.y;
buttonDown.timestamp = event->button.timestamp;
break;
default:
break;
}
break;
case SDL_EVENT_MOUSE_BUTTON_UP:
// Forward to sample so it doesn't get stuck in button down state.
result = true;
switch (event->button.button) {
case SDL_BUTTON_LEFT:
if (SDL_fabs(event->button.x - buttonDown.x) < 5
&& SDL_fabs(event->button.y - buttonDown.y) < 5
&& (event->button.timestamp - buttonDown.timestamp) < 100) {
// Advance to the next sample.
++sampleIndex;
invokeSample(Direction::eForward);
}
break;
default:
break;
}
break;
// On macOS drop events come also when Launch Pad sends a file open event.
case SDL_EVENT_DROP_BEGIN:
// Opens of multiple selected files from Finder/LaunchPad come as
// a BEGIN, COMPLETE sequence per file. Only clear infiles after a
// suitable pause between COMPLETE and BEGIN.
if (event->drop.timestamp - dropCompleteTime > 500) {
infiles.clear();
}
break;
case SDL_EVENT_DROP_FILE:
infiles.push_back(event->drop.data);
break;
case SDL_EVENT_DROP_COMPLETE:
if (!infiles.empty()) {
// Guard against the drop being text.
dropCompleteTime = event->drop.timestamp;
sampleIndex.setNumSamples((uint32_t)infiles.size());
invokeSample(Direction::eForward);
}
break;
case SDL_EVENT_USER:
if (event->user.code == SwipeDetector::swipeGesture) {
// This is horrible.
uint64_t udirection = reinterpret_cast<uint64_t>(event->user.data1);
SwipeDetector::Direction direction =
static_cast<SwipeDetector::Direction>(udirection);
switch (direction) {
case SwipeDetector::Direction::left:
++sampleIndex;
invokeSample(Direction::eForward);
break;
case SwipeDetector::Direction::right:
--sampleIndex;
invokeSample(Direction::eBack);
break;
default:
result = true;
}
} else {
result = true;
}
break;
default:
result = swipeDetector.doEvent(event);
}
if (result) {
// Further processing required.
if (pCurSample != nullptr)
result = pCurSample->doEvent(event); // Give sample a chance.
if (result)
return GLAppSDL::doEvent(event); // Pass to base class.
}
return result;
}
void
GLLoadTests::windowResized()
{
if (pCurSample != nullptr)
pCurSample->resize(w_width, w_height);
}
void
GLLoadTests::drawFrame(uint32_t msTicks)
{
if (pCurSample != nullptr)
pCurSample->run(msTicks);
GLAppSDL::drawFrame(msTicks);
}
#if 0
void
GLLoadTests::getOverlayText(GLTextOverlay * textOverlay)
{
if (enableTextOverlay && pCurSample != nullptr) {
pCurSample->getOverlayText(textOverlay);
}
}
#endif
void
GLLoadTests::invokeSample(Direction dir)
{
const sampleInvocation* sampleInv = &siSamples[sampleIndex];
delete pCurSample;
// Certain events can be triggered during new sample initialization
// while pCurSample is not valid, e.g. FOCUS_LOST. Protect against
// problems from this by indicating there is no current sample.
pCurSample = nullptr;
uint32_t unsupportedTypeExceptions = 0;
std::string fileTitle;
for (;;) {
try {
if (infiles.size() > 0) {
fileTitle = "Viewing file ";
fileTitle += infiles[sampleIndex];
pCurSample = showFile(infiles[sampleIndex]);
} else {
sampleInv = &siSamples[sampleIndex];
pCurSample = sampleInv->createSample(w_width, w_height,
sampleInv->args,
sBasePath);
}
break;
} catch (unsupported_ctype& e) {
(void)e; // To quiet unused variable warnings from some compilers.
unsupportedTypeExceptions++;
if (unsupportedTypeExceptions == sampleIndex.getNumSamples()) {
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR,
infiles.size() > 0 ? fileTitle.c_str() : sampleInv->title,
"None of the specified samples or files use texture types "
"supported on this platform.",
NULL);
exit(0);
} else {
dir == Direction::eForward ? ++sampleIndex : --sampleIndex;
}
} catch (std::exception& e) {
const SDL_MessageBoxButtonData buttons[] = {
/* .flags, .buttonid, .text */
{ SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 0, "Continue" },
{ SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, 1, "Abort" },
};
#if 0
const SDL_MessageBoxColorScheme colorScheme = {
{ /* .colors (.r, .g, .b) */
/* [SDL_MESSAGEBOX_COLOR_BACKGROUND] */
{ 255, 0, 0 },
/* [SDL_MESSAGEBOX_COLOR_TEXT] */
{ 0, 255, 0 },
/* [SDL_MESSAGEBOX_COLOR_BUTTON_BORDER] */
{ 255, 255, 0 },
/* [SDL_MESSAGEBOX_COLOR_BUTTON_BACKGROUND] */
{ 0, 0, 255 },
/* [SDL_MESSAGEBOX_COLOR_BUTTON_SELECTED] */
{ 255, 0, 255 }
}
};
#endif
const SDL_MessageBoxData messageboxdata = {
SDL_MESSAGEBOX_ERROR, // .flags
NULL, // .window
infiles.size() > 0 ?
fileTitle.c_str() : sampleInv->title, // .title
e.what(), // .message
SDL_arraysize(buttons), // .numbuttons
buttons, // .buttons
NULL //&colorScheme // .colorScheme
};
int buttonid;
if (!SDL_ShowMessageBox(&messageboxdata, &buttonid)) {
SDL_Log("error displaying error message box");
exit(1);
}
if (buttonid == 0) {
// Continue
dir == Direction::eForward ? ++sampleIndex : --sampleIndex;
} else {
// We've been told to quit or no button was pressed.
exit(1);
}
}
}
setAppTitle(infiles.size() > 0 ? fileTitle.c_str() : sampleInv->title);
pCurSample->resize(w_width, w_height);
}
void
GLLoadTests::onFPSUpdate()
{
// Using onFPSUpdate avoids rewriting the title every frame.
//setWindowTitle(pCurSample->title);
GLAppSDL::onFPSUpdate();
}
@@ -0,0 +1,100 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
#ifndef GL_LOAD_TESTS_H
#define GL_LOAD_TESTS_H
/*
* Copyright 2015-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @file
* @~English
*
* @brief Definition of app for running a set of OpenGL load tests.
*/
#include <string>
#include <vector>
#include "GLAppSDL.h"
#include "LoadTestSample.h"
#include "SwipeDetector.h"
class GLLoadTests : public GLAppSDL {
public:
/** A table of samples and arguments */
typedef struct sampleInvocation_ {
const LoadTestSample::PFN_create createSample;
const char* const args;
const char* const title;
} sampleInvocation;
GLLoadTests(const sampleInvocation samples[],
const uint32_t numSamples,
const char* const name,
const SDL_GLProfile profile,
const int majorVersion,
const int minorVersion);
virtual ~GLLoadTests();
virtual bool doEvent(SDL_Event* event);
virtual void drawFrame(uint32_t msTicks);
virtual void finalize();
//virtual void getOverlayText(TextOverlay* textOverlay, float yOffset);
virtual bool initialize(Args& args);
virtual void onFPSUpdate();
virtual void windowResized();
protected:
enum class Direction {
eForward,
eBack
};
void invokeSample(Direction dir);
LoadTestSample* showFile(const std::string& filename);
LoadTestSample* pCurSample;
bool quit = false;
const sampleInvocation* const siSamples;
class sampleIndex {
public:
sampleIndex(const uint32_t numSamples) : numSamples(numSamples) {
index = 0;
}
sampleIndex& operator++() {
if (++index >= numSamples)
index = 0;
return *this;
}
sampleIndex& operator--() {
if (--index > numSamples /* underflow */)
index = numSamples-1;
return *this;
}
operator int32_t() {
return index;
}
uint32_t getNumSamples() { return numSamples; }
void setNumSamples(uint32_t ns) { numSamples = ns; }
protected:
uint32_t numSamples;
uint32_t index;
} sampleIndex;
std::vector<std::string> infiles;
Sint64 dropCompleteTime = 0;
struct {
float x;
float y;
Sint64 timestamp;
} buttonDown;
SwipeDetector swipeDetector;
};
#endif /* GL_LOAD_TESTS_H */
@@ -0,0 +1,250 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2018-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file DrawTexture.cpp
* @brief Draw textures at actual size using the DrawTexture functions
* from OES_draw_texture.
*
* @author Mark Callow
*/
#if defined(_WIN32)
#if _MSC_VER < 1900
#define snprintf _snprintf
#endif
#define _CRT_SECURE_NO_WARNINGS
#endif
#include <iomanip>
#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 "frame.h"
/* ------------------------------------------------------------------------ */
#if 0
static int isPowerOfTwo (int x)
{
if (x < 0) x = -1;
return ((x != 0) && !(x & (x - 1)));
}
#endif
/* ------------------------------------------------------------------------ */
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)
: LoadTestSample(width, height, sBasePath)
{
GLint iCropRect[4] = {0, 0, 0, 0};
const GLchar* szExtensions = (const GLchar*)glGetString(GL_EXTENSIONS);
const char* filename;
std::string pathname;
GLenum target;
GLboolean npotTexture;
GLenum glerror;
ktxTexture* kTexture;
KTX_error_code ktxresult;
GLint sign_s = 1, sign_t = 1;
bInitialized = false;
gnTexture = 0;
if (strstr(szExtensions, "OES_draw_texture") != NULL) {
glDrawTexsOES = (PFNGLDRAWTEXSOESPROC)SDL_GL_GetProcAddress("glDrawTexsOES");
glDrawTexiOES = (PFNGLDRAWTEXIOESPROC)SDL_GL_GetProcAddress("glDrawTexiOES");
glDrawTexxOES = (PFNGLDRAWTEXXOESPROC)SDL_GL_GetProcAddress("glDrawTexxOES");
glDrawTexfOES = (PFNGLDRAWTEXFOESPROC)SDL_GL_GetProcAddress("glDrawTexfOES");
glDrawTexsvOES = (PFNGLDRAWTEXSVOESPROC)SDL_GL_GetProcAddress("glDrawTexsvOES");
glDrawTexivOES = (PFNGLDRAWTEXIVOESPROC)SDL_GL_GetProcAddress("glDrawTexivOES");
glDrawTexxvOES = (PFNGLDRAWTEXXVOESPROC)SDL_GL_GetProcAddress("glDrawTexxvOES");
glDrawTexfvOES = (PFNGLDRAWTEXFVOESPROC)SDL_GL_GetProcAddress("glDrawTexfvOES");
} else {
/* Can't do anything */
std::stringstream message;
message << "DrawTexture: this OpenGL ES implementation does not support"
<< " OES_draw_texture. Can't Run Test";
throw std::runtime_error(message.str());
}
if (strstr(szExtensions, "OES_texture_npot") != NULL)
bNpotSupported = GL_TRUE;
else
bNpotSupported = GL_FALSE;
if ((filename = strchr(szArgs, ' ')) != NULL) {
npotTexture = GL_FALSE;
if (!strncmp(szArgs, "--npot ", 7)) {
npotTexture = GL_TRUE;
#if defined(DEBUG)
} else {
assert(0); /* Unknown argument in sampleInvocations */
#endif
}
} else {
filename = szArgs;
npotTexture = GL_FALSE;
}
if (npotTexture && !bNpotSupported) {
/* Load error texture. */
filename = "no-npot.ktx";
}
pathname = getAssetPath() + filename;
ktxresult = ktxTexture_CreateFromNamedFile(pathname.c_str(),
KTX_TEXTURE_CREATE_NO_FLAGS,
&kTexture);
if (KTX_SUCCESS != ktxresult) {
std::stringstream message;
message << "Creation of ktxTexture from \"" << pathname
<< "\" failed: " << ktxErrorString(ktxresult);
throw std::runtime_error(message.str());
}
ktxresult = ktxTexture_GLUpload(kTexture, &gnTexture, &target, &glerror);
if (KTX_SUCCESS == ktxresult) {
if (target != GL_TEXTURE_2D) {
/* Can only draw 2D textures */
glDeleteTextures(1, &gnTexture);
return;
}
if (kTexture->orientation.x == KTX_ORIENT_X_LEFT)
sign_s = -1;
if (kTexture->orientation.y == KTX_ORIENT_Y_DOWN)
sign_t = -1;
iCropRect[2] = uTexWidth = kTexture->baseWidth;
iCropRect[3] = uTexHeight = kTexture->baseHeight;
iCropRect[2] *= sign_s;
iCropRect[3] *= sign_t;
glEnable(target);
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);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
glTexParameteriv(target, GL_TEXTURE_CROP_RECT_OES, iCropRect);
/* Check for any errors */
assert(GL_NO_ERROR == glGetError());
ktxTexture_Destroy(kTexture);
} else {
std::stringstream message;
message << "Load of texture from \"" << pathname << "\" failed: ";
if (ktxresult == KTX_GL_ERROR) {
message << std::showbase << "GL error " << std::hex << glerror
<< "occurred.";
} else {
message << ktxErrorString(ktxresult);
}
throw std::runtime_error(message.str());
}
glClearColor(0.4f, 0.4f, 0.5f, 1.0f);
glColor4f(1.0f, 1.0f, 0.0f, 1.0f);
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_BYTE, 0, (GLvoid *)frame_position);
bInitialized = GL_TRUE;
}
/* ------------------------------------------------------------------------ */
DrawTexture::~DrawTexture()
{
if (bInitialized) {
glDeleteTextures(1, &gnTexture);
}
assert(GL_NO_ERROR == glGetError());
}
/* ------------------------------------------------------------------------ */
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
framePMatrix = glm::ortho(0.f, (float)uWidth, 0.f, (float)uHeight);
// Move (0,0,0) to the center of the window.
framePMatrix *= glm::translate(glm::mat4(),
glm::vec3((float)uWidth/2, (float)uHeight/2, 0));
glMatrixMode(GL_PROJECTION);
glLoadMatrixf(glm::value_ptr(framePMatrix));
glMatrixMode(GL_MODELVIEW);
// 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.]
glLoadIdentity();
glScalef((float)(uWidth - 1) / 2, (float)(uHeight - 1) / 2, 1);
}
/* ------------------------------------------------------------------------ */
void
DrawTexture::run(uint32_t /*msTicks*/)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glDisable(GL_TEXTURE_2D);
glDrawArrays(GL_LINE_LOOP, 0, 4);
glEnable(GL_TEXTURE_2D);
glDrawTexiOES(uWidth/2 - uTexWidth/2,
(uHeight)/2 - uTexHeight/2,
0,
uTexWidth, uTexHeight);
assert(GL_NO_ERROR == glGetError());
}
/* ------------------------------------------------------------------------ */
@@ -0,0 +1,66 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2018-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file DrawTexture.h
* @brief Draw textures at actual size using the DrawTexture functions
* from OES_draw_texture.
*
* @author Mark Callow
*/
#ifndef DRAW_TEXTURE_H
#define DRAW_TEXTURE_H
#include <GLES/gl.h>
#include <GLES/glext.h>
#include "LoadTestSample.h"
class DrawTexture : public LoadTestSample {
public:
DrawTexture(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath);
~DrawTexture();
virtual void resize(uint32_t width, uint32_t height);
virtual void run(uint32_t msTicks);
//virtual void getOverlayText(GLTextOverlay *textOverlay);
static LoadTestSample*
create(uint32_t width, uint32_t height,
const char* const szArgs, const std::string sBasePath);
protected:
PFNGLDRAWTEXSOESPROC glDrawTexsOES;
PFNGLDRAWTEXIOESPROC glDrawTexiOES;
PFNGLDRAWTEXXOESPROC glDrawTexxOES;
PFNGLDRAWTEXFOESPROC glDrawTexfOES;
PFNGLDRAWTEXSVOESPROC glDrawTexsvOES;
PFNGLDRAWTEXIVOESPROC glDrawTexivOES;
PFNGLDRAWTEXXVOESPROC glDrawTexxvOES;
PFNGLDRAWTEXFVOESPROC glDrawTexfvOES;
uint32_t uWidth;
uint32_t uHeight;
uint32_t uTexWidth;
uint32_t uTexHeight;
glm::mat4 framePMatrix;
GLuint gnTexture;
bool bNpotSupported;
bool bInitialized;
};
#endif /* DRAW_TEXTURE_H */
@@ -0,0 +1,109 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2018-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file ES1LoadTests.cpp
* @brief List of tests of the KTX loader for OpenGL ES 1.1.
*
* The loader is tested by loading and drawing KTX textures in various formats
* using the DrawTexture and TexturedCube samples.
*/
#include <sstream>
#include <ktx.h>
#include "GLLoadTests.h"
#include "DrawTexture.h"
#include "TexturedCube.h"
LoadTestSample*
GLLoadTests::showFile(const std::string& filename)
{
KTX_error_code ktxresult;
ktxTexture* kTexture;
ktxresult = ktxTexture_CreateFromNamedFile(filename.c_str(),
KTX_TEXTURE_CREATE_NO_FLAGS,
&kTexture);
if (KTX_SUCCESS != ktxresult) {
std::stringstream message;
message << "Creation of ktxTexture from \"" << getAssetPath()
<< filename << "\" failed: " << ktxErrorString(ktxresult);
throw std::runtime_error(message.str());
}
LoadTestSample::PFN_create createViewer;
LoadTestSample* pViewer;
createViewer = DrawTexture::create;
ktxTexture_Destroy(kTexture);
pViewer = createViewer(w_width, w_height, filename.c_str(), "");
return pViewer;
}
const GLLoadTests::sampleInvocation siSamples[] = {
{ DrawTexture::create,
"--npot hi_mark.ktx",
"RGB8 NPOT HI Logo"
},
{ DrawTexture::create,
"--npot luminance-reference-metadata.ktx",
"LUMINANCE8 NPOT"
},
{ DrawTexture::create,
"orient-up-metadata.ktx",
"RGB8 + KTXOrientation up"
},
{ DrawTexture::create,
"orient-down-metadata.ktx",
"RGB8 + KTXOrientation down"
},
{ DrawTexture::create,
"etc1.ktx",
"ETC1 RGB8"
},
{ DrawTexture::create,
"etc2-rgb.ktx",
"ETC2 RGB8"
},
{ DrawTexture::create,
"etc2-rgba1.ktx",
"ETC2 RGB8A1"
},
{ DrawTexture::create,
"etc2-rgba8.ktx",
"ETC2 RGB8A8"
},
{ DrawTexture::create,
"rgba-reference.ktx",
"RGBA8"
},
{ TexturedCube::create,
"rgb-reference.ktx",
"RGB8"
},
{ TexturedCube::create,
"rgb-amg-reference.ktx",
"RGB8 + Auto Mipmap"
},
{ TexturedCube::create,
"rgb-mipmap-reference.ktx",
"RGB8 Color/level mipmap"
},
{ TexturedCube::create,
"--npot hi_mark_sq.ktx",
"RGB8 NPOT HI Logo"
},
};
const int iNumSamples = sizeof(siSamples) / sizeof(GLLoadTests::sampleInvocation);
AppBaseSDL* theApp = new GLLoadTests(siSamples, iNumSamples,
"KTX Loader Tests for OpenGL ES 1",
SDL_GL_CONTEXT_PROFILE_ES, 1, 1);
@@ -0,0 +1,202 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2018-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file TexturedCube.cpp
* @brief Draw a textured cube.
*/
#if defined(_WIN32)
#if _MSC_VER < 1900
#define snprintf _snprintf
#endif
#define _CRT_SECURE_NO_WARNINGS
#endif
#include <iomanip>
#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 "TexturedCube.h"
#include "cube.h"
#if defined(_WIN32)
#define snprintf _snprintf
#endif
/* ------------------------------------------------------------------------- */
LoadTestSample*
TexturedCube::create(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath)
{
return new TexturedCube(width, height, szArgs, sBasePath);
}
TexturedCube::TexturedCube(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath)
: LoadTestSample(width, height, sBasePath)
{
const GLchar* szExtensions = (const GLchar*)glGetString(GL_EXTENSIONS);
const char* filename;
std::string pathname;
GLuint gnTexture = 0;
GLenum target;
GLenum glerror;
GLboolean npotSupported, npotTexture;
ktxTexture* kTexture;
KTX_error_code ktxresult;
if (strstr(szExtensions, "OES_texture_npot") != NULL)
npotSupported = GL_TRUE;
else
npotSupported = GL_FALSE;
if ((filename = strchr(szArgs, ' ')) != NULL) {
npotTexture = GL_FALSE;
if (!strncmp(szArgs, "--npot ", 7)) {
npotTexture = GL_TRUE;
#if defined(DEBUG)
} else {
assert(0); /* Unknown argument in sampleInvocations */
#endif
}
} else {
filename = szArgs;
npotTexture = GL_FALSE;
}
if (npotTexture && !npotSupported) {
/* Load error texture. */
filename = "no-npot.ktx";
}
pathname = getAssetPath() + filename;
ktxresult = ktxTexture_CreateFromNamedFile(pathname.c_str(),
KTX_TEXTURE_CREATE_NO_FLAGS,
&kTexture);
if (KTX_SUCCESS != ktxresult) {
std::stringstream message;
message << "Creation of ktxTexture from \"" << pathname
<< "\" failed: " << ktxErrorString(ktxresult);
throw std::runtime_error(message.str());
}
ktxresult = ktxTexture_GLUpload(kTexture, &gnTexture, &target, &glerror);
if (KTX_SUCCESS == ktxresult) {
if (target != GL_TEXTURE_2D) {
/* Can only draw 2D textures */
glDeleteTextures(1, &gnTexture);
return;
}
glEnable(target);
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);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
ktxTexture_Destroy(kTexture);
} else {
std::stringstream message;
message << "Load of texture from \"" << pathname << "\" failed: ";
if (ktxresult == KTX_GL_ERROR) {
message << std::showbase << "GL error " << std::hex << glerror
<< "occurred.";
} else {
message << ktxErrorString(ktxresult);
}
throw std::runtime_error(message.str());
}
/* By default dithering is enabled. Dithering does not provide visual
* improvement in this sample so disable it to improve performance.
*/
glDisable(GL_DITHER);
glEnable(GL_CULL_FACE);
glClearColor(0.2f,0.3f,0.4f,1.0f);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glVertexPointer(3, GL_FLOAT, 0, cube_face);
glColorPointer(4, GL_FLOAT, 0, cube_color);
glTexCoordPointer(2, GL_FLOAT, 0, cube_texture);
}
TexturedCube::~TexturedCube()
{
glDisable(GL_TEXTURE_2D);
glEnable(GL_DITHER);
glDisable(GL_CULL_FACE);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
assert(GL_NO_ERROR == glGetError());
}
void
TexturedCube::resize(uint32_t uWidth, uint32_t uHeight)
{
glm::mat4 matProj;
glViewport(0, 0, uWidth, uHeight);
glMatrixMode( GL_PROJECTION );
matProj = glm::perspective(glm::radians(45.f),
uWidth / (float)uHeight,
1.f, 100.f);
glLoadIdentity();
glLoadMatrixf(glm::value_ptr(matProj));
glMatrixMode( GL_MODELVIEW );
assert(GL_NO_ERROR == glGetError());
}
void
TexturedCube::run(uint32_t msTicks)
{
/* Setup the view matrix : just turn around the cube. */
const float fDistance = 5.0f;
glm::vec3 eye((float)cos( msTicks*0.001f ) * fDistance,
(float)sin( msTicks*0.0007f ) * fDistance,
(float)sin( msTicks*0.001f ) * fDistance);
glm::vec3 look(0.,0.,0.);
glm::vec3 up(0.,1.,0.);
glm::mat4 matView = glm::lookAt( eye, look, up );
glLoadIdentity();
glLoadMatrixf(glm::value_ptr(matView));
/* Draw */
glClear( GL_COLOR_BUFFER_BIT );
glDrawElements(GL_TRIANGLES, CUBE_NUM_INDICES,
GL_UNSIGNED_SHORT, cube_index_buffer);
assert(GL_NO_ERROR == glGetError());
}
/* ------------------------------------------------------------------------- */
@@ -0,0 +1,38 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2018-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file TexturedCube.cpp
* @brief Draw a textured cube.
*/
#ifndef TEXTURED_CUBE_H
#define TEXTURED_CUBE_H
#include <GLES/gl.h>
#include <GLES/glext.h>
#include "LoadTestSample.h"
class TexturedCube : public LoadTestSample {
public:
TexturedCube(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath);
~TexturedCube();
virtual void resize(uint32_t width, uint32_t height);
virtual void run(uint32_t msTicks);
//virtual void getOverlayText(GLTextOverlay *textOverlay);
static LoadTestSample*
create(uint32_t width, uint32_t height,
const char* const szArgs, const std::string sBasePath);
};
#endif /* TEXTURED_CUBE_H */
@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2015-2024 Mark Callow
SPDX-License-Identifier: Apache-2.0
WARNING: Do not attempt to edit the Info.plist you see in the Xcode
project. It is unavoidably configured from this by CMake. Edit here.
-->
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key>
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
<key>CFBundleDisplayName</key>
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
<key>CFBundleGetInfoString</key>
<string>${MACOSX_BUNDLE_INFO_STRING}</string>
<key>NSHumanReadableCopyright</key>
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleVersion</key>
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
<key>CFBundleShortVersionString</key>
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>NSMainNibFile</key>
<string></string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIApplicationSupportsIndirectInputEvents</key>
<string>YES</string>
</dict>
</plist>
@@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}
@@ -0,0 +1,57 @@
{
"images" : [
{
"orientation" : "portrait",
"idiom" : "iphone",
"filename" : "launch-portrait-iphone@x2.png",
"extent" : "full-screen",
"minimum-system-version" : "7.0",
"scale" : "2x"
},
{
"extent" : "full-screen",
"idiom" : "iphone",
"subtype" : "retina4",
"filename" : "launch-portrait-iphone@r4.png",
"minimum-system-version" : "7.0",
"orientation" : "portrait",
"scale" : "2x"
},
{
"orientation" : "portrait",
"idiom" : "ipad",
"filename" : "launch-portrait@x1.png",
"extent" : "full-screen",
"minimum-system-version" : "7.0",
"scale" : "1x"
},
{
"orientation" : "landscape",
"idiom" : "ipad",
"filename" : "launch-landscape@x1.png",
"extent" : "full-screen",
"minimum-system-version" : "7.0",
"scale" : "1x"
},
{
"orientation" : "portrait",
"idiom" : "ipad",
"filename" : "launch-portrait@2x.png",
"extent" : "full-screen",
"minimum-system-version" : "7.0",
"scale" : "2x"
},
{
"orientation" : "landscape",
"idiom" : "ipad",
"filename" : "launch-landscape@x2.png",
"extent" : "full-screen",
"minimum-system-version" : "7.0",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2017-2020 The Khronos Group Inc.
SPDX-License-Identifier: Apache-2.0
-->
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22685"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Llm-lL-Icb"/>
<viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" textAlignment="center" lineBreakMode="middleTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="GJd-Yh-RWb">
<rect key="frame" x="20" y="300" width="560" height="88"/>
<string key="text">KTX Texture
OpenGL ${version} Loader Tests</string>
<fontDescription key="fontDescription" name="HelveticaNeue" family="Helvetica Neue" pointSize="36"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<color key="shadowColor" red="0.66666668653488159" green="0.66666668653488159" blue="0.66666668653488159" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<size key="shadowOffset" width="1" height="2"/>
</label>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="GJd-Yh-RWb" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leadingMargin" id="7go-Ai-m2p"/>
<constraint firstItem="GJd-Yh-RWb" firstAttribute="trailing" secondItem="Ze5-6b-2t3" secondAttribute="trailingMargin" id="G9l-Nb-7mW"/>
<constraint firstItem="GJd-Yh-RWb" firstAttribute="top" secondItem="Llm-lL-Icb" secondAttribute="bottom" constant="162" id="PSH-VH-8d0"/>
<constraint firstItem="xb3-aO-Qok" firstAttribute="top" secondItem="GJd-Yh-RWb" secondAttribute="bottom" constant="325" id="U3h-5B-haO"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
</document>
@@ -0,0 +1,30 @@
# Copyright 2023 Khronos Group, Inc.
# SPDX-License-Identifier: Apache-2.0
# If this file's extension is .desktop DO NOT edit it. It is generated from
# glloadtests.desktop.in.
#
# If the extension is .in feel free to edit it. After editing you MUST
# run cmake project configuration again to generate updated output files.
[Desktop Entry]
Version=1.0
Name=${version} Load Tests
Name[ja]=${version} Load Tests
Name[fr]=${version} Load Tests
GenericName=KTX loader tests and file viewer
GenericName[ja]=KTXローダーテストとファイルビューア
GenericName[fr]=Tests de chargeur KTX et visionneuse de fichiers
Comment=View KTX files using OpenGL or run GLUpload functionality tests.
Comment[fr]=Affichez les fichiers KTX à l'aide d'OpenGL ou exécutez des tests de fonctionnalité GLUpload.
Comment[ja]=OpenGLを使用してKTXファイルを表示するか、GLUpload機能テストを実行します。
TryExec=${target}
Exec=${target} %F
Terminal=false
Type=Application
Keywords=Ktx;Texture;Viewer
Keywords[ja]=Ktx;Texture;Viewer
Keywords[fr]=Ktx;Texture;Viewer
Icon=${TARGET_INSTALL_RESOURCES_FULL_DIR}/ktx_app.svg
Categories=Utility;
StartupNotify=true
MimeType=image/ktx;image/ktx2;
@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2015-2024 Mark Callow
SPDX-License-Identifier: Apache-2.0
WARNING: Do not attempt to edit the Info.plist you see in the Xcode
project. It is unavoidably configured from this by CMake. Edit here.
-->
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key>
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
<key>CFBundleDisplayName</key>
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
<key>CFBundleGetInfoString</key>
<string>${MACOSX_BUNDLE_INFO_STRING}</string>
<key>NSHumanReadableCopyright</key>
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
<key>CFBundleIconFile</key>
<string>${MACOSX_BUNDLE_ICON_FILE}</string>
<key>CFBundleIdentifier</key>
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleVersion</key>
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
<key>CFBundleShortVersionString</key>
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>LSIsAppleDefaultForType</key>
<false/>
<key>LSHandlerRank</key>
<string>Alternate</string>
<key>CFBundleTypeIconFile</key>
<string>${KTX_DOC_ICON}</string>
<key>LSItemContentTypes</key>
<array>
<string>public.ktx2</string>
<string>public.ktx</string>
</array>
<key>CFBundleTypeExtensions</key>
<array>
<string>ktx</string>
<string>ktx2</string>
</array>
<key>CFBundleTypeMIMETypes</key>
<array>
<string>image/ktx</string>
<string>image/ktx2</string>
</array>
<key>CFBundleTypeName</key>
<string>KTX texture file</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSTypeIsPackage</key>
<false/>
</dict>
</array>
<key>LSApplicationCategoryType</key>
<string>public.app-category.graphics-design</string>
<key>NSHighResolutionCapable</key>
<true/>
<key>NSMainNibFile</key>
<string></string>
<key>UISupportedInterfaceOrientations</key>
<array/>
</dict>
</plist>
@@ -0,0 +1,424 @@
/* -*- 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());
}
/* ------------------------------------------------------------------------- */
@@ -0,0 +1,71 @@
/* -*- tab-iWidth: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2018-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief Definition of texture loading test using glDrawTexture.
*
* @author Mark Callow
*/
#ifndef DRAW_TEXTURE_H
#define DRAW_TEXTURE_H
#include "GL3LoadTestSample.h"
class DrawTexture : public GL3LoadTestSample {
public:
DrawTexture(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath);
~DrawTexture();
virtual void resize(uint32_t width, uint32_t height);
virtual void run(uint32_t msTicks);
//virtual void getOverlayText(GLTextOverlay *textOverlay);
static LoadTestSample*
create(uint32_t width, uint32_t height,
const char* const szArgs, const std::string sBasePath);
protected:
void processArgs(std::string sArgs);
int preloadImages = 0;
uint32_t uWidth;
uint32_t uHeight;
uint32_t uTexWidth;
uint32_t uTexHeight;
glm::mat4 frameMvMatrix;
glm::mat4 quadMvMatrix;
glm::mat4 pMatrix;
GLuint gnTexture;
GLuint gnTexProg;
GLuint gnColProg;
#define FRAME 0
#define QUAD 1
GLuint gnVaos[2];
GLuint gnVbo;
GLint gulMvMatrixLocTP;
GLint gulPMatrixLocTP;
GLint gulSamplerLocTP;
GLint gulMvMatrixLocCP;
GLint gulPMatrixLocCP;
bool bInitialized;
ktx_transcode_fmt_e transcodeTarget;
};
#endif /* DRAW_TEXTURE_H */
@@ -0,0 +1,326 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 sts expandtab: */
/*
* Copyright 2018-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief Encode a texture then texture a cube with it, transcoding if necessary.
*
* This is used principally to check the encoders are properly linked on platforms where the ktx tools are
* unavailable and libktx is a static library.
*
* @author Mark Callow, github.com/MarkCallow.
*/
#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 "EncodeTexture.h"
#include "GLTextureTranscoder.hpp"
#include "TranscodeTargetStrToFmt.h"
#include "cube.h"
#include "argparser.h"
#include "ltexceptions.h"
/* ------------------------------------------------------------------------- */
extern const GLchar* pszVs;
extern const GLchar *pszDecalFs, *pszDecalSrgbEncodeFs;
/* ------------------------------------------------------------------------- */
LoadTestSample*
EncodeTexture::create(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath)
{
return new EncodeTexture(width, height, szArgs, sBasePath);
}
EncodeTexture::EncodeTexture(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath)
: GL3LoadTestSample(width, height, szArgs, sBasePath)
{
std::string filename;
GLenum target;
GLenum glerror;
GLuint gnDecalFs, gnVs;
GLsizeiptr offset;
ktxTexture2* kTexture;
KTX_error_code ktxresult;
bInitialized = GL_FALSE;
gnTexture = 0;
encodeTarget = EF_ETC1S;
transcodeTarget = KTX_TTF_NOSELECTION;
processArgs(szArgs);
filename = getAssetPath() + ktxfilename;
ktxresult = ktxTexture_CreateFromNamedFile(filename.c_str(),
KTX_TEXTURE_CREATE_NO_FLAGS,
(ktxTexture**)&kTexture);
if (KTX_SUCCESS != ktxresult) {
std::stringstream message;
message << "Creation of ktxTexture from \"" << filename
<< "\" failed: " << ktxErrorString(ktxresult);
throw std::runtime_error(message.str());
}
if (!kTexture->isCompressed) {
switch (encodeTarget) {
case EF_ASTC:
ktxresult = ktxTexture2_CompressAstc(kTexture, 0);
break;
case EF_ETC1S:
ktxresult = ktxTexture2_CompressBasis(kTexture, 0);
break;
case EF_UASTC:
{
ktxBasisParams params = { };
params.structSize = sizeof(params);
params.uastc = KTX_TRUE;
params.threadCount = 1;
ktxresult = ktxTexture2_CompressBasisEx(kTexture, &params);
break;
}
}
if (KTX_SUCCESS != ktxresult) {
std::stringstream message;
message << "Encoding of ktxTexture2 to " << encodeTarget
<< " failed: " << ktxErrorString(ktxresult);
throw std::runtime_error(message.str());
}
}
if (ktxTexture2_NeedsTranscoding(kTexture)) {
TextureTranscoder tc;
tc.transcode((ktxTexture2*)kTexture);
}
ktxresult = ktxTexture_GLUpload(ktxTexture(kTexture), &gnTexture, &target,
&glerror);
if (KTX_SUCCESS == ktxresult) {
if (target != GL_TEXTURE_2D) {
/* Can only draw 2D textures */
std::stringstream message;
glDeleteTextures(1, &gnTexture);
message << "App can only draw 2D textures.";
throw std::runtime_error(message.str());
}
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);
ktxTexture_Destroy(ktxTexture(kTexture));
assert(GL_NO_ERROR == glGetError());
} else {
std::stringstream message;
message << "Load of texture from \"" << filename << "\" failed: ";
if (ktxresult == KTX_GL_ERROR && glerror == GL_INVALID_ENUM) {
throw unsupported_ctype();
} else {
if (ktxresult == KTX_GL_ERROR) {
message << std::showbase << "GL error " << std::hex << glerror
<< " occurred.";
} else {
message << ktxErrorString(ktxresult);
}
throw std::runtime_error(message.str());
}
}
// By default dithering is enabled. Dithering does not provide visual
// improvement in this sample so disable it to improve performance.
glDisable(GL_DITHER);
glEnable(GL_CULL_FACE);
glClearColor(0.2f,0.3f,0.4f,1.0f);
// Create a VAO and bind it.
glGenVertexArrays(1, &gnVao);
glBindVertexArray(gnVao);
// Must have vertex data in buffer objects to use VAO's on ES3/GL Core
glGenBuffers(2, gnVbo);
glBindBuffer(GL_ARRAY_BUFFER, gnVbo[0]);
// Must be done after the VAO is bound
// WebGL requires different buffers for data and indices.
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gnVbo[1]);
// Create the buffer data store.
glBufferData(GL_ARRAY_BUFFER,
sizeof(cube_face) + sizeof(cube_color) + sizeof(cube_texture)
+ sizeof(cube_normal), NULL, GL_STATIC_DRAW);
// Interleave data copying and attrib pointer setup so offset is only
// computed once.
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
glEnableVertexAttribArray(3);
offset = 0;
glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(cube_face), cube_face);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (GLvoid*)offset);
offset += sizeof(cube_face);
glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(cube_color), cube_color);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, (GLvoid*)offset);
offset += sizeof(cube_color);
glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(cube_texture), cube_texture);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, (GLvoid*)offset);
offset += sizeof(cube_texture);
glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(cube_normal), cube_normal);
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 0, (GLvoid*)offset);
offset += sizeof(cube_normal);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(cube_index_buffer),
cube_index_buffer, GL_STATIC_DRAW);
const GLchar* actualDecalFs;
if (framebufferColorEncoding() == GL_LINEAR) {
actualDecalFs = pszDecalSrgbEncodeFs;
} else {
actualDecalFs = pszDecalFs;
}
try {
makeShader(GL_VERTEX_SHADER, pszVs, &gnVs);
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(gnDecalFs);
assert(GL_NO_ERROR == glGetError());
bInitialized = GL_TRUE;
}
EncodeTexture::~EncodeTexture()
{
glEnable(GL_DITHER);
glDisable(GL_CULL_FACE);
if (bInitialized) {
glUseProgram(0);
glDeleteTextures(1, &gnTexture);
glDeleteProgram(gnTexProg);
glDeleteBuffers(2, gnVbo);
glDeleteVertexArrays(1, &gnVao);
}
assert(GL_NO_ERROR == glGetError());
}
/* ------------------------------------------------------------------------- */
void
EncodeTexture::processArgs(std::string sArgs)
{
// Options descriptor
struct argparser::option longopts[] = {
{"encode", argparser::option::required_argument, nullptr, 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 1:
if (!ap.optarg.compare("astc"))
encodeTarget = EF_ASTC;
else if (!ap.optarg.compare("etc1s"))
encodeTarget = EF_ETC1S;
else if (!ap.optarg.compare("uastc"))
encodeTarget = EF_UASTC;
else
assert(false && "Error in args in sample table");
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
EncodeTexture::resize(uint32_t uWidth, uint32_t uHeight)
{
glm::mat4 matProj;
glViewport(0, 0, uWidth, uHeight);
matProj = glm::perspective(glm::radians(45.f),
uWidth / (float)uHeight,
1.f, 100.f);
glUniformMatrix4fv(gulPMatrixLocTP, 1, GL_FALSE, glm::value_ptr(matProj));
}
void
EncodeTexture::run(uint32_t msTicks)
{
// Setup the view matrix : just turn around the cube.
const float fDistance = 5.0f;
glm::vec3 eye((float)cos( msTicks*0.001f ) * fDistance,
(float)sin( msTicks*0.0007f ) * fDistance,
(float)sin( msTicks*0.001f ) * fDistance);
glm::vec3 look(0.,0.,0.);
glm::vec3 up(0.,1.,0.);
glm::mat4 matView = glm::lookAt( eye, look, up );
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glUniformMatrix4fv(gulMvMatrixLocTP, 1, GL_FALSE, glm::value_ptr(matView));
glDrawElements(GL_TRIANGLES, CUBE_NUM_INDICES, GL_UNSIGNED_SHORT, 0);
assert(GL_NO_ERROR == glGetError());
}
/* ------------------------------------------------------------------------- */
@@ -0,0 +1,74 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2018-2022 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief GLLoadTestSample derived class for encoding a texture and texturing a cube with it.
*
* @author Mark Callow, github.com/MarkCallow.
*/
#ifndef ENCODE_TEXTURE_H
#define ENCODE_TEXTURE_H
#include "GL3LoadTestSample.h"
#include <iostream>
class EncodeTexture : public GL3LoadTestSample {
public:
EncodeTexture(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath);
~EncodeTexture();
virtual void resize(uint32_t width, uint32_t height);
virtual void run(uint32_t msTicks);
//virtual void getOverlayText(GLTextOverlay *textOverlay);
static LoadTestSample*
create(uint32_t width, uint32_t height,
const char* const szArgs, const std::string sBasePath);
protected:
void processArgs(std::string sArgs);
GLuint gnTexture;
GLuint gnTexProg;
GLuint gnVao;
GLuint gnVbo[2];
GLint gulMvMatrixLocTP;
GLint gulPMatrixLocTP;
GLint gulSamplerLocTP;
bool bInitialized;
ktx_transcode_fmt_e transcodeTarget;
enum encode_fmt_e { EF_ASTC = 1, EF_ETC1S = 2, EF_UASTC = 3 };
friend std::ostream& operator<<(std::ostream& os, encode_fmt_e format);
encode_fmt_e encodeTarget;
};
inline std::ostream& operator<<(std::ostream& os, EncodeTexture::encode_fmt_e format)
{
switch (format) {
case EncodeTexture::EF_ASTC:
os << "astc";
break;
case EncodeTexture::EF_ETC1S:
os << "etc1s";
break;
case EncodeTexture::EF_UASTC:
os << "uastc";
break;
}
return os;
}
#endif /* ENCODE_TEXTURE_H */
@@ -0,0 +1,281 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2017-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @class GLLoadTestSample
* @~English
*
* @brief Definition of a base class for OpenGL texture loading test samples.
*
* @author Mark Callow, github.com/MarkCallow.
*/
#include <string.h>
#include <sstream>
#include <ktx.h>
#include <SDL3/SDL_platform.h>
#include "GL3LoadTestSample.h"
/* ------------------------------------------------------------------------- */
static const GLchar* pszESLangVer = "#version 300 es\n";
// location layout qualifier did not appear until version 330.
static const GLchar* pszGLLangVer = "#version 330 core\n";
/* ------------------------------------------------------------------------- */
void
GL3LoadTestSample::makeShader(GLenum type,
ShaderSource& source,
GLuint* shader)
{
GLint sh = glCreateShader(type);
GLint shaderCompiled;
if (strstr((const char*)glGetString(GL_VERSION), "GL ES") == NULL)
source.insert(source.cbegin(), pszGLLangVer);
else
source.insert(source.cbegin(), pszESLangVer);
glShaderSource(sh, (GLsizei)source.size(), source.data(), NULL);
glCompileShader(sh);
// Check if compilation succeeded
glGetShaderiv(sh, GL_COMPILE_STATUS, &shaderCompiled);
if (!shaderCompiled) {
// An error happened, first retrieve the length of the log message
int logLength, charsWritten;
char* infoLog;
std::stringstream message;
glGetShaderiv(sh, GL_INFO_LOG_LENGTH, &logLength);
// Allocate enough space for the message and retrieve it
infoLog = new char[logLength];
glGetShaderInfoLog(sh, logLength, &charsWritten, infoLog);
message << "makeShader compilation error" << std::endl << infoLog;
delete[] infoLog;
glDeleteShader(sh);
throw std::runtime_error(message.str());
} else {
*shader = sh;
}
}
void
GL3LoadTestSample::makeShader(GLenum type, const GLchar* const source,
GLuint* shader)
{
ShaderSource ss;
ss.push_back(source);
makeShader(type, ss, shader);
}
void
GL3LoadTestSample::makeProgram(GLuint vs, GLuint fs, GLuint* program)
{
GLint linked;
GLint fsCompiled, vsCompiled;
glGetShaderiv(vs, GL_COMPILE_STATUS, &fsCompiled);
glGetShaderiv(fs, GL_COMPILE_STATUS, &vsCompiled);
if (fsCompiled && vsCompiled) {
GLuint prog = glCreateProgram();
glAttachShader(prog, vs);
glAttachShader(prog, fs);
glLinkProgram(prog);
// Check if linking succeeded in the same way we checked for compilation success
glGetProgramiv(prog, GL_LINK_STATUS, &linked);
if (!linked) {
int logLength, charsWritten;
char* infoLog;
std::stringstream message;
glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLength);
infoLog = new char[logLength];
glGetProgramInfoLog(prog, logLength, &charsWritten, infoLog);
message << "makeProgram link error" << std::endl << infoLog;
delete[] infoLog;
glDeleteProgram(prog);
throw std::runtime_error(message.str());
}
*program = prog;
} else {
std::stringstream message;
message << "makeProgram: either vertex or fragment shader is not compiled.";
throw std::runtime_error(message.str());
}
}
#if !defined(GL_COMPRESSED_SRGB_PVRTC_2BPPV1_EXT)
#define GL_COMPRESSED_SRGB_PVRTC_2BPPV1_EXT 0x8A54
#define GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG 0x8C01
#define GL_COMPRESSED_RGBA_PVRTC_2BPPV2_IMG 0x9137
#endif
#if !defined(GL_COMPRESSED_RG_RGTC2)
#define GL_COMPRESSED_RG_RGTC2 0x8DBD
#endif
#if !defined(GL_COMPRESSED_RGBA_BPTC_UNORM)
#define GL_COMPRESSED_RGBA_BPTC_UNORM 0x8E8C
#endif
#if !defined(GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT)
#define GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT 0x8E8E
#endif
void
GL3LoadTestSample::determineCompressedTexFeatures(compressedTexFeatures& features)
{
ktx_int32_t numCompressedFormats;
memset(&features, false, sizeof(features));
glGetIntegerv(GL_NUM_COMPRESSED_TEXTURE_FORMATS, &numCompressedFormats);
GLint* formats = new GLint[numCompressedFormats];
glGetIntegerv(GL_COMPRESSED_TEXTURE_FORMATS, formats);
for (ktx_int32_t i = 0; i < numCompressedFormats; i++) {
if (formats[i] == GL_COMPRESSED_RGBA8_ETC2_EAC)
features.etc2 = true;
if (formats[i] == GL_ETC1_RGB8_OES)
features.etc1 = true;
if (formats[i] == GL_COMPRESSED_RGBA_S3TC_DXT5_EXT)
features.bc3 = true;
if (formats[i] == GL_COMPRESSED_RG_RGTC2)
features.rgtc = true;
if (formats[i] == GL_COMPRESSED_SRGB_PVRTC_2BPPV1_EXT)
features.pvrtc_srgb = true;
if (formats[i] == GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG)
features.pvrtc1 = true;
if (formats[i] == GL_COMPRESSED_RGBA_PVRTC_2BPPV2_IMG)
features.pvrtc2 = true;
if (formats[i] == GL_COMPRESSED_RGBA_ASTC_4x4_KHR)
features.astc_ldr = true;
if (formats[i] == GL_COMPRESSED_RGBA_BPTC_UNORM)
features.bc7 = true;
if (formats[i] == GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT)
features.bc6h = true;
}
delete[] formats;
// Just in case COMPRESSED_TEXTURE_FORMATS didn't return anything.
// There is no ETC2 extension. It went into core in OpenGL ES 2.0.
// ARB_es_compatibility is not a good indicator. ETC2 could be supported
// by software decompression. Better to report unsupported.
if (!features.etc1 && SDL_GL_ExtensionSupported("GL_OES_compressed_ETC1_RGB8_texture"))
features.etc1 = true;;
if (!features.bc3 && SDL_GL_ExtensionSupported("GL_EXT_texture_compression_s3tc"))
features.bc3 = true;
if (!features.rgtc && SDL_GL_ExtensionSupported("GL_ARB_texture_compression_rgtc"))
features.rgtc = true;
if (!features.pvrtc1 && SDL_GL_ExtensionSupported("GL_IMG_texture_compression_pvrtc"))
features.pvrtc1 = true;
if (!features.pvrtc2 && SDL_GL_ExtensionSupported("GL_IMG_texture_compression_pvrtc2"))
features.pvrtc2 = true;
if (!features.pvrtc_srgb && SDL_GL_ExtensionSupported("GL_EXT_pvrtc_sRGB"))
features.pvrtc_srgb = true;
if (!(features.bc7 && features.bc6h) && SDL_GL_ExtensionSupported("GL_ARB_texture_compression_bptc"))
features.bc6h = features.bc7 = true;
if (!features.astc_ldr && SDL_GL_ExtensionSupported("GL_KHR_texture_compression_astc_ldr"))
features.astc_ldr = true;
// The only way to identify this support is the extension string.
// The format name is the same.
if (SDL_GL_ExtensionSupported("GL_KHR_texture_compression_astc_hdr"))
features.astc_hdr = true;
}
bool
GL3LoadTestSample::contextSupportsSwizzle()
{
bool esProfile = false;
GLint majorVersion, minorVersion;
if (strstr((const char*)glGetString(GL_VERSION), "GL ES") != NULL) {
esProfile = true;
}
// MAJOR & MINOR only introduced in GL {,ES} 3.0
glGetIntegerv(GL_MAJOR_VERSION, &majorVersion);
glGetIntegerv(GL_MINOR_VERSION, &minorVersion);
if (glGetError() != GL_NO_ERROR) {
// This is not a GL {,ES} 3.0 context...
assert(false);
return false;
}
if (esProfile)
return true; // ES 3.0+ has swizzle
else if (majorVersion == 3 && minorVersion < 3)
return false; // Swizzle was introduced in OpenGL 3.3.
else
return true;
}
#if !defined(SDL_PLATFORM_IOS)
// SDL only defines this on IOS and on Windows undefined != 0
#define SDL_PLATFORM_IOS 0
#endif
GLint
GL3LoadTestSample::framebufferColorEncoding()
{
GLint encoding = GL_SRGB;
GLenum attachment;
#if !defined(GL_BACK_LEFT)
#define GL_BACK_LEFT 0x0402
#endif
if (strstr((const char*)glGetString(GL_VERSION), "GL ES") == NULL)
attachment = GL_BACK_LEFT;
else if (SDL_PLATFORM_IOS)
// iOS does not use the default framebuffer.
attachment = GL_COLOR_ATTACHMENT0;
else
attachment = GL_BACK;
glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, attachment,
GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING,
&encoding);
return encoding;
}
// Emscripten assimp port not yet available.
#if !defined(__EMSCRIPTEN__)
void
GL3LoadTestSample::loadMesh(std::string filename,
glMeshLoader::MeshBuffer& meshBuffer,
std::vector<glMeshLoader::VertexLayout> vertexLayout,
float scale)
{
GLMeshLoader *mesh = new GLMeshLoader();
mesh->LoadMesh(filename);
assert(mesh->m_Entries.size() > 0);
mesh->CreateBuffers(
meshBuffer,
vertexLayout,
scale);
meshBuffer.dim = mesh->dim.size;
delete(mesh);
}
#else
void
GL3LoadTestSample::loadMesh(std::string,
glMeshLoader::MeshBuffer&,
std::vector<glMeshLoader::VertexLayout>,
float)
{
}
#endif
@@ -0,0 +1,76 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2017-2020 Mark Callow, <khronos at callow dot im>.
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef GL3_LOAD_TEST_SAMPLE_H
#define GL3_LOAD_TEST_SAMPLE_H
#include <ktx.h>
#include "LoadTestSample.h"
#include "mygl.h"
#include "utils/GLMeshLoader.hpp"
#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
class GL3LoadTestSample : public LoadTestSample {
public:
typedef uint64_t ticks_t;
GL3LoadTestSample(uint32_t width, uint32_t height,
const char* const /*szArgs*/,
const std::string sBasePath)
: LoadTestSample(width, height, sBasePath)
{
}
virtual ~GL3LoadTestSample() { }
virtual int doEvent(SDL_Event* event) { return LoadTestSample::doEvent(event); };
virtual void resize(uint32_t width, uint32_t height) = 0;
virtual void run(uint32_t msTicks) = 0;
//virtual void getOverlayText(TextOverlay *textOverlay) { };
typedef LoadTestSample* (*PFN_create)(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath);
protected:
using ShaderSource = std::vector<const GLchar*>;
virtual void keyPressed(uint32_t /*keyCode*/) { }
virtual void viewChanged() { }
static bool contextSupportsSwizzle();
std::string ktxfilename;
int externalFile = 0;
struct compressedTexFeatures {
bool astc_ldr;
bool astc_hdr;
bool bc6h;
bool bc7;
bool etc1;
bool etc2;
bool bc3;
bool pvrtc1;
bool pvrtc_srgb;
bool pvrtc2;
bool rgtc;
};
static void determineCompressedTexFeatures(compressedTexFeatures& features);
static GLint framebufferColorEncoding();
void loadMesh(std::string filename, glMeshLoader::MeshBuffer& meshBuffer,
std::vector<glMeshLoader::VertexLayout> vertexLayout,
float scale);
static void makeShader(GLenum type, ShaderSource& source, GLuint* shader);
static void makeShader(GLenum type, const GLchar* const source,
GLuint* shader);
static void makeProgram(GLuint vs, GLuint fs, GLuint* program);
};
#endif /* GL3_LOAD_TEST_SAMPLE_H */
@@ -0,0 +1,278 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2015-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @file
* @~English
*
* @brief Instantiate GLLoadTests app with set of tests for OpenGL 3.3+ and
* OpenGL ES 3.x
*/
#include <string>
#include <regex>
#include "GLLoadTests.h"
#include "EncodeTexture.h"
#include "DrawTexture.h"
#include "TexturedCube.h"
#include "Texture3d.h"
#include "TextureCubemap.h"
#include "TextureArray.h"
#include "TextureMipmap.h"
#if !defined TEST_COMPRESSION
#define TEST_COMPRESSION 1
#endif
LoadTestSample*
GLLoadTests::showFile(const std::string& filename)
{
KTX_error_code ktxresult;
ktxTexture* kTexture;
ktxresult = ktxTexture_CreateFromNamedFile(filename.c_str(),
KTX_TEXTURE_CREATE_NO_FLAGS,
&kTexture);
if (KTX_SUCCESS != ktxresult) {
std::stringstream message;
message << "Creation of ktxTexture from \"" << filename
<< "\" failed: " << ktxErrorString(ktxresult);
throw std::runtime_error(message.str());
}
LoadTestSample::PFN_create createViewer;
LoadTestSample* pViewer;
if (kTexture->numDimensions == 3)
createViewer = Texture3d::create;
else if (kTexture->isArray && kTexture->isCubemap) {
// TODO: Add cubemap array app.
std::stringstream message;
message << "Display of cubemap array textures not yet implemented.";
throw std::runtime_error(message.str());
} else if (kTexture->isArray) {
createViewer = TextureArray::create;
} else if (kTexture->isCubemap) {
#if !defined(__EMSCRIPTEN__)
createViewer = TextureCubemap::create;
#else
throw std::runtime_error("Emscripten viewer can't display cube maps"
" because there is no libassimp support.");
#endif
} else if (kTexture->numLevels > 1 || kTexture->generateMipmaps) {
// TODO: Add option to choose to display showing the individual
// mipmaps vs. DrawTexture that displays a single rect using the
// mipmaps, if present.
createViewer = TextureMipmap::create;
} else {
createViewer = DrawTexture::create;
}
ktxTexture_Destroy(kTexture);
// Escape any spaces in filename.
std::string args = "--external " + std::regex_replace( filename, std::regex(" "), "\\ " );
pViewer = createViewer(w_width, w_height, args.c_str(), sBasePath);
return pViewer;
}
const GLLoadTests::sampleInvocation siSamples[] = {
{ DrawTexture::create,
"etc1s_Iron_Bars_001_normal.ktx2",
"Transcode of ETC1S+BasisLZ Compressed KTX2 XY normal map mipmapped"
},
{ DrawTexture::create,
"uastc_Iron_Bars_001_normal.ktx2",
"Transcode of UASTC+zstd Compressed KTX2 XY normal map mipmapped"
},
{ DrawTexture::create,
"color_grid_uastc_zstd.ktx2",
"Transcode of UASTC+Zstd Compressed KTX2 RGB not mipmapped "
},
{ DrawTexture::create,
"color_grid_zstd.ktx2",
"Zstd Compressed KTX2 RGB not mipmapped"
},
{ DrawTexture::create,
"color_grid_uastc.ktx2",
"Transcode of UASTC Compressed KTX2 RGB not mipmapped"
},
{ DrawTexture::create,
"color_grid_basis.ktx2",
"Transcode of ETC1S+BasisLZ Compressed KTX2 RGB not mipmapped"
},
{ DrawTexture::create,
"kodim17_basis.ktx2",
"Transcode of ETC1S+BasisLZ Compressed KTX2 RGB not mipmapped"
},
{ DrawTexture::create,
"--transcode-target RGBA4444 kodim17_basis.ktx2",
"Transcode of ETC1S+BasisLZ Compressed KTX2 RGB not mipmapped to RGBA4444"
},
{ EncodeTexture::create,
"FlightHelmet_baseColor_basis.ktx2",
"Transcode of ETC1S+BasisLZ Compressed KTX2 RGBA not mipmapped"
},
#if TEST_COMPRESSION
{ EncodeTexture::create,
"--encode etc1s rgba-reference-u.ktx2",
"Encode to ETC1S+BasisLZ then Transcode of Compressed KTX2 RGBA not mipmapped"
},
{ EncodeTexture::create,
"--encode uastc rgba-reference-u.ktx2",
"Encode to UASTC then Transcode of Compressed KTX2 RGBA not mipmapped"
},
{ EncodeTexture::create,
"--encode astc rgba-reference-u.ktx2",
"Encode to ASTC then display RGBA not mipmapped"
},
#endif
#if !defined(__EMSCRIPTEN__)
{ TextureCubemap::create,
"cubemap_goldengate_uastc_rdo4_zstd5_rd.ktx2",
"Transcode of UASTC+rdo+zstd Compressed KTX2 Cube Map Transcoded"
},
{ TextureCubemap::create,
"cubemap_yokohama_basis_rd.ktx2",
"Transcode of ETC1S/BasisLZ Compressed KTX2 mipmapped cube map",
},
#endif
{ DrawTexture::create,
"orient-down-metadata-u.ktx2",
"KTX2: RGB8 + KTXOrientation down"
},
{ DrawTexture::create,
"--preload orient-down-metadata-u.ktx2",
"KTX2: RGB8 + KTXOrientation down with pre-loaded images"
},
{ TextureArray::create,
"texturearray_bc3_unorm.ktx2",
"KTX2: BC3 (S3TC DXT5) Compressed Texture Array"
},
{ TextureArray::create,
"texturearray_astc_8x8_unorm.ktx2",
"KTX2: ASTC 8x8 Compressed Texture Array"
},
{ TextureArray::create,
"texturearray_etc2_unorm.ktx2",
"KTX2: ETC2 Compressed Texture Array"
},
{ Texture3d::create,
"3dtex_7_reference_u.ktx2",
"RGBA8 3d Texture, Depth == 7"
},
{ TexturedCube::create,
"rgb-mipmap-reference-u.ktx2",
"KTX2: RGB8 Color/level mipmap"
},
{ DrawTexture::create,
"hi_mark.ktx",
"RGB8 NPOT HI Logo"
},
{ DrawTexture::create,
"orient-up-metadata.ktx",
"RGB8 + KTXOrientation up"
},
{ DrawTexture::create,
"orient-down-metadata.ktx",
"RGB8 + KTXOrientation down"
},
{ DrawTexture::create,
"not4_rgb888_srgb.ktx",
"RGB8 2D, Row length not Multiple of 4"
},
{ DrawTexture::create,
"etc1.ktx",
"ETC1 RGB8"
},
{ DrawTexture::create,
"etc2-rgb.ktx",
"ETC2 RGB8"
},
{ DrawTexture::create,
"etc2-rgba1.ktx",
"ETC2 RGB8A1"
},
{ DrawTexture::create,
"etc2-rgba8.ktx",
"ETC2 RGB8A8"
},
{ DrawTexture::create,
"etc2-sRGB.ktx",
"ETC2 sRGB8"
},
{ DrawTexture::create,
"etc2-sRGBa1.ktx",
"ETC2 sRGB8A1"
},
{ DrawTexture::create,
"etc2-sRGBa8.ktx",
"ETC2 sRGB8A8"
},
{ DrawTexture::create,
"rgba-reference.ktx",
"RGBA8"
},
{ DrawTexture::create,
"rgb-reference.ktx",
"RGB8"
},
{ DrawTexture::create,
"conftestimage_R11_EAC.ktx",
"ETC2 R11"
},
{ DrawTexture::create,
"conftestimage_SIGNED_R11_EAC.ktx",
"ETC2 Signed R11"
},
{ DrawTexture::create,
"conftestimage_RG11_EAC.ktx",
"ETC2 RG11"
},
{ DrawTexture::create,
"conftestimage_SIGNED_RG11_EAC.ktx",
"ETC2 Signed RG11"
},
{ TextureArray::create,
"texturearray_bc3_unorm.ktx",
"BC3 (S3TC DXT5) Compressed Texture Array"
},
{ TextureArray::create,
"texturearray_astc_8x8_unorm.ktx",
"ASTC 8x8 Compressed Texture Array"
},
{ TextureArray::create,
"texturearray_etc2_unorm.ktx",
"ETC2 Compressed Texture Array"
},
{ TexturedCube::create,
"rgb-amg-reference.ktx",
"RGB8 + Auto Mipmap"
},
{ TexturedCube::create,
"rgb-mipmap-reference.ktx",
"RGB8 Color/level mipmap"
},
{ TexturedCube::create,
"hi_mark_sq.ktx",
"RGB8 NPOT HI Logo"
},
};
const uint32_t uNumSamples = sizeof(siSamples) / sizeof(GLLoadTests::sampleInvocation);
#if !(defined(GL_CONTEXT_PROFILE) && defined(GL_CONTEXT_MAJOR_VERSION) && defined(GL_CONTEXT_MINOR_VERSION))
#error GL_CONTEXT_PROFILE, GL_CONTEXT_MAJOR_VERSION & GL_CONTEXT_MINOR_VERSION must be defined.
#endif
AppBaseSDL* theApp = new GLLoadTests(siSamples, uNumSamples,
"KTX Loader Tests for GL3 & ES3",
GL_CONTEXT_PROFILE,
GL_CONTEXT_MAJOR_VERSION,
GL_CONTEXT_MINOR_VERSION);
@@ -0,0 +1,476 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2018-2021 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @file
* @~English
*
* @brief Base for samplesusing instancing such as array texture display.
*
* @author Mark Callow, github.com/MarkCallow.
*/
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <algorithm>
#include <time.h>
#include <sstream>
#include <vector>
#include <ktx.h>
#include "argparser.h"
#include "InstancedSampleBase.h"
#include "TranscodeTargetStrToFmt.h"
#include "GLTextureTranscoder.hpp"
#include "ltexceptions.h"
#define member_size(type, member) sizeof(((type *)0)->member)
using namespace std;
const GLchar* InstancedSampleBase::pszInstancingFsDeclarations =
"precision mediump float;\n"
"in vec3 UVW;\n\n"
"layout (location = 0) out vec4 outFragColor;\n\n";
const GLchar* InstancedSampleBase::pszSrgbEncodeFunc =
"vec3 srgb_encode(vec3 color) {\n"
" float r = color.r < 0.0031308 ? 12.92 * color.r : 1.055 * pow(color.r, 1.0/2.4) - 0.055;\n"
" float g = color.g < 0.0031308 ? 12.92 * color.g : 1.055 * pow(color.g, 1.0/2.4) - 0.055;\n"
" float b = color.b < 0.0031308 ? 12.92 * color.b : 1.055 * pow(color.b, 1.0/2.4) - 0.055;\n"
" return vec3(r, g, b);\n"
"}\n\n";
const GLchar* InstancedSampleBase::pszInstancingFsMain =
"void main()\n"
"{\n"
" outFragColor = texture(uSampler, UVW);\n"
"}";
const GLchar* InstancedSampleBase::pszInstancingSrgbEncodeFsMain =
"void main()\n"
"{\n"
" vec4 t_color = texture(uSampler, UVW);\n"
" outFragColor.rgb = srgb_encode(t_color.rgb);\n"
" outFragColor.a = t_color.a;\n"
"}";
const GLchar* InstancedSampleBase::pszInstancingVsDeclarations =
"layout (location = 0) in vec4 inPos;\n"
"layout (location = 1) in vec2 inUV;\n\n"
"struct Instance\n"
"{\n"
" mat4 model;\n"
"};\n\n"
"//layout (binding = 0) uniform UBO\n"
"layout(std140) uniform UBO\n"
"{\n"
" mat4 projection;\n"
" mat4 view;\n"
" Instance instance[INSTANCE_COUNT];\n"
"} ubo;\n\n"
"out vec3 UVW;\n\n";
/* ------------------------------------------------------------------------- */
/**
* @internal
* @class InstancedSampleBase
* @~English
*
* @brief Test loading of 2D texture arrays.
*/
InstancedSampleBase::InstancedSampleBase(uint32_t width, uint32_t height,
const char* const szArgs,
const string sBasePath)
: GL3LoadTestSample(width, height, szArgs, sBasePath),
texUnit(GL_TEXTURE0), uniformBufferBindId(0),
bInitialized(false)
{
zoom = -15.0f;
rotationSpeed = 0.25f;
//rotation = glm::vec3(-15.0f, 35.0f, 0.0f);
rotation = glm::vec3(15.0f, 35.0f, 0.0f);
gnTexture = 0;
// Ensure we're using the desired unit
glActiveTexture(texUnit);
processArgs(szArgs);
KTX_error_code ktxresult;
ktxTexture* kTexture;
GLenum glerror;
string ktxfilepath = externalFile ? ktxfilename
: getAssetPath() + ktxfilename;
ktxresult =
ktxTexture_CreateFromNamedFile(ktxfilepath.c_str(),
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)) {
transcodeTarget = KTX_TTF_NOSELECTION;
TextureTranscoder tc;
tc.transcode((ktxTexture2*)kTexture, transcodeTarget);
}
ktxresult = ktxTexture_GLUpload(kTexture, &gnTexture, &texTarget,
&glerror);
if (KTX_SUCCESS != ktxresult) {
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 && glerror == GL_INVALID_ENUM) {
throw unsupported_ctype();
} else {
message << std::showbase << "GL error " << std::hex << glerror
<< " occurred.";
throw std::runtime_error(message.str());
}
}
if (kTexture->generateMipmaps) {
// GLUpload will have generated the mipmaps already.
uint32_t maxDim = (std::max)(
(std::max)(kTexture->baseWidth, kTexture->baseHeight),
kTexture->baseDepth);
textureInfo.numLevels = (uint32_t)floor(log2(maxDim)) + 1;
} else {
textureInfo.numLevels = kTexture->numLevels;
}
textureInfo.numLayers = kTexture->numLayers;
textureInfo.baseDepth = kTexture->baseDepth;
if (textureInfo.numLevels > 1)
bIsMipmapped = true;
else
bIsMipmapped = false;
// Checking if KVData contains keys of interest would go here.
ktxTexture_Destroy(kTexture);
}
InstancedSampleBase::~InstancedSampleBase()
{
cleanup();
}
void
InstancedSampleBase::resize(uint32_t width, uint32_t height)
{
this->w_width = width;
this->w_height = height;
glViewport(0, 0, width, height);
updateUniformBufferMatrices();
}
void
InstancedSampleBase::run(uint32_t /*msTicks*/)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Keep these permanently bound
//glBindVertexArray(gnVao);
// Must be done after the VAO is bound
//glBindBuffer(GL_ARRAY_BUFFER, gnVbo[0]);
//glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gnVbo[1]);
glDrawElementsInstanced(GL_TRIANGLES, quad.indexCount,
GL_UNSIGNED_INT, (GLvoid*)quad.indicesOffset,
instanceCount);
assert(GL_NO_ERROR == glGetError());
}
//===================================================================
void
InstancedSampleBase::processArgs(string sArgs)
{
// Options descriptor
struct argparser::option longopts[] = {
{"external", argparser::option::no_argument, &externalFile, 1},
{"transcode-target", argparser::option::required_argument, nullptr, 2},
{NULL, argparser::option::no_argument, NULL, 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
InstancedSampleBase::cleanup()
{
glEnable(GL_DITHER);
glEnable(GL_CULL_FACE);
glFrontFace(GL_CCW);
glDisable(GL_DEPTH_TEST);
if (bInitialized) {
glUseProgram(0);
glDeleteTextures(1, &gnTexture);
glDeleteProgram(gnInstancingProg);
glDeleteBuffers(2, quad.gnVbo);
glDeleteVertexArrays(1, &quad.gnVao);
delete uboVS.instance;
}
assert(GL_NO_ERROR == glGetError());
}
// Setup vertices for a single uv-mapped quad
void
InstancedSampleBase::generateQuad()
{
#define dim 2.5f
//std::vector<TAVertex> vertices =
TAVertex vertices[] =
{
{ { dim, dim, 0.0f }, { 1.0f, 1.0f } },
{ { -dim, dim, 0.0f }, { 0.0f, 1.0f } },
{ { -dim, -dim, 0.0f }, { 0.0f, 0.0f } },
{ { dim, -dim, 0.0f }, { 1.0f, 0.0f } }
};
#undef dim
// Setup indices
uint32_t indices[] = { 0,1,2, 2,3,0 };
quad.indexCount = static_cast<uint32_t>(ARRAY_LEN(indices));
// Create a VAO and bind it.
glGenVertexArrays(1, &quad.gnVao);
glBindVertexArray(quad.gnVao);
// Must have vertex data in buffer objects to use VAO's on ES3/GL Core
glGenBuffers(2, quad.gnVbo);
glBindBuffer(GL_ARRAY_BUFFER, quad.gnVbo[0]);
// Must be done after the VAO is bound
// WebGL requires different buffers for data and indices.
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, quad.gnVbo[1]);
// Create the buffer data store.
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), NULL, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
GLsizeiptr offset = 0;
glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(vertices), vertices);
quad.verticesOffset = offset;
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(TAVertex),
(GLvoid*)offset);
offset += member_size(TAVertex, pos);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE,
sizeof(TAVertex), (GLvoid*)offset);
offset = sizeof(vertices);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices),
indices, GL_STATIC_DRAW);
quad.indicesOffset = 0;
}
#define _PAD16(nbytes) (ktx_uint32_t)(16 * ceilf((float)(nbytes) / 16))
void
InstancedSampleBase::prepareUniformBuffers()
{
uProgramUniforms = glGetUniformBlockIndex(gnInstancingProg, "UBO");
if (uProgramUniforms == -1) {
std::stringstream message;
message << "prepareUniformBuffers: UBO not found in program";
throw std::runtime_error(message.str());
}
// INSTANCE_COUNT is set in GLSL code via define set in prepareProgram.
//
// Elements of the array of UboInstanceData will be aligned on 16-byte
// boundaries per the std140 rule for mat4/vec4. _PAD16 is unnecessary
// right now but will become so if anything is added to the ubo before
// the UboInstanceData. _PAD16 is put here as a warning.
uint32_t uboSize = _PAD16(sizeof(uboVS.matrices))
+ instanceCount * sizeof(UboInstanceData);
uboVS.instance = new UboInstanceData[instanceCount];
glGenBuffers(1, &gnUbo);
glBindBuffer(GL_UNIFORM_BUFFER, gnUbo);
// Create the data store.
glBufferData(GL_UNIFORM_BUFFER, uboSize, 0, GL_DYNAMIC_DRAW);
float offset = 1.5f;
float center = (instanceCount * offset) / 2;
for (uint32_t i = 0; i < instanceCount; i++)
{
// Instance model matrix
uboVS.instance[i].model
= glm::translate(glm::mat4(), glm::vec3(0.0f,
i * offset - center, 0.0f));
uboVS.instance[i].model = glm::rotate(uboVS.instance[i].model,
glm::radians(120.0f),
glm::vec3(1.0f, 0.0f, 0.0f));
}
// Update instanced part of the uniform buffer
// N.B. See comment re _PAD16 before uboSize above.
uint32_t dataOffset = _PAD16(sizeof(uboVS.matrices));
uint32_t dataSize = instanceCount * sizeof(UboInstanceData);
glBufferSubData(GL_UNIFORM_BUFFER, dataOffset, dataSize, uboVS.instance);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
glBindBufferBase(GL_UNIFORM_BUFFER, uniformBufferBindId, gnUbo);
glUseProgram(gnInstancingProg);
glUniformBlockBinding(gnInstancingProg, uProgramUniforms,
uniformBufferBindId);
updateUniformBufferMatrices();
glUseProgram(0);
assert(glGetError() == GL_NO_ERROR);
}
void
InstancedSampleBase::updateUniformBufferMatrices()
{
// Only updates the uniform buffer block part containing the global matrices
// Projection
uboVS.matrices.projection = glm::perspective(glm::radians(60.0f),
(float)w_width / w_height,
.01f, 256.f);
// View
uboVS.matrices.view = glm::translate(glm::mat4(),
glm::vec3(0.0f, 1.0f, zoom));
uboVS.matrices.view *= glm::translate(glm::mat4(), cameraPos);
uboVS.matrices.view = glm::rotate(uboVS.matrices.view,
glm::radians(rotation.x),
glm::vec3(1.0f, 0.0f, 0.0f));
uboVS.matrices.view = glm::rotate(uboVS.matrices.view,
glm::radians(rotation.y),
glm::vec3(0.0f, 1.0f, 0.0f));
uboVS.matrices.view = glm::rotate(uboVS.matrices.view,
glm::radians(rotation.z),
glm::vec3(0.0f, 0.0f, 1.0f));
#if !defined(EMSCRIPTEN)
// Only update the matrices part of the uniform buffer
uint8_t *pData = (uint8_t*)glMapBufferRange(GL_UNIFORM_BUFFER, 0,
sizeof(uboVS.matrices),
GL_MAP_WRITE_BIT);
memcpy(pData, &uboVS.matrices, sizeof(uboVS.matrices));
glUnmapBuffer(GL_UNIFORM_BUFFER);
#else
glBufferSubData(GL_UNIFORM_BUFFER, 0,
sizeof(uboVS.matrices), &uboVS.matrices);
#endif
}
void
InstancedSampleBase::prepareSampler()
{
glBindTexture(texTarget, gnTexture);
if (bIsMipmapped)
glTexParameteri(texTarget,
GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
else
glTexParameteri(texTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(texTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(texTarget, 0);
glUseProgram(gnInstancingProg);
if ((uSampler = glGetUniformLocation(gnInstancingProg,
"uSampler")) == -1) {
std::stringstream message;
message << "prepareSampler: uSampler not found in program";
throw std::runtime_error(message.str());
}
glUniform1i(uSampler, texUnit - GL_TEXTURE0);
glUseProgram(0);
}
void
InstancedSampleBase::prepareProgram(ShaderSource& fs, ShaderSource& vs)
{
GLuint gnInstancingFs, gnInstancingVs;
try {
std::stringstream ssDefine;
ssDefine << "#define INSTANCE_COUNT " << instanceCount << "U" << endl;
// str().c_str() doesn't work because str goes outof scope immediately.
// Hence this 2 step process.
string sDefine = ssDefine.str();
vs.insert(vs.begin(), sDefine.c_str());
makeShader(GL_VERTEX_SHADER, vs, &gnInstancingVs);
makeShader(GL_FRAGMENT_SHADER, fs, &gnInstancingFs);
makeProgram(gnInstancingVs, gnInstancingFs, &gnInstancingProg);
} catch (std::exception& e) {
(void)e; // To quiet unused variable warnings from some compilers.
throw;
}
glDeleteShader(gnInstancingVs);
glDeleteShader(gnInstancingFs);
}
void
InstancedSampleBase::prepare(ShaderSource& fs, ShaderSource& vs)
{
// By default dithering is enabled. Dithering does not provide visual
// improvement in this sample so disable it to improve performance.
glDisable(GL_DITHER);
glFrontFace(GL_CW);
glDisable(GL_CULL_FACE);
glEnable(GL_DEPTH_TEST);
glClearColor(0.2f,0.3f,0.4f,1.0f);
//prepareSamplerAndView();
//setupVertexDescriptions();
generateQuad();
prepareProgram(fs, vs);
prepareUniformBuffers();
prepareSampler();
glUseProgram(gnInstancingProg);
glBindTexture(texTarget, gnTexture);
}
@@ -0,0 +1,121 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2018-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _INSTANCE_SAMPLE_BASE_H_
#define _INSTANCE_SAMPLE_BASE_H_
#include <vector>
#include "GL3LoadTestSample.h"
#include <glm/gtc/matrix_transform.hpp>
#define VERTEX_BUFFER_BIND_ID 0
#define ENABLE_VALIDATION false
class InstancedSampleBase : public GL3LoadTestSample
{
public:
InstancedSampleBase(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath);
~InstancedSampleBase();
virtual void resize(uint32_t width, uint32_t height);
virtual void run(uint32_t msTicks);
//virtual void getOverlayText(VulkanTextOverlay *textOverlay);
protected:
using ShaderSource = GL3LoadTestSample::ShaderSource;
const GLuint texUnit;
const GLuint uniformBufferBindId;
GLenum texTarget;
GLuint gnTexture;
GLuint gnInstancingProg;
GLuint gnUbo;
ktx_transcode_fmt_e transcodeTarget;
bool bInitialized;
bool bIsMipmapped;
struct textureInfo {
uint32_t numLayers;
uint32_t numLevels;
uint32_t baseDepth;
} textureInfo;
uint32_t instanceCount;
// Vertex layout for this example
struct TAVertex {
float pos[3];
float uv[2];
};
struct MeshBuffer {
uint32_t indexCount;
glm::vec3 dim;
GLuint gnVao;
GLuint gnVbo[2];
GLsizeiptr verticesOffset;
GLsizeiptr indicesOffset;
};
MeshBuffer quad;
struct UboInstanceData {
// Model matrix
glm::mat4 model;
};
struct {
// Global matrices
struct {
glm::mat4 projection;
glm::mat4 view;
} matrices;
// N.B. The UBO structure declared in the shader has the array of
// instance data inside the structure rather than pointed at from the
// structure. The start of the array will be aligned on a 16-byte
// boundary as it starts with a matrix.
//
// Separate data for each instance
UboInstanceData *instance;
} uboVS;
GLint uProgramUniforms;
GLint uSampler;
static const GLchar* pszInstancingFsDeclarations;
static const GLchar* pszSrgbEncodeFunc;
static const GLchar* pszInstancingFsMain;
static const GLchar* pszInstancingSrgbEncodeFsMain;
static const GLchar* pszInstancingVsDeclarations;
void cleanup();
// Setup vertices for a single uv-mapped quad
void generateQuad();
void prepareUniformBuffers();
void updateUniformBufferMatrices();
void prepareSampler();
void prepareProgram(ShaderSource& fs, ShaderSource& vs);
void prepare(ShaderSource& fs, ShaderSource& vs);
void processArgs(std::string sArgs);
virtual void viewChanged()
{
updateUniformBufferMatrices();
}
};
#endif /* _INSTANCE_SAMPLE_BASE_H_ */
@@ -0,0 +1,107 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2021 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @file
* @~English
*
* @brief Definition of test sample for loading and displaying the slices of a 3d texture.
*
* @author Mark Callow, github.com/MarkCallow.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <algorithm>
#include <time.h>
#include <sstream>
#include <vector>
#include <ktx.h>
#include "argparser.h"
#include "Texture3d.h"
#include "ltexceptions.h"
#define member_size(type, member) sizeof(((type *)0)->member)
const GLchar* pszFsSampler3dDeclaration =
"uniform mediump sampler3D uSampler;\n\n";
const GLchar* psz3dVsMain =
"void main()\n"
"{\n"
" UVW = vec3(inUV, float(gl_InstanceID) / float(INSTANCE_COUNT - 1U));\n"
" mat4 modelView = ubo.view * ubo.instance[gl_InstanceID].model;\n"
" gl_Position = ubo.projection * modelView * inPos;\n"
"}";
/* ------------------------------------------------------------------------- */
LoadTestSample*
Texture3d::create(uint32_t width, uint32_t height,
const char* const szArgs, const std::string sBasePath)
{
return new Texture3d(width, height, szArgs, sBasePath);
}
/**
* @internal
* @class Texture3d
* @~English
*
* @brief Test loading of 2D texture arrays.
*/
Texture3d::Texture3d(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath)
: InstancedSampleBase(width, height, szArgs, sBasePath)
{
zoom = -15.0f;
if (texTarget != GL_TEXTURE_3D) {
std::stringstream message;
message << "Texture3d requires an 3d texture.";
throw std::runtime_error(message.str());
}
// Checking if KVData contains keys of interest would go here.
instanceCount = textureInfo.baseDepth;
InstancedSampleBase::ShaderSource fs;
InstancedSampleBase::ShaderSource vs;
fs.push_back(pszInstancingFsDeclarations);
fs.push_back(pszFsSampler3dDeclaration);
if (framebufferColorEncoding() == GL_LINEAR) {
fs.push_back(pszSrgbEncodeFunc);
fs.push_back(pszInstancingSrgbEncodeFsMain);
} else {
fs.push_back(pszInstancingFsMain);;
}
vs.push_back(pszInstancingVsDeclarations);
vs.push_back(psz3dVsMain);
try {
prepare(fs, vs);
} catch (std::exception& e) {
(void)e; // To quiet unused variable warnings from some compilers.
cleanup();
throw;
}
// Texture was bound by prepare()
// Set this so it is easier to recognize that the texture has the expected
// slices.
glTexParameteri(texTarget, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
bInitialized = true;
}
@@ -0,0 +1,41 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2021 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @file
* @~English
*
* @brief Declaration of test sample for loading and displaying the slices of a 3d texture..
*
* @author Mark Callow, github.com/MarkCallow.
*/
#include <vector>
#include "InstancedSampleBase.h"
#include <glm/gtc/matrix_transform.hpp>
#define VERTEX_BUFFER_BIND_ID 0
#define ENABLE_VALIDATION false
class Texture3d : public InstancedSampleBase
{
public:
Texture3d(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath);
static LoadTestSample*
create(uint32_t width, uint32_t height,
const char* const szArgs, const std::string sBasePath);
protected:
using ShaderSource = GL3LoadTestSample::ShaderSource;
};
@@ -0,0 +1,100 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2018-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @file
* @~English
*
* @brief Definition of test sample for loading and displaying the layers of a 2D array texture.
*
* @author Mark Callow, github.com/MarkCallow.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <algorithm>
#include <time.h>
#include <sstream>
#include <vector>
#include <ktx.h>
#include "argparser.h"
#include "TextureArray.h"
#include "ltexceptions.h"
#define member_size(type, member) sizeof(((type *)0)->member)
const GLchar* pszFsArraySamplerDeclaration =
"uniform mediump sampler2DArray uSampler;\n\n";
const GLchar* pszArrayVsMain =
"void main()\n"
"{\n"
" UVW = vec3(inUV, gl_InstanceID);\n"
" mat4 modelView = ubo.view * ubo.instance[gl_InstanceID].model;\n"
" gl_Position = ubo.projection * modelView * inPos;\n"
"}";
/* ------------------------------------------------------------------------- */
LoadTestSample*
TextureArray::create(uint32_t width, uint32_t height,
const char* const szArgs, const std::string sBasePath)
{
return new TextureArray(width, height, szArgs, sBasePath);
}
/**
* @internal
* @class TextureArray
* @~English
*
* @brief Test loading of 2D texture arrays.
*/
TextureArray::TextureArray(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath)
: InstancedSampleBase(width, height, szArgs, sBasePath)
{
zoom = -15.0f;
if (texTarget != GL_TEXTURE_2D_ARRAY) {
std::stringstream message;
message << "TextureArray requires an array texture.";
throw std::runtime_error(message.str());
}
// Checking if KVData contains keys of interest would go here.
instanceCount = textureInfo.numLayers;
InstancedSampleBase::ShaderSource fs;
InstancedSampleBase::ShaderSource vs;
fs.push_back(pszInstancingFsDeclarations);
fs.push_back(pszFsArraySamplerDeclaration);
if (framebufferColorEncoding() == GL_LINEAR) {
fs.push_back(pszSrgbEncodeFunc);
fs.push_back(pszInstancingSrgbEncodeFsMain);
} else {
fs.push_back(pszInstancingFsMain);;
}
vs.push_back(pszInstancingVsDeclarations);
vs.push_back(pszArrayVsMain);
try {
prepare(fs, vs);
} catch (std::exception& e) {
(void)e; // To quiet unused variable warnings from some compilers.
cleanup();
throw;
}
bInitialized = true;
}
@@ -0,0 +1,41 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2021 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @file
* @~English
*
* @brief Declaration of test sample for loading and displaying the layers of a 2D array texture.
*
* @author Mark Callow, github.com/MarkCallow.
*/
#include <vector>
#include "InstancedSampleBase.h"
#include <glm/gtc/matrix_transform.hpp>
#define VERTEX_BUFFER_BIND_ID 0
#define ENABLE_VALIDATION false
class TextureArray : public InstancedSampleBase
{
public:
TextureArray(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath);
static LoadTestSample*
create(uint32_t width, uint32_t height,
const char* const szArgs, const std::string sBasePath);
protected:
using ShaderSource = GL3LoadTestSample::ShaderSource;
};
@@ -0,0 +1,710 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @file
* @~English
*
* @brief Test loading of 2D texture arrays.
*
* @author Mark Callow, github.com/MarkCallow.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <algorithm>
#include <time.h>
#include <sstream>
#include <vector>
#include <ktx.h>
#include "argparser.h"
#include "TextureCubemap.h"
#include "GLTextureTranscoder.hpp"
#include "SwipeDetector.h"
#include "ltexceptions.h"
#define member_size(type, member) sizeof(((type *)0)->member)
const GLchar* pszReflectFs =
"precision highp float;"
"uniform UBO\n"
"{\n"
" mat4 projection;\n"
" mat4 modelView;\n"
" mat4 skyboxView;\n"
" mat4 invModelView;\n"
" mat4 uvwTransform;\n"
" float lodBias;\n"
"} ubo;\n\n"
"uniform samplerCube uSamplerColor;\n\n"
"in vec3 vPos;\n"
"in vec3 vNormal;\n"
"in float vLodBias;\n"
"in vec3 vViewVec;\n"
"in vec3 vLightVec;\n\n"
"layout (location = 0) out vec4 outFragColor;\n\n"
"void main()\n"
"{\n"
" vec3 cI = normalize (vPos);\n"
" vec3 cR = reflect (cI, normalize(vNormal));\n\n"
" cR = vec3(ubo.uvwTransform * ubo.invModelView * vec4(cR, 0.0));\n\n"
" vec4 color = texture(uSamplerColor, cR, vLodBias);\n\n"
" vec3 N = normalize(vNormal);\n"
" vec3 L = normalize(vLightVec);\n"
" vec3 V = normalize(vViewVec);\n"
" vec3 R = reflect(-L, N);\n"
" vec3 ambient = vec3(0.5) * color.rgb;\n"
" vec3 diffuse = max(dot(N, L), 0.0) * vec3(1.0);\n"
" vec3 specular = pow(max(dot(R, V), 0.0), 16.0) * vec3(0.5);\n"
" outFragColor = vec4(ambient + diffuse * color.rgb + specular, 1.0);\n"
"}\n";
const GLchar* pszReflectSrgbEncodeFs =
"precision highp float;"
"uniform UBO\n"
"{\n"
" mat4 projection;\n"
" mat4 modelView;\n"
" mat4 skyboxView;\n"
" mat4 invModelView;\n"
" mat4 uvwTransform;\n"
" float lodBias;\n"
"} ubo;\n\n"
"uniform samplerCube uSamplerColor;\n\n"
"in vec3 vPos;\n"
"in vec3 vNormal;\n"
"in float vLodBias;\n"
"in vec3 vViewVec;\n"
"in vec3 vLightVec;\n\n"
"layout (location = 0) out vec4 outFragColor;\n\n"
"vec3 srgb_encode(vec3 color) {\n"
" float r = color.r < 0.0031308 ? 12.92 * color.r : 1.055 * pow(color.r, 1.0/2.4) - 0.055;\n"
" float g = color.g < 0.0031308 ? 12.92 * color.g : 1.055 * pow(color.g, 1.0/2.4) - 0.055;\n"
" float b = color.b < 0.0031308 ? 12.92 * color.b : 1.055 * pow(color.b, 1.0/2.4) - 0.055;\n"
" return vec3(r, g, b);\n"
"}\n\n"
"void main()\n"
"{\n"
" vec3 cI = normalize (vPos);\n"
" vec3 cR = reflect (cI, normalize(vNormal));\n\n"
" cR = vec3(ubo.uvwTransform * ubo.invModelView * vec4(cR, 0.0));\n\n"
" vec4 color = texture(uSamplerColor, cR, vLodBias);\n\n"
" vec3 N = normalize(vNormal);\n"
" vec3 L = normalize(vLightVec);\n"
" vec3 V = normalize(vViewVec);\n"
" vec3 R = reflect(-L, N);\n"
" vec3 ambient = vec3(0.5) * color.rgb;\n"
" vec3 diffuse = max(dot(N, L), 0.0) * vec3(1.0);\n"
" vec3 specular = pow(max(dot(R, V), 0.0), 16.0) * vec3(0.5);\n"
" color.rgb = srgb_encode(ambient + diffuse * color.rgb + specular);\n"
" outFragColor = vec4(color.rgb, 1.0);\n"
"}\n";
const GLchar* pszReflectVs =
"precision highp float;"
"layout (location = 0) in vec3 inPos;\n"
"layout (location = 1) in vec3 inNormal;\n\n"
"uniform UBO\n"
"{\n"
" mat4 projection;\n"
" mat4 modelView;\n"
" mat4 skyboxView;\n"
" mat4 invModelView;\n"
" mat4 uvwTransform;\n"
" float lodBias;\n"
"} ubo;\n"
"\n"
"out vec3 vPos;\n"
"out vec3 vNormal;\n"
"out float vLodBias;\n"
"out vec3 vViewVec;\n"
"out vec3 vLightVec;\n\n"
"void main()\n"
"{\n"
" gl_Position = ubo.projection * ubo.modelView * vec4(inPos, 1.0);\n\n"
" vPos = vec3(ubo.modelView * vec4(inPos, 1.0));\n"
" vNormal = mat3(ubo.modelView) * inNormal;\n"
" vLodBias = ubo.lodBias;\n\n"
" vec3 lightPos = vec3(0.0f, -5.0f, 5.0f);\n"
" vLightVec = lightPos.xyz - vPos.xyz;\n"
" vViewVec = -vPos.xyz;\n"
"}\n";
const GLchar* pszSkyboxFs =
"precision highp float;"
"uniform samplerCube uSamplerColor;\n\n"
"in vec3 vUVW;\n\n"
"layout (location = 0) out vec4 outFragColor;\n\n"
"void main()\n"
"{\n"
" outFragColor = texture(uSamplerColor, vUVW);\n"
"}\n";
const GLchar* pszSkyboxSrgbEncodeFs =
"precision highp float;"
"uniform samplerCube uSamplerColor;\n\n"
"in vec3 vUVW;\n\n"
"layout (location = 0) out vec4 outFragColor;\n\n"
"vec3 srgb_encode(vec3 color) {\n"
" float r = color.r < 0.0031308 ? 12.92 * color.r : 1.055 * pow(color.r, 1.0/2.4) - 0.055;\n"
" float g = color.g < 0.0031308 ? 12.92 * color.g : 1.055 * pow(color.g, 1.0/2.4) - 0.055;\n"
" float b = color.b < 0.0031308 ? 12.92 * color.b : 1.055 * pow(color.b, 1.0/2.4) - 0.055;\n"
" return vec3(r, g, b);\n"
"}\n\n"
"void main()\n"
"{\n"
" vec4 color = texture(uSamplerColor, vUVW);\n"
" outFragColor.rgb = srgb_encode(color.rgb);\n"
" outFragColor.a = color.a;\n"
"}\n";
const GLchar* pszSkyboxVs =
"precision highp float;"
"layout (location = 0) in vec3 inPos;\n\n"
"uniform UBO\n"
"{\n"
" mat4 projection;\n"
" mat4 modelView;\n"
" mat4 skyboxView;\n"
" mat4 invModelView;\n"
" mat4 uvwTransform;\n"
"} ubo;\n\n"
"out vec3 vUVW;\n\n"
"void main()\n"
"{\n"
" vUVW = (ubo.uvwTransform * vec4(inPos.xyz, 1.0)).xyz;\n"
" //vUVW = inPos.xyz;\n"
" gl_Position = (ubo.projection * ubo.skyboxView * vec4(inPos.xyz, 1.0)).xyww;\n"
"}\n";
/* ------------------------------------------------------------------------- */
// Vertex layout for this example
std::vector<glMeshLoader::VertexLayout> vertexLayout =
{
glMeshLoader::VERTEX_LAYOUT_POSITION,
glMeshLoader::VERTEX_LAYOUT_NORMAL,
glMeshLoader::VERTEX_LAYOUT_UV
};
LoadTestSample*
TextureCubemap::create(uint32_t width, uint32_t height,
const char* const szArgs, const std::string sBasePath)
{
return new TextureCubemap(width, height, szArgs, sBasePath);
}
/**
* @internal
* @class TextureCubemap
* @~English
*
* @brief Test loading of 2D texture arrays.
*/
TextureCubemap::TextureCubemap(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath)
: GL3LoadTestSample(width, height, szArgs, sBasePath),
cubemapTexUnit(GL_TEXTURE0), uniformBufferBindId(0),
bInitialized(false)
{
zoom = -4.0f;
rotationSpeed = 0.25f;
rotation = { -7.25f, 120.0f, 0.0f };
gnCubemapTexture = 0;
// Ensure we're using the desired unit
glActiveTexture(cubemapTexUnit);
processArgs(szArgs);
KTX_error_code ktxresult;
ktxTexture* kTexture;
GLenum glerror;
std::string ktxfilepath = externalFile ? ktxfilename
: getAssetPath() + ktxfilename;
ktxresult = ktxTexture_CreateFromNamedFile(ktxfilepath.c_str(),
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);
//transcoded = true;
}
ktxresult = ktxTexture_GLUpload(kTexture,
&gnCubemapTexture, &cubemapTexTarget,
&glerror);
if (KTX_SUCCESS != ktxresult) {
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 && glerror == GL_INVALID_ENUM) {
throw unsupported_ctype();
} else {
message << std::showbase << "GL error " << std::hex << glerror
<< " occurred.";
throw std::runtime_error(message.str());
}
}
if (cubemapTexTarget != GL_TEXTURE_CUBE_MAP) {
std::stringstream message;
message << "Loaded texture is not a cubemap texture.";
throw std::runtime_error(message.str());
}
numLayers = kTexture->numLayers;
if (numLayers > 1 || kTexture->generateMipmaps)
// GLUpload will have generated the mipmaps already.
bIsMipmapped = true;
else
bIsMipmapped = false;
levelCount = kTexture->numLevels;
if (kTexture->orientation.y == KTX_ORIENT_Y_DOWN) {
// Assume a KTX-compliant cube map. That means the faces are in a
// LH coord system with +y up, +z forward and +x on the right.
// Scale the skybox cube's z by -1 to convert it to LH coords to
// match the cube map while placing the +z face in the -z direction
// so it will be in front of the view. Alternatively we could multiply
// the cube's x by -1 which will place the +z face in the +z direction
// placing it behind the viewer.
ubo.uvwTransform = glm::scale(glm::mat4(1.0f), glm::vec3(1, 1, -1));
} else {
std::stringstream message;
message << "Cubemap faces have unsupported KTXorientation value.";
throw std::runtime_error(message.str());
}
ktxTexture_Destroy(kTexture);
try {
prepare();
} catch (std::exception& e) {
(void)e; // To quiet unused variable warnings from some compilers.
cleanup();
throw;
}
bInitialized = true;
}
TextureCubemap::~TextureCubemap()
{
cleanup();
}
int
TextureCubemap::doEvent(SDL_Event* event)
{
int result = 0;
switch(event->type) {
case SDL_EVENT_USER:
if (event->user.code == SwipeDetector::swipeGesture) {
SwipeDetector::Direction direction
= SwipeDetector::pointerToDirection(event->user.data1);
switch (direction) {
case SwipeDetector::Direction::up:
toggleObject(+1);
break;
case SwipeDetector::Direction::down:
toggleObject(-1);
break;
default:
result = 1;
}
} else {
result = 1;
}
break;
default:
result = 1;
}
if (result == 1)
result = GL3LoadTestSample::doEvent(event);
return result;
}
void
TextureCubemap::resize(uint32_t width, uint32_t height)
{
this->w_width = width;
this->w_height = height;
glViewport(0, 0, width, height);
updateUniformBuffers();
}
void
TextureCubemap::run(uint32_t /*msTicks*/)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Draw object.
glFrontFace(GL_CW); // Why is everything CW?
glCullFace(GL_BACK);
glUseProgram(gnReflectProg);
meshes.objects[meshes.objectIndex].Draw();
if (bDisplaySkybox) {
// Change so depth test passes when values are equal to the
// depth buffer's content. This works in conjunction with the
// gl_Position = inPos.xyww trick in the shader.
glDepthFunc(GL_LEQUAL);
// The cube is a regular mesh with the front faces on the outside.
// We're inside the cube so want to see the back faces.
glCullFace(GL_FRONT);
glUseProgram(gnSkyboxProg);
meshes.skybox.Draw();
// Revert to defaults for 3D object.
glDepthFunc(GL_LESS);
}
assert(GL_NO_ERROR == glGetError());
}
//===================================================================
void
TextureCubemap::processArgs(std::string sArgs)
{
// Options descriptor
struct argparser::option longopts[] = {
{"external", argparser::option::no_argument, &externalFile, 1},
{NULL, argparser::option::no_argument, NULL, 0}
};
argvector argv(sArgs);
argparser ap(argv);
int ch;
while ((ch = ap.getopt(nullptr, longopts, nullptr)) != -1) {
switch (ch) {
case 0: break;
default: assert(false); // Error in args in sample table.
}
}
assert(ap.optind < argv.size());
ktxfilename = argv[ap.optind];
}
/* ------------------------------------------------------------------------- */
void
TextureCubemap::cleanup()
{
glEnable(GL_DITHER);
glDisable(GL_CULL_FACE);
glCullFace(GL_BACK);
glFrontFace(GL_CCW);
glDisable(GL_DEPTH_TEST);
if (bInitialized) {
glUseProgram(0);
glDeleteTextures(1, &gnCubemapTexture);
glDeleteProgram(gnReflectProg);
glDeleteProgram(gnSkyboxProg);
meshes.skybox.FreeGLResources();
for (int i = 0; i < 3; i++) {
meshes.objects[i].FreeGLResources();
}
}
assert(GL_NO_ERROR == glGetError());
}
void
TextureCubemap::loadMeshes()
{
std::string filepath = getAssetPath();
// Skybox
loadMesh(filepath + "cube.obj", meshes.skybox, vertexLayout, 0.05f);
// Objects
meshes.objects.resize(3);
loadMesh(filepath + "sphere.obj", meshes.objects[0], vertexLayout, 0.05f);
loadMesh(filepath + "teapot.dae", meshes.objects[1], vertexLayout, 0.05f);
loadMesh(filepath + "torusknot.obj", meshes.objects[2], vertexLayout, 0.05f);
}
void
TextureCubemap::prepareUniformBuffers()
{
uReflectProgramUniforms = glGetUniformBlockIndex(gnReflectProg, "UBO");
if (uReflectProgramUniforms == -1) {
std::stringstream message;
message << "prepareUniformBuffers: UBO not found in reflect program";
throw std::runtime_error(message.str());
}
uSkyboxProgramUniforms = glGetUniformBlockIndex(gnSkyboxProg, "UBO");
if (uSkyboxProgramUniforms == -1) {
std::stringstream message;
message << "prepareUniformBuffers: UBO not found in skybox program";
throw std::runtime_error(message.str());
}
glGenBuffers(1, &gnUbo);
glBindBuffer(GL_UNIFORM_BUFFER, gnUbo);
// Create the data store.
glBufferData(GL_UNIFORM_BUFFER, sizeof(ubo), 0, GL_DYNAMIC_DRAW);
glBindBufferBase(GL_UNIFORM_BUFFER, uniformBufferBindId, gnUbo);
glUseProgram(gnReflectProg);
glUniformBlockBinding(gnReflectProg, uReflectProgramUniforms,
uniformBufferBindId);
glUseProgram(gnSkyboxProg);
glUniformBlockBinding(gnSkyboxProg, uSkyboxProgramUniforms,
uniformBufferBindId);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
updateUniformBuffers();
glUseProgram(0);
assert(glGetError() == GL_NO_ERROR);
}
void
TextureCubemap::updateUniformBuffers()
{
// Reflect / 3D object
glm::mat4 viewMatrix = glm::mat4(1.0f);
ubo.projection = glm::perspective(glm::radians(60.0f),
(float)w_width / (float)w_height,
0.001f, 256.0f);
viewMatrix = glm::translate(viewMatrix, glm::vec3(0.0f, 0.0f, zoom));
// Transform the z axis to what the viewer is perceiving as the z axis to make
// the behaviour of 2-finger rotation (the value in rotation.z) understandable.
glm::mat4 zAxisRotMatrix;
zAxisRotMatrix = glm::rotate(zAxisRotMatrix, glm::radians(180.0f - rotation.x),
glm::vec3(1.0f, 0.0f, 0.0f));
zAxisRotMatrix = glm::rotate(zAxisRotMatrix, glm::radians(rotation.y),
glm::vec3(0.0f, 1.0f, 0.0f));
zRotationAxis = normalize(zAxisRotMatrix * glm::vec4(0.0f, 0.0f, 1.0f, 1.0f));
// I do not understand why this is necessary. Assimp is supposed to
// put models in the GL coordinate system by default but the teapot is
// upside down. Since the other objects are symmetrical it is not possible
// to say if they are upside down.
glm::mat4 object;
object = glm::rotate(object, glm::radians(180.0f),
glm::vec3(1.0f, 0.0f, 0.0f));
ubo.modelView = viewMatrix * glm::translate(glm::mat4(), cameraPos);
ubo.modelView = glm::rotate(ubo.modelView, glm::radians(rotation.x),
glm::vec3(1.0f, 0.0f, 0.0f));
ubo.modelView = glm::rotate(ubo.modelView, glm::radians(rotation.y),
glm::vec3(0.0f, 1.0f, 0.0f));
ubo.modelView = glm::rotate(ubo.modelView, glm::radians(rotation.z),
zRotationAxis);
// Remove translation from modelView so the skybox doesn't move.
ubo.skyboxView = glm::mat4(glm::mat3(ubo.modelView));
// Do the inverse here because doing it in every fragment is a bit much.
ubo.invModelView = glm::inverse(ubo.modelView);
// Now add the object rotation.
ubo.modelView = ubo.modelView * object;
glBindBuffer(GL_UNIFORM_BUFFER, gnUbo);
#if !defined(EMSCRIPTEN)
uint8_t* pData = (uint8_t*)glMapBufferRange(GL_UNIFORM_BUFFER,
0, sizeof(ubo),
GL_MAP_WRITE_BIT);
memcpy(pData, &ubo, sizeof(ubo));
glUnmapBuffer(GL_UNIFORM_BUFFER);
#else
glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(ubo), &ubo);
#endif
glBindBuffer(GL_UNIFORM_BUFFER, 0);
}
void
TextureCubemap::prepareSampler()
{
glBindTexture(cubemapTexTarget, gnCubemapTexture);
if (bIsMipmapped)
glTexParameteri(cubemapTexTarget,
GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
else
glTexParameteri(cubemapTexTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(cubemapTexTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glUseProgram(gnReflectProg);
if ((uReflectCubemap = glGetUniformLocation(gnReflectProg,
"uSamplerColor")) == -1) {
std::stringstream message;
message << "prepareSampler: uSamplerColor not found in reflect program";
throw std::runtime_error(message.str());
}
glUniform1i(uReflectCubemap, cubemapTexUnit - GL_TEXTURE0);
glUseProgram(gnSkyboxProg);
if ((uSkyboxCubemap = glGetUniformLocation(gnSkyboxProg,
"uSamplerColor")) == -1) {
std::stringstream message;
message << "prepareSampler: uSamplerColor not found in skybox program";
throw std::runtime_error(message.str());
}
glUniform1i(uSkyboxCubemap, cubemapTexUnit - GL_TEXTURE0);
glUseProgram(0);
}
void
TextureCubemap::preparePrograms()
{
GLuint gnReflectFs, gnReflectVs, gnSkyboxFs, gnSkyboxVs;
const GLchar* actualReflectFs;
const GLchar* actualSkyboxFs;
if (framebufferColorEncoding() == GL_LINEAR) {
actualReflectFs = pszReflectSrgbEncodeFs;
actualSkyboxFs = pszSkyboxSrgbEncodeFs;
} else {
actualReflectFs = pszReflectFs;
actualSkyboxFs = pszSkyboxFs;
}
try {
makeShader(GL_VERTEX_SHADER, pszReflectVs, &gnReflectVs);
makeShader(GL_FRAGMENT_SHADER, actualReflectFs, &gnReflectFs);
makeProgram(gnReflectVs, gnReflectFs, &gnReflectProg);
makeShader(GL_VERTEX_SHADER, pszSkyboxVs, &gnSkyboxVs);
makeShader(GL_FRAGMENT_SHADER, actualSkyboxFs, &gnSkyboxFs);
makeProgram(gnSkyboxVs, gnSkyboxFs, &gnSkyboxProg);
} catch (std::exception& e) {
(void)e; // To quiet unused variable warnings from some compilers.
throw;
}
glDeleteShader(gnReflectVs);
glDeleteShader(gnReflectFs);
glDeleteShader(gnSkyboxVs);
glDeleteShader(gnSkyboxFs);
}
void
TextureCubemap::prepare()
{
// By default dithering is enabled. Dithering does not provide visual
// improvement in this sample so disable it to improve performance.
glDisable(GL_DITHER);
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
glClearColor(0.2f,0.3f,0.4f,1.0f);
loadMeshes();
preparePrograms();
prepareUniformBuffers();
prepareSampler();
}
void
TextureCubemap::toggleSkyBox()
{
bDisplaySkybox = !bDisplaySkybox;
}
void
TextureCubemap::toggleObject(int direction)
{
meshes.objectIndex += direction;
if (meshes.objectIndex >= static_cast<int32_t>(meshes.objects.size())) {
meshes.objectIndex = 0;
} else if (meshes.objectIndex < 0) {
meshes.objectIndex = static_cast<int32_t>(meshes.objects.size()) - 1;
}
}
void
TextureCubemap::changeLodBias(float delta)
{
ubo.lodBias += delta;
if (ubo.lodBias < 0.0f)
{
ubo.lodBias = 0.0f;
}
if (ubo.lodBias > levelCount)
{
ubo.lodBias = (float)levelCount;
}
updateUniformBuffers();
}
void
TextureCubemap::keyPressed(uint32_t keyCode)
{
switch (keyCode)
{
case 's':
toggleSkyBox();
break;
case ' ':
toggleObject(+1);
break;
case SDLK_KP_PLUS:
changeLodBias(0.1f);
break;
case SDLK_KP_MINUS:
changeLodBias(-0.1f);
break;
}
}
@@ -0,0 +1,114 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @file
* @~English
*
* @brief Test loading of a cubemap.
*
* @author Mark Callow, github.com/MarkCallow.
*/
#include <vector>
#include "GL3LoadTestSample.h"
#include <glm/gtc/matrix_transform.hpp>
#define VERTEX_BUFFER_BIND_ID 0
#define ENABLE_VALIDATION false
class TextureCubemap : public GL3LoadTestSample
{
public:
TextureCubemap(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath);
~TextureCubemap();
virtual int doEvent(SDL_Event* event);
virtual void resize(uint32_t width, uint32_t height);
virtual void run(uint32_t msTicks);
//virtual void getOverlayText(VulkanTextOverlay *textOverlay);
static LoadTestSample*
create(uint32_t width, uint32_t height,
const char* const szArgs, const std::string sBasePath);
protected:
const GLuint cubemapTexUnit;
const GLuint uniformBufferBindId;
GLuint levelCount = 0;
GLenum cubemapTexTarget;
GLuint gnCubemapTexture;
GLuint gnReflectProg;
GLuint gnSkyboxProg;
GLuint gnUbo;
bool bInitialized;
bool bIsMipmapped;
bool bDisplaySkybox = true;
uint32_t numLayers;
// Vertex layout for this example
struct TAVertex {
float pos[3];
float uv[2];
};
struct {
glMeshLoader::MeshBuffer skybox;
std::vector<glMeshLoader::MeshBuffer> objects;
int32_t objectIndex = 0;
} meshes;
struct {
// Global matrices
glm::mat4 projection;
glm::mat4 modelView;
glm::mat4 skyboxView;
glm::mat4 invModelView;
glm::mat4 uvwTransform;
float lodBias = 0.0f;
} ubo;
GLint uReflectProgramUniforms;
GLint uSkyboxProgramUniforms;
GLint uReflectCubemap;
GLint uSkyboxCubemap;
glm::vec3 zRotationAxis;
void cleanup();
void loadMeshes();
void prepareUniformBuffers();
void updateUniformBuffers();
void prepareSampler();
void preparePrograms();
void prepare();
void toggleSkyBox();
void toggleObject(int direction);
void changeLodBias(float delta);
void processArgs(std::string sArgs);
virtual void keyPressed(uint32_t keyCode);
virtual void viewChanged()
{
updateUniformBuffers();
}
GLuint skyboxVAO, skyboxVBO;
};
@@ -0,0 +1,125 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2018-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @file
* @~English
*
* @brief Definition of test sample for loading and displaying all the levels of a 2D mipmapped texture.
*
* @author Mark Callow, github.com/MarkCallow.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <algorithm>
#include <time.h>
#include <sstream>
#include <vector>
#include <ktx.h>
#include "argparser.h"
#include "TextureMipmap.h"
#include "ltexceptions.h"
#define member_size(type, member) sizeof(((type *)0)->member)
const GLchar* pszLodFsDeclarations =
"precision mediump float;\n"
"in vec2 UV;\n"
"flat in float lambda;\n\n"
"layout (location = 0) out vec4 outFragColor;\n\n"
"uniform mediump sampler2D uSampler;\n\n";
const GLchar* pszLodFsMain =
"void main()\n"
"{\n"
" outFragColor = textureLod(uSampler, UV, lambda);\n"
"}";
const GLchar* pszLodSrgbEncodeFsMain =
"void main()\n"
"{\n"
" vec4 t_color = textureLod(uSampler, UV, lambda);\n"
" outFragColor.rgb = srgb_encode(t_color.rgb);\n"
" outFragColor.a = t_color.a;\n"
"}";
const GLchar* pszLodVsMain =
"out vec2 UV;\n"
"flat out float lambda;\n\n"
"void main()\n"
"{\n"
" UV = inUV;\n"
" lambda = gl_InstanceID + 0.5;\n"
" mat4 modelView = ubo.view * ubo.instance[gl_InstanceID].model;\n"
" gl_Position = ubo.projection * modelView * inPos;\n"
"}";
/* ------------------------------------------------------------------------- */
LoadTestSample*
TextureMipmap::create(uint32_t width, uint32_t height,
const char* const szArgs, const std::string sBasePath)
{
return new TextureMipmap(width, height, szArgs, sBasePath);
}
/**
* @internal
* @class TextureMipmap
* @~English
*
* @brief Test loading of 2D texture arrays.
*/
TextureMipmap::TextureMipmap(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath)
: InstancedSampleBase(width, height, szArgs, sBasePath)
{
zoom = -15.0f;
if (texTarget != GL_TEXTURE_2D || textureInfo.numLevels == 1) {
std::stringstream message;
message << "TextureMipmap requires a 2D mipmapped texture.";
throw std::runtime_error(message.str());
}
// Checking if KVData contains keys of interest would go here.
instanceCount = textureInfo.numLevels;
InstancedSampleBase::ShaderSource fs;
InstancedSampleBase::ShaderSource vs;
fs.push_back(pszLodFsDeclarations);
if (framebufferColorEncoding() == GL_LINEAR) {
fs.push_back(pszSrgbEncodeFunc);
fs.push_back(pszLodSrgbEncodeFsMain);
} else {
fs.push_back(pszLodFsMain);;
}
vs.push_back(pszInstancingVsDeclarations);
vs.push_back(pszLodVsMain);
try {
prepare(fs, vs);
} catch (std::exception& e) {
(void)e; // To quiet unused variable warnings from some compilers.
cleanup();
throw;
}
bInitialized = true;
}
@@ -0,0 +1,41 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2021 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @file
* @~English
*
* @brief Declaration of test sample for loading and displaying all the levels of a 2D mipmapped texture.
*
* @author Mark Callow, github.com/MarkCallow.
*/
#include <vector>
#include "InstancedSampleBase.h"
#include <glm/gtc/matrix_transform.hpp>
#define VERTEX_BUFFER_BIND_ID 0
#define ENABLE_VALIDATION false
class TextureMipmap : public InstancedSampleBase
{
public:
TextureMipmap(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath);
static LoadTestSample*
create(uint32_t width, uint32_t height,
const char* const szArgs, const std::string sBasePath);
protected:
using ShaderSource = GL3LoadTestSample::ShaderSource;
};
@@ -0,0 +1,245 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 sts expandtab: */
/*
* Copyright 2018-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief Draw a textured cube.
*
* @author Mark Callow, github.com/MarkCallow.
*/
#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 "TexturedCube.h"
#include "cube.h"
/* ------------------------------------------------------------------------- */
extern const GLchar* pszVs;
extern const GLchar *pszDecalFs, *pszDecalSrgbEncodeFs;
/* ------------------------------------------------------------------------- */
LoadTestSample*
TexturedCube::create(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath)
{
return new TexturedCube(width, height, szArgs, sBasePath);
}
TexturedCube::TexturedCube(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath)
: GL3LoadTestSample(width, height, szArgs, sBasePath)
{
std::string filename;
GLenum target;
GLenum glerror;
GLuint gnDecalFs, gnVs;
GLsizeiptr offset;
ktxTexture* kTexture;
KTX_error_code ktxresult;
bInitialized = GL_FALSE;
gnTexture = 0;
filename = getAssetPath() + szArgs;
ktxresult = ktxTexture_CreateFromNamedFile(filename.c_str(),
KTX_TEXTURE_CREATE_NO_FLAGS,
&kTexture);
if (KTX_SUCCESS != ktxresult) {
std::stringstream message;
message << "Creation of ktxTexture from \"" << filename
<< "\" failed: " << ktxErrorString(ktxresult);
throw std::runtime_error(message.str());
}
ktxresult = ktxTexture_GLUpload(kTexture, &gnTexture, &target, &glerror);
if (KTX_SUCCESS == ktxresult) {
if (target != GL_TEXTURE_2D) {
/* Can only draw 2D textures */
std::stringstream message;
glDeleteTextures(1, &gnTexture);
message << "App can only draw 2D textures.";
throw std::runtime_error(message.str());
}
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);
ktxTexture_Destroy(kTexture);
assert(GL_NO_ERROR == glGetError());
} else {
std::stringstream message;
message << "Load of texture from \"" << filename << "\" failed: ";
if (ktxresult == KTX_GL_ERROR) {
message << std::showbase << "GL error " << std::hex << glerror
<< " occurred.";
} else {
message << ktxErrorString(ktxresult);
}
throw std::runtime_error(message.str());
}
// By default dithering is enabled. Dithering does not provide visual
// improvement in this sample so disable it to improve performance.
glDisable(GL_DITHER);
glEnable(GL_CULL_FACE);
glClearColor(0.2f,0.3f,0.4f,1.0f);
// Create a VAO and bind it.
glGenVertexArrays(1, &gnVao);
glBindVertexArray(gnVao);
// Must have vertex data in buffer objects to use VAO's on ES3/GL Core
glGenBuffers(2, gnVbo);
glBindBuffer(GL_ARRAY_BUFFER, gnVbo[0]);
// Must be done after the VAO is bound
// WebGL requires different buffers for data and indices.
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gnVbo[1]);
// Create the buffer data store.
glBufferData(GL_ARRAY_BUFFER,
sizeof(cube_face) + sizeof(cube_color) + sizeof(cube_texture)
+ sizeof(cube_normal), NULL, GL_STATIC_DRAW);
// Interleave data copying and attrib pointer setup so offset is only
// computed once.
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
glEnableVertexAttribArray(3);
offset = 0;
glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(cube_face), cube_face);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (GLvoid*)offset);
offset += sizeof(cube_face);
glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(cube_color), cube_color);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, (GLvoid*)offset);
offset += sizeof(cube_color);
glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(cube_texture), cube_texture);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, (GLvoid*)offset);
offset += sizeof(cube_texture);
glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(cube_normal), cube_normal);
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 0, (GLvoid*)offset);
offset += sizeof(cube_normal);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(cube_index_buffer),
cube_index_buffer, GL_STATIC_DRAW);
const GLchar* actualDecalFs;
if (framebufferColorEncoding() == GL_LINEAR) {
actualDecalFs = pszDecalSrgbEncodeFs;
} else {
actualDecalFs = pszDecalFs;
}
try {
makeShader(GL_VERTEX_SHADER, pszVs, &gnVs);
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;
}
try {
makeShader(GL_VERTEX_SHADER, pszVs, &gnVs);
makeShader(GL_FRAGMENT_SHADER, pszDecalFs, &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(gnDecalFs);
assert(GL_NO_ERROR == glGetError());
bInitialized = GL_TRUE;
}
TexturedCube::~TexturedCube()
{
glEnable(GL_DITHER);
glEnable(GL_CULL_FACE);
if (bInitialized) {
glUseProgram(0);
glDeleteTextures(1, &gnTexture);
glDeleteProgram(gnTexProg);
glDeleteBuffers(2, gnVbo);
glDeleteVertexArrays(1, &gnVao);
}
assert(GL_NO_ERROR == glGetError());
}
void
TexturedCube::resize(uint32_t uWidth, uint32_t uHeight)
{
glm::mat4 matProj;
glViewport(0, 0, uWidth, uHeight);
matProj = glm::perspective(glm::radians(45.f),
uWidth / (float)uHeight,
1.f, 100.f);
glUniformMatrix4fv(gulPMatrixLocTP, 1, GL_FALSE, glm::value_ptr(matProj));
}
void
TexturedCube::run(uint32_t msTicks)
{
// Setup the view matrix : just turn around the cube.
const float fDistance = 5.0f;
glm::vec3 eye((float)cos( msTicks*0.001f ) * fDistance,
(float)sin( msTicks*0.0007f ) * fDistance,
(float)sin( msTicks*0.001f ) * fDistance);
glm::vec3 look(0.,0.,0.);
glm::vec3 up(0.,1.,0.);
glm::mat4 matView = glm::lookAt( eye, look, up );
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glUniformMatrix4fv(gulMvMatrixLocTP, 1, GL_FALSE, glm::value_ptr(matView));
glDrawElements(GL_TRIANGLES, CUBE_NUM_INDICES, GL_UNSIGNED_SHORT, 0);
assert(GL_NO_ERROR == glGetError());
}
/* ------------------------------------------------------------------------- */
@@ -0,0 +1,51 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2018-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief GLLoadTestSample derived class for drawing a textured cube.
*
* @author Mark Callow, github.com/MarkCallow.
*/
#ifndef TEXTURED_CUBE_H
#define TEXTURED_CUBE_H
#include "GL3LoadTestSample.h"
class TexturedCube : public GL3LoadTestSample {
public:
TexturedCube(uint32_t width, uint32_t height,
const char* const szArgs,
const std::string sBasePath);
~TexturedCube();
virtual void resize(uint32_t width, uint32_t height);
virtual void run(uint32_t msTicks);
//virtual void getOverlayText(GLTextOverlay *textOverlay);
static LoadTestSample*
create(uint32_t width, uint32_t height,
const char* const szArgs, const std::string sBasePath);
protected:
GLuint gnTexture;
GLuint gnTexProg;
GLuint gnVao;
GLuint gnVbo[2];
GLint gulMvMatrixLocTP;
GLint gulPMatrixLocTP;
GLint gulSamplerLocTP;
bool bInitialized;
};
#endif /* TEXTURED_CUBE_H */
@@ -0,0 +1,81 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2016-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief Include appropriate version of gl.h for the shader-based tests.
*
* This is a separate header to avoid repetition of these conditionals.
*/
#ifndef MYGL_H
#define MYGL_H
#include <SDL3/SDL_video.h> // For the SDL_GL_CONTEXT_PROFILE macros
#if GL_CONTEXT_PROFILE == SDL_GL_CONTEXT_PROFILE_CORE
#ifdef _WIN32
#include <windows.h>
#undef KTX_USE_GETPROC /* Must use GETPROC on Windows */
#define KTX_USE_GETPROC 1
#else
#if !defined(KTX_USE_GETPROC)
#define KTX_USE_GETPROC 0
#endif
#endif
#if KTX_USE_GETPROC
#include <GL/glew.h>
#else
#define GL_GLEXT_PROTOTYPES
#include <GL/glcorearb.h>
#endif
//#define GL_APIENTRY APIENTRY
#elif GL_CONTEXT_PROFILE == SDL_GL_CONTEXT_PROFILE_COMPATIBILITY
#error This application is not intended to run in compatibility mode.
#elif GL_CONTEXT_PROFILE == SDL_GL_CONTEXT_PROFILE_ES
#if GL_CONTEXT_MAJOR_VERSION == 1
#error This application cannot run on OpenGL ES 1.
#elif GL_CONTEXT_MAJOR_VERSION == 2
#define GL_GLEXT_PROTOTYPES
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#elif GL_CONTEXT_MAJOR_VERSION == 3
#define GL_GLEXT_PROTOTYPES
#include <GLES3/gl3.h>
#include <GLES2/gl2ext.h>
#else
#error Unexpected GL_CONTEXT_MAJOR_VERSION
#endif
#endif
/* To help find supported transcode targets */
#if !defined(GL_ETC1_RGB8_OES)
#define GL_ETC1_RGB8_OES 0x8D64
#endif
#if !defined(GL_COMPRESSED_RGB8_ETC2)
#define GL_COMPRESSED_RGB8_ETC2 0x9274
#endif
#if !defined(GL_COMPRESSED_RGBA_S3TC_DXT5_EXT)
#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3
#endif
#endif /* MYGL_H */
@@ -0,0 +1,115 @@
/* -*- tab-iWidth: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2008 HI Corporation.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief Shaders used by the DrawTexture and TexturedCube samples.
*/
#include "GL/glcorearb.h"
#include "ktx.h"
const GLchar* pszVs =
"layout(location = 0) in vec4 position;\n"
"layout(location = 1) in vec4 color;\n"
"layout(location = 2) in vec4 texcoord;\n\n"
"out vec4 v_color;\n"
"out vec2 v_texcoord;\n\n"
"uniform highp mat4 mvmatrix;\n"
"uniform highp mat4 pmatrix;\n\n"
"void main(void)\n"
"{\n"
" mat4 mvpmatrix = pmatrix * mvmatrix;\n"
" v_color = color;\n"
" v_texcoord = texcoord.xy;\n"
" gl_Position = mvpmatrix * position;\n"
"}";
const GLchar* pszDecalFs =
"precision mediump float;\n\n"
"uniform sampler2D sampler;\n\n"
"in vec4 v_color;\n"
"in vec2 v_texcoord;\n"
"out vec4 fragcolor;\n\n"
"void main(void)\n"
"{\n"
" vec4 color = texture(sampler, v_texcoord);\n"
" // DECAL\n"
" fragcolor.rgb = v_color.rgb * (1.0f - color.a) + color.rgb * color.a;\n"
" // No need for blending with page or screen background.\n"
" fragcolor.a = 1.0f;\n"
"}";
const GLchar* pszColorFs =
"precision mediump float;\n\n"
"in vec4 v_color;\n"
"out vec4 fragcolor;\n\n"
"void main(void)\n"
"{\n"
" fragcolor = v_color;\n"
"}";
// For use when sRGB rendering is not available. Note this is really a
// workaround for broken GL implementations (most) where "linear" framebuffers
// are really sRGB framebuffers but since they are "linear" GL does not encode
// the output of the fs to sRGB so we do it ourselves.
const GLchar* pszDecalSrgbEncodeFs =
"precision mediump float;\n\n"
"uniform sampler2D sampler;\n\n"
"in vec4 v_color;\n"
"in vec2 v_texcoord;\n"
"out vec4 fragcolor;\n\n"
"vec3 srgb_encode(vec3 color) {\n"
" float r = color.r < 0.0031308 ? 12.92 * color.r : 1.055 * pow(color.r, 1.0/2.4) - 0.055;\n"
" float g = color.g < 0.0031308 ? 12.92 * color.g : 1.055 * pow(color.g, 1.0/2.4) - 0.055;\n"
" float b = color.b < 0.0031308 ? 12.92 * color.b : 1.055 * pow(color.b, 1.0/2.4) - 0.055;\n"
" return vec3(r, g, b);\n"
"}\n\n"
"void main(void)\n"
"{\n"
" vec4 t_color = texture(sampler, v_texcoord);\n"
" vec3 lin_fragcolor;\n"
" // DECAL\n"
" lin_fragcolor = v_color.rgb * (1.0f - t_color.a) + t_color.rgb * t_color.a;\n"
" fragcolor.rgb = srgb_encode(lin_fragcolor);\n"
" // No need for blending with page or screen background.\n"
" fragcolor.a = 1.0f;\n"
"}";
const GLchar* pszColorSrgbEncodeFs =
"precision mediump float;\n\n"
"in vec4 v_color;\n"
"out vec4 fragcolor;\n\n"
"vec3 srgb_encode(vec3 color) {\n"
" float r = color.r < 0.0031308 ? 12.92 * color.r : 1.055 * pow(color.r, 1.0/2.4) - 0.055;\n"
" float g = color.g < 0.0031308 ? 12.92 * color.g : 1.055 * pow(color.g, 1.0/2.4) - 0.055;\n"
" float b = color.b < 0.0031308 ? 12.92 * color.b : 1.055 * pow(color.b, 1.0/2.4) - 0.055;\n"
" return vec3(r, g, b);\n"
"}\n\n"
"void main(void)\n"
"{\n"
" fragcolor.rgb = srgb_encode(v_color.rgb);\n"
" fragcolor.a = v_color.a;\n"
"}";
/* ----------------------------------------------------------------------------- */
@@ -0,0 +1,516 @@
/*
* Copyright 2017-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdlib.h>
#include <string>
#include <fstream>
#include <iostream>
#include <sstream>
#include <map>
#include <stdexcept>
#include <vector>
#ifdef _WIN32
#include <windows.h>
#include <fcntl.h>
#include <io.h>
#else
#endif
// Emscripten assimp port not yet available.
#if !defined(__EMSCRIPTEN__)
#include <assimp/Importer.hpp>
#include <assimp/scene.h>
#include <assimp/postprocess.h>
#endif
#include "disable_glm_warnings.h"
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "reenable_warnings.h"
#if defined(__ANDROID__)
#include <android/asset_manager.h>
#endif
//#include <glad/glad.h>
namespace glMeshLoader
{
typedef enum VertexLayout {
VERTEX_LAYOUT_POSITION = 0x0,
VERTEX_LAYOUT_NORMAL = 0x1,
VERTEX_LAYOUT_COLOR = 0x2,
VERTEX_LAYOUT_UV = 0x3,
VERTEX_LAYOUT_TANGENT = 0x4,
VERTEX_LAYOUT_BITANGENT = 0x5,
VERTEX_LAYOUT_DUMMY_FLOAT = 0x6,
VERTEX_LAYOUT_DUMMY_VEC4 = 0x7
} VertexLayout;
struct MeshBufferInfo
{
GLuint name = 0;
size_t size = 0;
};
struct MeshBuffer
{
GLuint vao = 0;
MeshBufferInfo vertices;
MeshBufferInfo indices;
GLuint primitiveType;
uint32_t indexCount = 0;
glm::vec3 dim;
glm::mat4 modelTransform; // To display the model correctly in the
// GL coordinate system.
void FreeGLResources() {
if (vertices.name)
glDeleteBuffers(1, &vertices.name);
if (indices.name)
glDeleteBuffers(1, &indices.name);
if (vao)
glDeleteVertexArrays(1, &vao);
}
~MeshBuffer() {
FreeGLResources();
}
glm::mat4& getModelTransform() { return modelTransform; }
void Draw() {
glBindVertexArray(vao);
glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
}
};
#if !defined(__EMSCRIPTEN__)
// Get vertex size from vertex layout
static uint32_t vertexSize(std::vector<glMeshLoader::VertexLayout> layout)
{
uint32_t vSize = 0;
for (auto& layoutDetail : layout)
{
switch (layoutDetail)
{
// UV only has two components
case VERTEX_LAYOUT_UV:
vSize += 2 * sizeof(float);
break;
default:
vSize += 3 * sizeof(float);
}
}
return vSize;
}
#endif
}
#if !defined(__EMSCRIPTEN__)
// Simple mesh class for getting all the necessary stuff from models
// loaded via ASSIMP.
class GLMeshLoader {
private:
struct Vertex
{
glm::vec3 m_pos;
glm::vec2 m_tex;
glm::vec3 m_normal;
glm::vec3 m_color;
glm::vec3 m_tangent;
glm::vec3 m_binormal;
Vertex() {}
Vertex(const glm::vec3& pos, const glm::vec2& tex, const glm::vec3& normal, const glm::vec3& tangent, const glm::vec3& bitangent, const glm::vec3& color)
{
m_pos = pos;
m_tex = tex;
m_normal = normal;
m_color = color;
m_tangent = tangent;
m_binormal = bitangent;
}
};
struct MeshEntry {
uint32_t NumIndices;
uint32_t MaterialIndex;
uint32_t vertexBase;
uint32_t primitiveType;
std::vector<Vertex> Vertices;
std::vector<unsigned int> Indices;
};
aiMatrix4x4 rootTransform;
public:
#if defined(__ANDROID__)
AAssetManager* assetManager = nullptr;
#endif
std::vector<MeshEntry> m_Entries;
struct Dimension
{
glm::vec3 min = glm::vec3(FLT_MAX);
glm::vec3 max = glm::vec3(-FLT_MAX);
glm::vec3 size;
} dim;
uint32_t numVertices = 0;
Assimp::Importer importer;
const aiScene* pScene;
~GLMeshLoader()
{
m_Entries.clear();
}
// Load a mesh with some default flags
void LoadMesh(const std::string& filename)
{
int flags = aiProcess_Triangulate | aiProcess_PreTransformVertices
| aiProcess_JoinIdenticalVertices
| aiProcess_CalcTangentSpace
| aiProcess_GenSmoothNormals;
LoadMesh(filename, flags);
}
// Load the mesh with custom flags
void LoadMesh(const std::string& filename, int flags)
{
#if defined(__ANDROID__)
// Meshes are stored inside the apk on Android (compressed)
// So they need to be loaded via the asset manager
AAsset* asset = AAssetManager_open(assetManager, filename.c_str(),
AASSET_MODE_STREAMING);
assert(asset);
size_t size = AAsset_getLength(asset);
assert(size > 0);
void *meshData = malloc(size);
AAsset_read(asset, meshData, size);
AAsset_close(asset);
pScene = importer.ReadFileFromMemory(meshData, size, flags);
free(meshData);
#else
pScene = importer.ReadFile(filename.c_str(), flags);
#endif
if(!pScene || pScene->mFlags & AI_SCENE_FLAGS_INCOMPLETE
|| !pScene->mRootNode)
{
std::stringstream message;
message << " Import via ASSIMP from\"" << filename << "\" failed. "
<< importer.GetErrorString() << std::endl;
throw std::runtime_error(message.str());
}
InitFromScene(pScene, filename);
#if 0
// retrieve the directory path of the filepath
directory = path.substr(0, path.find_last_of('/'));
// process ASSIMP's root node recursively
processNode(scene->mRootNode, scene);
#endif
rootTransform = pScene->mRootNode->mTransformation;
}
void InitFromScene(const aiScene* pSrcScene, const std::string& /*filename*/)
{
m_Entries.resize(pSrcScene->mNumMeshes);
// Counters
for (unsigned int i = 0; i < m_Entries.size(); i++)
{
m_Entries[i].vertexBase = numVertices;
numVertices += pSrcScene->mMeshes[i]->mNumVertices;
}
// Initialize the meshes in the scene one by one
for (unsigned int i = 0; i < m_Entries.size(); i++)
{
const aiMesh* paiMesh = pSrcScene->mMeshes[i];
InitMesh(i, paiMesh, pSrcScene);
}
}
void InitMesh(unsigned int index, const aiMesh* paiMesh, const aiScene* pSrcScene)
{
m_Entries[index].MaterialIndex = paiMesh->mMaterialIndex;
aiColor3D pColor(0.f, 0.f, 0.f);
pSrcScene->mMaterials[paiMesh->mMaterialIndex]->Get(AI_MATKEY_COLOR_DIFFUSE, pColor);
aiVector3D Zero3D(0.0f, 0.0f, 0.0f);
for (unsigned int i = 0; i < paiMesh->mNumVertices; i++) {
aiVector3D* pPos = &(paiMesh->mVertices[i]);
aiVector3D* pNormal = &(paiMesh->mNormals[i]);
aiVector3D *pTexCoord;
if (paiMesh->HasTextureCoords(0))
{
pTexCoord = &(paiMesh->mTextureCoords[0][i]);
}
else {
pTexCoord = &Zero3D;
}
aiVector3D* pTangent = (paiMesh->HasTangentsAndBitangents()) ?
&(paiMesh->mTangents[i]) : &Zero3D;
aiVector3D* pBiTangent = (paiMesh->HasTangentsAndBitangents()) ?
&(paiMesh->mBitangents[i]) : &Zero3D;
Vertex v(glm::vec3(pPos->x, -pPos->y, pPos->z),
glm::vec2(pTexCoord->x , pTexCoord->y),
glm::vec3(pNormal->x, pNormal->y, pNormal->z),
glm::vec3(pTangent->x, pTangent->y, pTangent->z),
glm::vec3(pBiTangent->x, pBiTangent->y, pBiTangent->z),
glm::vec3(pColor.r, pColor.g, pColor.b)
);
dim.max.x = fmax(pPos->x, dim.max.x);
dim.max.y = fmax(pPos->y, dim.max.y);
dim.max.z = fmax(pPos->z, dim.max.z);
dim.min.x = fmin(pPos->x, dim.min.x);
dim.min.y = fmin(pPos->y, dim.min.y);
dim.min.z = fmin(pPos->z, dim.min.z);
m_Entries[index].Vertices.push_back(v);
}
dim.size = dim.max - dim.min;
for (unsigned int i = 0; i < paiMesh->mNumFaces; i++)
{
const aiFace& Face = paiMesh->mFaces[i];
if (Face.mNumIndices != 3)
continue;
m_Entries[index].Indices.push_back(Face.mIndices[0]);
m_Entries[index].Indices.push_back(Face.mIndices[1]);
m_Entries[index].Indices.push_back(Face.mIndices[2]);
}
switch (paiMesh->mPrimitiveTypes) {
case aiPrimitiveType_POINT:
m_Entries[index].primitiveType = GL_POINTS ;
break;
case aiPrimitiveType_LINE:
m_Entries[index].primitiveType = GL_LINES;
break;
case aiPrimitiveType_TRIANGLE:
m_Entries[index].primitiveType = GL_TRIANGLES;
break;
default: assert(false); // Shouldn't happen because of triangulate.
}
}
void CreateBuffers(glMeshLoader::MeshBuffer& meshBuffer,
std::vector<glMeshLoader::VertexLayout> layout,
float scale)
{
std::vector<float> vertexBuffer;
for (uint32_t m = 0; m < m_Entries.size(); m++)
{
for (uint32_t i = 0; i < m_Entries[m].Vertices.size(); i++)
{
// Push vertex data depending on layout
for (auto& layoutDetail : layout)
{
// Position
if (layoutDetail == glMeshLoader::VERTEX_LAYOUT_POSITION)
{
vertexBuffer.push_back(m_Entries[m].Vertices[i].m_pos.x * scale);
vertexBuffer.push_back(m_Entries[m].Vertices[i].m_pos.y * scale);
vertexBuffer.push_back(m_Entries[m].Vertices[i].m_pos.z * scale);
}
// Normal
if (layoutDetail == glMeshLoader::VERTEX_LAYOUT_NORMAL)
{
vertexBuffer.push_back(m_Entries[m].Vertices[i].m_normal.x);
vertexBuffer.push_back(-m_Entries[m].Vertices[i].m_normal.y);
vertexBuffer.push_back(m_Entries[m].Vertices[i].m_normal.z);
}
// Texture coordinates
if (layoutDetail == glMeshLoader::VERTEX_LAYOUT_UV)
{
vertexBuffer.push_back(m_Entries[m].Vertices[i].m_tex.s);
vertexBuffer.push_back(m_Entries[m].Vertices[i].m_tex.t);
}
// Color
if (layoutDetail == glMeshLoader::VERTEX_LAYOUT_COLOR)
{
vertexBuffer.push_back(m_Entries[m].Vertices[i].m_color.r);
vertexBuffer.push_back(m_Entries[m].Vertices[i].m_color.g);
vertexBuffer.push_back(m_Entries[m].Vertices[i].m_color.b);
}
// Tangent
if (layoutDetail == glMeshLoader::VERTEX_LAYOUT_TANGENT)
{
vertexBuffer.push_back(m_Entries[m].Vertices[i].m_tangent.x);
vertexBuffer.push_back(m_Entries[m].Vertices[i].m_tangent.y);
vertexBuffer.push_back(m_Entries[m].Vertices[i].m_tangent.z);
}
// Bitangent
if (layoutDetail == glMeshLoader::VERTEX_LAYOUT_BITANGENT)
{
vertexBuffer.push_back(m_Entries[m].Vertices[i].m_binormal.x);
vertexBuffer.push_back(m_Entries[m].Vertices[i].m_binormal.y);
vertexBuffer.push_back(m_Entries[m].Vertices[i].m_binormal.z);
}
// Dummy layout components for padding
if (layoutDetail == glMeshLoader::VERTEX_LAYOUT_DUMMY_FLOAT)
{
vertexBuffer.push_back(0.0f);
}
if (layoutDetail == glMeshLoader::VERTEX_LAYOUT_DUMMY_VEC4)
{
vertexBuffer.push_back(0.0f);
vertexBuffer.push_back(0.0f);
vertexBuffer.push_back(0.0f);
vertexBuffer.push_back(0.0f);
}
}
}
}
meshBuffer.vertices.size = vertexBuffer.size() * sizeof(float);
dim.min *= scale;
dim.max *= scale;
dim.size *= scale;
meshBuffer.modelTransform[0][0] = rootTransform.a1;
meshBuffer.modelTransform[0][1] = rootTransform.b1;
meshBuffer.modelTransform[0][2] = rootTransform.c1;
meshBuffer.modelTransform[0][3] = rootTransform.d1;
meshBuffer.modelTransform[1][0] = rootTransform.a2;
meshBuffer.modelTransform[1][1] = rootTransform.b2;
meshBuffer.modelTransform[1][2] = rootTransform.c2;
meshBuffer.modelTransform[1][3] = rootTransform.d2;
meshBuffer.modelTransform[2][0] = rootTransform.a3;
meshBuffer.modelTransform[2][1] = rootTransform.b3;
meshBuffer.modelTransform[2][2] = rootTransform.c3;
meshBuffer.modelTransform[2][3] = rootTransform.d3;
meshBuffer.modelTransform[3][0] = rootTransform.a4;
meshBuffer.modelTransform[3][1] = rootTransform.b4;
meshBuffer.modelTransform[3][2] = rootTransform.c4;
meshBuffer.modelTransform[3][3] = rootTransform.d4;
std::vector<uint32_t> indexBuffer;
for (uint32_t m = 0; m < m_Entries.size(); m++)
{
uint32_t indexBase = (uint32_t)indexBuffer.size();
for (uint32_t i = 0; i < m_Entries[m].Indices.size(); i++)
{
indexBuffer.push_back(m_Entries[m].Indices[i] + indexBase);
}
}
meshBuffer.indices.size = indexBuffer.size() * sizeof(uint32_t);
meshBuffer.indexCount = (uint32_t)indexBuffer.size();
meshBuffer.primitiveType = m_Entries[0].primitiveType;
// Make the GL buffers
glGenVertexArrays(1, &meshBuffer.vao);
glBindVertexArray(meshBuffer.vao);
// Setup vertices.
glGenBuffers(1, &meshBuffer.vertices.name);
glBindBuffer(GL_ARRAY_BUFFER, meshBuffer.vertices.name);
// Create the buffer data store and upload the vertices.
glBufferData(GL_ARRAY_BUFFER, meshBuffer.vertices.size,
vertexBuffer.data(), GL_STATIC_DRAW);
GLuint attribNumber = 0;
GLsizeiptr attribOffset = 0;
GLsizei stride = vertexSize(layout);
for (auto& layoutDetail : layout)
{
// Position
if (layoutDetail == glMeshLoader::VERTEX_LAYOUT_POSITION) {
glEnableVertexAttribArray(attribNumber);
glVertexAttribPointer(attribNumber, 3, GL_FLOAT, GL_FALSE,
stride, (void *)attribOffset);
attribOffset += sizeof(float) * 3;
attribNumber++;
}
// Normal
if (layoutDetail == glMeshLoader::VERTEX_LAYOUT_NORMAL) {
glEnableVertexAttribArray(attribNumber);
glVertexAttribPointer(attribNumber, 3, GL_FLOAT, GL_FALSE,
stride, (void *)attribOffset);
attribOffset += sizeof(float) * 3;
attribNumber++;
}
// Texture coordinates
if (layoutDetail == glMeshLoader::VERTEX_LAYOUT_UV) {
glEnableVertexAttribArray(attribNumber);
glVertexAttribPointer(attribNumber, 2, GL_FLOAT, GL_FALSE,
stride, (void *)attribOffset);
attribOffset += sizeof(float) * 2;
attribNumber++;
}
// Color
if (layoutDetail == glMeshLoader::VERTEX_LAYOUT_COLOR) {
glEnableVertexAttribArray(attribNumber);
glVertexAttribPointer(attribNumber, 3, GL_FLOAT, GL_FALSE,
stride, (void *)attribOffset);
attribOffset += sizeof(float) * 3;
attribNumber++;
}
// Tangent
if (layoutDetail == glMeshLoader::VERTEX_LAYOUT_TANGENT) {
glEnableVertexAttribArray(attribNumber);
glVertexAttribPointer(attribNumber, 3, GL_FLOAT, GL_FALSE,
stride, (void *)attribOffset);
attribOffset += sizeof(float) * 3;
attribNumber++;
}
// Bitangent
if (layoutDetail == glMeshLoader::VERTEX_LAYOUT_BITANGENT) {
glEnableVertexAttribArray(attribNumber);
glVertexAttribPointer(attribNumber, 3, GL_FLOAT, GL_FALSE,
stride, (void *)attribOffset);
attribOffset += sizeof(float) * 3;
attribNumber++;
}
// Dummy layout components for padding
if (layoutDetail == glMeshLoader::VERTEX_LAYOUT_DUMMY_FLOAT) {
vertexBuffer.push_back(0.0f);
}
if (layoutDetail == glMeshLoader::VERTEX_LAYOUT_DUMMY_VEC4) {
vertexBuffer.push_back(0.0f);
vertexBuffer.push_back(0.0f);
vertexBuffer.push_back(0.0f);
vertexBuffer.push_back(0.0f);
}
}
// Setup indices
glGenBuffers(1, &meshBuffer.indices.name);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, meshBuffer.indices.name);
// Create the buffer data store and upload the elements.
glBufferData(GL_ELEMENT_ARRAY_BUFFER, meshBuffer.indices.size,
indexBuffer.data(), GL_STATIC_DRAW);
glBindVertexArray(0);
}
};
#endif // !defined(__EMSCRIPTEN__)
@@ -0,0 +1,153 @@
/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2017-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <ktx.h>
#if !defined(GL_COMPRESSED_SRGB_PVRTC_2BPPV1_EXT)
#define GL_COMPRESSED_SRGB_PVRTC_2BPPV1_EXT 0x8A54
#define GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG 0x8C01
#define GL_COMPRESSED_RGBA_PVRTC_2BPPV2_IMG 0x9137
#endif
#if !defined(GL_COMPRESSED_RG_RGTC2)
#define GL_COMPRESSED_RG_RGTC2 0x8DBD
#endif
#if !defined(GL_COMPRESSED_RGBA_BPTC_UNORM)
#define GL_COMPRESSED_RGBA_BPTC_UNORM 0x8E8C
#endif
#if !defined(GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT)
#define GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT 0x8E8E
#endif
class TextureTranscoder {
public:
TextureTranscoder() {
determineCompressedTexFeatures(deviceFeatures);
if (deviceFeatures.astc_ldr)
defaultTf = KTX_TTF_ASTC_4x4_RGBA;
else if (deviceFeatures.bc3)
defaultTf = KTX_TTF_BC1_OR_3;
else if (deviceFeatures.etc2)
defaultTf = KTX_TTF_ETC; // Let transcoder decide RGB or RGBA
else if (deviceFeatures.pvrtc1)
defaultTf = KTX_TTF_PVRTC1_4_RGBA;
else if (deviceFeatures.etc1)
defaultTf = KTX_TTF_ETC1_RGB;
else {
std::stringstream message;
message << "OpenGL implementation does not support any available transcode target.";
throw std::runtime_error(message.str());
}
}
void transcode(ktxTexture2* kTexture,
ktx_transcode_fmt_e otf = KTX_TTF_NOSELECTION) {
KTX_error_code ktxresult;
ktx_transcode_fmt_e tf;
if (otf != KTX_TTF_NOSELECTION) {
tf = otf;
} else {
khr_df_model_e colorModel = ktxTexture2_GetColorModel_e(kTexture);
if (colorModel == KHR_DF_MODEL_UASTC && deviceFeatures.astc_ldr) {
tf = KTX_TTF_ASTC_4x4_RGBA;
} else if (colorModel == KHR_DF_MODEL_ETC1S && deviceFeatures.etc2) {
tf = KTX_TTF_ETC;
} else {
tf = defaultTf;
}
}
ktxresult = ktxTexture2_TranscodeBasis(kTexture, tf, 0);
if (KTX_SUCCESS != ktxresult) {
std::stringstream message;
message << "Transcoding of ktxTexture2 to "
<< ktxTranscodeFormatString(tf) << " failed: "
<< ktxErrorString(ktxresult);
throw std::runtime_error(message.str());
}
}
protected:
ktx_transcode_fmt_e defaultTf;
struct compressedTexFeatures {
bool astc_ldr;
bool astc_hdr;
bool bc6h;
bool bc7;
bool etc1;
bool etc2;
bool bc3;
bool pvrtc1;
bool pvrtc_srgb;
bool pvrtc2;
bool rgtc;
} deviceFeatures;
void determineCompressedTexFeatures(compressedTexFeatures& features) {
ktx_int32_t numCompressedFormats;
memset(&features, false, sizeof(features));
glGetIntegerv(GL_NUM_COMPRESSED_TEXTURE_FORMATS, &numCompressedFormats);
GLint* formats = new GLint[numCompressedFormats];
glGetIntegerv(GL_COMPRESSED_TEXTURE_FORMATS, formats);
for (ktx_int32_t i = 0; i < numCompressedFormats; i++) {
if (formats[i] == GL_COMPRESSED_RGBA8_ETC2_EAC)
features.etc2 = true;
if (formats[i] == GL_ETC1_RGB8_OES)
features.etc1 = true;
if (formats[i] == GL_COMPRESSED_RGBA_S3TC_DXT5_EXT)
features.bc3 = true;
if (formats[i] == GL_COMPRESSED_RG_RGTC2)
features.rgtc = true;
if (formats[i] == GL_COMPRESSED_SRGB_PVRTC_2BPPV1_EXT)
features.pvrtc_srgb = true;
if (formats[i] == GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG)
features.pvrtc1 = true;
if (formats[i] == GL_COMPRESSED_RGBA_PVRTC_2BPPV2_IMG)
features.pvrtc2 = true;
if (formats[i] == GL_COMPRESSED_RGBA_ASTC_4x4_KHR)
features.astc_ldr = true;
if (formats[i] == GL_COMPRESSED_RGBA_BPTC_UNORM)
features.bc7 = true;
if (formats[i] == GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT)
features.bc6h = true;
}
delete[] formats;
// Just in case COMPRESSED_TEXTURE_FORMATS didn't return anything.
// There is no ETC2 extension. It went into core in OpenGL ES 2.0.
// ARB_es_compatibility is not a good indicator. ETC2 could be supported
// by software decompression. Better to report unsupported.
if (!features.etc1 && SDL_GL_ExtensionSupported("GL_OES_compressed_ETC1_RGB8_texture"))
features.etc1 = true;;
if (!features.bc3 && SDL_GL_ExtensionSupported("GL_EXT_texture_compression_s3tc"))
features.bc3 = true;
if (!features.rgtc && SDL_GL_ExtensionSupported("GL_ARB_texture_compression_rgtc"))
features.rgtc = true;
if (!features.pvrtc1 && SDL_GL_ExtensionSupported("GL_IMG_texture_compression_pvrtc"))
features.pvrtc1 = true;
if (!features.pvrtc2 && SDL_GL_ExtensionSupported("GL_IMG_texture_compression_pvrtc2"))
features.pvrtc2 = true;
if (!features.pvrtc_srgb && SDL_GL_ExtensionSupported("GL_EXT_pvrtc_sRGB"))
features.pvrtc_srgb = true;
if (!(features.bc7 && features.bc6h) && SDL_GL_ExtensionSupported("GL_ARB_texture_compression_bptc"))
features.bc6h = features.bc7 = true;
if (!features.astc_ldr && SDL_GL_ExtensionSupported("GL_KHR_texture_compression_astc_ldr"))
features.astc_ldr = true;
// The only way to identify this support is the extension string.
// The format name is the same.
if (SDL_GL_ExtensionSupported("GL_KHR_texture_compression_astc_hdr"))
features.astc_hdr = true;
}
};