diff --git a/assets/opensb/interface/graphicsmenu/body.png b/assets/opensb/interface/graphicsmenu/body.png deleted file mode 100644 index e721afa..0000000 Binary files a/assets/opensb/interface/graphicsmenu/body.png and /dev/null differ diff --git a/assets/opensb/interface/graphicsmenu/body.png.patch.lua b/assets/opensb/interface/graphicsmenu/body.png.patch.lua new file mode 100644 index 0000000..0c811cd --- /dev/null +++ b/assets/opensb/interface/graphicsmenu/body.png.patch.lua @@ -0,0 +1,6 @@ +function patch(image) + -- Camera Pan Speed + image:copyInto({119, 68}, image:process("?crop=19;68;117;87")) + -- Anti-Aliasing + image:copyInto({119, 26}, image:process("?crop=19;26;117;35")) +end \ No newline at end of file diff --git a/assets/opensb/interface/windowconfig/graphicsmenu.config.patch b/assets/opensb/interface/windowconfig/graphicsmenu.config.patch deleted file mode 100644 index d8da2c5..0000000 --- a/assets/opensb/interface/windowconfig/graphicsmenu.config.patch +++ /dev/null @@ -1,70 +0,0 @@ -{ - "paneLayout": { - "panefeature": { "positionLocked": false }, - "cameraSpeedLabel" : { - "type" : "label", - "position" : [170, 112], - "hAnchor" : "mid", - "value" : "CAMERA PAN SPEED" - }, - "cameraSpeedValueLabel" : { - "type" : "label", - "position" : [202, 99], - "hAnchor" : "mid", - "value" : "Replace Me" - }, - "cameraSpeedSlider" : { - "type" : "slider", - "position" : [126, 98], - "gridImage" : "/interface/optionsmenu/smallselection.png" - } - }, - "zoomList": [ - 1, - 1.125, - 1.25, - 1.375, - 1.5, - 1.675, - 1.75, - 1.875, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - 26, - 27, - 28, - 29, - 30, - 31, - 32 - ], - "cameraSpeedList" : [ - 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, - 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0, - 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8, 2.9, 3.0, - 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, 4.0, - 4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7, 4.8, 4.9, 5.0 - ] -} \ No newline at end of file diff --git a/assets/opensb/interface/windowconfig/graphicsmenu.config.patch.lua b/assets/opensb/interface/windowconfig/graphicsmenu.config.patch.lua new file mode 100644 index 0000000..b8c18b1 --- /dev/null +++ b/assets/opensb/interface/windowconfig/graphicsmenu.config.patch.lua @@ -0,0 +1,35 @@ +-- gadgets +local function jcopy(base) return sb.jsonMerge(base, {}) end + +local function clone(base, a, b) + local copy = jcopy(base[a]) + base[b] = copy + return copy +end + +local function shift(thing, x, y) + thing.position[1] = thing.position[1] + (tonumber(x) or 0) + thing.position[2] = thing.position[2] + (tonumber(y) or 0) + return thing +end + +-- patch function, called by the game +function patch(config) + local layout = config.paneLayout + layout.panefeature.positionLocked = false + + -- Create the camera pan speed widgets + shift(clone(layout, "zoomLabel", "cameraSpeedLabel"), 100).value = "CAMERA PAN SPEED" + shift(clone(layout, "zoomSlider", "cameraSpeedSlider"), 100) + shift(clone(layout, "zoomValueLabel", "cameraSpeedValueLabel"), 100) + -- Populate camera speed list + config.cameraSpeedList = jarray() + for i = 1, 50 do config.cameraSpeedList[i] = i / 10 end + for i = 1, 32 do config.zoomList[i] = i end + + -- Create anti-aliasing toggle + shift(clone(layout, "multiTextureLabel", "antiAliasingLabel"), 100).value = "SUPER-SAMPLED AA" + shift(clone(layout, "multiTextureCheckbox", "antiAliasingCheckbox"), 100) + + return config +end \ No newline at end of file diff --git a/assets/opensb/rendering/effects/interface.config b/assets/opensb/rendering/effects/interface.config index b4860d6..4616e72 100644 --- a/assets/opensb/rendering/effects/interface.config +++ b/assets/opensb/rendering/effects/interface.config @@ -1,5 +1,5 @@ { - "blitFrameBuffer" : "world", + "blitFrameBuffer" : "main", "effectParameters" : {}, "effectTextures" : {}, diff --git a/assets/opensb/rendering/effects/interface.frag b/assets/opensb/rendering/effects/interface.frag index 0ba2d60..3dd79c8 100644 --- a/assets/opensb/rendering/effects/interface.frag +++ b/assets/opensb/rendering/effects/interface.frag @@ -1,27 +1,29 @@ -#version 110 +#version 130 uniform sampler2D texture0; uniform sampler2D texture1; uniform sampler2D texture2; uniform sampler2D texture3; -varying vec2 fragmentTextureCoordinate; -varying float fragmentTextureIndex; -varying vec4 fragmentColor; +in vec2 fragmentTextureCoordinate; +flat in int fragmentTextureIndex; +in vec4 fragmentColor; + +out vec4 outColor; void main() { vec4 texColor; - if (fragmentTextureIndex > 2.9) { + if (fragmentTextureIndex == 3) texColor = texture2D(texture3, fragmentTextureCoordinate); - } else if (fragmentTextureIndex > 1.9) { + else if (fragmentTextureIndex == 2) texColor = texture2D(texture2, fragmentTextureCoordinate); - } else if (fragmentTextureIndex > 0.9) { + else if (fragmentTextureIndex == 1) texColor = texture2D(texture1, fragmentTextureCoordinate); - } else { + else texColor = texture2D(texture0, fragmentTextureCoordinate); - } + if (texColor.a <= 0.0) discard; - gl_FragColor = texColor * fragmentColor; + outColor = texColor * fragmentColor; } \ No newline at end of file diff --git a/assets/opensb/rendering/effects/interface.vert b/assets/opensb/rendering/effects/interface.vert index 612a788..14a7cea 100644 --- a/assets/opensb/rendering/effects/interface.vert +++ b/assets/opensb/rendering/effects/interface.vert @@ -1,4 +1,4 @@ -#version 110 +#version 130 uniform vec2 textureSize0; uniform vec2 textureSize1; @@ -7,28 +7,32 @@ uniform vec2 textureSize3; uniform vec2 screenSize; uniform mat3 vertexTransform; -attribute vec2 vertexPosition; -attribute vec2 vertexTextureCoordinate; -attribute float vertexTextureIndex; -attribute vec4 vertexColor; +in vec2 vertexPosition; +in vec4 vertexColor; +in vec2 vertexTextureCoordinate; +in int vertexData; -varying vec2 fragmentTextureCoordinate; -varying float fragmentTextureIndex; -varying vec4 fragmentColor; +out vec2 fragmentTextureCoordinate; +flat out int fragmentTextureIndex; +out vec4 fragmentColor; void main() { vec2 screenPosition = (vertexTransform * vec3(vertexPosition, 1.0)).xy; - - if (vertexTextureIndex > 2.9) { + 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 > 1.9) { + else if (vertexTextureIndex == 2) fragmentTextureCoordinate = vertexTextureCoordinate / textureSize2; - } else if (vertexTextureIndex > 0.9) { + else if (vertexTextureIndex == 1) fragmentTextureCoordinate = vertexTextureCoordinate / textureSize1; - } else { + else fragmentTextureCoordinate = vertexTextureCoordinate / textureSize0; - } + fragmentTextureIndex = vertexTextureIndex; fragmentColor = vertexColor; - gl_Position = vec4(screenPosition / screenSize * 2.0 - 1.0, 0.0, 1.0); } \ No newline at end of file diff --git a/assets/opensb/rendering/effects/world.config b/assets/opensb/rendering/effects/world.config index 68b84fa..d8ec1a9 100644 --- a/assets/opensb/rendering/effects/world.config +++ b/assets/opensb/rendering/effects/world.config @@ -1,5 +1,5 @@ { - "frameBuffer" : "world", + "frameBuffer" : "main", "effectParameters" : { "lightMapEnabled" : { diff --git a/assets/opensb/rendering/effects/world.frag b/assets/opensb/rendering/effects/world.frag index 7da662d..8b4d77c 100644 --- a/assets/opensb/rendering/effects/world.frag +++ b/assets/opensb/rendering/effects/world.frag @@ -1,4 +1,4 @@ -#version 110 +#version 130 uniform sampler2D texture0; uniform sampler2D texture1; @@ -9,11 +9,13 @@ uniform vec2 lightMapSize; uniform sampler2D lightMap; uniform float lightMapMultiplier; -varying vec2 fragmentTextureCoordinate; -varying float fragmentTextureIndex; -varying vec4 fragmentColor; -varying float fragmentLightMapMultiplier; -varying vec2 fragmentLightMapCoordinate; +in vec2 fragmentTextureCoordinate; +flat in int fragmentTextureIndex; +in vec4 fragmentColor; +in float fragmentLightMapMultiplier; +in vec2 fragmentLightMapCoordinate; + +out vec4 outColor; vec4 cubic(float v) { vec4 n = vec4(1.0, 2.0, 3.0, 4.0) - v; @@ -64,15 +66,15 @@ vec3 sampleLight(vec2 coord, vec2 scale) { void main() { vec4 texColor; - if (fragmentTextureIndex > 2.9) { + if (fragmentTextureIndex == 3) texColor = texture2D(texture3, fragmentTextureCoordinate); - } else if (fragmentTextureIndex > 1.9) { + else if (fragmentTextureIndex == 2) texColor = texture2D(texture2, fragmentTextureCoordinate); - } else if (fragmentTextureIndex > 0.9) { + else if (fragmentTextureIndex == 1) texColor = texture2D(texture1, fragmentTextureCoordinate); - } else { + else texColor = texture2D(texture0, fragmentTextureCoordinate); - } + if (texColor.a <= 0.0) discard; @@ -82,5 +84,5 @@ void main() { finalColor.a = fragmentColor.a; else if (lightMapEnabled && finalLightMapMultiplier > 0.0) finalColor.rgb *= sampleLight(fragmentLightMapCoordinate, 1.0 / lightMapSize) * finalLightMapMultiplier; - gl_FragColor = finalColor; + outColor = finalColor; } \ No newline at end of file diff --git a/assets/opensb/rendering/effects/world.vert b/assets/opensb/rendering/effects/world.vert index 67998bc..ce8a9fc 100644 --- a/assets/opensb/rendering/effects/world.vert +++ b/assets/opensb/rendering/effects/world.vert @@ -1,4 +1,4 @@ -#version 110 +#version 130 uniform vec2 textureSize0; uniform vec2 textureSize1; @@ -10,31 +10,37 @@ uniform vec2 lightMapSize; uniform vec2 lightMapScale; uniform vec2 lightMapOffset; -attribute vec2 vertexPosition; -attribute vec2 vertexTextureCoordinate; -attribute float vertexTextureIndex; -attribute vec4 vertexColor; -attribute float vertexParam1; +in vec2 vertexPosition; +in vec2 vertexTextureCoordinate; +in vec4 vertexColor; +in int vertexData; -varying vec2 fragmentTextureCoordinate; -varying float fragmentTextureIndex; -varying vec4 fragmentColor; -varying float fragmentLightMapMultiplier; -varying vec2 fragmentLightMapCoordinate; +out vec2 fragmentTextureCoordinate; +flat out int fragmentTextureIndex; +out vec4 fragmentColor; +out float fragmentLightMapMultiplier; +out vec2 fragmentLightMapCoordinate; void main() { vec2 screenPosition = (vertexTransform * vec3(vertexPosition, 1.0)).xy; - fragmentLightMapMultiplier = vertexParam1; + + if (((vertexData >> 3) & 0x1) == 1) + screenPosition.x = round(screenPosition.x); + if (((vertexData >> 4) & 0x1) == 1) + screenPosition.y = round(screenPosition.y); + + fragmentLightMapMultiplier = float((vertexData >> 2) & 0x1); + int vertexTextureIndex = vertexData & 0x3; fragmentLightMapCoordinate = (screenPosition / lightMapScale) - lightMapOffset * lightMapSize / screenSize; - if (vertexTextureIndex > 2.9) { + if (vertexTextureIndex == 3) fragmentTextureCoordinate = vertexTextureCoordinate / textureSize3; - } else if (vertexTextureIndex > 1.9) { + else if (vertexTextureIndex == 2) fragmentTextureCoordinate = vertexTextureCoordinate / textureSize2; - } else if (vertexTextureIndex > 0.9) { + else if (vertexTextureIndex == 1) fragmentTextureCoordinate = vertexTextureCoordinate / textureSize1; - } else { + else fragmentTextureCoordinate = vertexTextureCoordinate / textureSize0; - } + fragmentTextureIndex = vertexTextureIndex; fragmentColor = vertexColor; gl_Position = vec4(screenPosition / screenSize * 2.0 - 1.0, 0.0, 1.0); diff --git a/assets/opensb/rendering/opengl20.config.patch b/assets/opensb/rendering/opengl.config similarity index 62% rename from assets/opensb/rendering/opengl20.config.patch rename to assets/opensb/rendering/opengl.config index 3d85c1a..8ca132e 100644 --- a/assets/opensb/rendering/opengl20.config.patch +++ b/assets/opensb/rendering/opengl.config @@ -1,5 +1,5 @@ { "frameBuffers" : { - "world" : {} + "main" : {} } } \ No newline at end of file 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_opengl20.cpp b/source/application/StarRenderer_opengl.cpp similarity index 71% rename from source/application/StarRenderer_opengl20.cpp rename to source/application/StarRenderer_opengl.cpp index ca29a70..c816049 100644 --- a/source/application/StarRenderer_opengl20.cpp +++ b/source/application/StarRenderer_opengl.cpp @@ -1,4 +1,4 @@ -#include "StarRenderer_opengl20.hpp" +#include "StarRenderer_opengl.hpp" #include "StarJsonExtra.hpp" #include "StarCasting.hpp" #include "StarLogging.hpp" @@ -8,7 +8,7 @@ namespace Star { size_t const MultiTextureCount = 4; char const* DefaultVertexShader = R"SHADER( -#version 110 +#version 130 uniform vec2 textureSize0; uniform vec2 textureSize1; @@ -17,59 +17,70 @@ uniform vec2 textureSize3; uniform vec2 screenSize; uniform mat3 vertexTransform; -attribute vec2 vertexPosition; -attribute vec2 vertexTextureCoordinate; -attribute float vertexTextureIndex; -attribute vec4 vertexColor; -attribute float vertexParam1; +in vec2 vertexPosition; +in vec4 vertexColor; +in vec2 vertexTextureCoordinate; +in int vertexData; -varying vec2 fragmentTextureCoordinate; -varying float fragmentTextureIndex; -varying vec4 fragmentColor; +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 (vertexTextureIndex > 2.9) { + 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 > 1.9) { + else if (vertexTextureIndex == 2) fragmentTextureCoordinate = vertexTextureCoordinate / textureSize2; - } else if (vertexTextureIndex > 0.9) { + else if (vertexTextureIndex == 1) fragmentTextureCoordinate = vertexTextureCoordinate / textureSize1; - } else { + else fragmentTextureCoordinate = vertexTextureCoordinate / textureSize0; - } + fragmentTextureIndex = vertexTextureIndex; fragmentColor = vertexColor; } )SHADER"; char const* DefaultFragmentShader = R"SHADER( -#version 110 +#version 130 uniform sampler2D texture0; uniform sampler2D texture1; uniform sampler2D texture2; uniform sampler2D texture3; -varying vec2 fragmentTextureCoordinate; -varying float fragmentTextureIndex; -varying vec4 fragmentColor; +in vec2 fragmentTextureCoordinate; +flat in int fragmentTextureIndex; +in vec4 fragmentColor; + +out vec4 outColor; 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; - } + 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"; -OpenGl20Renderer::OpenGl20Renderer() { +OpenGlRenderer::OpenGlRenderer() { if (glewInit() != GLEW_OK) throw RendererException("Could not initialize GLEW"); @@ -97,11 +108,12 @@ OpenGl20Renderer::OpenGl20Renderer() { m_limitTextureGroupSize = false; m_useMultiTexturing = true; + m_multiSampling = false; logGlErrorSummary("OpenGL errors during renderer initialization"); } -OpenGl20Renderer::~OpenGl20Renderer() { +OpenGlRenderer::~OpenGlRenderer() { for (auto& effect : m_effects) glDeleteProgram(effect.second.program); @@ -109,27 +121,40 @@ OpenGl20Renderer::~OpenGl20Renderer() { logGlErrorSummary("OpenGL errors during shutdown"); } -String OpenGl20Renderer::rendererId() const { +String OpenGlRenderer::rendererId() const { return "OpenGL20"; } -Vec2U OpenGl20Renderer::screenSize() const { +Vec2U OpenGlRenderer::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()); +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 })); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, size[0] , size[1], 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); + + 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, GL_TEXTURE_2D, texture->glTextureId(), 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, texture->glTextureId(), 0); auto framebufferStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER); if (framebufferStatus != GL_FRAMEBUFFER_COMPLETE) @@ -137,21 +162,25 @@ OpenGl20Renderer::GlFrameBuffer::GlFrameBuffer(Json const& fbConfig) : config(fb } -OpenGl20Renderer::GlFrameBuffer::~GlFrameBuffer() { +OpenGlRenderer::GlFrameBuffer::~GlFrameBuffer() { glDeleteFramebuffers(1, &id); texture.reset(); } -void OpenGl20Renderer::loadConfig(Json const& config) { +void OpenGlRenderer::loadConfig(Json const& config) { m_frameBuffers.clear(); - for (auto& pair : config.getObject("frameBuffers", {})) - m_frameBuffers[pair.first] = make_ref(pair.second); + 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 OpenGl20Renderer::loadEffectConfig(String const& name, Json const& effectConfig, StringMap const& shaders) { +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); @@ -294,13 +323,13 @@ void OpenGl20Renderer::loadEffectConfig(String const& name, Json const& effectCo logGlErrorSummary("OpenGL errors setting effect config"); } -void OpenGl20Renderer::setEffectParameter(String const& parameterName, RenderEffectParameter const& value) { +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("OpenGL20Renderer::setEffectParameter '{}' parameter type mismatch", parameterName); + throw RendererException::format("OpenGlRenderer::setEffectParameter '{}' parameter type mismatch", parameterName); flushImmediatePrimitives(); @@ -320,7 +349,7 @@ void OpenGl20Renderer::setEffectParameter(String const& parameterName, RenderEff ptr->parameterValue = value; } -void OpenGl20Renderer::setEffectTexture(String const& textureName, ImageView const& image) { +void OpenGlRenderer::setEffectTexture(String const& textureName, ImageView const& image) { auto ptr = m_currentEffect->textures.ptr(textureName); if (!ptr) return; @@ -341,7 +370,7 @@ void OpenGl20Renderer::setEffectTexture(String const& textureName, ImageView con } } -bool OpenGl20Renderer::switchEffectConfig(String const& name) { +bool OpenGlRenderer::switchEffectConfig(String const& name) { flushImmediatePrimitives(); auto find = m_effects.find(name); if (find == m_effects.end()) @@ -368,7 +397,7 @@ bool OpenGl20Renderer::switchEffectConfig(String const& name) { return true; } -void OpenGl20Renderer::setScissorRect(Maybe const& scissorRect) { +void OpenGlRenderer::setScissorRect(Maybe const& scissorRect) { if (scissorRect == m_scissorRect) return; @@ -383,22 +412,39 @@ void OpenGl20Renderer::setScissorRect(Maybe const& scissorRect) { } } -TexturePtr OpenGl20Renderer::createTexture(Image const& texture, TextureAddressing addressing, TextureFiltering filtering) { +TexturePtr OpenGlRenderer::createTexture(Image const& texture, TextureAddressing addressing, TextureFiltering filtering) { return createGlTexture(texture, addressing, filtering); } -void OpenGl20Renderer::setSizeLimitEnabled(bool enabled) { +void OpenGlRenderer::setSizeLimitEnabled(bool enabled) { m_limitTextureGroupSize = enabled; } -void OpenGl20Renderer::setMultiTexturingEnabled(bool enabled) { +void OpenGlRenderer::setMultiTexturingEnabled(bool enabled) { m_useMultiTexturing = enabled; } -TextureGroupPtr OpenGl20Renderer::createTextureGroup(TextureGroupSize textureSize, TextureFiltering filtering) { +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; @@ -419,39 +465,44 @@ TextureGroupPtr OpenGl20Renderer::createTextureGroup(TextureGroupSize textureSiz return glTextureGroup; } -RenderBufferPtr OpenGl20Renderer::createRenderBuffer() { +RenderBufferPtr OpenGlRenderer::createRenderBuffer() { return createGlRenderBuffer(); } -List& OpenGl20Renderer::immediatePrimitives() { +List& OpenGlRenderer::immediatePrimitives() { return m_immediatePrimitives; } -void OpenGl20Renderer::render(RenderPrimitive primitive) { +void OpenGlRenderer::render(RenderPrimitive primitive) { m_immediatePrimitives.append(std::move(primitive)); } -void OpenGl20Renderer::renderBuffer(RenderBufferPtr const& renderBuffer, Mat3F const& transformation) { +void OpenGlRenderer::renderBuffer(RenderBufferPtr const& renderBuffer, Mat3F const& transformation) { flushImmediatePrimitives(); renderGlBuffer(*convert(renderBuffer.get()), transformation); } -void OpenGl20Renderer::flush() { +void OpenGlRenderer::flush() { flushImmediatePrimitives(); } -void OpenGl20Renderer::setScreenSize(Vec2U screenSize) { +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) { - 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); + 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 OpenGl20Renderer::startFrame() { +void OpenGlRenderer::startFrame() { if (m_scissorRect) glDisable(GL_SCISSOR_TEST); @@ -469,7 +520,7 @@ void OpenGl20Renderer::startFrame() { glEnable(GL_SCISSOR_TEST); } -void OpenGl20Renderer::finishFrame() { +void OpenGlRenderer::finishFrame() { flushImmediatePrimitives(); // Make sure that the immediate render buffer doesn't needlessly lock texutres // from being compressed. @@ -494,14 +545,14 @@ void OpenGl20Renderer::finishFrame() { logGlErrorSummary("OpenGL errors this frame"); } -OpenGl20Renderer::GlTextureAtlasSet::GlTextureAtlasSet(unsigned atlasNumCells) +OpenGlRenderer::GlTextureAtlasSet::GlTextureAtlasSet(unsigned atlasNumCells) : TextureAtlasSet(16, atlasNumCells) {} -GLuint OpenGl20Renderer::GlTextureAtlasSet::createAtlasTexture(Vec2U const& size, PixelFormat pixelFormat) { +GLuint OpenGlRenderer::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()"); + throw RendererException("Could not generate texture in OpenGlRenderer::TextureGroup::createAtlasTexture()"); glBindTexture(GL_TEXTURE_2D, glTextureId); @@ -520,11 +571,11 @@ GLuint OpenGl20Renderer::GlTextureAtlasSet::createAtlasTexture(Vec2U const& size return glTextureId; } -void OpenGl20Renderer::GlTextureAtlasSet::destroyAtlasTexture(GLuint const& glTexture) { +void OpenGlRenderer::GlTextureAtlasSet::destroyAtlasTexture(GLuint const& glTexture) { glDeleteTextures(1, &glTexture); } -void OpenGl20Renderer::GlTextureAtlasSet::copyAtlasPixels( +void OpenGlRenderer::GlTextureAtlasSet::copyAtlasPixels( GLuint const& glTexture, Vec2U const& bottomLeft, Image const& image) { glBindTexture(GL_TEXTURE_2D, glTexture); @@ -540,23 +591,23 @@ void OpenGl20Renderer::GlTextureAtlasSet::copyAtlasPixels( else if (pixelFormat == PixelFormat::BGRA32) format = GL_BGRA; else - throw RendererException("Unsupported texture format in OpenGL20Renderer::TextureGroup::copyAtlasPixels"); + 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()); } -OpenGl20Renderer::GlTextureGroup::GlTextureGroup(unsigned atlasNumCells) +OpenGlRenderer::GlTextureGroup::GlTextureGroup(unsigned atlasNumCells) : textureAtlasSet(atlasNumCells) {} -OpenGl20Renderer::GlTextureGroup::~GlTextureGroup() { +OpenGlRenderer::GlTextureGroup::~GlTextureGroup() { textureAtlasSet.reset(); } -TextureFiltering OpenGl20Renderer::GlTextureGroup::filtering() const { +TextureFiltering OpenGlRenderer::GlTextureGroup::filtering() const { return textureAtlasSet.textureFiltering; } -TexturePtr OpenGl20Renderer::GlTextureGroup::create(Image const& texture) { +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(); @@ -570,78 +621,78 @@ TexturePtr OpenGl20Renderer::GlTextureGroup::create(Image const& texture) { return glGroupedTexture; } -OpenGl20Renderer::GlGroupedTexture::~GlGroupedTexture() { +OpenGlRenderer::GlGroupedTexture::~GlGroupedTexture() { if (parentAtlasTexture) parentGroup->textureAtlasSet.freeTexture(parentAtlasTexture); } -Vec2U OpenGl20Renderer::GlGroupedTexture::size() const { +Vec2U OpenGlRenderer::GlGroupedTexture::size() const { return parentAtlasTexture->imageSize(); } -TextureFiltering OpenGl20Renderer::GlGroupedTexture::filtering() const { +TextureFiltering OpenGlRenderer::GlGroupedTexture::filtering() const { return parentGroup->filtering(); } -TextureAddressing OpenGl20Renderer::GlGroupedTexture::addressing() const { +TextureAddressing OpenGlRenderer::GlGroupedTexture::addressing() const { return TextureAddressing::Clamp; } -GLuint OpenGl20Renderer::GlGroupedTexture::glTextureId() const { +GLuint OpenGlRenderer::GlGroupedTexture::glTextureId() const { return parentAtlasTexture->atlasTexture(); } -Vec2U OpenGl20Renderer::GlGroupedTexture::glTextureSize() const { +Vec2U OpenGlRenderer::GlGroupedTexture::glTextureSize() const { return parentGroup->textureAtlasSet.atlasTextureSize(); } -Vec2U OpenGl20Renderer::GlGroupedTexture::glTextureCoordinateOffset() const { +Vec2U OpenGlRenderer::GlGroupedTexture::glTextureCoordinateOffset() const { return parentAtlasTexture->atlasTextureCoordinates().min(); } -void OpenGl20Renderer::GlGroupedTexture::incrementBufferUseCount() { +void OpenGlRenderer::GlGroupedTexture::incrementBufferUseCount() { if (bufferUseCount == 0) parentAtlasTexture->setLocked(true); ++bufferUseCount; } -void OpenGl20Renderer::GlGroupedTexture::decrementBufferUseCount() { +void OpenGlRenderer::GlGroupedTexture::decrementBufferUseCount() { starAssert(bufferUseCount != 0); if (bufferUseCount == 1) parentAtlasTexture->setLocked(false); --bufferUseCount; } -OpenGl20Renderer::GlLoneTexture::~GlLoneTexture() { +OpenGlRenderer::GlLoneTexture::~GlLoneTexture() { if (textureId != 0) glDeleteTextures(1, &textureId); } -Vec2U OpenGl20Renderer::GlLoneTexture::size() const { +Vec2U OpenGlRenderer::GlLoneTexture::size() const { return textureSize; } -TextureFiltering OpenGl20Renderer::GlLoneTexture::filtering() const { +TextureFiltering OpenGlRenderer::GlLoneTexture::filtering() const { return textureFiltering; } -TextureAddressing OpenGl20Renderer::GlLoneTexture::addressing() const { +TextureAddressing OpenGlRenderer::GlLoneTexture::addressing() const { return textureAddressing; } -GLuint OpenGl20Renderer::GlLoneTexture::glTextureId() const { +GLuint OpenGlRenderer::GlLoneTexture::glTextureId() const { return textureId; } -Vec2U OpenGl20Renderer::GlLoneTexture::glTextureSize() const { +Vec2U OpenGlRenderer::GlLoneTexture::glTextureSize() const { return textureSize; } -Vec2U OpenGl20Renderer::GlLoneTexture::glTextureCoordinateOffset() const { +Vec2U OpenGlRenderer::GlLoneTexture::glTextureCoordinateOffset() const { return Vec2U(); } -OpenGl20Renderer::GlRenderBuffer::~GlRenderBuffer() { +OpenGlRenderer::GlRenderBuffer::~GlRenderBuffer() { for (auto const& texture : usedTextures) { if (auto gt = as(texture.get())) gt->decrementBufferUseCount(); @@ -650,7 +701,7 @@ OpenGl20Renderer::GlRenderBuffer::~GlRenderBuffer() { glDeleteBuffers(1, &vb.vertexBuffer); } -void OpenGl20Renderer::GlRenderBuffer::set(List& primitives) { +void OpenGlRenderer::GlRenderBuffer::set(List& primitives) { for (auto const& texture : usedTextures) { if (auto gt = as(texture.get())) gt->decrementBufferUseCount(); @@ -718,47 +769,58 @@ void OpenGl20Renderer::GlRenderBuffer::set(List& primitives) { 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)); + 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; }; - float textureIndex = 0.0f; + 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); - appendBufferVertex(tri->b, textureIndex, textureOffset); - appendBufferVertex(tri->c, textureIndex, textureOffset); + 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)); - appendBufferVertex(quad->a, textureIndex, textureOffset); - appendBufferVertex(quad->b, textureIndex, textureOffset); - appendBufferVertex(quad->c, textureIndex, textureOffset); + // = 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); - appendBufferVertex(quad->c, textureIndex, textureOffset); - appendBufferVertex(quad->d, textureIndex, textureOffset); + 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) { - appendBufferVertex(poly->vertexes[0], textureIndex, textureOffset); - appendBufferVertex(poly->vertexes[i], textureIndex, textureOffset); - appendBufferVertex(poly->vertexes[i + 1], textureIndex, textureOffset); + 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); } } } @@ -771,7 +833,7 @@ void OpenGl20Renderer::GlRenderBuffer::set(List& primitives) { glDeleteBuffers(1, &vb.vertexBuffer); } -bool OpenGl20Renderer::logGlErrorSummary(String prefix) { +bool OpenGlRenderer::logGlErrorSummary(String prefix) { if (GLenum error = glGetError()) { Logger::error("{}: ", prefix); do { @@ -798,7 +860,7 @@ bool OpenGl20Renderer::logGlErrorSummary(String prefix) { return false; } -void OpenGl20Renderer::uploadTextureImage(PixelFormat pixelFormat, Vec2U size, uint8_t const* data) { +void OpenGlRenderer::uploadTextureImage(PixelFormat pixelFormat, Vec2U size, uint8_t const* data) { glPixelStorei(GL_UNPACK_ALIGNMENT, 1); Maybe internalFormat; @@ -821,13 +883,13 @@ void OpenGl20Renderer::uploadTextureImage(PixelFormat pixelFormat, Vec2U size, u internalFormat = GL_RGBA32F; format = GL_RGBA; } else - throw RendererException("Unsupported texture format in OpenGL20Renderer::uploadTextureImage"); + 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 OpenGl20Renderer::flushImmediatePrimitives() { +void OpenGlRenderer::flushImmediatePrimitives() { if (m_immediatePrimitives.empty()) return; @@ -836,7 +898,7 @@ void OpenGl20Renderer::flushImmediatePrimitives() { renderGlBuffer(*m_immediateRenderBuffer, Mat3F::identity()); } -auto OpenGl20Renderer::createGlTexture(ImageView const& image, TextureAddressing addressing, TextureFiltering filtering) +auto OpenGlRenderer::createGlTexture(ImageView const& image, TextureAddressing addressing, TextureFiltering filtering) ->RefPtr { auto glLoneTexture = make_ref(); glLoneTexture->textureFiltering = filtering; @@ -845,7 +907,7 @@ auto OpenGl20Renderer::createGlTexture(ImageView const& image, TextureAddressing glGenTextures(1, &glLoneTexture->textureId); if (glLoneTexture->textureId == 0) - throw RendererException("Could not generate texture in OpenGL20Renderer::createGlTexture"); + throw RendererException("Could not generate texture in OpenGlRenderer::createGlTexture"); glBindTexture(GL_TEXTURE_2D, glLoneTexture->textureId); @@ -872,14 +934,14 @@ auto OpenGl20Renderer::createGlTexture(ImageView const& image, TextureAddressing return glLoneTexture; } -auto OpenGl20Renderer::createGlRenderBuffer() -> shared_ptr { +auto OpenGlRenderer::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) { +void OpenGlRenderer::renderGlBuffer(GlRenderBuffer const& renderBuffer, Mat3F const& transformation) { for (auto const& vb : renderBuffer.vertexBuffers) { glUniformMatrix3fv(m_vertexTransformUniform, 1, GL_TRUE, transformation.ptr()); @@ -900,30 +962,24 @@ void OpenGl20Renderer::renderGlBuffer(GlRenderBuffer const& renderBuffer, Mat3F glEnableVertexAttribArray(m_positionAttribute); glEnableVertexAttribArray(m_texCoordAttribute); - glEnableVertexAttribArray(m_texIndexAttribute); glEnableVertexAttribArray(m_colorAttribute); + glEnableVertexAttribArray(m_dataAttribute); - 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_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)); - - if (m_param1Attribute != -1) { - glEnableVertexAttribArray(m_param1Attribute); - glVertexAttribPointer(m_param1Attribute, 1, GL_FLOAT, GL_FALSE, sizeof(GlRenderVertex), (GLvoid*)offsetof(GlRenderVertex, param1)); - } + 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 OpenGl20Renderer::setupGlUniforms(Effect& effect) { +void OpenGlRenderer::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_texCoordAttribute = effect.getAttribute("vertexTextureCoordinate"); + m_dataAttribute = effect.getAttribute("vertexData"); m_textureUniforms.clear(); m_textureSizeUniforms.clear(); @@ -940,14 +996,14 @@ void OpenGl20Renderer::setupGlUniforms(Effect& effect) { glUniform2f(m_screenSizeUniform, m_screenSize[0], m_screenSize[1]); } -RefPtr OpenGl20Renderer::getGlFrameBuffer(String const& id) { +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 OpenGl20Renderer::blitGlFrameBuffer(RefPtr const& frameBuffer) { +void OpenGlRenderer::blitGlFrameBuffer(RefPtr const& frameBuffer) { if (frameBuffer->blitted) return; @@ -963,7 +1019,7 @@ void OpenGl20Renderer::blitGlFrameBuffer(RefPtr const& frameBuffe frameBuffer->blitted = true; } -void OpenGl20Renderer::switchGlFrameBuffer(RefPtr const& frameBuffer) { +void OpenGlRenderer::switchGlFrameBuffer(RefPtr const& frameBuffer) { if (m_currentFrameBuffer == frameBuffer) return; @@ -971,7 +1027,7 @@ void OpenGl20Renderer::switchGlFrameBuffer(RefPtr const& frameBuf m_currentFrameBuffer = frameBuffer; } -GLuint OpenGl20Renderer::Effect::getAttribute(String const& name) { +GLuint OpenGlRenderer::Effect::getAttribute(String const& name) { auto find = attributes.find(name); if (find == attributes.end()) { GLuint attrib = glGetAttribLocation(program, name.utf8Ptr()); @@ -981,7 +1037,7 @@ GLuint OpenGl20Renderer::Effect::getAttribute(String const& name) { return find->second; } -GLuint OpenGl20Renderer::Effect::getUniform(String const& name) { +GLuint OpenGlRenderer::Effect::getUniform(String const& name) { auto find = uniforms.find(name); if (find == uniforms.end()) { GLuint uniform = glGetUniformLocation(program, name.utf8Ptr()); diff --git a/source/application/StarRenderer_opengl20.hpp b/source/application/StarRenderer_opengl.hpp similarity index 88% rename from source/application/StarRenderer_opengl20.hpp rename to source/application/StarRenderer_opengl.hpp index fa406b6..563ac96 100644 --- a/source/application/StarRenderer_opengl20.hpp +++ b/source/application/StarRenderer_opengl.hpp @@ -7,16 +7,16 @@ namespace Star { -STAR_CLASS(OpenGl20Renderer); +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 OpenGl20Renderer : public Renderer { +class OpenGlRenderer : public Renderer { public: - OpenGl20Renderer(); - ~OpenGl20Renderer(); + OpenGlRenderer(); + ~OpenGlRenderer(); String rendererId() const override; Vec2U screenSize() const override; @@ -34,6 +34,7 @@ public: 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; @@ -112,12 +113,22 @@ private: 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 screenCoordinate; - Vec2F textureCoordinate; - float textureIndex; + Vec2F pos; + Vec2F uv; Vec4B color; - float param1; + union Packed { + uint32_t packed; + GlPackedVertexData vars; + } pack; }; struct GlRenderBuffer : public RenderBuffer { @@ -166,6 +177,7 @@ private: Json config; bool blitted = false; + unsigned multisample = 0; GlFrameBuffer(Json const& config); ~GlFrameBuffer(); @@ -199,25 +211,25 @@ private: void setupGlUniforms(Effect& effect); - RefPtr getGlFrameBuffer(String const& id); - void blitGlFrameBuffer(RefPtr const& frameBuffer); - void switchGlFrameBuffer(RefPtr const& frameBuffer); + 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; - + 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; @@ -230,6 +242,7 @@ private: 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; diff --git a/source/base/StarAssets.cpp b/source/base/StarAssets.cpp index 53c008d..7c1a67b 100644 --- a/source/base/StarAssets.cpp +++ b/source/base/StarAssets.cpp @@ -870,6 +870,42 @@ ImageConstPtr Assets::readImage(String const& path) const { image = memorySource->image(p->sourceName); if (!image) image = make_shared(Image::readPng(p->source->open(p->sourceName))); + + if (!p->patchSources.empty()) { + MutexLocker luaLocker(m_luaMutex); + LuaEngine* luaEngine = as(m_luaEngine.get()); + LuaValue result = luaEngine->createUserData(*image); + luaLocker.unlock(); + for (auto const& pair : p->patchSources) { + auto& patchPath = pair.first; + auto& patchSource = pair.second; + auto patchStream = patchSource->read(patchPath); + if (patchPath.endsWith(".lua")) { + luaLocker.lock(); + LuaContextPtr& context = m_patchContexts[patchPath]; + if (!context) { + context = make_shared(luaEngine->createContext()); + context->load(patchStream, patchPath); + } + auto newResult = context->invokePath("patch", result, path); + if (!newResult.is()) { + if (auto ud = newResult.ptr()) { + if (ud->is()) + result = std::move(newResult); + else + Logger::warn("Patch '{}' for image '{}' returned a non-Image userdata value, ignoring"); + } else { + Logger::warn("Patch '{}' for image '{}' returned a non-Image value, ignoring"); + } + } + luaLocker.unlock(); + } else { + Logger::warn("Patch '{}' for image '{}' isn't a Lua script, ignoring", patchPath, path); + } + } + image = make_shared(std::move(result.get().get())); + } + return image; } throw AssetException(strf("No such asset '{}'", path)); @@ -916,32 +952,32 @@ Json Assets::readJson(String const& path) const { auto& patchPath = pair.first; auto& patchSource = pair.second; auto patchStream = patchSource->read(patchPath); - if (pair.first.endsWith(".lua")) { - MutexLocker luaLocker(m_luaMutex); - // Kae: i don't like that lock. perhaps have a LuaEngine and patch context cache per worker thread later on? - LuaContextPtr& context = m_patchContexts[patchPath]; - if (!context) { - context = make_shared(as(m_luaEngine.get())->createContext()); - context->load(patchStream, patchPath); - } - auto newResult = context->invokePath("patch", result, path); - if (newResult) - result = std::move(newResult); - } else { - auto patchJson = inputUtf8Json(patchStream.begin(), patchStream.end(), false); - if (patchJson.isType(Json::Type::Array)) { - auto patchData = patchJson.toArray(); - try { - result = checkPatchArray(patchPath, patchSource, result, patchData, {}); - } catch (JsonPatchTestFail const& e) { - Logger::debug("Patch test failure from file {} in source: '{}' at '{}'. Caused by: {}", patchPath, patchSource->metadata().value("name", ""), m_assetSourcePaths.getLeft(patchSource), e.what()); - } catch (JsonPatchException const& e) { - Logger::error("Could not apply patch from file {} in source: '{}' at '{}'. Caused by: {}", patchPath, patchSource->metadata().value("name", ""), m_assetSourcePaths.getLeft(patchSource), e.what()); - } - } else if (patchJson.isType(Json::Type::Object)) {//Kae: Do a good ol' json merge instead if the .patch file is a Json object - result = jsonMergeNulling(result, patchJson.toObject()); - } + if (patchPath.endsWith(".lua")) { + MutexLocker luaLocker(m_luaMutex); + // Kae: i don't like that lock. perhaps have a LuaEngine and patch context cache per worker thread later on? + LuaContextPtr& context = m_patchContexts[patchPath]; + if (!context) { + context = make_shared(as(m_luaEngine.get())->createContext()); + context->load(patchStream, patchPath); } + auto newResult = context->invokePath("patch", result, path); + if (newResult) + result = std::move(newResult); + } else { + auto patchJson = inputUtf8Json(patchStream.begin(), patchStream.end(), false); + if (patchJson.isType(Json::Type::Array)) { + auto patchData = patchJson.toArray(); + try { + result = checkPatchArray(patchPath, patchSource, result, patchData, {}); + } catch (JsonPatchTestFail const& e) { + Logger::debug("Patch test failure from file {} in source: '{}' at '{}'. Caused by: {}", patchPath, patchSource->metadata().value("name", ""), m_assetSourcePaths.getLeft(patchSource), e.what()); + } catch (JsonPatchException const& e) { + Logger::error("Could not apply patch from file {} in source: '{}' at '{}'. Caused by: {}", patchPath, patchSource->metadata().value("name", ""), m_assetSourcePaths.getLeft(patchSource), e.what()); + } + } else if (patchJson.isType(Json::Type::Object)) {//Kae: Do a good ol' json merge instead if the .patch file is a Json object + result = jsonMergeNulling(result, patchJson.toObject()); + } + } } return result; } catch (std::exception const& e) { diff --git a/source/client/StarClientApplication.cpp b/source/client/StarClientApplication.cpp index bcdc077..c83c9e7 100644 --- a/source/client/StarClientApplication.cpp +++ b/source/client/StarClientApplication.cpp @@ -58,6 +58,7 @@ Json const AdditionalDefaultConfiguration = Json::parseJson(R"JSON( "fullscreen" : false, "borderless" : false, "maximized" : true, + "antiAliasing" : false, "zoomLevel" : 3.0, "cameraSpeedFactor" : 1.0, "speechBubbles" : true, @@ -375,6 +376,7 @@ void ClientApplication::render() { auto assets = m_root->assets(); auto& renderer = Application::renderer(); + renderer->setMultiSampling(config->get("antiAliasing").optBool().value(false) ? 4 : 0); renderer->switchEffectConfig("interface"); if (m_guiContext->windowWidth() >= m_crossoverRes[0] && m_guiContext->windowHeight() >= m_crossoverRes[1]) @@ -452,7 +454,7 @@ void ClientApplication::renderReload() { Logger::warn("No rendering config found for renderer with id '{}'", renderer->rendererId()); }; - renderer->loadConfig(assets->json("/rendering/opengl20.config")); + renderer->loadConfig(assets->json("/rendering/opengl.config")); loadEffectConfig("world"); loadEffectConfig("interface"); diff --git a/source/frontend/StarGraphicsMenu.cpp b/source/frontend/StarGraphicsMenu.cpp index f1a93a9..0a87ef6 100644 --- a/source/frontend/StarGraphicsMenu.cpp +++ b/source/frontend/StarGraphicsMenu.cpp @@ -74,6 +74,12 @@ GraphicsMenu::GraphicsMenu() { m_localChanges.set("useMultiTexturing", fetchChild("multiTextureCheckbox")->isChecked()); syncGui(); }); + reader.registerCallback("antiAliasingCheckbox", [=](Widget*) { + bool checked = fetchChild("antiAliasingCheckbox")->isChecked(); + m_localChanges.set("antiAliasing", checked); + Root::singleton().configuration()->set("antiAliasing", checked); + syncGui(); + }); reader.registerCallback("monochromeCheckbox", [=](Widget*) { bool checked = fetchChild("monochromeCheckbox")->isChecked(); m_localChanges.set("monochromeLighting", checked); @@ -133,6 +139,7 @@ StringList const GraphicsMenu::ConfigKeys = { "borderless", "limitTextureAtlasSize", "useMultiTexturing", + "antiAliasing", "monochromeLighting" }; @@ -187,6 +194,7 @@ void GraphicsMenu::syncGui() { fetchChild("borderlessCheckbox")->setChecked(m_localChanges.get("borderless").toBool()); fetchChild("textureLimitCheckbox")->setChecked(m_localChanges.get("limitTextureAtlasSize").toBool()); fetchChild("multiTextureCheckbox")->setChecked(m_localChanges.get("useMultiTexturing").optBool().value(true)); + fetchChild("antiAliasingCheckbox")->setChecked(m_localChanges.get("antiAliasing").toBool()); fetchChild("monochromeCheckbox")->setChecked(m_localChanges.get("monochromeLighting").toBool()); } diff --git a/source/rendering/StarWorldCamera.cpp b/source/rendering/StarWorldCamera.cpp index 0344bd6..81e599f 100644 --- a/source/rendering/StarWorldCamera.cpp +++ b/source/rendering/StarWorldCamera.cpp @@ -2,12 +2,13 @@ namespace Star { -void WorldCamera::setCenterWorldPosition(Vec2F const& position) { +void WorldCamera::setCenterWorldPosition(Vec2F const& position, bool force) { // Only actually move the world center if a half pixel distance has been // moved in any direction. This is sort of arbitrary, but helps prevent // judder if the camera is at a boundary and floating point inaccuracy is // causing the focus to jitter back and forth across the boundary. - if (fabs(position[0] - m_worldCenter[0]) < 1.0f / (TilePixels * m_pixelRatio * 2) && fabs(position[1] - m_worldCenter[1]) < 1.0f / (TilePixels * m_pixelRatio * 2)) + if (fabs(position[0] - m_worldCenter[0]) < 1.0f / (TilePixels * m_pixelRatio * 2) + && fabs(position[1] - m_worldCenter[1]) < 1.0f / (TilePixels * m_pixelRatio * 2) && !force) return; // First, make sure the camera center position is inside the main x diff --git a/source/rendering/StarWorldCamera.hpp b/source/rendering/StarWorldCamera.hpp index e022560..6354167 100644 --- a/source/rendering/StarWorldCamera.hpp +++ b/source/rendering/StarWorldCamera.hpp @@ -20,8 +20,8 @@ public: // Set the camera center position (in world space) to as close to the given // location as possible while keeping the screen within world bounds. + void setCenterWorldPosition(Vec2F const& position, bool force = false); // Returns the actual camera position. - void setCenterWorldPosition(Vec2F const& position); Vec2F centerWorldPosition() const; // Transforms world coordinates into one set of screen coordinates. Since @@ -121,7 +121,11 @@ inline Vec2F WorldCamera::tileMinScreen() const { } inline void WorldCamera::update(float dt) { - m_pixelRatio = lerp(exp(-20.0f * dt), m_targetPixelRatio, m_pixelRatio); + float newPixelRatio = lerp(exp(-20.0f * dt), m_targetPixelRatio, m_pixelRatio); + if (m_pixelRatio != newPixelRatio) { + m_pixelRatio = newPixelRatio; + setCenterWorldPosition(m_worldCenter, true); + } } }