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,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;
}
}