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 net.torvald.tsvm.peripheral.*;
import java.util.HashMap;
public class TerranBASIC {
public static String appTitle = "TerranBASIC";
@@ -25,7 +27,10 @@ public class TerranBASIC {
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);
new Lwjgl3Application(new VMGUI(tbasrunner, WIDTH, HEIGHT), appConfig);
}

View File

@@ -378,59 +378,76 @@ METADATA -
uint16 HEIGHT
uint16 FPS (0: play as fast as can)
uint32 NUMBER OF FRAMES
uint16 PACKET TYPE
uint16 GLOBAL PACKET TYPE (will be deprecated; please use 255,0)
byte[12] RESERVED
Packet Type Low Byte:
0: 256-Colour frame
1: 256-Colour frame with palette data
2: 4096-Colour frame (stored as two byte-planes)
4: iPF no-alpha indicator (see iPF Type Numbers for details)
5: iPF with alpha indicator (see iPF Type Numbers for details)
16: Series of JPEGs
18: Series of PNGs
20: Series of TGAs
21: Series of TGA/GZs
255: Every frame specifies the type
Packet Types:
<video>
0,0: 256-Colour frame
1,0: 256-Colour frame with palette data
2,0: 4096-Colour frame (stored as two byte-planes)
4,t: iPF no-alpha indicator (see iPF Type Numbers for details)
5,t: iPF with alpha indicator (see iPF Type Numbers for details)
16,0: Series of JPEGs
18,0: Series of PNGs
20,0: Series of TGAs
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)
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
* FRAMEDATA COMPRESSED IN GZIP
TYPE 1 Packet -
GLOBAL TYPE 1 Packet -
byte[512] Palette Data
uint32 SIZE OF FRAMEDATA
* FRAMEDATA COMPRESSED IN GZIP
TYPE 2 Packet -
GLOBAL TYPE 2 Packet -
uint32 SIZE OF FRAMEDATA BYTE-PLANE 1
* FRAMEDATA COMPRESSED IN GZIP
uint32 SIZE OF FRAMEDATA BYTE-PLANE 2
* FRAMEDATA COMPRESSED IN GZIP
iPF Packet -
GLOBAL iPF Packet -
uint32 SIZE OF FRAMEDATA
* 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
* FRAMEDATA (COMPRESSED IN GZIP for TGA/GZ)
TYPE 255 Packet -
GLOBAL TYPE 255 Packet -
uint16 TYPE OF PACKET // follows the Metadata Packet Type scheme
uint32 SIZE OF 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 update()
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) {
this.color = if (predicate()) colourIfTrue else colourIfFalse
override fun dispose() {
}
}
}
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.Gdx
import com.badlogic.gdx.Input.Buttons
import com.badlogic.gdx.files.FileHandle
import com.badlogic.gdx.graphics.*
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)
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 var currentVMselection: Int? = 0 // null: emulator menu is selected
var currentVMselection: Int? = 0 // null: emulator menu is selected
lateinit var batch: SpriteBatch
lateinit var fbatch: FlippingSpriteBatch
@@ -117,6 +118,8 @@ class VMEmuExecutable(val windowWidth: Int, val windowHeight: Int, var panelsX:
vms[index] = VMRunnerInfo(vm, profileName)
}
internal fun getCurrentlySelectedVM(): VMRunnerInfo? = if (currentVMselection == null) null else vms[currentVMselection!!]
private fun writeProfilesToFile(outFile: FileHandle) {
val out = StringBuilder()
out.append('{')
@@ -376,6 +379,7 @@ class VMEmuExecutable(val windowWidth: Int, val windowHeight: Int, var panelsX:
override fun dispose() {
super.dispose()
tabs.forEach { it.dispose() }
batch.dispose()
fbatch.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 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 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 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)
}
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.color = EmulatorGuiToolkit.Theme.COL_ACTIVE2
@@ -447,7 +458,22 @@ class VMEmuExecutable(val windowWidth: Int, val windowHeight: Int, var panelsX:
private fun updateMenu() {
// 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
tabs[menuTabSel].update()
@@ -584,11 +610,12 @@ object EmulatorGuiToolkit {
val COL_HIGHLIGHT = Color(0xe43380ff.toInt()) // magenta
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_WELL = Color(0x374854ff.toInt())
val COL_WELL2 = Color(0x3e5261ff.toInt())
val COL_WELL2 = Color(0x3f5360ff.toInt())
}
}