mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2024-11-25 18:33:11 +00:00
bring in the logic probe; fixed z80 debugger; moved apple2 to BasicScanlinePlatform
This commit is contained in:
parent
d4873545b2
commit
37c7ba8eb2
@ -121,6 +121,9 @@ export interface Platform {
|
||||
getCPUState?() : CpuState;
|
||||
|
||||
debugSymbols? : DebugSymbols;
|
||||
|
||||
startProbing?() : ProbeRecorder;
|
||||
stopProbing?() : void;
|
||||
}
|
||||
|
||||
export interface Preset {
|
||||
@ -1070,6 +1073,7 @@ export function lookupSymbol(platform:Platform, addr:number, extra:boolean) {
|
||||
var sym = addr2symbol[addr];
|
||||
return extra ? (sym + " + $" + hex(start-addr)) : sym;
|
||||
}
|
||||
if (!extra) break;
|
||||
addr--;
|
||||
}
|
||||
return "";
|
||||
@ -1198,8 +1202,9 @@ export abstract class BasicZ80ScanlinePlatform extends BaseZ80Platform {
|
||||
/// new style
|
||||
|
||||
import { Bus, Resettable, FrameBased, VideoSource, SampledAudioSource, AcceptsROM, AcceptsKeyInput, SavesState, SavesInputState, HasCPU } from "./devices";
|
||||
import { CPUClockHook, RasterFrameBased } from "./devices";
|
||||
import { Probeable, RasterFrameBased } from "./devices";
|
||||
import { SampledAudio } from "./audio";
|
||||
import { ProbeRecorder } from "./recorder";
|
||||
|
||||
interface Machine extends Bus, Resettable, FrameBased, AcceptsROM, HasCPU, SavesState<EmuState>, SavesInputState<any> {
|
||||
}
|
||||
@ -1216,6 +1221,9 @@ function hasKeyInput(arg:any): arg is AcceptsKeyInput {
|
||||
function isRaster(arg:any): arg is RasterFrameBased {
|
||||
return typeof arg.getRasterY === 'function';
|
||||
}
|
||||
function hasProbe(arg:any): arg is Probeable {
|
||||
return typeof arg.connectProbe == 'function';
|
||||
}
|
||||
|
||||
export abstract class BaseMachinePlatform<T extends Machine> extends BaseDebugPlatform implements Platform {
|
||||
machine : T;
|
||||
@ -1224,6 +1232,9 @@ export abstract class BaseMachinePlatform<T extends Machine> extends BaseDebugPl
|
||||
video : RasterVideo;
|
||||
audio : SampledAudio;
|
||||
poller : ControllerPoller;
|
||||
probeRecorder : ProbeRecorder;
|
||||
startProbing;
|
||||
stopProbing;
|
||||
|
||||
abstract newMachine() : T;
|
||||
abstract getToolForFilename(s:string) : string;
|
||||
@ -1246,7 +1257,7 @@ export abstract class BaseMachinePlatform<T extends Machine> extends BaseDebugPl
|
||||
saveControlsState() { return this.machine.saveControlsState(); }
|
||||
|
||||
start() {
|
||||
var m = this.machine;
|
||||
const m = this.machine;
|
||||
this.timer = new AnimationTimer(60, this.nextFrame.bind(this));
|
||||
if (hasVideo(m)) {
|
||||
var vp = m.getVideoParams();
|
||||
@ -1264,6 +1275,16 @@ export abstract class BaseMachinePlatform<T extends Machine> extends BaseDebugPl
|
||||
this.video.setKeyboardEvents(m.setKeyInput.bind(m));
|
||||
this.poller = new ControllerPoller(m.setKeyInput.bind(m));
|
||||
}
|
||||
if (hasProbe(m)) {
|
||||
this.probeRecorder = new ProbeRecorder(m);
|
||||
this.startProbing = () => {
|
||||
m.connectProbe(this.probeRecorder);
|
||||
return this.probeRecorder;
|
||||
};
|
||||
this.stopProbing = () => {
|
||||
m.connectProbe(null);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
loadROM(title, data) {
|
||||
|
@ -124,12 +124,12 @@ export class BusHook implements Hook<Bus> {
|
||||
var oldread = bus.read.bind(bus);
|
||||
var oldwrite = bus.write.bind(bus);
|
||||
bus.read = (a:number):number => {
|
||||
profiler.logRead(a);
|
||||
var val = oldread(a);
|
||||
profiler.logRead(a,val);
|
||||
return val;
|
||||
}
|
||||
bus.write = (a:number,v:number) => {
|
||||
profiler.logWrite(a);
|
||||
profiler.logWrite(a,v);
|
||||
oldwrite(a,v);
|
||||
}
|
||||
this.unhook = () => {
|
||||
@ -174,25 +174,34 @@ export class CPUInsnHook implements Hook<CPU&InstructionBased> {
|
||||
|
||||
/// PROFILER
|
||||
|
||||
export interface ProbeTime {
|
||||
logClocks(clocks:number);
|
||||
logNewScanline();
|
||||
logNewFrame();
|
||||
}
|
||||
|
||||
export interface ProbeCPU {
|
||||
logExecute(address:number);
|
||||
logInterrupt(type:number);
|
||||
}
|
||||
|
||||
export interface ProbeBus {
|
||||
logRead(address:number);
|
||||
logWrite(address:number);
|
||||
logRead(address:number, value:number);
|
||||
logWrite(address:number, value:number);
|
||||
}
|
||||
|
||||
export interface ProbeIO {
|
||||
logIORead(address:number);
|
||||
logIOWrite(address:number);
|
||||
logIORead(address:number, value:number);
|
||||
logIOWrite(address:number, value:number);
|
||||
}
|
||||
|
||||
export interface ProbeAll extends ProbeCPU, ProbeBus, ProbeIO {
|
||||
export interface ProbeAll extends ProbeTime, ProbeCPU, ProbeBus, ProbeIO {
|
||||
}
|
||||
|
||||
export class NullProbe implements ProbeAll {
|
||||
logClocks() {}
|
||||
logNewScanline() {}
|
||||
logNewFrame() {}
|
||||
logExecute() {}
|
||||
logInterrupt() {}
|
||||
logRead() {}
|
||||
@ -204,15 +213,15 @@ export class NullProbe implements ProbeAll {
|
||||
/// CONVENIENCE
|
||||
|
||||
export interface BasicMachineControlsState {
|
||||
in: Uint8Array;
|
||||
inputs: Uint8Array;
|
||||
}
|
||||
|
||||
export interface BasicMachineState extends BasicMachineControlsState {
|
||||
c: any; // TODO
|
||||
b: Uint8Array;
|
||||
ram: Uint8Array;
|
||||
}
|
||||
|
||||
export abstract class BasicMachine implements HasCPU, Bus, SampledAudioSource, AcceptsROM,
|
||||
export abstract class BasicMachine implements HasCPU, Bus, SampledAudioSource, AcceptsROM, Probeable,
|
||||
SavesState<BasicMachineState>, SavesInputState<BasicMachineControlsState> {
|
||||
|
||||
abstract cpuFrequency : number;
|
||||
@ -234,6 +243,9 @@ export abstract class BasicMachine implements HasCPU, Bus, SampledAudioSource, A
|
||||
scanline : number;
|
||||
frameCycles : number;
|
||||
|
||||
nullProbe = new NullProbe();
|
||||
probe : ProbeAll = this.nullProbe;
|
||||
|
||||
abstract read(a:number) : number;
|
||||
abstract write(a:number, v:number) : void;
|
||||
abstract startScanline() : void;
|
||||
@ -251,6 +263,9 @@ export abstract class BasicMachine implements HasCPU, Bus, SampledAudioSource, A
|
||||
connectVideo(pixels:Uint32Array) : void {
|
||||
this.pixels = pixels;
|
||||
}
|
||||
connectProbe(probe: ProbeAll) : void {
|
||||
this.probe = probe || this.nullProbe;
|
||||
}
|
||||
reset() {
|
||||
this.cpu.reset();
|
||||
}
|
||||
@ -260,33 +275,63 @@ export abstract class BasicMachine implements HasCPU, Bus, SampledAudioSource, A
|
||||
}
|
||||
loadState(state) {
|
||||
this.cpu.loadState(state.c);
|
||||
this.ram.set(state.b);
|
||||
this.inputs.set(state.in);
|
||||
this.ram.set(state.ram);
|
||||
this.inputs.set(state.inputs);
|
||||
}
|
||||
saveState() {
|
||||
return {
|
||||
c:this.cpu.saveState(),
|
||||
b:this.ram.slice(0),
|
||||
in:this.inputs.slice(0),
|
||||
ram:this.ram.slice(0),
|
||||
inputs:this.inputs.slice(0),
|
||||
};
|
||||
}
|
||||
loadControlsState(state) {
|
||||
this.inputs.set(state.in);
|
||||
this.inputs.set(state.inputs);
|
||||
}
|
||||
saveControlsState() {
|
||||
return {
|
||||
in:this.inputs.slice(0)
|
||||
inputs:this.inputs.slice(0)
|
||||
};
|
||||
}
|
||||
advance(cycles : number) : number {
|
||||
for (var i=0; i<cycles; i+=this.advanceCPU())
|
||||
advanceMultiple(cycles : number) : number {
|
||||
for (var i=0; i<cycles; i+=this.advance())
|
||||
;
|
||||
return i;
|
||||
}
|
||||
advanceCPU() {
|
||||
advance() {
|
||||
var c = this.cpu as any;
|
||||
if (c.advanceClock) return c.advanceClock();
|
||||
else if (c.advanceInsn) return c.advanceInsn(1);
|
||||
var n = 1;
|
||||
this.probe.logExecute(this.cpu.getPC());
|
||||
if (c.advanceClock) c.advanceClock();
|
||||
else if (c.advanceInsn) n = c.advanceInsn(1);
|
||||
this.probe.logClocks(n);
|
||||
return n;
|
||||
}
|
||||
connectCPUMemoryBus(membus:Bus) : void {
|
||||
this.cpu.connectMemoryBus({
|
||||
read: (a) => {
|
||||
let val = membus.read(a);
|
||||
this.probe.logRead(a,val);
|
||||
return val;
|
||||
},
|
||||
write: (a,v) => {
|
||||
this.probe.logWrite(a,v);
|
||||
membus.write(a,v);
|
||||
}
|
||||
});
|
||||
}
|
||||
connectCPUIOBus(iobus:Bus) : void {
|
||||
this.cpu['connectIOBus']({
|
||||
read: (a) => {
|
||||
let val = iobus.read(a);
|
||||
this.probe.logIORead(a,val);
|
||||
return val;
|
||||
},
|
||||
write: (a,v) => {
|
||||
this.probe.logIOWrite(a,v);
|
||||
iobus.write(a,v);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -298,6 +343,7 @@ export abstract class BasicScanlineMachine extends BasicMachine implements Raste
|
||||
advanceFrame(maxClocks:number, trap) : number {
|
||||
var clock = 0;
|
||||
var endLineClock = 0;
|
||||
this.probe.logNewFrame();
|
||||
for (var sl=0; sl<this.numTotalScanlines; sl++) {
|
||||
endLineClock += this.cpuCyclesPerLine;
|
||||
this.scanline = sl;
|
||||
@ -308,9 +354,10 @@ export abstract class BasicScanlineMachine extends BasicMachine implements Raste
|
||||
sl = 999;
|
||||
break;
|
||||
}
|
||||
clock += this.advance(endLineClock - clock);
|
||||
clock += this.advance();
|
||||
}
|
||||
this.drawScanline();
|
||||
this.probe.logNewScanline();
|
||||
}
|
||||
return clock;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
|
||||
import { MOS6502, MOS6502State } from "../cpu/MOS6502";
|
||||
import { Bus, RasterFrameBased, SavesState, SavesInputState, AcceptsROM, AcceptsKeyInput, noise, Resettable, SampledAudioSource, SampledAudioSink, HasCPU } from "../devices";
|
||||
import { BasicScanlineMachine, noise } from "../devices";
|
||||
import { KeyFlags } from "../emu"; // TODO
|
||||
import { lzgmini } from "../util";
|
||||
|
||||
@ -21,6 +21,7 @@ interface AppleIIStateBase {
|
||||
}
|
||||
|
||||
interface AppleIIControlsState {
|
||||
inputs : Uint8Array; // unused?
|
||||
kbdlatch : number;
|
||||
}
|
||||
|
||||
@ -29,18 +30,23 @@ interface AppleIIState extends AppleIIStateBase, AppleIIControlsState {
|
||||
grswitch : number;
|
||||
}
|
||||
|
||||
export class AppleII implements HasCPU, Bus, RasterFrameBased, SampledAudioSource, AcceptsROM, AcceptsKeyInput,
|
||||
AppleIIStateBase, SavesState<AppleIIState>, SavesInputState<AppleIIControlsState> {
|
||||
export class AppleII extends BasicScanlineMachine {
|
||||
|
||||
cpuFrequency = 1023000;
|
||||
sampleRate = this.cpuFrequency;
|
||||
cpuCyclesPerLine = 65; // approx: http://www.cs.columbia.edu/~sedwards/apple2fpga/
|
||||
cpuCyclesPerFrame = 65*262;
|
||||
canvasWidth = 280;
|
||||
numVisibleScanlines = 192;
|
||||
numTotalScanlines = 262;
|
||||
defaultROMSize = 0xbf00-0x803; // TODO
|
||||
|
||||
ram = new Uint8Array(0x13000); // 64K + 16K LC RAM - 4K hardware + 12K ROM
|
||||
rom : Uint8Array;
|
||||
bios : Uint8Array;
|
||||
cpu = new MOS6502();
|
||||
audio : SampledAudioSink;
|
||||
pixels : Uint32Array;
|
||||
grdirty = new Array(0xc000 >> 7);
|
||||
grparams = {dirty:this.grdirty, grswitch:GR_TXMODE, mem:this.ram};
|
||||
ap2disp;
|
||||
pgmbin : Uint8Array;
|
||||
rnd = 1;
|
||||
kbdlatch = 0;
|
||||
soundstate = 0;
|
||||
@ -52,14 +58,14 @@ export class AppleII implements HasCPU, Bus, RasterFrameBased, SampledAudioSourc
|
||||
// bank 1 is E000-FFFF, bank 2 is D000-DFFF
|
||||
bank2rdoffset=0;
|
||||
bank2wroffset=0;
|
||||
lastFrameCycles=0;
|
||||
|
||||
constructor() {
|
||||
this.rom = new lzgmini().decode(APPLEIIGO_LZG);
|
||||
this.ram.set(this.rom, 0xd000);
|
||||
super();
|
||||
this.bios = new lzgmini().decode(APPLEIIGO_LZG);
|
||||
this.ram.set(this.bios, 0xd000);
|
||||
this.ram[0xbf00] = 0x4c; // fake DOS detect for C
|
||||
this.ram[0xbf6f] = 0x01; // fake DOS detect for C
|
||||
this.cpu.connectMemoryBus(this);
|
||||
this.connectCPUMemoryBus(this);
|
||||
}
|
||||
saveState() : AppleIIState {
|
||||
// TODO: automagic
|
||||
@ -73,6 +79,7 @@ export class AppleII implements HasCPU, Bus, RasterFrameBased, SampledAudioSourc
|
||||
auxRAMselected: this.auxRAMselected,
|
||||
auxRAMbank: this.auxRAMbank,
|
||||
writeinhibit: this.writeinhibit,
|
||||
inputs: null
|
||||
};
|
||||
}
|
||||
loadState(s:AppleIIState) {
|
||||
@ -89,13 +96,13 @@ export class AppleII implements HasCPU, Bus, RasterFrameBased, SampledAudioSourc
|
||||
this.ap2disp.invalidate(); // repaint entire screen
|
||||
}
|
||||
saveControlsState() : AppleIIControlsState {
|
||||
return {kbdlatch:this.kbdlatch};
|
||||
return {inputs:null,kbdlatch:this.kbdlatch};
|
||||
}
|
||||
loadControlsState(s:AppleIIControlsState) {
|
||||
this.kbdlatch = s.kbdlatch;
|
||||
}
|
||||
reset() {
|
||||
this.cpu.reset();
|
||||
super.reset();
|
||||
this.rnd = 1;
|
||||
// execute until $c600 boot
|
||||
for (var i=0; i<2000000; i++) {
|
||||
@ -113,7 +120,7 @@ export class AppleII implements HasCPU, Bus, RasterFrameBased, SampledAudioSourc
|
||||
return this.ram[address];
|
||||
} else if (address >= 0xd000) {
|
||||
if (!this.auxRAMselected)
|
||||
return this.rom[address - 0xd000];
|
||||
return this.bios[address - 0xd000];
|
||||
else if (address >= 0xe000)
|
||||
return this.ram[address];
|
||||
else
|
||||
@ -175,8 +182,8 @@ export class AppleII implements HasCPU, Bus, RasterFrameBased, SampledAudioSourc
|
||||
// JMP VM_BASE
|
||||
case 0xc600: {
|
||||
// load program into RAM
|
||||
if (this.pgmbin)
|
||||
this.ram.set(this.pgmbin.slice(HDR_SIZE), PGM_BASE);
|
||||
if (this.rom)
|
||||
this.ram.set(this.rom.slice(HDR_SIZE), PGM_BASE);
|
||||
return 0x4c;
|
||||
}
|
||||
case 0xc601: return VM_BASE&0xff;
|
||||
@ -201,36 +208,26 @@ export class AppleII implements HasCPU, Bus, RasterFrameBased, SampledAudioSourc
|
||||
this.ram[address + this.bank2wroffset] = val;
|
||||
}
|
||||
}
|
||||
loadROM(data:Uint8Array) {
|
||||
this.pgmbin = data.slice();
|
||||
}
|
||||
getVideoParams() {
|
||||
return {width:280, height:192};
|
||||
}
|
||||
getAudioParams() {
|
||||
return {sampleRate:cpuFrequency, stereo:false};
|
||||
}
|
||||
|
||||
connectVideo(pixels:Uint32Array) {
|
||||
this.pixels = pixels;
|
||||
this.ap2disp = pixels && new Apple2Display(this.pixels, this.grparams);
|
||||
super.connectVideo(pixels);
|
||||
this.ap2disp = this.pixels && new Apple2Display(this.pixels, this.grparams);
|
||||
}
|
||||
connectAudio(audio:SampledAudioSink) {
|
||||
this.audio = audio;
|
||||
startScanline() {
|
||||
}
|
||||
advanceFrame(maxCycles, trap) : number {
|
||||
maxCycles = Math.min(maxCycles, cpuCyclesPerFrame);
|
||||
for (var i=0; i<maxCycles; i++) {
|
||||
if (trap && (this.lastFrameCycles=i)>=0 && trap()) break;
|
||||
this.cpu.advanceClock();
|
||||
this.audio.feedSample(this.soundstate, 1);
|
||||
}
|
||||
drawScanline() {
|
||||
// TODO: draw scanline via ap2disp
|
||||
}
|
||||
advanceFrame(maxClocks:number, trap) : number {
|
||||
var clocks = super.advanceFrame(maxClocks, trap);
|
||||
this.ap2disp && this.ap2disp.updateScreen();
|
||||
return (this.lastFrameCycles = i);
|
||||
return clocks;
|
||||
}
|
||||
|
||||
getRasterX() { return this.lastFrameCycles % cpuCyclesPerLine; }
|
||||
getRasterY() { return Math.floor(this.lastFrameCycles / cpuCyclesPerLine); }
|
||||
|
||||
advance() {
|
||||
this.audio.feedSample(this.soundstate, 1);
|
||||
return super.advance();
|
||||
}
|
||||
|
||||
setKeyInput(key:number, code:number, flags:number) : void {
|
||||
if (flags & KeyFlags.KeyPress) {
|
||||
// convert to uppercase for Apple ][
|
||||
|
@ -50,8 +50,8 @@ export class VicDual extends BasicScanlineMachine {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.cpu.connectMemoryBus(this);
|
||||
this.cpu.connectIOBus(this.newIOBus());
|
||||
this.connectCPUMemoryBus(this);
|
||||
this.connectCPUIOBus(this.newIOBus());
|
||||
this.inputs.set([0xff, 0xff, 0xff, 0xff ^ 0x8]); // most things active low
|
||||
this.display = new VicDualDisplay();
|
||||
this.handler = newKeyboardHandler(this.inputs, CARNIVAL_KEYCODE_MAP, this.getKeyboardFunction());
|
||||
|
@ -177,3 +177,86 @@ export class EmuProfilerImpl implements EmuProfiler {
|
||||
this.log(a | PROFOP_INTERRUPT);
|
||||
}
|
||||
}
|
||||
|
||||
/////
|
||||
|
||||
import { Probeable, ProbeAll } from "./devices";
|
||||
|
||||
export enum ProbeFlags {
|
||||
CLOCKS = 0x00000000,
|
||||
EXECUTE = 0x01000000,
|
||||
MEM_READ = 0x02000000,
|
||||
MEM_WRITE = 0x04000000,
|
||||
IO_READ = 0x08000000,
|
||||
IO_WRITE = 0x10000000,
|
||||
INTERRUPT = 0x20000000,
|
||||
SCANLINE = 0x7e000000,
|
||||
FRAME = 0x7f000000,
|
||||
}
|
||||
|
||||
class ProbeFrame {
|
||||
data : Uint32Array;
|
||||
len : number;
|
||||
}
|
||||
|
||||
export class ProbeRecorder implements ProbeAll {
|
||||
|
||||
buf = new Uint32Array(0x100000);
|
||||
idx = 0;
|
||||
fclk = 0;
|
||||
sl = 0;
|
||||
m : Probeable;
|
||||
|
||||
constructor(m:Probeable) {
|
||||
this.m = m;
|
||||
}
|
||||
start() {
|
||||
this.m.connectProbe(this);
|
||||
this.reset();
|
||||
}
|
||||
stop() {
|
||||
this.m.connectProbe(null);
|
||||
}
|
||||
reset() {
|
||||
this.idx = 0;
|
||||
}
|
||||
log(a:number) {
|
||||
// TODO: coalesce READ and EXECUTE
|
||||
if (this.idx >= this.buf.length) return;
|
||||
this.buf[this.idx++] = a;
|
||||
}
|
||||
logClocks(clocks:number) {
|
||||
if (clocks) {
|
||||
this.fclk += clocks;
|
||||
this.log(clocks | ProbeFlags.CLOCKS);
|
||||
}
|
||||
}
|
||||
logNewScanline() {
|
||||
this.log(ProbeFlags.SCANLINE);
|
||||
this.sl++;
|
||||
}
|
||||
logNewFrame() {
|
||||
this.log(ProbeFlags.FRAME);
|
||||
this.sl = 0;
|
||||
}
|
||||
logExecute(address:number) {
|
||||
this.log(address | ProbeFlags.EXECUTE);
|
||||
}
|
||||
logInterrupt(type:number) {
|
||||
this.log(type | ProbeFlags.INTERRUPT);
|
||||
}
|
||||
logRead(address:number, value:number) {
|
||||
this.log(address | ProbeFlags.MEM_READ);
|
||||
}
|
||||
logWrite(address:number, value:number) {
|
||||
this.log(address | ProbeFlags.MEM_WRITE);
|
||||
}
|
||||
logIORead(address:number, value:number) {
|
||||
this.log(address | ProbeFlags.IO_READ);
|
||||
}
|
||||
logIOWrite(address:number, value:number) {
|
||||
this.log(address | ProbeFlags.IO_WRITE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
10
src/ui.ts
10
src/ui.ts
@ -254,6 +254,14 @@ function refreshWindowList() {
|
||||
return new Views.ProfileView();
|
||||
});
|
||||
}
|
||||
if (platform.startProbing) {
|
||||
addWindowItem("#eventprobe", "Event Probe", () => {
|
||||
return new Views.EventProbeView();
|
||||
});
|
||||
addWindowItem("#heatmap", "Heat Map", () => {
|
||||
return new Views.HeatMapView();
|
||||
});
|
||||
}
|
||||
addWindowItem('#asseteditor', 'Asset Editor', () => {
|
||||
return new Views.AssetEditorView();
|
||||
});
|
||||
@ -1251,7 +1259,7 @@ function updateDebugWindows() {
|
||||
projectWindows.tick();
|
||||
debugTickPaused = true;
|
||||
}
|
||||
setTimeout(updateDebugWindows, 200);
|
||||
setTimeout(updateDebugWindows, 100);
|
||||
}
|
||||
|
||||
function setWaitDialog(b : boolean) {
|
||||
|
174
src/views.ts
174
src/views.ts
@ -7,7 +7,7 @@ import { Platform, EmuState, ProfilerOutput, lookupSymbol, BaseDebugPlatform } f
|
||||
import { hex, lpad, rpad, safeident, rgb2bgr } from "./util";
|
||||
import { CodeAnalyzer } from "./analysis";
|
||||
import { platform, platform_id, compparams, current_project, lastDebugState, projectWindows } from "./ui";
|
||||
import { EmuProfilerImpl } from "./recorder";
|
||||
import { EmuProfilerImpl, ProbeRecorder, ProbeFlags } from "./recorder";
|
||||
import * as pixed from "./pixed/pixeleditor";
|
||||
declare var Mousetrap;
|
||||
|
||||
@ -922,6 +922,178 @@ export class ProfileView implements ProjectView {
|
||||
|
||||
///
|
||||
|
||||
// TODO: clear buffer when scrubbing
|
||||
|
||||
abstract class ProbeViewBase {
|
||||
probe : ProbeRecorder;
|
||||
maindiv : HTMLElement;
|
||||
canvas : HTMLCanvasElement;
|
||||
ctx : CanvasRenderingContext2D;
|
||||
recreateOnResize = true;
|
||||
|
||||
createCanvas(parent:HTMLElement, width:number, height:number) {
|
||||
var div = document.createElement('div');
|
||||
var canvas = document.createElement('canvas');
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
canvas.style.width = '100%';
|
||||
canvas.style.height = '100%';
|
||||
parent.appendChild(div);
|
||||
div.appendChild(canvas);
|
||||
this.canvas = canvas;
|
||||
this.ctx = canvas.getContext('2d');
|
||||
this.initCanvas();
|
||||
return this.maindiv = div;
|
||||
}
|
||||
initCanvas() {
|
||||
}
|
||||
|
||||
setVisible(showing : boolean) : void {
|
||||
if (showing) {
|
||||
this.probe = platform.startProbing();
|
||||
} else {
|
||||
platform.stopProbing();
|
||||
this.probe = null;
|
||||
}
|
||||
}
|
||||
|
||||
clear() {
|
||||
var ctx = this.ctx;
|
||||
ctx.globalCompositeOperation = 'source-over';
|
||||
ctx.globalAlpha = 0.5;
|
||||
ctx.fillStyle = '#000000';
|
||||
ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
ctx.globalAlpha = 1.0;
|
||||
ctx.globalCompositeOperation = 'lighter';
|
||||
}
|
||||
|
||||
tick() {
|
||||
var p = this.probe;
|
||||
if (!p || !p.idx) return; // if no probe, or if empty
|
||||
var row=0;
|
||||
var col=0;
|
||||
var ctx = this.ctx;
|
||||
this.clear();
|
||||
for (var i=0; i<p.idx; i++) {
|
||||
var word = p.buf[i];
|
||||
var addr = word & 0xffffff;
|
||||
var op = word & 0xff000000;
|
||||
switch (op) {
|
||||
case ProbeFlags.SCANLINE: row++; col=0; break;
|
||||
case ProbeFlags.FRAME: row=0; col=0; break;
|
||||
case ProbeFlags.CLOCKS: col += addr; break;
|
||||
default:
|
||||
this.drawEvent(op, addr, col, row);
|
||||
break;
|
||||
}
|
||||
}
|
||||
p.reset();
|
||||
}
|
||||
|
||||
setContextForOp(op) {
|
||||
var ctx = this.ctx;
|
||||
switch (op) {
|
||||
//case ProbeFlags.EXECUTE: ctx.fillStyle = "green"; break;
|
||||
case ProbeFlags.MEM_READ: ctx.fillStyle = "white"; break;
|
||||
case ProbeFlags.MEM_WRITE: ctx.fillStyle = "red"; break;
|
||||
case ProbeFlags.IO_READ: ctx.fillStyle = "green"; break;
|
||||
case ProbeFlags.IO_WRITE: ctx.fillStyle = "magenta"; break;
|
||||
case ProbeFlags.INTERRUPT: ctx.fillStyle = "yellow"; break;
|
||||
default: ctx.fillStyle = "blue"; break;
|
||||
}
|
||||
}
|
||||
|
||||
abstract drawEvent(op, addr, col, row);
|
||||
}
|
||||
|
||||
abstract class ProbeBitmapViewBase extends ProbeViewBase {
|
||||
imageData : ImageData;
|
||||
datau32 : Uint32Array;
|
||||
recreateOnResize = false;
|
||||
|
||||
initCanvas() {
|
||||
this.imageData = this.ctx.createImageData(this.canvas.width, this.canvas.height);
|
||||
this.datau32 = new Uint32Array(this.imageData.data.buffer);
|
||||
}
|
||||
refresh() {
|
||||
this.tick();
|
||||
this.datau32.fill(0xff000000);
|
||||
}
|
||||
tick() {
|
||||
super.tick();
|
||||
this.ctx.putImageData(this.imageData, 0, 0);
|
||||
}
|
||||
clear() {
|
||||
this.datau32.fill(0xff000000);
|
||||
}
|
||||
}
|
||||
|
||||
export class HeatMapView extends ProbeBitmapViewBase implements ProjectView {
|
||||
|
||||
createDiv(parent : HTMLElement) {
|
||||
return this.createCanvas(parent, 256, 256);
|
||||
}
|
||||
|
||||
drawEvent(op, addr, col, row) {
|
||||
var x = addr & 0xff;
|
||||
var y = (addr >> 8) & 0xff;
|
||||
var col;
|
||||
switch (op) {
|
||||
case ProbeFlags.EXECUTE: col = 0x0f3f0f; break;
|
||||
case ProbeFlags.MEM_READ: col = 0x3f0101; break;
|
||||
case ProbeFlags.MEM_WRITE: col = 0x000f3f; break;
|
||||
case ProbeFlags.IO_READ: col = 0x001f01; break;
|
||||
case ProbeFlags.IO_WRITE: col = 0x003f3f; break;
|
||||
case ProbeFlags.INTERRUPT: col = 0x3f3f00; break;
|
||||
default: col = 0x1f1f1f; break;
|
||||
}
|
||||
var data = this.datau32[addr & 0xffff];
|
||||
data = (data & 0x7f7f7f) << 1;
|
||||
data = data | col | 0xff000000;
|
||||
this.datau32[addr & 0xffff] = data;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class EventProbeView extends ProbeViewBase implements ProjectView {
|
||||
symcache : Map<number,symbol> = new Map();
|
||||
|
||||
createDiv(parent : HTMLElement) {
|
||||
return this.createCanvas( parent, $(parent).width(), $(parent).height() );
|
||||
}
|
||||
|
||||
drawEvent(op, addr, col, row) {
|
||||
var ctx = this.ctx;
|
||||
var xscale = this.canvas.width / 128; // TODO: pixels
|
||||
var yscale = this.canvas.height / 262; // TODO: lines
|
||||
var x = col * xscale;
|
||||
var y = row * yscale;
|
||||
var sym = this.getSymbol(addr);
|
||||
if (!sym && op == ProbeFlags.IO_WRITE) sym = hex(addr,4);
|
||||
//if (!sym && op == ProbeFlags.IO_READ) sym = hex(addr,4);
|
||||
if (sym) {
|
||||
this.setContextForOp(op);
|
||||
ctx.fillText(sym, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
getSymbol(addr:number) : string {
|
||||
var sym = this.symcache[addr];
|
||||
if (!sym) {
|
||||
sym = lookupSymbol(platform, addr, false);
|
||||
this.symcache[addr] = sym;
|
||||
}
|
||||
return sym;
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.tick();
|
||||
this.symcache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
export class AssetEditorView implements ProjectView, pixed.EditorContext {
|
||||
maindiv : JQuery;
|
||||
cureditordiv : JQuery;
|
||||
|
@ -125,6 +125,7 @@ function testPlatform(platid, romname, maxframes, callback) {
|
||||
platform.reset(); // reset again
|
||||
var state0b = platform.saveState();
|
||||
//TODO: vcs fails assert.deepEqual(state0a, state0b);
|
||||
//if (platform.startProbing) platform.startProbing();
|
||||
platform.resume(); // so that recorder works
|
||||
platform.setRecorder(rec);
|
||||
for (var i=0; i<maxframes; i++) {
|
||||
|
Loading…
Reference in New Issue
Block a user