emulator: malloc viewer

This commit is contained in:
minjaesong
2023-01-03 00:50:48 +09:00
parent 5cfbf2ac24
commit e0d1948bfc
7 changed files with 235 additions and 35 deletions

View File

@@ -5,6 +5,8 @@ import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration;
import com.badlogic.gdx.graphics.glutils.ShaderProgram; import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import net.torvald.tsvm.peripheral.*; import net.torvald.tsvm.peripheral.*;
import java.util.HashMap;
public class TerranBASIC { public class TerranBASIC {
public static String appTitle = "TerranBASIC"; public static String appTitle = "TerranBASIC";
@@ -25,7 +27,10 @@ public class TerranBASIC {
appConfig.setWindowedMode(WIDTH, HEIGHT); appConfig.setWindowedMode(WIDTH, HEIGHT);
VM tbasvm = new VM("./assets", 64 << 10, new TheRealWorld(), new VMProgramRom[]{TBASRelBios.INSTANCE}, 2); HashMap<String, VMWatchdog> watchdogs = new HashMap<>();
watchdogs.put("TEVD_SYNC", TevdSyncWatchdog.INSTANCE);
VM tbasvm = new VM("./assets", 64 << 10, new TheRealWorld(), new VMProgramRom[]{TBASRelBios.INSTANCE}, 2, watchdogs);
EmulInstance tbasrunner = new EmulInstance(tbasvm, "net.torvald.tsvm.peripheral.ReferenceGraphicsAdapter", "assets/disk0", 560, 448); EmulInstance tbasrunner = new EmulInstance(tbasvm, "net.torvald.tsvm.peripheral.ReferenceGraphicsAdapter", "assets/disk0", 560, 448);
new Lwjgl3Application(new VMGUI(tbasrunner, WIDTH, HEIGHT), appConfig); new Lwjgl3Application(new VMGUI(tbasrunner, WIDTH, HEIGHT), appConfig);
} }

View File

@@ -378,59 +378,76 @@ METADATA -
uint16 HEIGHT uint16 HEIGHT
uint16 FPS (0: play as fast as can) uint16 FPS (0: play as fast as can)
uint32 NUMBER OF FRAMES uint32 NUMBER OF FRAMES
uint16 PACKET TYPE uint16 GLOBAL PACKET TYPE (will be deprecated; please use 255,0)
byte[12] RESERVED byte[12] RESERVED
Packet Type Low Byte: Packet Types:
0: 256-Colour frame <video>
1: 256-Colour frame with palette data 0,0: 256-Colour frame
2: 4096-Colour frame (stored as two byte-planes) 1,0: 256-Colour frame with palette data
4: iPF no-alpha indicator (see iPF Type Numbers for details) 2,0: 4096-Colour frame (stored as two byte-planes)
5: iPF with alpha indicator (see iPF Type Numbers for details) 4,t: iPF no-alpha indicator (see iPF Type Numbers for details)
16: Series of JPEGs 5,t: iPF with alpha indicator (see iPF Type Numbers for details)
18: Series of PNGs 16,0: Series of JPEGs
20: Series of TGAs 18,0: Series of PNGs
21: Series of TGA/GZs 20,0: Series of TGAs
255: Every frame specifies the type 21,0: Series of TGA/GZs
255,0: Every frame specifies the type
<audio>
0,16: Raw PCM Mono
1,16: Raw PCM Stereo
2,16: ADPCM Mono
3,16: ADPCM Stereo
<special>
255,255: sync packet (wait until the next frame)
Packet Type High Byte (iPF Type Numbers) Packet Type High Byte (iPF Type Numbers)
0..7: iPF Type 1..8 0..7: iPF Type 1..8
Packet Types for Audio (High Byte=16)
0: Raw PCM Mono
1: Raw PCM Stereo
2: ADPCM Mono
3: ADPCM Stereo
TYPE 0 Packet -
GLOBAL TYPE 0 Packet -
uint32 SIZE OF FRAMEDATA uint32 SIZE OF FRAMEDATA
* FRAMEDATA COMPRESSED IN GZIP * FRAMEDATA COMPRESSED IN GZIP
TYPE 1 Packet - GLOBAL TYPE 1 Packet -
byte[512] Palette Data byte[512] Palette Data
uint32 SIZE OF FRAMEDATA uint32 SIZE OF FRAMEDATA
* FRAMEDATA COMPRESSED IN GZIP * FRAMEDATA COMPRESSED IN GZIP
TYPE 2 Packet - GLOBAL TYPE 2 Packet -
uint32 SIZE OF FRAMEDATA BYTE-PLANE 1 uint32 SIZE OF FRAMEDATA BYTE-PLANE 1
* FRAMEDATA COMPRESSED IN GZIP * FRAMEDATA COMPRESSED IN GZIP
uint32 SIZE OF FRAMEDATA BYTE-PLANE 2 uint32 SIZE OF FRAMEDATA BYTE-PLANE 2
* FRAMEDATA COMPRESSED IN GZIP * FRAMEDATA COMPRESSED IN GZIP
iPF Packet - GLOBAL iPF Packet -
uint32 SIZE OF FRAMEDATA uint32 SIZE OF FRAMEDATA
* FRAMEDATA COMPRESSED IN GZIP // only the actual gzip (and no UNCOMPRESSED SIZE) of the "Blocks.gz" is stored * FRAMEDATA COMPRESSED IN GZIP // only the actual gzip (and no UNCOMPRESSED SIZE) of the "Blocks.gz" is stored
TYPE 16+ Packet - GLOBAL TYPE 16+ Packet -
uint32 SIZE OF FRAMEDATA BYTE-PLANE 1 uint32 SIZE OF FRAMEDATA BYTE-PLANE 1
* FRAMEDATA (COMPRESSED IN GZIP for TGA/GZ) * FRAMEDATA (COMPRESSED IN GZIP for TGA/GZ)
TYPE 255 Packet - GLOBAL TYPE 255 Packet -
uint16 TYPE OF PACKET // follows the Metadata Packet Type scheme uint16 TYPE OF PACKET // follows the Metadata Packet Type scheme
uint32 SIZE OF PACKET uint32 SIZE OF PACKET
* FRAMEDATA or PACKET * FRAMEDATA or PACKET
Sync Packet (subset of GLOBAL TYPE 255 Packet) -
uint16 0xFFFF (type of packet for Global Type 255)
Frame Timing
If the global type is not 255, each packet is interpreted as a single full frame, and then will wait for the next
frame time; For type 255 however, the assumption no longer holds and each frame can have multiple packets, and thus
needs explicit "sync" packet for proper frame timing.
NOTE FROM DEVELOPER
In the future, the global packet type will be deprecated.
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------

View File

@@ -0,0 +1,24 @@
package net.torvald.tsvm
import com.badlogic.gdx.graphics.g2d.SpriteBatch
/**
* Created by minjaesong on 2023-01-02.
*/
class DummyMenu(parent: VMEmuExecutable, x: Int, y: Int, w: Int, h: Int) : EmuMenu(parent, x, y, w, h) {
override fun show() {
}
override fun hide() {
}
override fun update() {
}
override fun render(batch: SpriteBatch) {
}
override fun dispose() {
}
}

View File

@@ -11,5 +11,6 @@ abstract class EmuMenu(val parent: VMEmuExecutable, val x: Int, val y: Int, val
abstract fun hide() abstract fun hide()
abstract fun update() abstract fun update()
abstract fun render(batch: SpriteBatch) abstract fun render(batch: SpriteBatch)
abstract fun dispose()
} }

View File

@@ -0,0 +1,123 @@
package net.torvald.tsvm
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.graphics.Pixmap
import com.badlogic.gdx.graphics.Texture
import com.badlogic.gdx.graphics.g2d.SpriteBatch
import net.torvald.tsvm.EmulatorGuiToolkit.Theme.COL_LAND
import net.torvald.tsvm.EmulatorGuiToolkit.Theme.COL_WELL
import net.torvald.tsvm.VMEmuExecutableWrapper.Companion.FONT
/**
* Created by minjaesong on 2023-01-02.
*/
class MMUMenu(parent: VMEmuExecutable, x: Int, y: Int, w: Int, h: Int) : EmuMenu(parent, x, y, w, h) {
override fun show() {
}
override fun hide() {
}
override fun update() {
}
override fun render(batch: SpriteBatch) {
batch.color = Color.WHITE
val vmInfo = parent.getCurrentlySelectedVM()
if (vmInfo == null) {
batch.inUse {
FONT.draw(batch, "Please select a VM", 12f, 11f + 0* FONT.H)
}
}
else vmInfo.let { (vm, vmName) ->
batch.inUse {
FONT.draw(batch, "Allocated size: ${vm.allocatedBlockCount * vm.MALLOC_UNIT}", 12f, 11f + 0* FONT.H)
}
drawAllocMap(batch, vm, 62f, 2f * FONT.H)
}
}
private val plotColset = intArrayOf(
0xea5545ff.toInt(), 0xf46a9bff.toInt(), 0xef9b20ff.toInt(), 0xedbf33ff.toInt(), 0xede15bff.toInt(), 0xbdcf32ff.toInt(),
0x87bc45ff.toInt(), 0x27aeefff.toInt(), 0xb33dc6ff.toInt(), 0xe60049ff.toInt(), 0x0bb4ffff.toInt(), 0x50e991ff.toInt(),
0xe6d800ff.toInt(), 0x9b19f5ff.toInt(), 0xffa300ff.toInt(), 0xdc0ab4ff.toInt(), 0xb3d4ffff.toInt(), 0x00bfa0ff.toInt(),
)
private val plotColours = plotColset.map { Color(it) }
private val memmapPixmap = Pixmap(512, 256, Pixmap.Format.RGBA8888)
private var mallocMap: List<Pair<Int, Int>> = listOf()
private fun drawAllocMap(batch: SpriteBatch, vm: VM, x: Float, y: Float) {
// clear the memmapPixmap
memmapPixmap.setColor(0)
memmapPixmap.fill()
// unallocated map as black
for (i in 0 until vm.memsize / vm.MALLOC_UNIT) {
paintPixel(i.toInt(), 255)
}
try {
// try to update the mallocMap
mallocMap = vm.javaClass.getDeclaredField("mallocSizes").let {
it.isAccessible = true
it.get(vm) as HashMap<Int, Int>
}.entries.map { it.key to it.value }.sortedBy { it.first }
}
catch (e: ConcurrentModificationException) { /* skip update for this frame */ }
// allocated map
mallocMap.forEachIndexed { index, (ptr, size) ->
for (i in 0 until size) {
paintPixel(ptr + i, plotColset[ptr % plotColset.size])
}
}
val memmapTex = Texture(memmapPixmap)
batch.inUse {
// draw allocation map
batch.color = COL_WELL
batch.fillRect(x, y, 512, 256)
batch.color = Color.WHITE
batch.draw(memmapTex, x, y)
// draw textual list
mallocMap.forEachIndexed { index, (ptr, size) ->
// hackishly draw textual list
if (index < 52) {
val xoff = 15f + 155f * (index / 13)
val yoff = 286f + ((index % 13) * FONT.H)
batch.color = plotColours[ptr % plotColset.size]
batch.fillRect(xoff, yoff + 1, 10, 10)
batch.color = Color.WHITE
FONT.draw(batch, " $size at $ptr", xoff, yoff)
}
}
}
memmapTex.dispose()
}
private fun paintPixel(index: Int, colour: Int) {
memmapPixmap.setColor(colour)
memmapPixmap.drawPixel(index / 256, index % 256)
}
override fun dispose() {
memmapPixmap.dispose()
}
}

View File

@@ -206,7 +206,10 @@ class ProfilesMenu(parent: VMEmuExecutable, x: Int, y: Int, w: Int, h: Int) : Em
} }
} }
private fun SpriteBatch.setColourBy(colourIfTrue: Color = EmulatorGuiToolkit.Theme.COL_ACTIVE3, colourIfFalse: Color = Color.WHITE, predicate: () -> Boolean) { override fun dispose() {
this.color = if (predicate()) colourIfTrue else colourIfFalse
} }
} }
fun SpriteBatch.setColourBy(colourIfTrue: Color = EmulatorGuiToolkit.Theme.COL_ACTIVE3, colourIfFalse: Color = Color.WHITE, predicate: () -> Boolean) {
this.color = if (predicate()) colourIfTrue else colourIfFalse
}

View File

@@ -2,6 +2,7 @@ package net.torvald.tsvm
import com.badlogic.gdx.ApplicationAdapter import com.badlogic.gdx.ApplicationAdapter
import com.badlogic.gdx.Gdx import com.badlogic.gdx.Gdx
import com.badlogic.gdx.Input.Buttons
import com.badlogic.gdx.files.FileHandle import com.badlogic.gdx.files.FileHandle
import com.badlogic.gdx.graphics.* import com.badlogic.gdx.graphics.*
import com.badlogic.gdx.graphics.g2d.SpriteBatch import com.badlogic.gdx.graphics.g2d.SpriteBatch
@@ -64,11 +65,11 @@ class VMEmuExecutable(val windowWidth: Int, val windowHeight: Int, var panelsX:
val watchdogs = hashMapOf<String, VMWatchdog>("TEVD_SYNC" to TEVD_SYNC) val watchdogs = hashMapOf<String, VMWatchdog>("TEVD_SYNC" to TEVD_SYNC)
private data class VMRunnerInfo(val vm: VM, val name: String) data class VMRunnerInfo(val vm: VM, val name: String)
private val vms = arrayOfNulls<VMRunnerInfo>(this.panelsX * this.panelsY - 1) // index: # of the window where the reboot was requested private val vms = arrayOfNulls<VMRunnerInfo>(this.panelsX * this.panelsY - 1) // index: # of the window where the reboot was requested
private var currentVMselection: Int? = 0 // null: emulator menu is selected var currentVMselection: Int? = 0 // null: emulator menu is selected
lateinit var batch: SpriteBatch lateinit var batch: SpriteBatch
lateinit var fbatch: FlippingSpriteBatch lateinit var fbatch: FlippingSpriteBatch
@@ -117,6 +118,8 @@ class VMEmuExecutable(val windowWidth: Int, val windowHeight: Int, var panelsX:
vms[index] = VMRunnerInfo(vm, profileName) vms[index] = VMRunnerInfo(vm, profileName)
} }
internal fun getCurrentlySelectedVM(): VMRunnerInfo? = if (currentVMselection == null) null else vms[currentVMselection!!]
private fun writeProfilesToFile(outFile: FileHandle) { private fun writeProfilesToFile(outFile: FileHandle) {
val out = StringBuilder() val out = StringBuilder()
out.append('{') out.append('{')
@@ -376,6 +379,7 @@ class VMEmuExecutable(val windowWidth: Int, val windowHeight: Int, var panelsX:
override fun dispose() { override fun dispose() {
super.dispose() super.dispose()
tabs.forEach { it.dispose() }
batch.dispose() batch.dispose()
fbatch.dispose() fbatch.dispose()
fullscreenQuad.dispose() fullscreenQuad.dispose()
@@ -390,9 +394,16 @@ class VMEmuExecutable(val windowWidth: Int, val windowHeight: Int, var panelsX:
private val menuTabX = windowWidth * (panelsX-1) + 2 private val menuTabX = windowWidth * (panelsX-1) + 2
private val menuTabY =windowHeight * (panelsY-1) + FONT.H + 2 private val menuTabY =windowHeight * (panelsY-1) + FONT.H + 2
private val menuTabs = listOf("Profiles", "Machine", "COMs", "Cards", "Setup") private val menuTabs = listOf("Profiles", "MMU", "Machine", "COMs", "Cards", "Setup")
private val tabPos = (menuTabs + "").mapIndexed { index, _ -> 1 + menuTabs.subList(0, index).sumBy { it.length } + 2 * index } private val tabPos = (menuTabs + "").mapIndexed { index, _ -> 1 + menuTabs.subList(0, index).sumBy { it.length } + 2 * index }
private val tabs = listOf(ProfilesMenu(this, menuTabX, menuTabY, menuTabW, menuTabH)) private val tabs = listOf(
ProfilesMenu(this, menuTabX, menuTabY, menuTabW, menuTabH),
MMUMenu(this, menuTabX, menuTabY, menuTabW, menuTabH),
DummyMenu(this, menuTabX, menuTabY, menuTabW, menuTabH),
DummyMenu(this, menuTabX, menuTabY, menuTabW, menuTabH),
DummyMenu(this, menuTabX, menuTabY, menuTabW, menuTabH),
DummyMenu(this, menuTabX, menuTabY, menuTabW, menuTabH),
)
private var menuTabSel = 0 private var menuTabSel = 0
private var tabChangeRequested: Int? = 0 // null: not requested private var tabChangeRequested: Int? = 0 // null: not requested
@@ -423,7 +434,7 @@ class VMEmuExecutable(val windowWidth: Int, val windowHeight: Int, var panelsX:
FONT.draw(batch, menuTabs[k], textX, y) FONT.draw(batch, menuTabs[k], textX, y)
} }
else { else {
batch.color = EmulatorGuiToolkit.Theme.COL_TAB_NOT_SELECTED batch.color = if (k % 2 == 0) EmulatorGuiToolkit.Theme.COL_TAB_NOT_SELECTED else EmulatorGuiToolkit.Theme.COL_TAB_NOT_SELECTED2
batch.fillRect(textX - FONT.W, y, FONT.W * (menuTabs[k].length + 2f), FONT.H.toFloat()) batch.fillRect(textX - FONT.W, y, FONT.W * (menuTabs[k].length + 2f), FONT.H.toFloat())
batch.color = EmulatorGuiToolkit.Theme.COL_ACTIVE2 batch.color = EmulatorGuiToolkit.Theme.COL_ACTIVE2
@@ -447,7 +458,22 @@ class VMEmuExecutable(val windowWidth: Int, val windowHeight: Int, var panelsX:
private fun updateMenu() { private fun updateMenu() {
// update the tab // update the tab
var tabSelected = -1
val x = (panelsX - 1) * windowWidth
val y = (panelsY - 1) * windowHeight
val mx = Gdx.input.x
val my = Gdx.input.y
if (Gdx.input.isButtonPressed(Buttons.LEFT) && my in y until y + FONT.H) {
for (k in menuTabs.indices) {
val textX = x + FONT.W * tabPos[k]
if (mx in textX - FONT.W until textX - FONT.W + FONT.W * (menuTabs[k].length + 2)) {
tabSelected = k
}
}
}
if (tabSelected >= 0 && tabSelected != menuTabSel) {
tabChangeRequested = tabSelected
}
// actually update the view within the tabs // actually update the view within the tabs
tabs[menuTabSel].update() tabs[menuTabSel].update()
@@ -584,11 +610,12 @@ object EmulatorGuiToolkit {
val COL_HIGHLIGHT = Color(0xe43380ff.toInt()) // magenta val COL_HIGHLIGHT = Color(0xe43380ff.toInt()) // magenta
val COL_DISABLED = Color(0xaaaaaaff.toInt()) val COL_DISABLED = Color(0xaaaaaaff.toInt())
val COL_TAB_NOT_SELECTED = Color(0x503cd4ff) // dark blue val COL_TAB_NOT_SELECTED = Color(0x4d39cbff) // dark blue
val COL_TAB_NOT_SELECTED2 = Color(0x5949e0ff) // dark blue
val COL_LAND = Color(0x6b8ba2ff.toInt()) val COL_LAND = Color(0x6b8ba2ff.toInt())
val COL_WELL = Color(0x374854ff.toInt()) val COL_WELL = Color(0x374854ff.toInt())
val COL_WELL2 = Color(0x3e5261ff.toInt()) val COL_WELL2 = Color(0x3f5360ff.toInt())
} }
} }