diff --git a/tsvm_core/src/net/torvald/tsvm/peripheral/IOSpace.kt b/tsvm_core/src/net/torvald/tsvm/peripheral/IOSpace.kt index 6cae0a8..57ee373 100644 --- a/tsvm_core/src/net/torvald/tsvm/peripheral/IOSpace.kt +++ b/tsvm_core/src/net/torvald/tsvm/peripheral/IOSpace.kt @@ -3,6 +3,8 @@ package net.torvald.tsvm.peripheral import com.badlogic.gdx.Gdx import com.badlogic.gdx.Input import com.badlogic.gdx.InputProcessor +import com.badlogic.gdx.math.Vector2 +import com.badlogic.gdx.utils.viewport.Viewport import net.torvald.AddressOverflowException import net.torvald.DanglingPointerException import net.torvald.UnsafeHelper @@ -18,10 +20,18 @@ class IOSpace(val vm: VM) : PeriBase("io"), InputProcessor { return vm } - /** Absolute x-position of the computer GUI */ - var guiPosX = 0 - /** Absolute y-position of the computer GUI */ - var guiPosY = 0 + /** + * Viewport that maps screen pixels (as reported by `Gdx.input.x/y`) to the VM's + * logical framebuffer coordinate space. The host application owns the rendering + * camera, so the host is responsible for installing a viewport whose world + * coordinates match the VM framebuffer (origin top-left, world size = framebuffer + * size in pixels) and whose screen rectangle matches where the VM is drawn. + * + * If left null, `Gdx.input.x/y` is forwarded verbatim — only correct when the VM + * occupies the entire window at 1:1 scale. + */ + var inputViewport: Viewport? = null + private val tmpMouseVec = Vector2() /** Accepts a keycode */ private val keyboardBuffer = CircularArray(32, true) @@ -296,9 +306,20 @@ class IOSpace(val vm: VM) : PeriBase("io"), InputProcessor { keyEventBuffers.fill(0) if (isFocused) { - // store mouse info - mouseX = (Gdx.input.x + guiPosX).toShort() - mouseY = (Gdx.input.y + guiPosY).toShort() + // store mouse info; unproject through the host-provided viewport so the + // VM sees logical framebuffer pixels regardless of window magnification, + // letterboxing or sub-region placement done by an embedding GDX app. + val vp = inputViewport + if (vp != null) { + tmpMouseVec.set(Gdx.input.x.toFloat(), Gdx.input.y.toFloat()) + vp.unproject(tmpMouseVec) + mouseX = tmpMouseVec.x.toInt().toShort() + mouseY = tmpMouseVec.y.toInt().toShort() + } + else { + mouseX = Gdx.input.x.toShort() + mouseY = Gdx.input.y.toShort() + } mouseButtons = (if (Gdx.input.isButtonPressed(Input.Buttons.LEFT)) 1 else 0) or (if (Gdx.input.isButtonPressed(Input.Buttons.RIGHT)) 2 else 0) diff --git a/tsvm_executable/src/net/torvald/tsvm/VMGUI.kt b/tsvm_executable/src/net/torvald/tsvm/VMGUI.kt index 3be6ee1..fee0fbf 100644 --- a/tsvm_executable/src/net/torvald/tsvm/VMGUI.kt +++ b/tsvm_executable/src/net/torvald/tsvm/VMGUI.kt @@ -8,6 +8,8 @@ import com.badlogic.gdx.graphics.g2d.SpriteBatch import com.badlogic.gdx.graphics.g2d.TextureRegion import com.badlogic.gdx.graphics.glutils.FrameBuffer import com.badlogic.gdx.graphics.glutils.ShaderProgram +import com.badlogic.gdx.utils.viewport.StretchViewport +import com.badlogic.gdx.utils.viewport.Viewport import net.torvald.terrarum.DefaultGL32Shaders import net.torvald.tsvm.peripheral.* import net.torvald.tsvm.peripheral.GraphicsAdapter.Companion.DRAW_SHADER_VERT @@ -48,6 +50,14 @@ class VMGUI(val loaderInfo: EmulInstance, val viewportWidth: Int, val viewportHe lateinit var batch: SpriteBatch lateinit var camera: OrthographicCamera + /** + * Maps window pixels to the VM framebuffer (origin top-left, world size = + * viewportWidth × viewportHeight). Stretches to fill the whole window so it + * matches the `MAGN`-scaled blit at the end of [renderGame]. Handed to + * [IOSpace.inputViewport] so mouse coordinates unproject correctly. + */ + lateinit var inputViewport: Viewport + var gpu: GraphicsAdapter? = null lateinit var vmRunner: VMRunner lateinit var coroutineJob: Thread @@ -103,9 +113,20 @@ class VMGUI(val loaderInfo: EmulInstance, val viewportWidth: Int, val viewportHe gpuFBO = FrameBuffer(Pixmap.Format.RGBA8888, viewportWidth, viewportHeight, false) winFBO = FrameBuffer(Pixmap.Format.RGBA8888, viewportWidth, viewportHeight, false) + val inputCam = OrthographicCamera().also { + it.setToOrtho(true, viewportWidth.toFloat(), viewportHeight.toFloat()) + } + inputViewport = StretchViewport(viewportWidth.toFloat(), viewportHeight.toFloat(), inputCam) + inputViewport.update(Gdx.graphics.width, Gdx.graphics.height, true) + init() } + override fun resize(width: Int, height: Int) { + super.resize(width, height) + inputViewport.update(width, height, true) + } + private fun init() { vm.init() @@ -148,6 +169,7 @@ class VMGUI(val loaderInfo: EmulInstance, val viewportWidth: Int, val viewportHe } Gdx.input.inputProcessor = vm.getIO() + vm.getIO().inputViewport = inputViewport if (usememvwr) memvwr = Memvwr(vm)