Веб-сайт самохостера Lotigara

summaryrefslogtreecommitdiff
path: root/source/application/StarRenderer_opengl20.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'source/application/StarRenderer_opengl20.cpp')
-rw-r--r--source/application/StarRenderer_opengl20.cpp828
1 files changed, 828 insertions, 0 deletions
diff --git a/source/application/StarRenderer_opengl20.cpp b/source/application/StarRenderer_opengl20.cpp
new file mode 100644
index 0000000..82f9854
--- /dev/null
+++ b/source/application/StarRenderer_opengl20.cpp
@@ -0,0 +1,828 @@
+#include "StarRenderer_opengl20.hpp"
+#include "StarJsonExtra.hpp"
+#include "StarCasting.hpp"
+#include "StarLogging.hpp"
+
+namespace Star {
+
+size_t const MultiTextureCount = 4;
+
+char const* DefaultEffectConfig = R"JSON(
+ {
+ "vertexShader" : "
+ #version 110
+
+ uniform vec2 textureSize0;
+ uniform vec2 textureSize1;
+ uniform vec2 textureSize2;
+ uniform vec2 textureSize3;
+ uniform vec2 screenSize;
+ uniform mat3 vertexTransform;
+
+ attribute vec2 vertexPosition;
+ attribute vec2 vertexTextureCoordinate;
+ attribute float vertexTextureIndex;
+ attribute vec4 vertexColor;
+ attribute float vertexParam1;
+
+ varying vec2 fragmentTextureCoordinate;
+ varying float fragmentTextureIndex;
+ varying vec4 fragmentColor;
+
+ void main() {
+ vec2 screenPosition = (vertexTransform * vec3(vertexPosition, 1.0)).xy;
+ gl_Position = vec4(screenPosition / screenSize * 2.0 - 1.0, 0.0, 1.0);
+ if (vertexTextureIndex > 2.9) {
+ fragmentTextureCoordinate = vertexTextureCoordinate / textureSize3;
+ } else if (vertexTextureIndex > 1.9) {
+ fragmentTextureCoordinate = vertexTextureCoordinate / textureSize2;
+ } else if (vertexTextureIndex > 0.9) {
+ fragmentTextureCoordinate = vertexTextureCoordinate / textureSize1;
+ } else {
+ fragmentTextureCoordinate = vertexTextureCoordinate / textureSize0;
+ }
+ fragmentTextureIndex = vertexTextureIndex;
+ fragmentColor = vertexColor;
+ }
+ ",
+
+ "fragmentShader" : "
+ #version 110
+
+ uniform sampler2D texture0;
+ uniform sampler2D texture1;
+ uniform sampler2D texture2;
+ uniform sampler2D texture3;
+
+ varying vec2 fragmentTextureCoordinate;
+ varying float fragmentTextureIndex;
+ varying vec4 fragmentColor;
+
+ void main() {
+ if (fragmentTextureIndex > 2.9) {
+ gl_FragColor = texture2D(texture3, fragmentTextureCoordinate) * fragmentColor;
+ } else if (fragmentTextureIndex > 1.9) {
+ gl_FragColor = texture2D(texture2, fragmentTextureCoordinate) * fragmentColor;
+ } else if (fragmentTextureIndex > 0.9) {
+ gl_FragColor = texture2D(texture1, fragmentTextureCoordinate) * fragmentColor;
+ } else {
+ gl_FragColor = texture2D(texture0, fragmentTextureCoordinate) * fragmentColor;
+ }
+ }
+ "
+ }
+ )JSON";
+
+OpenGl20Renderer::OpenGl20Renderer() {
+ if (glewInit() != GLEW_OK)
+ throw RendererException("Could not initialize GLEW");
+
+ if (!GLEW_VERSION_2_0)
+ throw RendererException("OpenGL 2.0 not available!");
+
+ Logger::info("OpenGL version: '%s' vendor: '%s' renderer: '%s' shader: '%s'",
+ glGetString(GL_VERSION),
+ glGetString(GL_VENDOR),
+ glGetString(GL_RENDERER),
+ glGetString(GL_SHADING_LANGUAGE_VERSION));
+
+ glClearColor(0.0, 0.0, 0.0, 1.0);
+ glEnable(GL_TEXTURE_2D);
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ glDisable(GL_DEPTH_TEST);
+
+ m_whiteTexture = createGlTexture(Image::filled({1, 1}, Vec4B(255, 255, 255, 255), PixelFormat::RGBA32),
+ TextureAddressing::Clamp,
+ TextureFiltering::Nearest);
+ m_immediateRenderBuffer = createGlRenderBuffer();
+
+ setEffectConfig(Json::parse(DefaultEffectConfig));
+
+ m_limitTextureGroupSize = false;
+ m_useMultiTexturing = true;
+
+ logGlErrorSummary("OpenGL errors during renderer initialization");
+}
+
+OpenGl20Renderer::~OpenGl20Renderer() {
+ glDeleteProgram(m_program);
+
+ logGlErrorSummary("OpenGL errors during shutdown");
+}
+
+String OpenGl20Renderer::rendererId() const {
+ return "OpenGL20";
+}
+
+Vec2U OpenGl20Renderer::screenSize() const {
+ return m_screenSize;
+}
+
+void OpenGl20Renderer::setEffectConfig(Json const& effectConfig) {
+ flushImmediatePrimitives();
+
+ GLint status = 0;
+ char logBuffer[1024];
+
+ GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
+ String vertexSource = effectConfig.getString("vertexShader");
+ char const* vertexSourcePtr = vertexSource.utf8Ptr();
+ glShaderSource(vertexShader, 1, &vertexSourcePtr, NULL);
+ glCompileShader(vertexShader);
+
+ glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &status);
+ if (!status) {
+ glGetShaderInfoLog(vertexShader, sizeof(logBuffer), NULL, logBuffer);
+ throw RendererException(strf("Failed to compile vertex shader: %s\n", logBuffer));
+ }
+
+ GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
+ String fragmentSource = effectConfig.getString("fragmentShader");
+ char const* fragmentSourcePtr = fragmentSource.utf8Ptr();
+ glShaderSource(fragmentShader, 1, &fragmentSourcePtr, NULL);
+ glCompileShader(fragmentShader);
+
+ glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &status);
+ if (!status) {
+ glGetShaderInfoLog(fragmentShader, sizeof(logBuffer), NULL, logBuffer);
+ throw RendererException(strf("Failed to compile fragment shader: %s\n", logBuffer));
+ }
+
+ GLuint program = glCreateProgram();
+
+ glAttachShader(program, vertexShader);
+ glAttachShader(program, fragmentShader);
+ glLinkProgram(program);
+
+ glDeleteShader(vertexShader);
+ glDeleteShader(fragmentShader);
+
+ glGetProgramiv(program, GL_LINK_STATUS, &status);
+ if (!status) {
+ glGetProgramInfoLog(program, sizeof(logBuffer), NULL, logBuffer);
+ glDeleteProgram(program);
+ throw RendererException(strf("Failed to link program: %s\n", logBuffer));
+ }
+
+ if (m_program != 0)
+ glDeleteProgram(m_program);
+ m_program = program;
+ glUseProgram(m_program);
+
+ m_positionAttribute = glGetAttribLocation(m_program, "vertexPosition");
+ m_texCoordAttribute = glGetAttribLocation(m_program, "vertexTextureCoordinate");
+ m_texIndexAttribute = glGetAttribLocation(m_program, "vertexTextureIndex");
+ m_colorAttribute = glGetAttribLocation(m_program, "vertexColor");
+ m_param1Attribute = glGetAttribLocation(m_program, "vertexParam1");
+
+ m_textureUniforms.clear();
+ m_textureSizeUniforms.clear();
+ for (size_t i = 0; i < MultiTextureCount; ++i) {
+ m_textureUniforms.append(glGetUniformLocation(m_program, strf("texture%s", i).c_str()));
+ m_textureSizeUniforms.append(glGetUniformLocation(m_program, strf("textureSize%s", i).c_str()));
+ }
+ m_screenSizeUniform = glGetUniformLocation(m_program, "screenSize");
+ m_vertexTransformUniform = glGetUniformLocation(m_program, "vertexTransform");
+
+ for (size_t i = 0; i < MultiTextureCount; ++i) {
+ glUniform1i(m_textureUniforms[i], i);
+ }
+ glUniform2f(m_screenSizeUniform, m_screenSize[0], m_screenSize[1]);
+
+ m_effectParameters.clear();
+
+ for (auto const& p : effectConfig.getObject("effectParameters", {})) {
+ EffectParameter effectParameter;
+
+ effectParameter.parameterUniform = glGetUniformLocation(m_program, p.second.getString("uniform").utf8Ptr());
+ if (effectParameter.parameterUniform == -1) {
+ Logger::warn("OpenGL20 effect parameter '%s' has no associated uniform, skipping", p.first);
+ } else {
+ String type = p.second.getString("type");
+ if (type == "bool") {
+ effectParameter.parameterType = RenderEffectParameter::typeIndexOf<bool>();
+ } else if (type == "int") {
+ effectParameter.parameterType = RenderEffectParameter::typeIndexOf<int>();
+ } else if (type == "float") {
+ effectParameter.parameterType = RenderEffectParameter::typeIndexOf<float>();
+ } else if (type == "vec2") {
+ effectParameter.parameterType = RenderEffectParameter::typeIndexOf<Vec2F>();
+ } else if (type == "vec3") {
+ effectParameter.parameterType = RenderEffectParameter::typeIndexOf<Vec3F>();
+ } else if (type == "vec4") {
+ effectParameter.parameterType = RenderEffectParameter::typeIndexOf<Vec4F>();
+ } else {
+ throw RendererException::format("Unrecognized effect parameter type '%s'", type);
+ }
+
+ m_effectParameters[p.first] = effectParameter;
+
+ if (Json def = p.second.get("default", {})) {
+ if (type == "bool") {
+ setEffectParameter(p.first, def.toBool());
+ } else if (type == "int") {
+ setEffectParameter(p.first, (int)def.toInt());
+ } else if (type == "float") {
+ setEffectParameter(p.first, def.toFloat());
+ } else if (type == "vec2") {
+ setEffectParameter(p.first, jsonToVec2F(def));
+ } else if (type == "vec3") {
+ setEffectParameter(p.first, jsonToVec3F(def));
+ } else if (type == "vec4") {
+ setEffectParameter(p.first, jsonToVec4F(def));
+ }
+ }
+ }
+ }
+
+ m_effectTextures.clear();
+
+ // Assign each texture parameter a texture unit starting with MultiTextureCount, the first
+ // few texture units are used by the primary textures being drawn. Currently,
+ // maximum texture units are not checked.
+ unsigned parameterTextureUnit = MultiTextureCount;
+
+ for (auto const& p : effectConfig.getObject("effectTextures", {})) {
+ EffectTexture effectTexture;
+ effectTexture.textureUniform = glGetUniformLocation(m_program, p.second.getString("textureUniform").utf8Ptr());
+ if (effectTexture.textureUniform == -1) {
+ Logger::warn("OpenGL20 effect parameter '%s' has no associated uniform, skipping", p.first);
+ } else {
+ effectTexture.textureUnit = parameterTextureUnit++;
+ glUniform1i(effectTexture.textureUniform, effectTexture.textureUnit);
+
+ effectTexture.textureAddressing = TextureAddressingNames.getLeft(p.second.getString("textureAddressing", "clamp"));
+ effectTexture.textureFiltering = TextureFilteringNames.getLeft(p.second.getString("textureFiltering", "nearest"));
+ if (auto tsu = p.second.optString("textureSizeUniform")) {
+ effectTexture.textureSizeUniform = glGetUniformLocation(m_program, tsu->utf8Ptr());
+ if (effectTexture.textureSizeUniform == -1)
+ Logger::warn("OpenGL20 effect parameter '%s' has textureSizeUniform '%s' with no associated uniform", p.first, *tsu);
+ }
+
+ m_effectTextures[p.first] = effectTexture;
+ }
+ }
+
+ if (DebugEnabled)
+ logGlErrorSummary("OpenGL errors setting effect config");
+}
+
+void OpenGl20Renderer::setEffectParameter(String const& parameterName, RenderEffectParameter const& value) {
+ auto ptr = m_effectParameters.ptr(parameterName);
+ if (!ptr || (ptr->parameterValue && *ptr->parameterValue == value))
+ return;
+
+ if (ptr->parameterType != value.typeIndex())
+ throw RendererException::format("OpenGL20Renderer::setEffectParameter '%s' parameter type mismatch", parameterName);
+
+ flushImmediatePrimitives();
+
+ if (auto v = value.ptr<bool>())
+ glUniform1i(ptr->parameterUniform, *v);
+ else if (auto v = value.ptr<int>())
+ glUniform1i(ptr->parameterUniform, *v);
+ else if (auto v = value.ptr<float>())
+ glUniform1f(ptr->parameterUniform, *v);
+ else if (auto v = value.ptr<Vec2F>())
+ glUniform2f(ptr->parameterUniform, (*v)[0], (*v)[1]);
+ else if (auto v = value.ptr<Vec3F>())
+ glUniform3f(ptr->parameterUniform, (*v)[0], (*v)[1], (*v)[2]);
+ else if (auto v = value.ptr<Vec4F>())
+ glUniform4f(ptr->parameterUniform, (*v)[0], (*v)[1], (*v)[2], (*v)[3]);
+
+ ptr->parameterValue = value;
+}
+
+void OpenGl20Renderer::setEffectTexture(String const& textureName, Image const& image) {
+ auto ptr = m_effectTextures.ptr(textureName);
+ if (!ptr)
+ return;
+
+ flushImmediatePrimitives();
+
+ if (!ptr->textureValue || ptr->textureValue->textureId == 0) {
+ ptr->textureValue = createGlTexture(image, ptr->textureAddressing, ptr->textureFiltering);
+ } else {
+ glBindTexture(GL_TEXTURE_2D, ptr->textureValue->textureId);
+ ptr->textureValue->textureSize = image.size();
+ uploadTextureImage(image.pixelFormat(), image.size(), image.data());
+ }
+
+ if (ptr->textureSizeUniform != -1) {
+ auto textureSize = ptr->textureValue->glTextureSize();
+ glUniform2f(ptr->textureSizeUniform, textureSize[0], textureSize[1]);
+ }
+}
+
+void OpenGl20Renderer::setScissorRect(Maybe<RectI> const& scissorRect) {
+ if (scissorRect == m_scissorRect)
+ return;
+
+ flushImmediatePrimitives();
+
+ m_scissorRect = scissorRect;
+ if (m_scissorRect) {
+ glEnable(GL_SCISSOR_TEST);
+ glScissor(m_scissorRect->xMin(), m_scissorRect->yMin(), m_scissorRect->width(), m_scissorRect->height());
+ } else {
+ glDisable(GL_SCISSOR_TEST);
+ }
+}
+
+TexturePtr OpenGl20Renderer::createTexture(Image const& texture, TextureAddressing addressing, TextureFiltering filtering) {
+ return createGlTexture(texture, addressing, filtering);
+}
+
+void OpenGl20Renderer::setSizeLimitEnabled(bool enabled) {
+ m_limitTextureGroupSize = enabled;
+}
+
+void OpenGl20Renderer::setMultiTexturingEnabled(bool enabled) {
+ m_useMultiTexturing = enabled;
+}
+
+TextureGroupPtr OpenGl20Renderer::createTextureGroup(TextureGroupSize textureSize, TextureFiltering filtering) {
+ int maxTextureSize;
+ glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
+
+ // Large texture sizes are not always supported
+ if (textureSize == TextureGroupSize::Large && (m_limitTextureGroupSize || maxTextureSize < 4096))
+ textureSize = TextureGroupSize::Medium;
+
+ unsigned atlasNumCells;
+ if (textureSize == TextureGroupSize::Large)
+ atlasNumCells = 256;
+ else if (textureSize == TextureGroupSize::Medium)
+ atlasNumCells = 128;
+ else // TextureGroupSize::Small
+ atlasNumCells = 64;
+
+ Logger::info("detected supported OpenGL texture size %s, using atlasNumCells %s", maxTextureSize, atlasNumCells);
+
+ auto glTextureGroup = make_shared<GlTextureGroup>(atlasNumCells);
+ glTextureGroup->textureAtlasSet.textureFiltering = filtering;
+ m_liveTextureGroups.append(glTextureGroup);
+ return glTextureGroup;
+}
+
+RenderBufferPtr OpenGl20Renderer::createRenderBuffer() {
+ return createGlRenderBuffer();
+}
+
+void OpenGl20Renderer::render(RenderPrimitive primitive) {
+ m_immediatePrimitives.append(move(primitive));
+}
+
+void OpenGl20Renderer::renderBuffer(RenderBufferPtr const& renderBuffer, Mat3F const& transformation) {
+ flushImmediatePrimitives();
+ renderGlBuffer(*convert<GlRenderBuffer>(renderBuffer.get()), transformation);
+}
+
+void OpenGl20Renderer::flush() {
+ flushImmediatePrimitives();
+}
+
+void OpenGl20Renderer::setScreenSize(Vec2U screenSize) {
+ m_screenSize = screenSize;
+ glViewport(0, 0, m_screenSize[0], m_screenSize[1]);
+ glUniform2f(m_screenSizeUniform, m_screenSize[0], m_screenSize[1]);
+}
+
+void OpenGl20Renderer::startFrame() {
+ if (m_scissorRect)
+ glDisable(GL_SCISSOR_TEST);
+
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ if (m_scissorRect)
+ glEnable(GL_SCISSOR_TEST);
+}
+
+void OpenGl20Renderer::finishFrame() {
+ flushImmediatePrimitives();
+ // Make sure that the immediate render buffer doesn't needlessly lock texutres
+ // from being compressed.
+ m_immediateRenderBuffer->set({});
+
+ filter(m_liveTextureGroups, [](auto const& p) {
+ unsigned const CompressionsPerFrame = 1;
+
+ if (!p.unique() || p->textureAtlasSet.totalTextures() > 0) {
+ p->textureAtlasSet.compressionPass(CompressionsPerFrame);
+ return true;
+ }
+
+ return false;
+ });
+
+ if (DebugEnabled)
+ logGlErrorSummary("OpenGL errors this frame");
+}
+
+OpenGl20Renderer::GlTextureAtlasSet::GlTextureAtlasSet(unsigned atlasNumCells)
+ : TextureAtlasSet(16, atlasNumCells) {}
+
+GLuint OpenGl20Renderer::GlTextureAtlasSet::createAtlasTexture(Vec2U const& size, PixelFormat pixelFormat) {
+ GLuint glTextureId;
+ glGenTextures(1, &glTextureId);
+ if (glTextureId == 0)
+ throw RendererException("Could not generate texture in OpenGL20Renderer::TextureGroup::createAtlasTexture()");
+
+ glBindTexture(GL_TEXTURE_2D, glTextureId);
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ if (textureFiltering == TextureFiltering::Nearest) {
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ } else {
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ }
+
+ uploadTextureImage(pixelFormat, size, nullptr);
+ return glTextureId;
+}
+
+void OpenGl20Renderer::GlTextureAtlasSet::destroyAtlasTexture(GLuint const& glTexture) {
+ glDeleteTextures(1, &glTexture);
+}
+
+void OpenGl20Renderer::GlTextureAtlasSet::copyAtlasPixels(
+ GLuint const& glTexture, Vec2U const& bottomLeft, Image const& image) {
+ glBindTexture(GL_TEXTURE_2D, glTexture);
+
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+ if (image.pixelFormat() == PixelFormat::RGB24) {
+ glTexSubImage2D(GL_TEXTURE_2D, 0, bottomLeft[0], bottomLeft[1], image.width(), image.height(), GL_RGB, GL_UNSIGNED_BYTE, image.data());
+ } else if (image.pixelFormat() == PixelFormat::RGBA32) {
+ glTexSubImage2D(GL_TEXTURE_2D, 0, bottomLeft[0], bottomLeft[1], image.width(), image.height(), GL_RGBA, GL_UNSIGNED_BYTE, image.data());
+ } else if (image.pixelFormat() == PixelFormat::BGR24) {
+ glTexSubImage2D(GL_TEXTURE_2D, 0, bottomLeft[0], bottomLeft[1], image.width(), image.height(), GL_BGR, GL_UNSIGNED_BYTE, image.data());
+ } else if (image.pixelFormat() == PixelFormat::BGRA32) {
+ glTexSubImage2D(GL_TEXTURE_2D, 0, bottomLeft[0], bottomLeft[1], image.width(), image.height(), GL_BGRA, GL_UNSIGNED_BYTE, image.data());
+ } else {
+ throw RendererException("Unsupported texture format in OpenGL20Renderer::TextureGroup::copyAtlasPixels");
+ }
+}
+
+OpenGl20Renderer::GlTextureGroup::GlTextureGroup(unsigned atlasNumCells)
+ : textureAtlasSet(atlasNumCells) {}
+
+OpenGl20Renderer::GlTextureGroup::~GlTextureGroup() {
+ textureAtlasSet.reset();
+}
+
+TextureFiltering OpenGl20Renderer::GlTextureGroup::filtering() const {
+ return textureAtlasSet.textureFiltering;
+}
+
+TexturePtr OpenGl20Renderer::GlTextureGroup::create(Image const& texture) {
+ // If the image is empty, or would not fit in the texture atlas with border
+ // pixels, just create a regular texture
+ Vec2U atlasTextureSize = textureAtlasSet.atlasTextureSize();
+ if (texture.empty() || texture.width() + 2 > atlasTextureSize[0] || texture.height() + 2 > atlasTextureSize[1])
+ return createGlTexture(texture, TextureAddressing::Clamp, textureAtlasSet.textureFiltering);
+
+ auto glGroupedTexture = make_ref<GlGroupedTexture>();
+ glGroupedTexture->parentGroup = shared_from_this();
+ glGroupedTexture->parentAtlasTexture = textureAtlasSet.addTexture(texture);
+
+ return glGroupedTexture;
+}
+
+OpenGl20Renderer::GlGroupedTexture::~GlGroupedTexture() {
+ if (parentAtlasTexture)
+ parentGroup->textureAtlasSet.freeTexture(parentAtlasTexture);
+}
+
+Vec2U OpenGl20Renderer::GlGroupedTexture::size() const {
+ return parentAtlasTexture->imageSize();
+}
+
+TextureFiltering OpenGl20Renderer::GlGroupedTexture::filtering() const {
+ return parentGroup->filtering();
+}
+
+TextureAddressing OpenGl20Renderer::GlGroupedTexture::addressing() const {
+ return TextureAddressing::Clamp;
+}
+
+GLuint OpenGl20Renderer::GlGroupedTexture::glTextureId() const {
+ return parentAtlasTexture->atlasTexture();
+}
+
+Vec2U OpenGl20Renderer::GlGroupedTexture::glTextureSize() const {
+ return parentGroup->textureAtlasSet.atlasTextureSize();
+}
+
+Vec2U OpenGl20Renderer::GlGroupedTexture::glTextureCoordinateOffset() const {
+ return parentAtlasTexture->atlasTextureCoordinates().min();
+}
+
+void OpenGl20Renderer::GlGroupedTexture::incrementBufferUseCount() {
+ if (bufferUseCount == 0)
+ parentAtlasTexture->setLocked(true);
+ ++bufferUseCount;
+}
+
+void OpenGl20Renderer::GlGroupedTexture::decrementBufferUseCount() {
+ starAssert(bufferUseCount != 0);
+ if (bufferUseCount == 1)
+ parentAtlasTexture->setLocked(false);
+ --bufferUseCount;
+}
+
+OpenGl20Renderer::GlLoneTexture::~GlLoneTexture() {
+ if (textureId != 0)
+ glDeleteTextures(1, &textureId);
+}
+
+Vec2U OpenGl20Renderer::GlLoneTexture::size() const {
+ return textureSize;
+}
+
+TextureFiltering OpenGl20Renderer::GlLoneTexture::filtering() const {
+ return textureFiltering;
+}
+
+TextureAddressing OpenGl20Renderer::GlLoneTexture::addressing() const {
+ return textureAddressing;
+}
+
+GLuint OpenGl20Renderer::GlLoneTexture::glTextureId() const {
+ return textureId;
+}
+
+Vec2U OpenGl20Renderer::GlLoneTexture::glTextureSize() const {
+ return textureSize;
+}
+
+Vec2U OpenGl20Renderer::GlLoneTexture::glTextureCoordinateOffset() const {
+ return Vec2U();
+}
+
+OpenGl20Renderer::GlRenderBuffer::~GlRenderBuffer() {
+ for (auto const& texture : usedTextures) {
+ if (auto gt = as<GlGroupedTexture>(texture.get()))
+ gt->decrementBufferUseCount();
+ }
+ for (auto const& vb : vertexBuffers)
+ glDeleteBuffers(1, &vb.vertexBuffer);
+}
+
+void OpenGl20Renderer::GlRenderBuffer::set(List<RenderPrimitive> primitives) {
+ for (auto const& texture : usedTextures) {
+ if (auto gt = as<GlGroupedTexture>(texture.get()))
+ gt->decrementBufferUseCount();
+ }
+ usedTextures.clear();
+
+ auto oldVertexBuffers = take(vertexBuffers);
+
+ List<GLuint> currentTextures;
+ List<Vec2U> currentTextureSizes;
+ size_t currentVertexCount = 0;
+
+ auto finishCurrentBuffer = [&]() {
+ if (currentVertexCount > 0) {
+ GlVertexBuffer vb;
+ for (size_t i = 0; i < currentTextures.size(); ++i) {
+ vb.textures.append(GlVertexBufferTexture{currentTextures[i], currentTextureSizes[i]});
+ }
+ vb.vertexCount = currentVertexCount;
+ if (!oldVertexBuffers.empty()) {
+ auto oldVb = oldVertexBuffers.takeLast();
+ vb.vertexBuffer = oldVb.vertexBuffer;
+ glBindBuffer(GL_ARRAY_BUFFER, vb.vertexBuffer);
+ if (oldVb.vertexCount >= vb.vertexCount)
+ glBufferSubData(GL_ARRAY_BUFFER, 0, accumulationBuffer.size(), accumulationBuffer.ptr());
+ else
+ glBufferData(GL_ARRAY_BUFFER, accumulationBuffer.size(), accumulationBuffer.ptr(), GL_STREAM_DRAW);
+ } else {
+ glGenBuffers(1, &vb.vertexBuffer);
+ glBindBuffer(GL_ARRAY_BUFFER, vb.vertexBuffer);
+ glBufferData(GL_ARRAY_BUFFER, accumulationBuffer.size(), accumulationBuffer.ptr(), GL_STREAM_DRAW);
+ }
+
+ vertexBuffers.append(vb);
+
+ currentTextures.clear();
+ currentTextureSizes.clear();
+ accumulationBuffer.clear();
+ currentVertexCount = 0;
+ }
+ };
+
+ auto textureCount = useMultiTexturing ? MultiTextureCount : 1;
+ auto addCurrentTexture = [&](TexturePtr texture) -> pair<uint8_t, Vec2F> {
+ if (!texture)
+ texture = whiteTexture;
+
+ auto glTexture = as<GlTexture>(texture.get());
+ GLuint glTextureId = glTexture->glTextureId();
+
+ auto textureIndex = currentTextures.indexOf(glTextureId);
+ if (textureIndex == NPos) {
+ if (currentTextures.size() >= textureCount)
+ finishCurrentBuffer();
+
+ textureIndex = currentTextures.size();
+ currentTextures.append(glTextureId);
+ currentTextureSizes.append(glTexture->glTextureSize());
+ }
+
+ if (auto gt = as<GlGroupedTexture>(texture.get()))
+ gt->incrementBufferUseCount();
+ usedTextures.add(move(texture));
+
+ return {float(textureIndex), Vec2F(glTexture->glTextureCoordinateOffset())};
+ };
+
+ auto appendBufferVertex = [&](RenderVertex v, float textureIndex, Vec2F textureCoordinateOffset) {
+ GlRenderVertex glv {
+ v.screenCoordinate,
+ v.textureCoordinate + textureCoordinateOffset,
+ textureIndex,
+ v.color,
+ v.param1
+ };
+ accumulationBuffer.append((char const*)&glv, sizeof(GlRenderVertex));
+ ++currentVertexCount;
+ };
+
+ for (auto& primitive : primitives) {
+ float textureIndex;
+ Vec2F textureOffset;
+ if (auto tri = primitive.ptr<RenderTriangle>()) {
+ tie(textureIndex, textureOffset) = addCurrentTexture(move(tri->texture));
+
+ appendBufferVertex(tri->a, textureIndex, textureOffset);
+ appendBufferVertex(tri->b, textureIndex, textureOffset);
+ appendBufferVertex(tri->c, textureIndex, textureOffset);
+
+ } else if (auto quad = primitive.ptr<RenderQuad>()) {
+ tie(textureIndex, textureOffset) = addCurrentTexture(move(quad->texture));
+
+ appendBufferVertex(quad->a, textureIndex, textureOffset);
+ appendBufferVertex(quad->b, textureIndex, textureOffset);
+ appendBufferVertex(quad->c, textureIndex, textureOffset);
+
+ appendBufferVertex(quad->a, textureIndex, textureOffset);
+ appendBufferVertex(quad->c, textureIndex, textureOffset);
+ appendBufferVertex(quad->d, textureIndex, textureOffset);
+
+ } else if (auto poly = primitive.ptr<RenderPoly>()) {
+ if (poly->vertexes.size() > 2) {
+ tie(textureIndex, textureOffset) = addCurrentTexture(move(poly->texture));
+ for (size_t i = 1; i < poly->vertexes.size() - 1; ++i) {
+ appendBufferVertex(poly->vertexes[0], textureIndex, textureOffset);
+ appendBufferVertex(poly->vertexes[i], textureIndex, textureOffset);
+ appendBufferVertex(poly->vertexes[i + 1], textureIndex, textureOffset);
+ }
+ }
+ }
+ }
+
+ finishCurrentBuffer();
+
+ for (auto const& vb : oldVertexBuffers)
+ glDeleteBuffers(1, &vb.vertexBuffer);
+}
+
+void OpenGl20Renderer::logGlErrorSummary(String prefix) {
+ List<GLenum> errors;
+ while (GLenum error = glGetError())
+ errors.append(error);
+
+ if (!errors.empty()) {
+ String errorMessage = move(prefix);
+ errorMessage.append(": ");
+ for (auto const& error : errors) {
+ if (error == GL_INVALID_ENUM) {
+ errorMessage += " GL_INVALID_ENUM";
+ } else if (error == GL_INVALID_VALUE) {
+ errorMessage += " GL_INVALID_VALUE";
+ } else if (error == GL_INVALID_OPERATION) {
+ errorMessage += " GL_INVALID_OPERATION";
+ } else if (error == GL_INVALID_FRAMEBUFFER_OPERATION) {
+ errorMessage += " GL_INVALID_FRAMEBUFFER_OPERATION";
+ } else if (error == GL_OUT_OF_MEMORY) {
+ errorMessage += " GL_OUT_OF_MEMORY";
+ } else if (error == GL_STACK_UNDERFLOW) {
+ errorMessage += " GL_STACK_UNDERFLOW";
+ } else if (error == GL_STACK_OVERFLOW) {
+ errorMessage += " GL_STACK_OVERFLOW";
+ } else {
+ errorMessage += " <UNRECOGNIZED GL ERROR>";
+ }
+ }
+ Logger::error(errorMessage.utf8Ptr());
+ }
+}
+
+void OpenGl20Renderer::uploadTextureImage(PixelFormat pixelFormat, Vec2U size, uint8_t const* data) {
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+ if (pixelFormat == PixelFormat::RGB24) {
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, size[0], size[1], 0, GL_RGB, GL_UNSIGNED_BYTE, data);
+ } else if (pixelFormat == PixelFormat::RGBA32) {
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size[0], size[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
+ } else if (pixelFormat == PixelFormat::BGR24) {
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_BGR, size[0], size[1], 0, GL_BGR, GL_UNSIGNED_BYTE, data);
+ } else if (pixelFormat == PixelFormat::BGRA32) {
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_BGRA, size[0], size[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
+ } else {
+ starAssert(false);
+ }
+}
+
+void OpenGl20Renderer::flushImmediatePrimitives() {
+ if (m_immediatePrimitives.empty())
+ return;
+
+ m_immediateRenderBuffer->set(take(m_immediatePrimitives));
+ renderGlBuffer(*m_immediateRenderBuffer, Mat3F::identity());
+}
+
+auto OpenGl20Renderer::createGlTexture(Image const& image, TextureAddressing addressing, TextureFiltering filtering)
+ -> RefPtr<GlLoneTexture> {
+ auto glLoneTexture = make_ref<GlLoneTexture>();
+ glLoneTexture->textureFiltering = filtering;
+ glLoneTexture->textureAddressing = addressing;
+ glLoneTexture->textureSize = image.size();
+
+ if (image.empty())
+ return glLoneTexture;
+
+ glGenTextures(1, &glLoneTexture->textureId);
+ if (glLoneTexture->textureId == 0)
+ throw RendererException("Could not generate texture in OpenGL20Renderer::createGlTexture");
+
+ glBindTexture(GL_TEXTURE_2D, glLoneTexture->textureId);
+
+ if (addressing == TextureAddressing::Clamp) {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ } else {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+ }
+
+ if (filtering == TextureFiltering::Nearest) {
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ } else {
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ }
+
+ uploadTextureImage(image.pixelFormat(), image.size(), image.data());
+
+ return glLoneTexture;
+}
+
+auto OpenGl20Renderer::createGlRenderBuffer() -> shared_ptr<GlRenderBuffer> {
+ auto glrb = make_shared<GlRenderBuffer>();
+ glrb->whiteTexture = m_whiteTexture;
+ glrb->useMultiTexturing = m_useMultiTexturing;
+ return glrb;
+}
+
+void OpenGl20Renderer::renderGlBuffer(GlRenderBuffer const& renderBuffer, Mat3F const& transformation) {
+ for (auto const& vb : renderBuffer.vertexBuffers) {
+ glUniformMatrix3fv(m_vertexTransformUniform, 1, GL_TRUE, transformation.ptr());
+
+ for (size_t i = 0; i < vb.textures.size(); ++i) {
+ glUniform2f(m_textureSizeUniforms[i], vb.textures[i].size[0], vb.textures[i].size[1]);
+ glActiveTexture(GL_TEXTURE0 + i);
+ glBindTexture(GL_TEXTURE_2D, vb.textures[i].texture);
+ }
+
+ for (auto const& p : m_effectTextures) {
+ if (p.second.textureValue) {
+ glActiveTexture(GL_TEXTURE0 + p.second.textureUnit);
+ glBindTexture(GL_TEXTURE_2D, p.second.textureValue->textureId);
+ }
+ }
+
+ glBindBuffer(GL_ARRAY_BUFFER, vb.vertexBuffer);
+
+ glEnableVertexAttribArray(m_positionAttribute);
+ glEnableVertexAttribArray(m_texCoordAttribute);
+ glEnableVertexAttribArray(m_texIndexAttribute);
+ glEnableVertexAttribArray(m_colorAttribute);
+ glEnableVertexAttribArray(m_param1Attribute);
+
+ glVertexAttribPointer(m_positionAttribute, 2, GL_FLOAT, GL_FALSE, sizeof(GlRenderVertex), (GLvoid*)offsetof(GlRenderVertex, screenCoordinate));
+ glVertexAttribPointer(m_texCoordAttribute, 2, GL_FLOAT, GL_FALSE, sizeof(GlRenderVertex), (GLvoid*)offsetof(GlRenderVertex, textureCoordinate));
+ glVertexAttribPointer(m_texIndexAttribute, 1, GL_FLOAT, GL_FALSE, sizeof(GlRenderVertex), (GLvoid*)offsetof(GlRenderVertex, textureIndex));
+ glVertexAttribPointer(m_colorAttribute, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(GlRenderVertex), (GLvoid*)offsetof(GlRenderVertex, color));
+ glVertexAttribPointer(m_param1Attribute, 1, GL_FLOAT, GL_FALSE, sizeof(GlRenderVertex), (GLvoid*)offsetof(GlRenderVertex, param1));
+
+ glDrawArrays(GL_TRIANGLES, 0, vb.vertexCount);
+ }
+}
+
+}