diff --git a/src/main/resources/ocelot/desktop/ocelot.conf b/src/main/resources/ocelot/desktop/ocelot.conf index ae48448..ec95bd8 100644 --- a/src/main/resources/ocelot/desktop/ocelot.conf +++ b/src/main/resources/ocelot/desktop/ocelot.conf @@ -53,4 +53,9 @@ ocelot { # Otherwise, content will be shown only in windows renderScreenDataOnNodes: true } + + render { + # Whether mipmap scaling is enabled for screen windows (when shrinking). + screenWindowMipmap: true + } } diff --git a/src/main/scala/ocelot/desktop/Settings.scala b/src/main/scala/ocelot/desktop/Settings.scala index 11f2a07..5089a4c 100644 --- a/src/main/scala/ocelot/desktop/Settings.scala +++ b/src/main/scala/ocelot/desktop/Settings.scala @@ -50,6 +50,8 @@ class Settings(val config: Config) extends SettingsData { saveOnExit = config.getBooleanOrElse("ocelot.workspace.saveOnExit", default = true) openLastWorkspace = config.getBooleanOrElse("ocelot.workspace.openLastWorkspace", default = true) renderScreenDataOnNodes = config.getBooleanOrElse("ocelot.workspace.renderScreenDataOnNodes", default = true) + + screenWindowMipmap = config.getBooleanOrElse("ocelot.render.screenWindowMipmap", default = true) } object Settings extends Logging { diff --git a/src/main/scala/ocelot/desktop/graphics/Graphics.scala b/src/main/scala/ocelot/desktop/graphics/Graphics.scala index 41c7a10..d2b4f93 100644 --- a/src/main/scala/ocelot/desktop/graphics/Graphics.scala +++ b/src/main/scala/ocelot/desktop/graphics/Graphics.scala @@ -3,6 +3,7 @@ package ocelot.desktop.graphics import ocelot.desktop.color.{Color, RGBAColorNorm} import ocelot.desktop.geometry.{Rect2D, Size2D, Transform2D, Vector2D} import ocelot.desktop.graphics.IconSource.Animation +import ocelot.desktop.graphics.Texture.MinFilteringMode import ocelot.desktop.graphics.mesh.{Mesh2D, MeshInstance2D, MeshVertex2D} import ocelot.desktop.graphics.render.InstanceRenderer import ocelot.desktop.ui.UiHandler @@ -145,11 +146,19 @@ class Graphics(private var width: Int, private var height: Int, private var scal flush(mainTexture = viewport.textureColor) } - def blitScreenViewport(viewport: ScreenViewport, bounds: Rect2D, alpha: Float = 1.0f): Unit = { + def blitScreenViewport( + viewport: ScreenViewport, + bounds: Rect2D, + filteringMode: MinFilteringMode = MinFilteringMode.Nearest, + alpha: Float = 1.0f + ): Unit = { flush() foreground = RGBAColorNorm(1, 1, 1, alpha) spriteRect = Rect2D(0, 1f, 1f, -1f) _rect(bounds.x, bounds.y, bounds.w, bounds.h, fixUV = false) + + viewport.texture.setMinFilter(filteringMode) + flush(mainTexture = viewport.texture) } diff --git a/src/main/scala/ocelot/desktop/graphics/ScreenViewport.scala b/src/main/scala/ocelot/desktop/graphics/ScreenViewport.scala index da37f0e..8f71679 100644 --- a/src/main/scala/ocelot/desktop/graphics/ScreenViewport.scala +++ b/src/main/scala/ocelot/desktop/graphics/ScreenViewport.scala @@ -27,6 +27,8 @@ class ScreenViewport(graphics: Graphics, private var _width: Int, private var _h private var _foreground: RGBAColorNorm = RGBAColorNorm(0f, 0f, 0f) private var _background: RGBAColorNorm = RGBAColorNorm(1f, 1f, 1f) + private var mipmapDirty = false + override def freeResource(): Unit = { super.freeResource() @@ -110,6 +112,8 @@ class ScreenViewport(graphics: Graphics, private var _width: Int, private var _h Spritesheet.texture.bind() _font.texture.bind(1) renderer.flush() + + mipmapDirty = true } def renderWith(f: => Unit): Unit = { @@ -121,4 +125,11 @@ class ScreenViewport(graphics: Graphics, private var _width: Int, private var _h flush() } } + + def generateMipmap(): Unit = { + if (mipmapDirty) { + texture.generateMipmap() + mipmapDirty = false + } + } } diff --git a/src/main/scala/ocelot/desktop/graphics/Texture.scala b/src/main/scala/ocelot/desktop/graphics/Texture.scala index 3c3438c..7898ad0 100644 --- a/src/main/scala/ocelot/desktop/graphics/Texture.scala +++ b/src/main/scala/ocelot/desktop/graphics/Texture.scala @@ -1,5 +1,6 @@ package ocelot.desktop.graphics +import ocelot.desktop.graphics.Texture.MinFilteringMode import ocelot.desktop.util.{Logging, Resource} import org.lwjgl.BufferUtils import org.lwjgl.opengl._ @@ -7,7 +8,7 @@ import org.lwjgl.opengl._ import java.awt.image.BufferedImage import java.nio.ByteBuffer -class Texture extends Logging with Resource { +class Texture() extends Logging with Resource { val texture: Int = GL11.glGenTextures() bind() @@ -70,8 +71,30 @@ class Texture extends Logging with Resource { GL11.glBindTexture(GL11.GL_TEXTURE_2D, texture) } + def setMinFilter(filter: MinFilteringMode): Unit = { + bind() + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, filter.glValue) + } + + def generateMipmap(): Unit = { + bind() + GL30.glGenerateMipmap(GL11.GL_TEXTURE_2D) + } + override def freeResource(): Unit = { super.freeResource() GL11.glDeleteTextures(texture) } } + +object Texture { + class MinFilteringMode private(private[Texture] val glValue: Int, val needsMipmap: Boolean) + + object MinFilteringMode { + val Nearest = new MinFilteringMode(GL11.GL_NEAREST, false) + val NearestMipmapNearest = new MinFilteringMode(GL11.GL_NEAREST_MIPMAP_NEAREST, true) + val NearestMipmapLinear = new MinFilteringMode(GL11.GL_NEAREST_MIPMAP_LINEAR, true) + val LinearMipmapNearest = new MinFilteringMode(GL11.GL_LINEAR_MIPMAP_NEAREST, true) + val LinearMipmapLinear = new MinFilteringMode(GL11.GL_LINEAR_MIPMAP_LINEAR, true) + } +} diff --git a/src/main/scala/ocelot/desktop/node/nodes/ScreenNode.scala b/src/main/scala/ocelot/desktop/node/nodes/ScreenNode.scala index bbb7c45..24214bc 100644 --- a/src/main/scala/ocelot/desktop/node/nodes/ScreenNode.scala +++ b/src/main/scala/ocelot/desktop/node/nodes/ScreenNode.scala @@ -2,6 +2,7 @@ package ocelot.desktop.node.nodes import ocelot.desktop.color.{Color, IntColor} import ocelot.desktop.geometry.{Rect2D, Size2D} +import ocelot.desktop.graphics.Texture.MinFilteringMode import ocelot.desktop.graphics.{Graphics, IconSource, ScreenViewport} import ocelot.desktop.node.Node.{HighlightThickness, NoHighlightSize, Size} import ocelot.desktop.node.nodes.ScreenNode.{FontHeight, FontWidth, Margin} @@ -116,44 +117,55 @@ class ScreenNode(val screen: Screen) super.setupContextMenu(menu, event) } - private def drawScreenTexture(): Unit = { - if (bufferRendered) return + private def drawScreenTexture(needsMipmap: Boolean): Unit = { + if (!bufferRendered) { + viewport.renderWith { + val width = (screen.getWidth * FontWidth).toInt + val height = (screen.getHeight * FontHeight).toInt - viewport.renderWith { - val width = (screen.getWidth * FontWidth).toInt - val height = (screen.getHeight * FontHeight).toInt + viewport.resize(width, height) - viewport.resize(width, height) + var color: Short = 0 - var color: Short = 0 + for (y <- 0 until screen.getHeight) { + for (x <- 0 until screen.getWidth) { + if (x == 0 || viewport.font.charWidth(screen.data.buffer(y)(x - 1)) != 16) { + color = screen.data.color(y)(x) - for (y <- 0 until screen.getHeight) { - for (x <- 0 until screen.getWidth) { - if (x == 0 || viewport.font.charWidth(screen.data.buffer(y)(x - 1)) != 16) { - color = screen.data.color(y)(x) + viewport.background = IntColor(PackedColor.unpackBackground(color, screen.data.format)) + viewport.foreground = IntColor(PackedColor.unpackForeground(color, screen.data.format)) - viewport.background = IntColor(PackedColor.unpackBackground(color, screen.data.format)) - viewport.foreground = IntColor(PackedColor.unpackForeground(color, screen.data.format)) - - viewport.char(x * FontWidth, y * FontHeight, screen.data.buffer(y)(x)) + viewport.char(x * FontWidth, y * FontHeight, screen.data.buffer(y)(x)) + } } } } bufferRendered = true } + + if (needsMipmap) { + viewport.generateMipmap() + } } - def drawScreenData(g: Graphics, startX: Float, startY: Float, scaleX: Float, scaleY: Float): Unit = { + def drawScreenData( + g: Graphics, + startX: Float, + startY: Float, + scaleX: Float, + scaleY: Float, + filteringMode: MinFilteringMode + ): Unit = { g.save() g.scale(scaleX, scaleY) val startXScaled = startX / scaleX val startYScaled = startY / scaleY - drawScreenTexture() + drawScreenTexture(filteringMode.needsMipmap) val bounds = Rect2D(startXScaled, startYScaled, viewport.width, viewport.height) - g.blitScreenViewport(viewport, bounds) + g.blitScreenViewport(viewport, bounds, filteringMode = filteringMode) g.restore() } @@ -351,7 +363,8 @@ class ScreenNode(val screen: Screen) virtualScreenBounds.x + virtualScreenBounds.w / 2 - (pixelDataSize.width * scale) / 2, virtualScreenBounds.y + virtualScreenBounds.h / 2 - (pixelDataSize.height * scale) / 2, scale, - scale + scale, + MinFilteringMode.NearestMipmapNearest ) } // Drawing simple overlay otherwise diff --git a/src/main/scala/ocelot/desktop/ui/widget/settings/UISettingsTab.scala b/src/main/scala/ocelot/desktop/ui/widget/settings/UISettingsTab.scala index 5a62da5..6fba99c 100644 --- a/src/main/scala/ocelot/desktop/ui/widget/settings/UISettingsTab.scala +++ b/src/main/scala/ocelot/desktop/ui/widget/settings/UISettingsTab.scala @@ -68,6 +68,15 @@ class UISettingsTab extends SettingsTab { } }, Padding2D(bottom = 8)) + children :+= new PaddingBox(new Checkbox("Enable mipmaps for screen windows", + initialValue = Settings.get.screenWindowMipmap) { + override def minimumSize: Size2D = Size2D(512, 24) + + override def onValueChanged(value: Boolean): Unit = { + Settings.get.screenWindowMipmap = value + } + }, Padding2D(bottom = 8)) + children :+= new PaddingBox(new Slider((Settings.get.scaleFactor - 1) / 2, "Interface scale", 5) { override def minimumSize: Size2D = Size2D(512, 24) diff --git a/src/main/scala/ocelot/desktop/util/SettingsData.scala b/src/main/scala/ocelot/desktop/util/SettingsData.scala index 43ecf2f..d99bbe3 100644 --- a/src/main/scala/ocelot/desktop/util/SettingsData.scala +++ b/src/main/scala/ocelot/desktop/util/SettingsData.scala @@ -42,6 +42,8 @@ class SettingsData { var openLastWorkspace: Boolean = true var renderScreenDataOnNodes: Boolean = true + var screenWindowMipmap: Boolean = true + private val mirror = universe.runtimeMirror(getClass.getClassLoader).reflect(this) def updateWith(data: SettingsData): Unit = { diff --git a/src/main/scala/ocelot/desktop/windows/ScreenWindow.scala b/src/main/scala/ocelot/desktop/windows/ScreenWindow.scala index d28cb00..375289b 100644 --- a/src/main/scala/ocelot/desktop/windows/ScreenWindow.scala +++ b/src/main/scala/ocelot/desktop/windows/ScreenWindow.scala @@ -4,6 +4,7 @@ import ocelot.desktop.audio.SoundSource import ocelot.desktop.color.RGBAColor import ocelot.desktop.geometry.{Rect2D, Size2D, Vector2D} import ocelot.desktop.graphics.Graphics +import ocelot.desktop.graphics.Texture.MinFilteringMode import ocelot.desktop.node.nodes.ScreenNode import ocelot.desktop.node.nodes.ScreenNode.{FontHeight, FontWidth} import ocelot.desktop.ui.UiHandler @@ -12,7 +13,7 @@ import ocelot.desktop.ui.event.{DragEvent, KeyEvent, MouseEvent, ScrollEvent} import ocelot.desktop.ui.widget.window.BasicWindow import ocelot.desktop.util.{DrawUtils, Logging} import ocelot.desktop.windows.ScreenWindow._ -import ocelot.desktop.{ColorScheme, OcelotDesktop} +import ocelot.desktop.{ColorScheme, OcelotDesktop, Settings} import org.apache.commons.lang3.StringUtils import org.lwjgl.input.Keyboard import totoro.ocelot.brain.entity.Screen @@ -254,7 +255,12 @@ class ScreenWindow(screenNode: ScreenNode) extends BasicWindow with Logging { startX, startY, scaleX, - scaleY + scaleY, + if (Settings.get.screenWindowMipmap) { + MinFilteringMode.LinearMipmapLinear + } else { + MinFilteringMode.Nearest + } ) }