/* -*- 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 #include #include #include #include #include #include #include #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 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(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); }