477 lines
15 KiB
C++
477 lines
15 KiB
C++
/* -*- 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);
|
|
}
|
|
|
|
|