/* This file is part of Zutty. * Copyright (C) 2020 Tom Szilagyi * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * See the file LICENSE for the full license. */ #include "charvdev.h" #include "log.h" #include "options.h" #include #include #include namespace { static const char *computeShaderSource = #include "compute.glsl.h" ; static const char *vertexShaderSource = #include "vertex.glsl.h" ; static const char *fragmentShaderSource = #include "fragment.glsl.h" ; GLuint createShader (GLuint type, const char* src, const char* name) { GLint stat; GLuint shader = glCreateShader (type); glShaderSource (shader, 1, &src, nullptr); glCompileShader (shader); glGetShaderiv (shader, GL_COMPILE_STATUS, &stat); if (!stat) { char errlog[1000]; GLsizei len; glGetShaderInfoLog (shader, 1000, &len, errlog); logE << "Compiling " << name << " shader:\n" << errlog << std::endl; exit (1); } return shader; } void linkProgram (GLuint program, const char* name) { GLint stat; glLinkProgram (program); glGetProgramiv (program, GL_LINK_STATUS, &stat); if (!stat) { char errlog[1000]; GLsizei len; glGetProgramInfoLog (program, 1000, &len, errlog); logE << "Linking " << name << " program:\n" << errlog << std::endl; exit (1); } } void setupTexture (GLuint target, GLenum type, GLuint& texture) { if (texture) { glDeleteTextures (1, &texture); } glGenTextures (1, &texture); glActiveTexture (target); glBindTexture (type, texture); glPixelStorei (GL_UNPACK_ALIGNMENT, 1); glTexParameteri (type, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri (type, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri (type, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri (type, GL_TEXTURE_MIN_FILTER, GL_LINEAR); } void setupAtlasTexture (const zutty::Font& fnt, int idx) { glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, // mipmap level, always zero 0, 0, // X and Y offsets into texture area idx, // layer index offset fnt.getPx () * fnt.getNx (), fnt.getPy () * fnt.getNy (), 1, // number of layers, i.e., fonts, loaded GL_RED, GL_UNSIGNED_BYTE, fnt.getAtlasData ()); glCheckError (); } void setupAtlasMappingTexture (const zutty::Font& fnt, GLuint target, GLuint& texture) { auto atlasMap = std::vector (); atlasMap.resize (2 * 256 * 256, 0); // Pre-fill the mapping texture with references to "missing glyph" // and "replacement character" glyphs, if available in the font atlas. const auto itEnd = fnt.getAtlasMap ().end (); zutty::Font::AtlasPos apRC {}; { auto rcIt = fnt.getAtlasMap ().find (zutty::Unicode_Replacement_Character); if (rcIt != itEnd) apRC = rcIt->second; } zutty::Font::AtlasPos apMG {}; { auto mgIt = fnt.getAtlasMap ().find (zutty::Missing_Glyph_Marker); if (mgIt != itEnd) apMG = mgIt->second; } for (int k = 0; k < 256 * 256; ++k) { const auto& apos = ((k >= 0xd800 && k < 0xe000) || k >= 0xfffe) ? apRC : apMG; atlasMap [2 * k] = apos.x; atlasMap [2 * k + 1] = apos.y; } // Fill the mapping texture with supported characters for (auto it = fnt.getAtlasMap ().begin (); it != itEnd; ++it) { atlasMap [2 * it->first] = it->second.x; atlasMap [2 * it->first + 1] = it->second.y; } setupTexture (target, GL_TEXTURE_2D, texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, 256, 256, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, atlasMap.data ()); } template void setupStorageBuffer (GLuint index, GLuint& buffer, uint32_t n_items) { if (buffer) { glDeleteBuffers (1, &buffer); } glGenBuffers (1, &buffer); glBindBuffer (GL_SHADER_STORAGE_BUFFER, buffer); glBindBufferBase (GL_SHADER_STORAGE_BUFFER, index, buffer); std::size_t size = sizeof (T) * n_items; glBufferData (GL_SHADER_STORAGE_BUFFER, size, nullptr, GL_DYNAMIC_DRAW); } } // namespace namespace zutty { CharVdev::CharVdev (Fontpack* fontpk) : px (fontpk->getPx ()) , py (fontpk->getPy ()) { createShaders (); /* * Setup draw program */ glUseProgram (P_draw); glDisable (GL_CULL_FACE); glDisable (GL_DEPTH_TEST); glEnable (GL_BLEND); glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glCheckError (); static const GLfloat verts[4][2] = { { -1, 1 }, { 1, 1 }, { -1, -1 }, { 1, -1 } }; static const GLfloat texCoords[4][2] = { { 0, 0 }, { 1, 0 }, { 0, 1 }, { 1, 1 } }; glVertexAttribPointer (A_pos, 2, GL_FLOAT, GL_FALSE, 0, verts); glVertexAttribPointer (A_vertexTexCoord, 2, GL_FLOAT, GL_FALSE, 0, texCoords); glCheckError (); /* * Setup compute program */ glUseProgram (P_compute); glUniform2i (compU_glyphSize, px, py); glUniform2i (compU_sizeChars, nCols, nRows); glUniform1i (compU_showWraps, opts.showWraps ? 1 : 0); // Setup atlas texture setupTexture (GL_TEXTURE1, GL_TEXTURE_2D_ARRAY, T_atlas); const Font& reg = fontpk->getRegular (); glTexStorage3D (GL_TEXTURE_2D_ARRAY, 1, GL_R8, reg.getPx () * reg.getNx (), reg.getPy () * reg.getNy (), 4); // number of layers glCheckError (); setupAtlasTexture (reg, 0); setupAtlasTexture (fontpk->getBold (), 1); setupAtlasTexture (fontpk->getItalic (), 2); setupAtlasTexture (fontpk->getBoldItalic (), 3); setupAtlasMappingTexture (reg, GL_TEXTURE2, T_atlasMap); glUniform2fv (compU_ulMetrics, 8, fontpk->getUlMetrics ()); // Setup atlas texture for double-width characters if (fontpk->hasDoubleWidth ()) { hasDoubleWidth = true; setupTexture (GL_TEXTURE3, GL_TEXTURE_2D_ARRAY, T_atlas_dw); const Font& dw = fontpk->getDoubleWidth (); glTexStorage3D (GL_TEXTURE_2D_ARRAY, 1, GL_R8, dw.getPx () * dw.getNx (), dw.getPy () * dw.getNy (), 1); // number of layers glCheckError (); setupAtlasTexture (dw, 0); setupAtlasMappingTexture (dw, GL_TEXTURE3, T_atlasMap_dw); } glUniform1i (compU_hasDoubleWidth, hasDoubleWidth ? 1 : 0); // Now that it's all loaded into GL, no need to keep font data in-memory fontpk->releaseFonts (); } CharVdev::~CharVdev () { } bool CharVdev::resize (uint16_t pxWidth_, uint16_t pxHeight_) { assert (cells == nullptr); // no mapping in place if (pxWidth == pxWidth_ && pxHeight == pxHeight_) return false; pxWidth = pxWidth_; pxHeight = pxHeight_; nCols = std::max (1, (pxWidth - 2 * opts.border) / px); nRows = std::max (1, (pxHeight - 2 * opts.border) / py); logI << "Resize to " << pxWidth << " x " << pxHeight << " pixels, " << nCols << " x " << nRows << " chars" << std::endl; GLint viewWidth = nCols * px; GLint viewHeight = nRows * py; glViewport (opts.border, pxHeight - viewHeight - opts.border, viewWidth, viewHeight); glUseProgram (P_draw); glUniform2f (drawU_viewPixels, (GLfloat)viewWidth, (GLfloat)viewHeight); glUseProgram (P_compute); glUniform2i (compU_sizeChars, nCols, nRows); setupTexture (GL_TEXTURE0, GL_TEXTURE_2D, T_output); glTexStorage2D (GL_TEXTURE_2D, 1, GL_RGBA8, viewWidth, viewHeight); glBindImageTexture (0, T_output, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA8); glCheckError (); setupStorageBuffer (0, B_text, nRows * nCols); return true; } void CharVdev::setCursor (const Cursor& cursor) { static uint16_t prevPosX = 0; static uint16_t prevPosY = 0; glUseProgram (P_compute); glUniform3i (compU_cursorColor, cursor.color.red, cursor.color.green, cursor.color.blue); glUniform4i (compU_cursorPos, cursor.posX, cursor.posY, prevPosX, prevPosY); prevPosX = cursor.posX; prevPosY = cursor.posY; glUniform1i (compU_cursorStyle, static_cast (cursor.style)); } void CharVdev::setSelection (const Rect& sel) { static Rect prev; Rect damage (std::min (sel.tl, prev.tl), std::max (sel.br, prev.br)); uint32_t damageStart = nCols * damage.tl.y + damage.tl.x; uint32_t damageEnd = nCols * damage.br.y + damage.br.x + 1; prev = sel; glUseProgram (P_compute); glUniform4i (compU_selectRect, sel.tl.x, sel.tl.y, sel.br.x, sel.br.y); glUniform1i (compU_selectRectMode, static_cast (sel.rectangular)); glUniform2i (compU_selectDamage, damageStart, damageEnd); } void CharVdev::setDeltaFrame (bool delta) { glUseProgram (P_compute); glUniform1i (compU_deltaFrame, delta ? 1 : 0); } void CharVdev::draw () { assert (cells == nullptr); // no mapping in place glUseProgram (P_compute); glActiveTexture (GL_TEXTURE0); glBindTexture (GL_TEXTURE_2D, T_output); glActiveTexture (GL_TEXTURE1); glBindTexture (GL_TEXTURE_2D_ARRAY, T_atlas); glActiveTexture (GL_TEXTURE2); glBindTexture (GL_TEXTURE_2D, T_atlasMap); if (hasDoubleWidth) { glActiveTexture (GL_TEXTURE3); glBindTexture (GL_TEXTURE_2D_ARRAY, T_atlas_dw); glActiveTexture (GL_TEXTURE4); glBindTexture (GL_TEXTURE_2D, T_atlasMap_dw); } glCheckError (); glDispatchCompute (nCols, nRows, 1); glMemoryBarrier (GL_SHADER_IMAGE_ACCESS_BARRIER_BIT); glCheckError (); glUseProgram (P_draw); glClearColor (opts.bg.red / 255.0, opts.bg.green / 255.0, opts.bg.blue / 255.0, 1.0); glClear (GL_COLOR_BUFFER_BIT); glActiveTexture (GL_TEXTURE0); glBindTexture (GL_TEXTURE_2D, T_output); glEnableVertexAttribArray (A_pos); glEnableVertexAttribArray (A_vertexTexCoord); glDrawArrays (GL_TRIANGLE_STRIP, 0, 4); } CharVdev::Mapping::Mapping (uint16_t nCols_, uint16_t nRows_, Cell *& cells_) : nCols (nCols_) , nRows (nRows_) , cells (cells_) { }; CharVdev::Mapping::~Mapping () { assert (cells != nullptr); // mapping in place glUnmapBuffer (GL_SHADER_STORAGE_BUFFER); cells = nullptr; }; CharVdev::Mapping CharVdev::getMapping () { assert (cells == nullptr); // no mapping in place cells = reinterpret_cast ( glMapBufferRange (GL_SHADER_STORAGE_BUFFER, 0, sizeof (Cell) * nRows * nCols, GL_MAP_READ_BIT | GL_MAP_WRITE_BIT)); return CharVdev::Mapping (nCols, nRows, cells); }; // private methods void CharVdev::createShaders () { GLuint S_compute, S_fragment, S_vertex; S_compute = createShader (GL_COMPUTE_SHADER, computeShaderSource, "compute"); S_fragment = createShader (GL_FRAGMENT_SHADER, fragmentShaderSource, "fragment"); S_vertex = createShader (GL_VERTEX_SHADER, vertexShaderSource, "vertex"); P_compute = glCreateProgram (); glAttachShader (P_compute, S_compute); linkProgram (P_compute, "compute"); glUseProgram (P_compute); compU_glyphSize = glGetUniformLocation (P_compute, "glyphSize"); compU_sizeChars = glGetUniformLocation (P_compute, "sizeChars"); compU_ulMetrics = glGetUniformLocation (P_compute, "ulMetrics"); compU_cursorColor = glGetUniformLocation (P_compute, "cursorColor"); compU_cursorPos = glGetUniformLocation (P_compute, "cursorPos"); compU_cursorStyle = glGetUniformLocation (P_compute, "cursorStyle"); compU_selectRect = glGetUniformLocation (P_compute, "selectRect"); compU_selectRectMode = glGetUniformLocation (P_compute, "selectRectMode"); compU_selectDamage = glGetUniformLocation (P_compute, "selectDamage"); compU_deltaFrame = glGetUniformLocation (P_compute, "deltaFrame"); compU_showWraps = glGetUniformLocation (P_compute, "showWraps"); compU_hasDoubleWidth = glGetUniformLocation (P_compute, "hasDoubleWidth"); logT << "compute program:" << " uniform glyphSize=" << compU_glyphSize << " sizeChars=" << compU_sizeChars << " ulMetrics=" << compU_ulMetrics << " cursorColor=" << compU_cursorColor << " cursorPos=" << compU_cursorPos << " cursorStyle=" << compU_cursorStyle << " selectRect=" << compU_selectRect << " selectRectMode=" << compU_selectRectMode << " selectDamage=" << compU_selectDamage << " deltaFrame=" << compU_deltaFrame << " showWraps=" << compU_showWraps << " hasDoubleWidth=" << compU_hasDoubleWidth << std::endl; P_draw = glCreateProgram (); glAttachShader (P_draw, S_fragment); glAttachShader (P_draw, S_vertex); linkProgram (P_draw, "draw"); glUseProgram (P_draw); A_pos = glGetAttribLocation (P_draw, "pos"); A_vertexTexCoord = glGetAttribLocation (P_draw, "vertexTexCoord"); drawU_viewPixels = glGetUniformLocation (P_draw, "viewPixels"); logT << "draw program:" << " attrib pos=" << A_pos << " vertexTexCoord=" << A_vertexTexCoord << " uniform viewPixels=" << drawU_viewPixels << std::endl; } } // namespace zutty