From 8a8a0501590e83cbc598c7491fca0b767094466f Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Mon, 8 Apr 2024 14:22:22 +1000 Subject: 2 features: multi-sample anti-aliasing & Lua patches for images --- source/application/CMakeLists.txt | 4 +- source/application/StarMainApplication_sdl.cpp | 6 +- source/application/StarRenderer.hpp | 1 + source/application/StarRenderer_opengl.cpp | 1051 ++++++++++++++++++++++++ source/application/StarRenderer_opengl.hpp | 252 ++++++ source/application/StarRenderer_opengl20.cpp | 995 ---------------------- source/application/StarRenderer_opengl20.hpp | 239 ------ 7 files changed, 1309 insertions(+), 1239 deletions(-) create mode 100644 source/application/StarRenderer_opengl.cpp create mode 100644 source/application/StarRenderer_opengl.hpp delete mode 100644 source/application/StarRenderer_opengl20.cpp delete mode 100644 source/application/StarRenderer_opengl20.hpp (limited to 'source/application') diff --git a/source/application/CMakeLists.txt b/source/application/CMakeLists.txt index 692f2d5..aacecc3 100644 --- a/source/application/CMakeLists.txt +++ b/source/application/CMakeLists.txt @@ -20,14 +20,14 @@ SET (star_application_SOURCES SET (star_application_HEADERS ${star_application_HEADERS} StarP2PNetworkingService_pc.hpp StarPlatformServices_pc.hpp - StarRenderer_opengl20.hpp + StarRenderer_opengl.hpp ) SET (star_application_SOURCES ${star_application_SOURCES} StarMainApplication_sdl.cpp StarP2PNetworkingService_pc.cpp StarPlatformServices_pc.cpp - StarRenderer_opengl20.cpp + StarRenderer_opengl.cpp ) IF (STAR_ENABLE_STEAM_INTEGRATION) diff --git a/source/application/StarMainApplication_sdl.cpp b/source/application/StarMainApplication_sdl.cpp index 12a03ee..cb631ff 100644 --- a/source/application/StarMainApplication_sdl.cpp +++ b/source/application/StarMainApplication_sdl.cpp @@ -2,7 +2,7 @@ #include "StarLogging.hpp" #include "StarSignalHandler.hpp" #include "StarTickRateMonitor.hpp" -#include "StarRenderer_opengl20.hpp" +#include "StarRenderer_opengl.hpp" #include "StarTtlCache.hpp" #include "StarImage.hpp" #include "StarImageProcessing.hpp" @@ -335,7 +335,7 @@ public: SDL_PauseAudioDevice(m_sdlAudioOutputDevice, 0); } - m_renderer = make_shared(); + m_renderer = make_shared(); m_renderer->setScreenSize(m_windowSize); m_cursorCache.setTimeToLive(30000); @@ -930,7 +930,7 @@ private: bool m_audioEnabled = false; bool m_quitRequested = false; - OpenGl20RendererPtr m_renderer; + OpenGlRendererPtr m_renderer; ApplicationUPtr m_application; PcPlatformServicesUPtr m_platformServices; }; diff --git a/source/application/StarRenderer.hpp b/source/application/StarRenderer.hpp index 77b53bd..a926df0 100644 --- a/source/application/StarRenderer.hpp +++ b/source/application/StarRenderer.hpp @@ -153,6 +153,7 @@ public: TextureFiltering filtering = TextureFiltering::Nearest) = 0; virtual void setSizeLimitEnabled(bool enabled) = 0; virtual void setMultiTexturingEnabled(bool enabled) = 0; + virtual void setMultiSampling(unsigned multiSampling) = 0; virtual TextureGroupPtr createTextureGroup(TextureGroupSize size = TextureGroupSize::Medium, TextureFiltering filtering = TextureFiltering::Nearest) = 0; virtual RenderBufferPtr createRenderBuffer() = 0; diff --git a/source/application/StarRenderer_opengl.cpp b/source/application/StarRenderer_opengl.cpp new file mode 100644 index 0000000..c816049 --- /dev/null +++ b/source/application/StarRenderer_opengl.cpp @@ -0,0 +1,1051 @@ +#include "StarRenderer_opengl.hpp" +#include "StarJsonExtra.hpp" +#include "StarCasting.hpp" +#include "StarLogging.hpp" + +namespace Star { + +size_t const MultiTextureCount = 4; + +char const* DefaultVertexShader = R"SHADER( +#version 130 + +uniform vec2 textureSize0; +uniform vec2 textureSize1; +uniform vec2 textureSize2; +uniform vec2 textureSize3; +uniform vec2 screenSize; +uniform mat3 vertexTransform; + +in vec2 vertexPosition; +in vec4 vertexColor; +in vec2 vertexTextureCoordinate; +in int vertexData; + +out vec2 fragmentTextureCoordinate; +flat out int fragmentTextureIndex; +out 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 (((vertexData >> 3) & 0x1) == 1) + screenPosition.x = round(screenPosition.x); + if (((vertexData >> 4) & 0x1) == 1) + screenPosition.y = round(screenPosition.y); + int vertexTextureIndex = vertexData & 0x3; + if (vertexTextureIndex == 3) + fragmentTextureCoordinate = vertexTextureCoordinate / textureSize3; + else if (vertexTextureIndex == 2) + fragmentTextureCoordinate = vertexTextureCoordinate / textureSize2; + else if (vertexTextureIndex == 1) + fragmentTextureCoordinate = vertexTextureCoordinate / textureSize1; + else + fragmentTextureCoordinate = vertexTextureCoordinate / textureSize0; + + fragmentTextureIndex = vertexTextureIndex; + fragmentColor = vertexColor; +} +)SHADER"; + +char const* DefaultFragmentShader = R"SHADER( +#version 130 + +uniform sampler2D texture0; +uniform sampler2D texture1; +uniform sampler2D texture2; +uniform sampler2D texture3; + +in vec2 fragmentTextureCoordinate; +flat in int fragmentTextureIndex; +in vec4 fragmentColor; + +out vec4 outColor; + +void main() { + vec4 texColor; + if (fragmentTextureIndex == 3) + texColor = texture2D(texture3, fragmentTextureCoordinate); + else if (fragmentTextureIndex == 2) + texColor = texture2D(texture2, fragmentTextureCoordinate); + else if (fragmentTextureIndex == 1) + texColor = texture2D(texture1, fragmentTextureCoordinate); + else + texColor = texture2D(texture0, fragmentTextureCoordinate); + + if (texColor.a <= 0.0) + discard; + + outColor = texColor * fragmentColor; +} +)SHADER"; + +OpenGlRenderer::OpenGlRenderer() { + 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: '{}' vendor: '{}' renderer: '{}' shader: '{}'", + (const char*)glGetString(GL_VERSION), + (const char*)glGetString(GL_VENDOR), + (const char*)glGetString(GL_RENDERER), + (const char*)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(); + + loadEffectConfig("internal", JsonObject(), {{"vertex", DefaultVertexShader}, {"fragment", DefaultFragmentShader}}); + + m_limitTextureGroupSize = false; + m_useMultiTexturing = true; + m_multiSampling = false; + + logGlErrorSummary("OpenGL errors during renderer initialization"); +} + +OpenGlRenderer::~OpenGlRenderer() { + for (auto& effect : m_effects) + glDeleteProgram(effect.second.program); + + m_frameBuffers.clear(); + logGlErrorSummary("OpenGL errors during shutdown"); +} + +String OpenGlRenderer::rendererId() const { + return "OpenGL20"; +} + +Vec2U OpenGlRenderer::screenSize() const { + return m_screenSize; +} + +OpenGlRenderer::GlFrameBuffer::GlFrameBuffer(Json const& fbConfig) : config(fbConfig) { + texture = make_ref(); + texture->textureFiltering = TextureFiltering::Nearest; + texture->textureAddressing = TextureAddressing::Clamp; + texture->textureSize = {0, 0}; + glGenTextures(1, &texture->textureId); + if (texture->textureId == 0) + throw RendererException("Could not generate OpenGL texture for framebuffer"); + + multisample = GLEW_VERSION_4_0 ? config.getUInt("multisample", 0) : 0; + GLenum target = multisample ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D; + glBindTexture(target, texture->glTextureId()); + + Vec2U size = jsonToVec2U(config.getArray("size", { 256, 256 })); + + if (multisample) + glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, multisample, GL_RGBA8, size[0], size[1], GL_TRUE); + else + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, size[0], size[1], 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); + + glGenFramebuffers(1, &id); + if (!id) + throw RendererException("Failed to create OpenGL framebuffer"); + + glBindFramebuffer(GL_FRAMEBUFFER, id); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, texture->glTextureId(), 0); + + auto framebufferStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (framebufferStatus != GL_FRAMEBUFFER_COMPLETE) + throw RendererException("OpenGL framebuffer is not complete!"); +} + + +OpenGlRenderer::GlFrameBuffer::~GlFrameBuffer() { + glDeleteFramebuffers(1, &id); + texture.reset(); +} + +void OpenGlRenderer::loadConfig(Json const& config) { + m_frameBuffers.clear(); + + for (auto& pair : config.getObject("frameBuffers", {})) { + Json config = pair.second; + config = config.set("multisample", m_multiSampling); + m_frameBuffers[pair.first] = make_ref(config); + + } + setScreenSize(m_screenSize); + m_config = config; +} + +void OpenGlRenderer::loadEffectConfig(String const& name, Json const& effectConfig, StringMap const& shaders) { + if (auto effect = m_effects.ptr(name)) { + Logger::info("Reloading OpenGL effect {}", name); + glDeleteProgram(effect->program); + m_effects.erase(name); + } + + GLint status = 0; + char logBuffer[1024]; + + auto compileShader = [&](GLenum type, String const& name) -> GLuint { + GLuint shader = glCreateShader(type); + auto* source = shaders.ptr(name); + if (!source) + return 0; + char const* sourcePtr = source->utf8Ptr(); + glShaderSource(shader, 1, &sourcePtr, NULL); + glCompileShader(shader); + + glGetShaderiv(shader, GL_COMPILE_STATUS, &status); + if (!status) { + glGetShaderInfoLog(shader, sizeof(logBuffer), NULL, logBuffer); + throw RendererException(strf("Failed to compile {} shader: {}\n", name, logBuffer)); + } + + return shader; + }; + + GLuint vertexShader = 0, fragmentShader = 0; + try { + vertexShader = compileShader(GL_VERTEX_SHADER, "vertex"); + fragmentShader = compileShader(GL_FRAGMENT_SHADER, "fragment"); + } + catch (RendererException const& e) { + Logger::error("Shader compile error, using default: {}", e.what()); + if (vertexShader) glDeleteShader(vertexShader); + if (fragmentShader) glDeleteShader(fragmentShader); + vertexShader = compileShader(GL_VERTEX_SHADER, DefaultVertexShader); + fragmentShader = compileShader(GL_FRAGMENT_SHADER, DefaultFragmentShader); + } + + GLuint program = glCreateProgram(); + + if (vertexShader) + glAttachShader(program, vertexShader); + if (fragmentShader) + glAttachShader(program, fragmentShader); + glLinkProgram(program); + + if (vertexShader) + glDeleteShader(vertexShader); + if (fragmentShader) + 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: {}\n", logBuffer)); + } + + glUseProgram(m_program = program); + + auto& effect = m_effects.emplace(name, Effect()).first->second; + effect.program = m_program; + effect.config = effectConfig; + m_currentEffect = &effect; + setupGlUniforms(effect); + + 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 '{}' has no associated uniform, skipping", p.first); + } else { + String type = p.second.getString("type"); + if (type == "bool") { + effectParameter.parameterType = RenderEffectParameter::typeIndexOf(); + } else if (type == "int") { + effectParameter.parameterType = RenderEffectParameter::typeIndexOf(); + } else if (type == "float") { + effectParameter.parameterType = RenderEffectParameter::typeIndexOf(); + } else if (type == "vec2") { + effectParameter.parameterType = RenderEffectParameter::typeIndexOf(); + } else if (type == "vec3") { + effectParameter.parameterType = RenderEffectParameter::typeIndexOf(); + } else if (type == "vec4") { + effectParameter.parameterType = RenderEffectParameter::typeIndexOf(); + } else { + throw RendererException::format("Unrecognized effect parameter type '{}'", type); + } + + effect.parameters[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)); + } + } + } + } + + // 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 '{}' 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 '{}' has textureSizeUniform '{}' with no associated uniform", p.first, *tsu); + } + + effect.textures[p.first] = effectTexture; + } + } + + if (DebugEnabled) + logGlErrorSummary("OpenGL errors setting effect config"); +} + +void OpenGlRenderer::setEffectParameter(String const& parameterName, RenderEffectParameter const& value) { + auto ptr = m_currentEffect->parameters.ptr(parameterName); + if (!ptr || (ptr->parameterValue && *ptr->parameterValue == value)) + return; + + if (ptr->parameterType != value.typeIndex()) + throw RendererException::format("OpenGlRenderer::setEffectParameter '{}' parameter type mismatch", parameterName); + + flushImmediatePrimitives(); + + if (auto v = value.ptr()) + glUniform1i(ptr->parameterUniform, *v); + else if (auto v = value.ptr()) + glUniform1i(ptr->parameterUniform, *v); + else if (auto v = value.ptr()) + glUniform1f(ptr->parameterUniform, *v); + else if (auto v = value.ptr()) + glUniform2f(ptr->parameterUniform, (*v)[0], (*v)[1]); + else if (auto v = value.ptr()) + glUniform3f(ptr->parameterUniform, (*v)[0], (*v)[1], (*v)[2]); + else if (auto v = value.ptr()) + glUniform4f(ptr->parameterUniform, (*v)[0], (*v)[1], (*v)[2], (*v)[3]); + + ptr->parameterValue = value; +} + +void OpenGlRenderer::setEffectTexture(String const& textureName, ImageView const& image) { + auto ptr = m_currentEffect->textures.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.format, image.size, image.data); + } + + if (ptr->textureSizeUniform != -1) { + auto textureSize = ptr->textureValue->glTextureSize(); + glUniform2f(ptr->textureSizeUniform, textureSize[0], textureSize[1]); + } +} + +bool OpenGlRenderer::switchEffectConfig(String const& name) { + flushImmediatePrimitives(); + auto find = m_effects.find(name); + if (find == m_effects.end()) + return false; + + Effect& effect = find->second; + if (m_currentEffect == &effect) + return true; + + if (auto blitFrameBufferId = effect.config.optString("blitFrameBuffer")) + blitGlFrameBuffer(getGlFrameBuffer(*blitFrameBufferId)); + + if (auto frameBufferId = effect.config.optString("frameBuffer")) + switchGlFrameBuffer(getGlFrameBuffer(*frameBufferId)); + else { + m_currentFrameBuffer.reset(); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + } + + glUseProgram(m_program = effect.program); + setupGlUniforms(effect); + m_currentEffect = &effect; + + return true; +} + +void OpenGlRenderer::setScissorRect(Maybe 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 OpenGlRenderer::createTexture(Image const& texture, TextureAddressing addressing, TextureFiltering filtering) { + return createGlTexture(texture, addressing, filtering); +} + +void OpenGlRenderer::setSizeLimitEnabled(bool enabled) { + m_limitTextureGroupSize = enabled; +} + +void OpenGlRenderer::setMultiTexturingEnabled(bool enabled) { + m_useMultiTexturing = enabled; +} + +void OpenGlRenderer::setMultiSampling(unsigned multiSampling) { + if (m_multiSampling == multiSampling) + return; + + m_multiSampling = multiSampling; + if (m_multiSampling) { + glEnable(GL_MULTISAMPLE); + glEnable(GL_SAMPLE_SHADING); + glMinSampleShading((float)m_multiSampling); + } else { + glMinSampleShading(1.f); + glDisable(GL_SAMPLE_SHADING); + glDisable(GL_MULTISAMPLE); + } + loadConfig(m_config); +} + +TextureGroupPtr OpenGlRenderer::createTextureGroup(TextureGroupSize textureSize, TextureFiltering filtering) { + int maxTextureSize; + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize); + maxTextureSize = min(maxTextureSize, (2 << 14)); + // 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 {}, using atlasNumCells {}", maxTextureSize, atlasNumCells); + + auto glTextureGroup = make_shared(atlasNumCells); + glTextureGroup->textureAtlasSet.textureFiltering = filtering; + m_liveTextureGroups.append(glTextureGroup); + return glTextureGroup; +} + +RenderBufferPtr OpenGlRenderer::createRenderBuffer() { + return createGlRenderBuffer(); +} + +List& OpenGlRenderer::immediatePrimitives() { + return m_immediatePrimitives; +} + +void OpenGlRenderer::render(RenderPrimitive primitive) { + m_immediatePrimitives.append(std::move(primitive)); +} + +void OpenGlRenderer::renderBuffer(RenderBufferPtr const& renderBuffer, Mat3F const& transformation) { + flushImmediatePrimitives(); + renderGlBuffer(*convert(renderBuffer.get()), transformation); +} + +void OpenGlRenderer::flush() { + flushImmediatePrimitives(); +} + +void OpenGlRenderer::setScreenSize(Vec2U screenSize) { + m_screenSize = screenSize; + glViewport(0, 0, m_screenSize[0], m_screenSize[1]); + glUniform2f(m_screenSizeUniform, m_screenSize[0], m_screenSize[1]); + + for (auto& frameBuffer : m_frameBuffers) { + if (unsigned multisample = frameBuffer.second->multisample) { + glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, frameBuffer.second->texture->glTextureId()); + glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, multisample, GL_RGBA8, m_screenSize[0], m_screenSize[1], GL_TRUE); + } else { + glBindTexture(GL_TEXTURE_2D, frameBuffer.second->texture->glTextureId()); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, m_screenSize[0], m_screenSize[1], 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); + } + } +} + +void OpenGlRenderer::startFrame() { + if (m_scissorRect) + glDisable(GL_SCISSOR_TEST); + + for (auto& frameBuffer : m_frameBuffers) { + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, frameBuffer.second->id); + glClear(GL_COLOR_BUFFER_BIT); + frameBuffer.second->blitted = false; + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + glClear(GL_COLOR_BUFFER_BIT); + + if (m_scissorRect) + glEnable(GL_SCISSOR_TEST); +} + +void OpenGlRenderer::finishFrame() { + flushImmediatePrimitives(); + // Make sure that the immediate render buffer doesn't needlessly lock texutres + // from being compressed. + List empty; + m_immediateRenderBuffer->set(empty); + + 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; + }); + + // Blit if another shader hasn't + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + if (DebugEnabled) + logGlErrorSummary("OpenGL errors this frame"); +} + +OpenGlRenderer::GlTextureAtlasSet::GlTextureAtlasSet(unsigned atlasNumCells) + : TextureAtlasSet(16, atlasNumCells) {} + +GLuint OpenGlRenderer::GlTextureAtlasSet::createAtlasTexture(Vec2U const& size, PixelFormat pixelFormat) { + GLuint glTextureId; + glGenTextures(1, &glTextureId); + if (glTextureId == 0) + throw RendererException("Could not generate texture in OpenGlRenderer::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 OpenGlRenderer::GlTextureAtlasSet::destroyAtlasTexture(GLuint const& glTexture) { + glDeleteTextures(1, &glTexture); +} + +void OpenGlRenderer::GlTextureAtlasSet::copyAtlasPixels( + GLuint const& glTexture, Vec2U const& bottomLeft, Image const& image) { + glBindTexture(GL_TEXTURE_2D, glTexture); + + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + GLenum format; + auto pixelFormat = image.pixelFormat(); + if (pixelFormat == PixelFormat::RGB24) + format = GL_RGB; + else if (pixelFormat == PixelFormat::RGBA32) + format = GL_RGBA; + else if (pixelFormat == PixelFormat::BGR24) + format = GL_BGR; + else if (pixelFormat == PixelFormat::BGRA32) + format = GL_BGRA; + else + throw RendererException("Unsupported texture format in OpenGlRenderer::TextureGroup::copyAtlasPixels"); + + glTexSubImage2D(GL_TEXTURE_2D, 0, bottomLeft[0], bottomLeft[1], image.width(), image.height(), format, GL_UNSIGNED_BYTE, image.data()); +} + +OpenGlRenderer::GlTextureGroup::GlTextureGroup(unsigned atlasNumCells) + : textureAtlasSet(atlasNumCells) {} + +OpenGlRenderer::GlTextureGroup::~GlTextureGroup() { + textureAtlasSet.reset(); +} + +TextureFiltering OpenGlRenderer::GlTextureGroup::filtering() const { + return textureAtlasSet.textureFiltering; +} + +TexturePtr OpenGlRenderer::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->parentGroup = shared_from_this(); + glGroupedTexture->parentAtlasTexture = textureAtlasSet.addTexture(texture); + + return glGroupedTexture; +} + +OpenGlRenderer::GlGroupedTexture::~GlGroupedTexture() { + if (parentAtlasTexture) + parentGroup->textureAtlasSet.freeTexture(parentAtlasTexture); +} + +Vec2U OpenGlRenderer::GlGroupedTexture::size() const { + return parentAtlasTexture->imageSize(); +} + +TextureFiltering OpenGlRenderer::GlGroupedTexture::filtering() const { + return parentGroup->filtering(); +} + +TextureAddressing OpenGlRenderer::GlGroupedTexture::addressing() const { + return TextureAddressing::Clamp; +} + +GLuint OpenGlRenderer::GlGroupedTexture::glTextureId() const { + return parentAtlasTexture->atlasTexture(); +} + +Vec2U OpenGlRenderer::GlGroupedTexture::glTextureSize() const { + return parentGroup->textureAtlasSet.atlasTextureSize(); +} + +Vec2U OpenGlRenderer::GlGroupedTexture::glTextureCoordinateOffset() const { + return parentAtlasTexture->atlasTextureCoordinates().min(); +} + +void OpenGlRenderer::GlGroupedTexture::incrementBufferUseCount() { + if (bufferUseCount == 0) + parentAtlasTexture->setLocked(true); + ++bufferUseCount; +} + +void OpenGlRenderer::GlGroupedTexture::decrementBufferUseCount() { + starAssert(bufferUseCount != 0); + if (bufferUseCount == 1) + parentAtlasTexture->setLocked(false); + --bufferUseCount; +} + +OpenGlRenderer::GlLoneTexture::~GlLoneTexture() { + if (textureId != 0) + glDeleteTextures(1, &textureId); +} + +Vec2U OpenGlRenderer::GlLoneTexture::size() const { + return textureSize; +} + +TextureFiltering OpenGlRenderer::GlLoneTexture::filtering() const { + return textureFiltering; +} + +TextureAddressing OpenGlRenderer::GlLoneTexture::addressing() const { + return textureAddressing; +} + +GLuint OpenGlRenderer::GlLoneTexture::glTextureId() const { + return textureId; +} + +Vec2U OpenGlRenderer::GlLoneTexture::glTextureSize() const { + return textureSize; +} + +Vec2U OpenGlRenderer::GlLoneTexture::glTextureCoordinateOffset() const { + return Vec2U(); +} + +OpenGlRenderer::GlRenderBuffer::~GlRenderBuffer() { + for (auto const& texture : usedTextures) { + if (auto gt = as(texture.get())) + gt->decrementBufferUseCount(); + } + for (auto const& vb : vertexBuffers) + glDeleteBuffers(1, &vb.vertexBuffer); +} + +void OpenGlRenderer::GlRenderBuffer::set(List& primitives) { + for (auto const& texture : usedTextures) { + if (auto gt = as(texture.get())) + gt->decrementBufferUseCount(); + } + usedTextures.clear(); + + auto oldVertexBuffers = take(vertexBuffers); + + List currentTextures; + List 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.emplace_back(std::move(vb)); + + currentTextures.clear(); + currentTextureSizes.clear(); + accumulationBuffer.clear(); + currentVertexCount = 0; + } + }; + + auto textureCount = useMultiTexturing ? MultiTextureCount : 1; + auto addCurrentTexture = [&](TexturePtr texture) -> pair { + if (!texture) + texture = whiteTexture; + + auto glTexture = as(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(texture.get())) + gt->incrementBufferUseCount(); + usedTextures.add(std::move(texture)); + + return {float(textureIndex), Vec2F(glTexture->glTextureCoordinateOffset())}; + }; + + auto appendBufferVertex = [&](RenderVertex const& v, uint8_t textureIndex, Vec2F textureCoordinateOffset, RenderVertex const& prev, RenderVertex const& next) { + size_t off = accumulationBuffer.size(); + accumulationBuffer.resize(accumulationBuffer.size() + sizeof(GlRenderVertex)); + GlRenderVertex& glv = *(GlRenderVertex*)(accumulationBuffer.ptr() + off); + glv.pos = v.screenCoordinate; + glv.uv = v.textureCoordinate + textureCoordinateOffset; + glv.color = v.color; + glv.pack.vars.textureIndex = textureIndex; + glv.pack.vars.fullbright = v.param1 > 0.0f; + // Tell the vertex shader to round to the nearest pixel if the vertices form a straight + // edge, to ensure sharpness with supersampling. If we rounded *all* vertex positions, + // it'd cause slight visual issues with sprites rotating around a point. + glv.pack.vars.rX = min(abs(glv.pos.x() - prev.screenCoordinate.x()), abs(glv.pos.x() - next.screenCoordinate.x())) < 0.001f; + glv.pack.vars.rY = min(abs(glv.pos.y() - prev.screenCoordinate.y()), abs(glv.pos.y() - next.screenCoordinate.y())) < 0.001f; + glv.pack.vars.unused = 0; + ++currentVertexCount; + return glv; + }; + + uint8_t textureIndex = 0; + Vec2F textureOffset = {}; + for (auto& primitive : primitives) { + if (auto tri = primitive.ptr()) { + tie(textureIndex, textureOffset) = addCurrentTexture(std::move(tri->texture)); + + appendBufferVertex(tri->a, textureIndex, textureOffset, tri->c, tri->b); + appendBufferVertex(tri->b, textureIndex, textureOffset, tri->a, tri->c); + appendBufferVertex(tri->c, textureIndex, textureOffset, tri->b, tri->a); + + } else if (auto quad = primitive.ptr()) { + tie(textureIndex, textureOffset) = addCurrentTexture(std::move(quad->texture)); + + // = prev and next are altered - the diagonal across the quad is bad for the rounding check + appendBufferVertex(quad->a, textureIndex, textureOffset, quad->d, quad->b); + appendBufferVertex(quad->b, textureIndex, textureOffset, quad->a, quad->c); // + appendBufferVertex(quad->c, textureIndex, textureOffset, quad->b, quad->d); + + appendBufferVertex(quad->a, textureIndex, textureOffset, quad->d, quad->b); + appendBufferVertex(quad->c, textureIndex, textureOffset, quad->b, quad->d); // + appendBufferVertex(quad->d, textureIndex, textureOffset, quad->c, quad->a); + + } else if (auto poly = primitive.ptr()) { + if (poly->vertexes.size() > 2) { + tie(textureIndex, textureOffset) = addCurrentTexture(std::move(poly->texture)); + + for (size_t i = 1; i < poly->vertexes.size() - 1; ++i) { + RenderVertex const& a = poly->vertexes[0], + b = poly->vertexes[i], + c = poly->vertexes[i + 1]; + appendBufferVertex(a, textureIndex, textureOffset, c, b); + appendBufferVertex(b, textureIndex, textureOffset, a, c); + appendBufferVertex(c, textureIndex, textureOffset, b, a); + } + } + } + } + + vertexBuffers.reserve(primitives.size() * 6); + finishCurrentBuffer(); + + for (auto const& vb : oldVertexBuffers) + glDeleteBuffers(1, &vb.vertexBuffer); +} + +bool OpenGlRenderer::logGlErrorSummary(String prefix) { + if (GLenum error = glGetError()) { + Logger::error("{}: ", prefix); + do { + if (error == GL_INVALID_ENUM) { + Logger::error("GL_INVALID_ENUM"); + } else if (error == GL_INVALID_VALUE) { + Logger::error("GL_INVALID_VALUE"); + } else if (error == GL_INVALID_OPERATION) { + Logger::error("GL_INVALID_OPERATION"); + } else if (error == GL_INVALID_FRAMEBUFFER_OPERATION) { + Logger::error("GL_INVALID_FRAMEBUFFER_OPERATION"); + } else if (error == GL_OUT_OF_MEMORY) { + Logger::error("GL_OUT_OF_MEMORY"); + } else if (error == GL_STACK_UNDERFLOW) { + Logger::error("GL_STACK_UNDERFLOW"); + } else if (error == GL_STACK_OVERFLOW) { + Logger::error("GL_STACK_OVERFLOW"); + } else { + Logger::error(""); + } + } while ((error = glGetError())); + return true; + } + return false; +} + +void OpenGlRenderer::uploadTextureImage(PixelFormat pixelFormat, Vec2U size, uint8_t const* data) { + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + Maybe internalFormat; + GLenum format; + GLenum type = GL_UNSIGNED_BYTE; + if (pixelFormat == PixelFormat::RGB24) + format = GL_RGB; + else if (pixelFormat == PixelFormat::RGBA32) + format = GL_RGBA; + else if (pixelFormat == PixelFormat::BGR24) + format = GL_BGR; + else if (pixelFormat == PixelFormat::BGRA32) + format = GL_BGRA; + else { + type = GL_FLOAT; + if (pixelFormat == PixelFormat::RGB_F) { + internalFormat = GL_RGB32F; + format = GL_RGB; + } else if (pixelFormat == PixelFormat::RGBA_F) { + internalFormat = GL_RGBA32F; + format = GL_RGBA; + } else + throw RendererException("Unsupported texture format in OpenGlRenderer::uploadTextureImage"); + } + + glTexImage2D(GL_TEXTURE_2D, 0, internalFormat.value(format), size[0], size[1], 0, format, type, data); +} + +void OpenGlRenderer::flushImmediatePrimitives() { + if (m_immediatePrimitives.empty()) + return; + + m_immediateRenderBuffer->set(m_immediatePrimitives); + m_immediatePrimitives.resize(0); + renderGlBuffer(*m_immediateRenderBuffer, Mat3F::identity()); +} + +auto OpenGlRenderer::createGlTexture(ImageView const& image, TextureAddressing addressing, TextureFiltering filtering) + ->RefPtr { + auto glLoneTexture = make_ref(); + glLoneTexture->textureFiltering = filtering; + glLoneTexture->textureAddressing = addressing; + glLoneTexture->textureSize = image.size; + + glGenTextures(1, &glLoneTexture->textureId); + if (glLoneTexture->textureId == 0) + throw RendererException("Could not generate texture in OpenGlRenderer::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); + } + + + if (!image.empty()) + uploadTextureImage(image.format, image.size, image.data); + + return glLoneTexture; +} + +auto OpenGlRenderer::createGlRenderBuffer() -> shared_ptr { + auto glrb = make_shared(); + glrb->whiteTexture = m_whiteTexture; + glrb->useMultiTexturing = m_useMultiTexturing; + return glrb; +} + +void OpenGlRenderer::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_currentEffect->textures) { + 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_colorAttribute); + glEnableVertexAttribArray(m_dataAttribute); + + glVertexAttribPointer(m_positionAttribute, 2, GL_FLOAT, GL_FALSE, sizeof(GlRenderVertex), (GLvoid*)offsetof(GlRenderVertex, pos)); + glVertexAttribPointer(m_texCoordAttribute, 2, GL_FLOAT, GL_FALSE, sizeof(GlRenderVertex), (GLvoid*)offsetof(GlRenderVertex, uv)); + glVertexAttribPointer(m_colorAttribute, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(GlRenderVertex), (GLvoid*)offsetof(GlRenderVertex, color)); + glVertexAttribIPointer(m_dataAttribute, 1, GL_INT, sizeof(GlRenderVertex), (GLvoid*)offsetof(GlRenderVertex, pack)); + + glDrawArrays(GL_TRIANGLES, 0, vb.vertexCount); + } +} + +//Assumes the passed effect program is currently in use. +void OpenGlRenderer::setupGlUniforms(Effect& effect) { + m_positionAttribute = effect.getAttribute("vertexPosition"); + m_colorAttribute = effect.getAttribute("vertexColor"); + m_texCoordAttribute = effect.getAttribute("vertexTextureCoordinate"); + m_dataAttribute = effect.getAttribute("vertexData"); + + m_textureUniforms.clear(); + m_textureSizeUniforms.clear(); + for (size_t i = 0; i < MultiTextureCount; ++i) { + m_textureUniforms.append(effect.getUniform(strf("texture{}", i).c_str())); + m_textureSizeUniforms.append(effect.getUniform(strf("textureSize{}", i).c_str())); + } + m_screenSizeUniform = effect.getUniform("screenSize"); + m_vertexTransformUniform = effect.getUniform("vertexTransform"); + + for (size_t i = 0; i < MultiTextureCount; ++i) + glUniform1i(m_textureUniforms[i], i); + + glUniform2f(m_screenSizeUniform, m_screenSize[0], m_screenSize[1]); +} + +RefPtr OpenGlRenderer::getGlFrameBuffer(String const& id) { + if (auto ptr = m_frameBuffers.ptr(id)) + return *ptr; + else + throw RendererException::format("Frame buffer '{}' does not exist", id); +} + +void OpenGlRenderer::blitGlFrameBuffer(RefPtr const& frameBuffer) { + if (frameBuffer->blitted) + return; + + auto& size = m_screenSize; + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + glBindFramebuffer(GL_READ_FRAMEBUFFER, frameBuffer->id); + glBlitFramebuffer( + 0, 0, size[0], size[1], + 0, 0, size[0], size[1], + GL_COLOR_BUFFER_BIT, GL_NEAREST + ); + + frameBuffer->blitted = true; +} + +void OpenGlRenderer::switchGlFrameBuffer(RefPtr const& frameBuffer) { + if (m_currentFrameBuffer == frameBuffer) + return; + + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, frameBuffer->id); + m_currentFrameBuffer = frameBuffer; +} + +GLuint OpenGlRenderer::Effect::getAttribute(String const& name) { + auto find = attributes.find(name); + if (find == attributes.end()) { + GLuint attrib = glGetAttribLocation(program, name.utf8Ptr()); + attributes[name] = attrib; + return attrib; + } + return find->second; +} + +GLuint OpenGlRenderer::Effect::getUniform(String const& name) { + auto find = uniforms.find(name); + if (find == uniforms.end()) { + GLuint uniform = glGetUniformLocation(program, name.utf8Ptr()); + uniforms[name] = uniform; + return uniform; + } + return find->second; +} + + +} diff --git a/source/application/StarRenderer_opengl.hpp b/source/application/StarRenderer_opengl.hpp new file mode 100644 index 0000000..563ac96 --- /dev/null +++ b/source/application/StarRenderer_opengl.hpp @@ -0,0 +1,252 @@ +#pragma once + +#include "StarTextureAtlas.hpp" +#include "StarRenderer.hpp" + +#include "GL/glew.h" + +namespace Star { + +STAR_CLASS(OpenGlRenderer); + +constexpr size_t FrameBufferCount = 1; + +// OpenGL 2.0 implementation of Renderer. OpenGL context must be created and +// active during construction, destruction, and all method calls. +class OpenGlRenderer : public Renderer { +public: + OpenGlRenderer(); + ~OpenGlRenderer(); + + String rendererId() const override; + Vec2U screenSize() const override; + + void loadConfig(Json const& config) override; + void loadEffectConfig(String const& name, Json const& effectConfig, StringMap const& shaders) override; + + void setEffectParameter(String const& parameterName, RenderEffectParameter const& parameter) override; + void setEffectTexture(String const& textureName, ImageView const& image) override; + + void setScissorRect(Maybe const& scissorRect) override; + + bool switchEffectConfig(String const& name) override; + + TexturePtr createTexture(Image const& texture, TextureAddressing addressing, TextureFiltering filtering) override; + void setSizeLimitEnabled(bool enabled) override; + void setMultiTexturingEnabled(bool enabled) override; + void setMultiSampling(unsigned multiSampling) override; + TextureGroupPtr createTextureGroup(TextureGroupSize size, TextureFiltering filtering) override; + RenderBufferPtr createRenderBuffer() override; + + List& immediatePrimitives() override; + void render(RenderPrimitive primitive) override; + void renderBuffer(RenderBufferPtr const& renderBuffer, Mat3F const& transformation) override; + + void flush() override; + + void setScreenSize(Vec2U screenSize); + + void startFrame(); + void finishFrame(); + +private: + struct GlTextureAtlasSet : public TextureAtlasSet { + public: + GlTextureAtlasSet(unsigned atlasNumCells); + + GLuint createAtlasTexture(Vec2U const& size, PixelFormat pixelFormat) override; + void destroyAtlasTexture(GLuint const& glTexture) override; + void copyAtlasPixels(GLuint const& glTexture, Vec2U const& bottomLeft, Image const& image) override; + + TextureFiltering textureFiltering; + }; + + struct GlTextureGroup : enable_shared_from_this, public TextureGroup { + GlTextureGroup(unsigned atlasNumCells); + ~GlTextureGroup(); + + TextureFiltering filtering() const override; + TexturePtr create(Image const& texture) override; + + GlTextureAtlasSet textureAtlasSet; + }; + + struct GlTexture : public Texture { + virtual GLuint glTextureId() const = 0; + virtual Vec2U glTextureSize() const = 0; + virtual Vec2U glTextureCoordinateOffset() const = 0; + }; + + struct GlGroupedTexture : public GlTexture { + ~GlGroupedTexture(); + + Vec2U size() const override; + TextureFiltering filtering() const override; + TextureAddressing addressing() const override; + + GLuint glTextureId() const override; + Vec2U glTextureSize() const override; + Vec2U glTextureCoordinateOffset() const override; + + void incrementBufferUseCount(); + void decrementBufferUseCount(); + + unsigned bufferUseCount = 0; + shared_ptr parentGroup; + GlTextureAtlasSet::TextureHandle parentAtlasTexture = nullptr; + }; + + struct GlLoneTexture : public GlTexture { + ~GlLoneTexture(); + + Vec2U size() const override; + TextureFiltering filtering() const override; + TextureAddressing addressing() const override; + + GLuint glTextureId() const override; + Vec2U glTextureSize() const override; + Vec2U glTextureCoordinateOffset() const override; + + GLuint textureId = 0; + Vec2U textureSize; + TextureAddressing textureAddressing = TextureAddressing::Clamp; + TextureFiltering textureFiltering = TextureFiltering::Nearest; + }; + + struct GlPackedVertexData { + uint32_t textureIndex : 2; + uint32_t fullbright : 1; + uint32_t rX : 1; + uint32_t rY : 1; + uint32_t unused : 27; + }; + + struct GlRenderVertex { + Vec2F pos; + Vec2F uv; + Vec4B color; + union Packed { + uint32_t packed; + GlPackedVertexData vars; + } pack; + }; + + struct GlRenderBuffer : public RenderBuffer { + struct GlVertexBufferTexture { + GLuint texture; + Vec2U size; + }; + + struct GlVertexBuffer { + List textures; + GLuint vertexBuffer = 0; + size_t vertexCount = 0; + }; + + ~GlRenderBuffer(); + + void set(List& primitives) override; + + RefPtr whiteTexture; + ByteArray accumulationBuffer; + + HashSet usedTextures; + List vertexBuffers; + + bool useMultiTexturing{true}; + }; + + struct EffectParameter { + GLint parameterUniform = -1; + VariantTypeIndex parameterType = 0; + Maybe parameterValue; + }; + + struct EffectTexture { + GLint textureUniform = -1; + unsigned textureUnit = 0; + TextureAddressing textureAddressing = TextureAddressing::Clamp; + TextureFiltering textureFiltering = TextureFiltering::Linear; + GLint textureSizeUniform = -1; + RefPtr textureValue; + }; + + struct GlFrameBuffer : RefCounter { + GLuint id = 0; + RefPtr texture; + + Json config; + bool blitted = false; + unsigned multisample = 0; + + GlFrameBuffer(Json const& config); + ~GlFrameBuffer(); + }; + + class Effect { + public: + GLuint program = 0; + Json config; + StringMap parameters; + StringMap textures; + + StringMap attributes; + StringMap uniforms; + + GLuint getAttribute(String const& name); + GLuint getUniform(String const& name); + }; + + static bool logGlErrorSummary(String prefix); + static void uploadTextureImage(PixelFormat pixelFormat, Vec2U size, uint8_t const* data); + + + static RefPtr createGlTexture(ImageView const& image, TextureAddressing addressing, TextureFiltering filtering); + + shared_ptr createGlRenderBuffer(); + + void flushImmediatePrimitives(); + + void renderGlBuffer(GlRenderBuffer const& renderBuffer, Mat3F const& transformation); + + void setupGlUniforms(Effect& effect); + + RefPtr getGlFrameBuffer(String const& id); + void blitGlFrameBuffer(RefPtr const& frameBuffer); + void switchGlFrameBuffer(RefPtr const& frameBuffer); + + Vec2U m_screenSize; + + GLuint m_program = 0; + + GLint m_positionAttribute = -1; + GLint m_colorAttribute = -1; + GLint m_texCoordAttribute = -1; + GLint m_dataAttribute = -1; + List m_textureUniforms = {}; + List m_textureSizeUniforms = {}; + GLint m_screenSizeUniform = -1; + GLint m_vertexTransformUniform = -1; + + Json m_config; + + StringMap m_effects; + Effect* m_currentEffect; + + StringMap> m_frameBuffers; + RefPtr m_currentFrameBuffer; + + RefPtr m_whiteTexture; + + Maybe m_scissorRect; + + bool m_limitTextureGroupSize; + bool m_useMultiTexturing; + unsigned m_multiSampling; // if non-zero, is enabled and acts as sample count + List> m_liveTextureGroups; + + List m_immediatePrimitives; + shared_ptr m_immediateRenderBuffer; +}; + +} diff --git a/source/application/StarRenderer_opengl20.cpp b/source/application/StarRenderer_opengl20.cpp deleted file mode 100644 index ca29a70..0000000 --- a/source/application/StarRenderer_opengl20.cpp +++ /dev/null @@ -1,995 +0,0 @@ -#include "StarRenderer_opengl20.hpp" -#include "StarJsonExtra.hpp" -#include "StarCasting.hpp" -#include "StarLogging.hpp" - -namespace Star { - -size_t const MultiTextureCount = 4; - -char const* DefaultVertexShader = R"SHADER( -#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; -} -)SHADER"; - -char const* DefaultFragmentShader = R"SHADER( -#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; - } -} -)SHADER"; - -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: '{}' vendor: '{}' renderer: '{}' shader: '{}'", - (const char*)glGetString(GL_VERSION), - (const char*)glGetString(GL_VENDOR), - (const char*)glGetString(GL_RENDERER), - (const char*)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(); - - loadEffectConfig("internal", JsonObject(), {{"vertex", DefaultVertexShader}, {"fragment", DefaultFragmentShader}}); - - m_limitTextureGroupSize = false; - m_useMultiTexturing = true; - - logGlErrorSummary("OpenGL errors during renderer initialization"); -} - -OpenGl20Renderer::~OpenGl20Renderer() { - for (auto& effect : m_effects) - glDeleteProgram(effect.second.program); - - m_frameBuffers.clear(); - logGlErrorSummary("OpenGL errors during shutdown"); -} - -String OpenGl20Renderer::rendererId() const { - return "OpenGL20"; -} - -Vec2U OpenGl20Renderer::screenSize() const { - return m_screenSize; -} - -OpenGl20Renderer::GlFrameBuffer::GlFrameBuffer(Json const& fbConfig) : config(fbConfig) { - texture = createGlTexture(ImageView(), TextureAddressing::Clamp, TextureFiltering::Nearest); - glBindTexture(GL_TEXTURE_2D, texture->glTextureId()); - - Vec2U size = jsonToVec2U(config.getArray("size", { 256, 256 })); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, size[0] , size[1], 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); - - glGenFramebuffers(1, &id); - if (!id) - throw RendererException("Failed to create OpenGL framebuffer"); - - glBindFramebuffer(GL_FRAMEBUFFER, id); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture->glTextureId(), 0); - - auto framebufferStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER); - if (framebufferStatus != GL_FRAMEBUFFER_COMPLETE) - throw RendererException("OpenGL framebuffer is not complete!"); -} - - -OpenGl20Renderer::GlFrameBuffer::~GlFrameBuffer() { - glDeleteFramebuffers(1, &id); - texture.reset(); -} - -void OpenGl20Renderer::loadConfig(Json const& config) { - m_frameBuffers.clear(); - - for (auto& pair : config.getObject("frameBuffers", {})) - m_frameBuffers[pair.first] = make_ref(pair.second); - - setScreenSize(m_screenSize); -} - -void OpenGl20Renderer::loadEffectConfig(String const& name, Json const& effectConfig, StringMap const& shaders) { - if (auto effect = m_effects.ptr(name)) { - Logger::info("Reloading OpenGL effect {}", name); - glDeleteProgram(effect->program); - m_effects.erase(name); - } - - GLint status = 0; - char logBuffer[1024]; - - auto compileShader = [&](GLenum type, String const& name) -> GLuint { - GLuint shader = glCreateShader(type); - auto* source = shaders.ptr(name); - if (!source) - return 0; - char const* sourcePtr = source->utf8Ptr(); - glShaderSource(shader, 1, &sourcePtr, NULL); - glCompileShader(shader); - - glGetShaderiv(shader, GL_COMPILE_STATUS, &status); - if (!status) { - glGetShaderInfoLog(shader, sizeof(logBuffer), NULL, logBuffer); - throw RendererException(strf("Failed to compile {} shader: {}\n", name, logBuffer)); - } - - return shader; - }; - - GLuint vertexShader = 0, fragmentShader = 0; - try { - vertexShader = compileShader(GL_VERTEX_SHADER, "vertex"); - fragmentShader = compileShader(GL_FRAGMENT_SHADER, "fragment"); - } - catch (RendererException const& e) { - Logger::error("Shader compile error, using default: {}", e.what()); - if (vertexShader) glDeleteShader(vertexShader); - if (fragmentShader) glDeleteShader(fragmentShader); - vertexShader = compileShader(GL_VERTEX_SHADER, DefaultVertexShader); - fragmentShader = compileShader(GL_FRAGMENT_SHADER, DefaultFragmentShader); - } - - GLuint program = glCreateProgram(); - - if (vertexShader) - glAttachShader(program, vertexShader); - if (fragmentShader) - glAttachShader(program, fragmentShader); - glLinkProgram(program); - - if (vertexShader) - glDeleteShader(vertexShader); - if (fragmentShader) - 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: {}\n", logBuffer)); - } - - glUseProgram(m_program = program); - - auto& effect = m_effects.emplace(name, Effect()).first->second; - effect.program = m_program; - effect.config = effectConfig; - m_currentEffect = &effect; - setupGlUniforms(effect); - - 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 '{}' has no associated uniform, skipping", p.first); - } else { - String type = p.second.getString("type"); - if (type == "bool") { - effectParameter.parameterType = RenderEffectParameter::typeIndexOf(); - } else if (type == "int") { - effectParameter.parameterType = RenderEffectParameter::typeIndexOf(); - } else if (type == "float") { - effectParameter.parameterType = RenderEffectParameter::typeIndexOf(); - } else if (type == "vec2") { - effectParameter.parameterType = RenderEffectParameter::typeIndexOf(); - } else if (type == "vec3") { - effectParameter.parameterType = RenderEffectParameter::typeIndexOf(); - } else if (type == "vec4") { - effectParameter.parameterType = RenderEffectParameter::typeIndexOf(); - } else { - throw RendererException::format("Unrecognized effect parameter type '{}'", type); - } - - effect.parameters[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)); - } - } - } - } - - // 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 '{}' 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 '{}' has textureSizeUniform '{}' with no associated uniform", p.first, *tsu); - } - - effect.textures[p.first] = effectTexture; - } - } - - if (DebugEnabled) - logGlErrorSummary("OpenGL errors setting effect config"); -} - -void OpenGl20Renderer::setEffectParameter(String const& parameterName, RenderEffectParameter const& value) { - auto ptr = m_currentEffect->parameters.ptr(parameterName); - if (!ptr || (ptr->parameterValue && *ptr->parameterValue == value)) - return; - - if (ptr->parameterType != value.typeIndex()) - throw RendererException::format("OpenGL20Renderer::setEffectParameter '{}' parameter type mismatch", parameterName); - - flushImmediatePrimitives(); - - if (auto v = value.ptr()) - glUniform1i(ptr->parameterUniform, *v); - else if (auto v = value.ptr()) - glUniform1i(ptr->parameterUniform, *v); - else if (auto v = value.ptr()) - glUniform1f(ptr->parameterUniform, *v); - else if (auto v = value.ptr()) - glUniform2f(ptr->parameterUniform, (*v)[0], (*v)[1]); - else if (auto v = value.ptr()) - glUniform3f(ptr->parameterUniform, (*v)[0], (*v)[1], (*v)[2]); - else if (auto v = value.ptr()) - glUniform4f(ptr->parameterUniform, (*v)[0], (*v)[1], (*v)[2], (*v)[3]); - - ptr->parameterValue = value; -} - -void OpenGl20Renderer::setEffectTexture(String const& textureName, ImageView const& image) { - auto ptr = m_currentEffect->textures.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.format, image.size, image.data); - } - - if (ptr->textureSizeUniform != -1) { - auto textureSize = ptr->textureValue->glTextureSize(); - glUniform2f(ptr->textureSizeUniform, textureSize[0], textureSize[1]); - } -} - -bool OpenGl20Renderer::switchEffectConfig(String const& name) { - flushImmediatePrimitives(); - auto find = m_effects.find(name); - if (find == m_effects.end()) - return false; - - Effect& effect = find->second; - if (m_currentEffect == &effect) - return true; - - if (auto blitFrameBufferId = effect.config.optString("blitFrameBuffer")) - blitGlFrameBuffer(getGlFrameBuffer(*blitFrameBufferId)); - - if (auto frameBufferId = effect.config.optString("frameBuffer")) - switchGlFrameBuffer(getGlFrameBuffer(*frameBufferId)); - else { - m_currentFrameBuffer.reset(); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - } - - glUseProgram(m_program = effect.program); - setupGlUniforms(effect); - m_currentEffect = &effect; - - return true; -} - -void OpenGl20Renderer::setScissorRect(Maybe 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 {}, using atlasNumCells {}", maxTextureSize, atlasNumCells); - - auto glTextureGroup = make_shared(atlasNumCells); - glTextureGroup->textureAtlasSet.textureFiltering = filtering; - m_liveTextureGroups.append(glTextureGroup); - return glTextureGroup; -} - -RenderBufferPtr OpenGl20Renderer::createRenderBuffer() { - return createGlRenderBuffer(); -} - -List& OpenGl20Renderer::immediatePrimitives() { - return m_immediatePrimitives; -} - -void OpenGl20Renderer::render(RenderPrimitive primitive) { - m_immediatePrimitives.append(std::move(primitive)); -} - -void OpenGl20Renderer::renderBuffer(RenderBufferPtr const& renderBuffer, Mat3F const& transformation) { - flushImmediatePrimitives(); - renderGlBuffer(*convert(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]); - - for (auto& frameBuffer : m_frameBuffers) { - glBindTexture(GL_TEXTURE_2D, frameBuffer.second->texture->glTextureId()); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, m_screenSize[0], m_screenSize[1], 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); - } -} - -void OpenGl20Renderer::startFrame() { - if (m_scissorRect) - glDisable(GL_SCISSOR_TEST); - - for (auto& frameBuffer : m_frameBuffers) { - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, frameBuffer.second->id); - glClear(GL_COLOR_BUFFER_BIT); - frameBuffer.second->blitted = false; - } - - glBindFramebuffer(GL_FRAMEBUFFER, 0); - - 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. - List empty; - m_immediateRenderBuffer->set(empty); - - 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; - }); - - // Blit if another shader hasn't - glBindFramebuffer(GL_FRAMEBUFFER, 0); - - 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); - GLenum format; - auto pixelFormat = image.pixelFormat(); - if (pixelFormat == PixelFormat::RGB24) - format = GL_RGB; - else if (pixelFormat == PixelFormat::RGBA32) - format = GL_RGBA; - else if (pixelFormat == PixelFormat::BGR24) - format = GL_BGR; - else if (pixelFormat == PixelFormat::BGRA32) - format = GL_BGRA; - else - throw RendererException("Unsupported texture format in OpenGL20Renderer::TextureGroup::copyAtlasPixels"); - - glTexSubImage2D(GL_TEXTURE_2D, 0, bottomLeft[0], bottomLeft[1], image.width(), image.height(), format, GL_UNSIGNED_BYTE, image.data()); -} - -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->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(texture.get())) - gt->decrementBufferUseCount(); - } - for (auto const& vb : vertexBuffers) - glDeleteBuffers(1, &vb.vertexBuffer); -} - -void OpenGl20Renderer::GlRenderBuffer::set(List& primitives) { - for (auto const& texture : usedTextures) { - if (auto gt = as(texture.get())) - gt->decrementBufferUseCount(); - } - usedTextures.clear(); - - auto oldVertexBuffers = take(vertexBuffers); - - List currentTextures; - List 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.emplace_back(std::move(vb)); - - currentTextures.clear(); - currentTextureSizes.clear(); - accumulationBuffer.clear(); - currentVertexCount = 0; - } - }; - - auto textureCount = useMultiTexturing ? MultiTextureCount : 1; - auto addCurrentTexture = [&](TexturePtr texture) -> pair { - if (!texture) - texture = whiteTexture; - - auto glTexture = as(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(texture.get())) - gt->incrementBufferUseCount(); - usedTextures.add(std::move(texture)); - - return {float(textureIndex), Vec2F(glTexture->glTextureCoordinateOffset())}; - }; - - auto appendBufferVertex = [&](RenderVertex const& 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; - }; - - float textureIndex = 0.0f; - Vec2F textureOffset = {}; - for (auto& primitive : primitives) { - if (auto tri = primitive.ptr()) { - tie(textureIndex, textureOffset) = addCurrentTexture(std::move(tri->texture)); - - appendBufferVertex(tri->a, textureIndex, textureOffset); - appendBufferVertex(tri->b, textureIndex, textureOffset); - appendBufferVertex(tri->c, textureIndex, textureOffset); - - } else if (auto quad = primitive.ptr()) { - tie(textureIndex, textureOffset) = addCurrentTexture(std::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()) { - if (poly->vertexes.size() > 2) { - tie(textureIndex, textureOffset) = addCurrentTexture(std::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); - } - } - } - } - - vertexBuffers.reserve(primitives.size() * 6); - finishCurrentBuffer(); - - for (auto const& vb : oldVertexBuffers) - glDeleteBuffers(1, &vb.vertexBuffer); -} - -bool OpenGl20Renderer::logGlErrorSummary(String prefix) { - if (GLenum error = glGetError()) { - Logger::error("{}: ", prefix); - do { - if (error == GL_INVALID_ENUM) { - Logger::error("GL_INVALID_ENUM"); - } else if (error == GL_INVALID_VALUE) { - Logger::error("GL_INVALID_VALUE"); - } else if (error == GL_INVALID_OPERATION) { - Logger::error("GL_INVALID_OPERATION"); - } else if (error == GL_INVALID_FRAMEBUFFER_OPERATION) { - Logger::error("GL_INVALID_FRAMEBUFFER_OPERATION"); - } else if (error == GL_OUT_OF_MEMORY) { - Logger::error("GL_OUT_OF_MEMORY"); - } else if (error == GL_STACK_UNDERFLOW) { - Logger::error("GL_STACK_UNDERFLOW"); - } else if (error == GL_STACK_OVERFLOW) { - Logger::error("GL_STACK_OVERFLOW"); - } else { - Logger::error(""); - } - } while ((error = glGetError())); - return true; - } - return false; -} - -void OpenGl20Renderer::uploadTextureImage(PixelFormat pixelFormat, Vec2U size, uint8_t const* data) { - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - - Maybe internalFormat; - GLenum format; - GLenum type = GL_UNSIGNED_BYTE; - if (pixelFormat == PixelFormat::RGB24) - format = GL_RGB; - else if (pixelFormat == PixelFormat::RGBA32) - format = GL_RGBA; - else if (pixelFormat == PixelFormat::BGR24) - format = GL_BGR; - else if (pixelFormat == PixelFormat::BGRA32) - format = GL_BGRA; - else { - type = GL_FLOAT; - if (pixelFormat == PixelFormat::RGB_F) { - internalFormat = GL_RGB32F; - format = GL_RGB; - } else if (pixelFormat == PixelFormat::RGBA_F) { - internalFormat = GL_RGBA32F; - format = GL_RGBA; - } else - throw RendererException("Unsupported texture format in OpenGL20Renderer::uploadTextureImage"); - } - - glTexImage2D(GL_TEXTURE_2D, 0, internalFormat.value(format), size[0], size[1], 0, format, type, data); -} - -void OpenGl20Renderer::flushImmediatePrimitives() { - if (m_immediatePrimitives.empty()) - return; - - m_immediateRenderBuffer->set(m_immediatePrimitives); - m_immediatePrimitives.resize(0); - renderGlBuffer(*m_immediateRenderBuffer, Mat3F::identity()); -} - -auto OpenGl20Renderer::createGlTexture(ImageView const& image, TextureAddressing addressing, TextureFiltering filtering) - ->RefPtr { - auto glLoneTexture = make_ref(); - glLoneTexture->textureFiltering = filtering; - glLoneTexture->textureAddressing = addressing; - glLoneTexture->textureSize = image.size; - - 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); - } - - - if (!image.empty()) - uploadTextureImage(image.format, image.size, image.data); - - return glLoneTexture; -} - -auto OpenGl20Renderer::createGlRenderBuffer() -> shared_ptr { - auto glrb = make_shared(); - 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_currentEffect->textures) { - 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); - - 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)); - - if (m_param1Attribute != -1) { - glEnableVertexAttribArray(m_param1Attribute); - glVertexAttribPointer(m_param1Attribute, 1, GL_FLOAT, GL_FALSE, sizeof(GlRenderVertex), (GLvoid*)offsetof(GlRenderVertex, param1)); - } - - glDrawArrays(GL_TRIANGLES, 0, vb.vertexCount); - } -} - -//Assumes the passed effect program is currently in use. -void OpenGl20Renderer::setupGlUniforms(Effect& effect) { - m_positionAttribute = effect.getAttribute("vertexPosition"); - m_texCoordAttribute = effect.getAttribute("vertexTextureCoordinate"); - m_texIndexAttribute = effect.getAttribute("vertexTextureIndex"); - m_colorAttribute = effect.getAttribute("vertexColor"); - m_param1Attribute = effect.getAttribute("vertexParam1"); - - m_textureUniforms.clear(); - m_textureSizeUniforms.clear(); - for (size_t i = 0; i < MultiTextureCount; ++i) { - m_textureUniforms.append(effect.getUniform(strf("texture{}", i).c_str())); - m_textureSizeUniforms.append(effect.getUniform(strf("textureSize{}", i).c_str())); - } - m_screenSizeUniform = effect.getUniform("screenSize"); - m_vertexTransformUniform = effect.getUniform("vertexTransform"); - - for (size_t i = 0; i < MultiTextureCount; ++i) - glUniform1i(m_textureUniforms[i], i); - - glUniform2f(m_screenSizeUniform, m_screenSize[0], m_screenSize[1]); -} - -RefPtr OpenGl20Renderer::getGlFrameBuffer(String const& id) { - if (auto ptr = m_frameBuffers.ptr(id)) - return *ptr; - else - throw RendererException::format("Frame buffer '{}' does not exist", id); -} - -void OpenGl20Renderer::blitGlFrameBuffer(RefPtr const& frameBuffer) { - if (frameBuffer->blitted) - return; - - auto& size = m_screenSize; - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - glBindFramebuffer(GL_READ_FRAMEBUFFER, frameBuffer->id); - glBlitFramebuffer( - 0, 0, size[0], size[1], - 0, 0, size[0], size[1], - GL_COLOR_BUFFER_BIT, GL_NEAREST - ); - - frameBuffer->blitted = true; -} - -void OpenGl20Renderer::switchGlFrameBuffer(RefPtr const& frameBuffer) { - if (m_currentFrameBuffer == frameBuffer) - return; - - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, frameBuffer->id); - m_currentFrameBuffer = frameBuffer; -} - -GLuint OpenGl20Renderer::Effect::getAttribute(String const& name) { - auto find = attributes.find(name); - if (find == attributes.end()) { - GLuint attrib = glGetAttribLocation(program, name.utf8Ptr()); - attributes[name] = attrib; - return attrib; - } - return find->second; -} - -GLuint OpenGl20Renderer::Effect::getUniform(String const& name) { - auto find = uniforms.find(name); - if (find == uniforms.end()) { - GLuint uniform = glGetUniformLocation(program, name.utf8Ptr()); - uniforms[name] = uniform; - return uniform; - } - return find->second; -} - - -} diff --git a/source/application/StarRenderer_opengl20.hpp b/source/application/StarRenderer_opengl20.hpp deleted file mode 100644 index fa406b6..0000000 --- a/source/application/StarRenderer_opengl20.hpp +++ /dev/null @@ -1,239 +0,0 @@ -#pragma once - -#include "StarTextureAtlas.hpp" -#include "StarRenderer.hpp" - -#include "GL/glew.h" - -namespace Star { - -STAR_CLASS(OpenGl20Renderer); - -constexpr size_t FrameBufferCount = 1; - -// OpenGL 2.0 implementation of Renderer. OpenGL context must be created and -// active during construction, destruction, and all method calls. -class OpenGl20Renderer : public Renderer { -public: - OpenGl20Renderer(); - ~OpenGl20Renderer(); - - String rendererId() const override; - Vec2U screenSize() const override; - - void loadConfig(Json const& config) override; - void loadEffectConfig(String const& name, Json const& effectConfig, StringMap const& shaders) override; - - void setEffectParameter(String const& parameterName, RenderEffectParameter const& parameter) override; - void setEffectTexture(String const& textureName, ImageView const& image) override; - - void setScissorRect(Maybe const& scissorRect) override; - - bool switchEffectConfig(String const& name) override; - - TexturePtr createTexture(Image const& texture, TextureAddressing addressing, TextureFiltering filtering) override; - void setSizeLimitEnabled(bool enabled) override; - void setMultiTexturingEnabled(bool enabled) override; - TextureGroupPtr createTextureGroup(TextureGroupSize size, TextureFiltering filtering) override; - RenderBufferPtr createRenderBuffer() override; - - List& immediatePrimitives() override; - void render(RenderPrimitive primitive) override; - void renderBuffer(RenderBufferPtr const& renderBuffer, Mat3F const& transformation) override; - - void flush() override; - - void setScreenSize(Vec2U screenSize); - - void startFrame(); - void finishFrame(); - -private: - struct GlTextureAtlasSet : public TextureAtlasSet { - public: - GlTextureAtlasSet(unsigned atlasNumCells); - - GLuint createAtlasTexture(Vec2U const& size, PixelFormat pixelFormat) override; - void destroyAtlasTexture(GLuint const& glTexture) override; - void copyAtlasPixels(GLuint const& glTexture, Vec2U const& bottomLeft, Image const& image) override; - - TextureFiltering textureFiltering; - }; - - struct GlTextureGroup : enable_shared_from_this, public TextureGroup { - GlTextureGroup(unsigned atlasNumCells); - ~GlTextureGroup(); - - TextureFiltering filtering() const override; - TexturePtr create(Image const& texture) override; - - GlTextureAtlasSet textureAtlasSet; - }; - - struct GlTexture : public Texture { - virtual GLuint glTextureId() const = 0; - virtual Vec2U glTextureSize() const = 0; - virtual Vec2U glTextureCoordinateOffset() const = 0; - }; - - struct GlGroupedTexture : public GlTexture { - ~GlGroupedTexture(); - - Vec2U size() const override; - TextureFiltering filtering() const override; - TextureAddressing addressing() const override; - - GLuint glTextureId() const override; - Vec2U glTextureSize() const override; - Vec2U glTextureCoordinateOffset() const override; - - void incrementBufferUseCount(); - void decrementBufferUseCount(); - - unsigned bufferUseCount = 0; - shared_ptr parentGroup; - GlTextureAtlasSet::TextureHandle parentAtlasTexture = nullptr; - }; - - struct GlLoneTexture : public GlTexture { - ~GlLoneTexture(); - - Vec2U size() const override; - TextureFiltering filtering() const override; - TextureAddressing addressing() const override; - - GLuint glTextureId() const override; - Vec2U glTextureSize() const override; - Vec2U glTextureCoordinateOffset() const override; - - GLuint textureId = 0; - Vec2U textureSize; - TextureAddressing textureAddressing = TextureAddressing::Clamp; - TextureFiltering textureFiltering = TextureFiltering::Nearest; - }; - - struct GlRenderVertex { - Vec2F screenCoordinate; - Vec2F textureCoordinate; - float textureIndex; - Vec4B color; - float param1; - }; - - struct GlRenderBuffer : public RenderBuffer { - struct GlVertexBufferTexture { - GLuint texture; - Vec2U size; - }; - - struct GlVertexBuffer { - List textures; - GLuint vertexBuffer = 0; - size_t vertexCount = 0; - }; - - ~GlRenderBuffer(); - - void set(List& primitives) override; - - RefPtr whiteTexture; - ByteArray accumulationBuffer; - - HashSet usedTextures; - List vertexBuffers; - - bool useMultiTexturing{true}; - }; - - struct EffectParameter { - GLint parameterUniform = -1; - VariantTypeIndex parameterType = 0; - Maybe parameterValue; - }; - - struct EffectTexture { - GLint textureUniform = -1; - unsigned textureUnit = 0; - TextureAddressing textureAddressing = TextureAddressing::Clamp; - TextureFiltering textureFiltering = TextureFiltering::Linear; - GLint textureSizeUniform = -1; - RefPtr textureValue; - }; - - struct GlFrameBuffer : RefCounter { - GLuint id = 0; - RefPtr texture; - - Json config; - bool blitted = false; - - GlFrameBuffer(Json const& config); - ~GlFrameBuffer(); - }; - - class Effect { - public: - GLuint program = 0; - Json config; - StringMap parameters; - StringMap textures; - - StringMap attributes; - StringMap uniforms; - - GLuint getAttribute(String const& name); - GLuint getUniform(String const& name); - }; - - static bool logGlErrorSummary(String prefix); - static void uploadTextureImage(PixelFormat pixelFormat, Vec2U size, uint8_t const* data); - - - static RefPtr createGlTexture(ImageView const& image, TextureAddressing addressing, TextureFiltering filtering); - - shared_ptr createGlRenderBuffer(); - - void flushImmediatePrimitives(); - - void renderGlBuffer(GlRenderBuffer const& renderBuffer, Mat3F const& transformation); - - void setupGlUniforms(Effect& effect); - - RefPtr getGlFrameBuffer(String const& id); - void blitGlFrameBuffer(RefPtr const& frameBuffer); - void switchGlFrameBuffer(RefPtr const& frameBuffer); - - Vec2U m_screenSize; - - GLuint m_program = 0; - - GLint m_positionAttribute = -1; - GLint m_texCoordAttribute = -1; - GLint m_texIndexAttribute = -1; - GLint m_colorAttribute = -1; - GLint m_param1Attribute = -1; - - List m_textureUniforms = {}; - List m_textureSizeUniforms = {}; - GLint m_screenSizeUniform = -1; - GLint m_vertexTransformUniform = -1; - - StringMap m_effects; - Effect* m_currentEffect; - - StringMap> m_frameBuffers; - RefPtr m_currentFrameBuffer; - - RefPtr m_whiteTexture; - - Maybe m_scissorRect; - - bool m_limitTextureGroupSize; - bool m_useMultiTexturing; - List> m_liveTextureGroups; - - List m_immediatePrimitives; - shared_ptr m_immediateRenderBuffer; -}; - -} -- cgit v1.2.3