Compare commits

...
17 Commits
Author SHA1 Message Date
GuyPerfect b4feff41f0 Fix broken timer fix bug fixes 2024-12-26 13:23:56 -06:00
GuyPerfect 4449acada0 Fix timer clock computation 2024-12-26 12:57:44 -06:00
GuyPerfect 799ac9f51a Fix style check error 2024-12-26 12:28:10 -06:00
GuyPerfect b83b49221d Timer accuracy adjustments 2024-12-26 12:23:00 -06:00
GuyPerfect 27e926bd58 Fix byte writes to VIP cells, objects and worlds 2024-12-25 13:48:39 -06:00
GuyPerfect d802d39d8d Prevent non-debug writes to CTA 2024-12-23 09:56:54 -06:00
GuyPerfect 18b2c589e6 Add vbuCodeSize() 2024-11-21 18:50:31 -06:00
GuyPerfect f45636a491 VIP bugfix 2024-11-19 22:22:34 -06:00
GuyPerfect 9ce8c9c778 VIP bugfix 2024-11-19 22:21:06 -06:00
GuyPerfect c90b8137de VIP optimizations 2024-11-19 21:56:44 -06:00
GuyPerfect bc864644f7 Aid VSU performance 2024-11-17 14:57:56 -06:00
GuyPerfect e97b52e944 Cleanup adjustments 2024-11-17 12:31:37 -06:00
GuyPerfect 7e31fbd582 VSU performance tweak, add immediate notation setting 2024-11-11 20:05:14 -06:00
GuyPerfect b4b9131f39 Web: add panning, move mixing to wasm 2024-11-02 11:14:24 -05:00
GuyPerfect 8b9152dde9 Fix S*RAM, Sim.setVolume 2024-11-01 18:29:13 -05:00
GuyPerfect 26a0357afa Introduce web core 2024-11-01 15:03:49 -05:00
GuyPerfect 53826584b8 Fix disassembler not advancing past PC 2024-11-01 15:01:09 -05:00
14 changed files with 2807 additions and 454 deletions
-5
View File
@@ -91,11 +91,6 @@
#define CPU_XORBSU 82
#define CPU_XORNBSU 83
/* Functional operand types */
#define CPU_LITERAL 0
#define CPU_MEMORY 1
#define CPU_REGISTER 2
/* Bit string operations */
#define CPU_AND_BS 0
#define CPU_ANDN_BS 1
+41 -14
View File
@@ -5,6 +5,15 @@
/***************************** Module Functions ******************************/
/* Compute clocks until the next decrement to zero */
static uint32_t tmrGetUntil(VB *sim) {
uint32_t fullTick = sim->tmr.t_clk_sel ? 400 : 2000;
uint32_t thisTick = sim->tmr.clocks +
(sim->tmr.t_clk_sel ? 0 : 400 * (4 - sim->tmr.tick20));
return thisTick + fullTick *
(sim->tmr.counter == 0 ? sim->tmr.reload : sim->tmr.counter - 1);
}
/* Update the counter to a new value */
static void tmrUpdate(VB *sim, uint16_t value) {
if (value == 0 && sim->tmr.counter != 0) {
@@ -23,10 +32,6 @@ static void tmrUpdate(VB *sim, uint16_t value) {
/* Process component */
static void tmrEmulate(VB *sim, uint32_t clocks) {
/* Timer is disabled */
if (!sim->tmr.t_enb)
return;
/* Process all clocks */
for (;;) {
@@ -38,15 +43,22 @@ static void tmrEmulate(VB *sim, uint32_t clocks) {
}
/* Advance forward the component's number of clocks */
clocks -= sim->tmr.clocks;
sim->tmr.until -= sim->tmr.clocks;
sim->tmr.clocks = sim->tmr.t_clk_sel ? 400 : 2000;
clocks -= sim->tmr.clocks;
sim->tmr.until -= sim->tmr.clocks;
sim->tmr.clocks = 400;
sim->tmr.tick20 += sim->tmr.tick20 == 4 ? -4 : 1;
/* Do not decrement counter */
if (
!sim->tmr.t_enb ||
(!sim->tmr.t_clk_sel && sim->tmr.tick20 != 0)
) continue;
/* Advance to the next counter value */
tmrUpdate(sim, sim->tmr.counter == 0 ?
sim->tmr.reload : sim->tmr.counter - 1);
if (sim->tmr.counter == 0)
sim->tmr.until = sim->tmr.clocks * ((uint32_t)sim->tmr.reload + 1);
sim->tmr.until = tmrGetUntil(sim);
}
}
@@ -71,8 +83,10 @@ static void tmrReset(VB *sim) {
sim->tmr.z_stat = 0;
/* Other */
sim->tmr.clocks = 400;
sim->tmr.counter = 0xFFFF;
sim->tmr.reload = 0x0000;
sim->tmr.tick20 = 0;
}
/* Determine how many clocks are guaranteed to process */
@@ -88,14 +102,27 @@ static void tmrWriteControl(VB *sim, uint8_t value) {
if (
(value & 0x04) && /* Z-Stat-Clr */
(
sim->tmr.counter != 0 ||
!sim->tmr.t_enb
!sim->tmr.t_enb ||
(
sim->tmr.counter != 0 &&
!(value & 0x01) /* T-Enb */
)
)
) {
sim->tmr.z_stat = sim->tmr.counter != 0;
sim->cpu.irq &= ~0x0002;
}
/* Hardware bug: decrement on switch to 20us mode */
if (
!sim->tmr.t_clk_sel &&
(value & 0x10) && /* T-Clk-Sel */
sim->tmr.tick20 != 4
) {
tmrUpdate(sim, sim->tmr.counter == 0 ?
sim->tmr.reload : sim->tmr.counter - 1);
}
/* Parse fields */
sim->tmr.t_clk_sel = value >> 4 & 1;
sim->tmr.tim_z_int = value >> 3 & 1;
@@ -105,10 +132,8 @@ static void tmrWriteControl(VB *sim, uint8_t value) {
if (!sim->tmr.tim_z_int)
sim->cpu.irq &= ~0x0002;
/* Configure countdowns */
sim->tmr.clocks = sim->tmr.t_clk_sel ? 400 : 2000;
sim->tmr.until = sim->tmr.clocks * (sim->tmr.counter == 0 ?
(uint32_t) sim->tmr.reload + 1 : sim->tmr.counter);
/* Configure state */
sim->tmr.until = tmrGetUntil(sim);
/* TODO: Will Z-Stat raise an interrupt when Tim-Z-Int is set? */
}
@@ -117,12 +142,14 @@ static void tmrWriteControl(VB *sim, uint8_t value) {
static void tmrWriteHigh(VB *sim, uint8_t value) {
sim->tmr.reload = (uint16_t) value << 8 | (sim->tmr.reload & 0x00FF);
tmrUpdate(sim, sim->tmr.reload);
sim->tmr.until = tmrGetUntil(sim);
}
/* Write to the low data register */
static void tmrWriteLow(VB *sim, uint8_t value) {
sim->tmr.reload = (sim->tmr.reload & 0xFF00) | value;
tmrUpdate(sim, sim->tmr.reload);
sim->tmr.until = tmrGetUntil(sim);
}
+74 -13
View File
@@ -8,8 +8,11 @@
/*********************************** Types ***********************************/
/* Output image */
typedef uint8_t Pixels[2][384*224];
/* VIP BG map cell */
typedef struct {
uint8_t *palette; /* Points to a VB.vip.gplt */
uint8_t *pixels; /* Points to a VB.vip.characters */
} Cell;
/* VSU channel */
typedef struct {
@@ -64,9 +67,58 @@ typedef struct {
} wave;
/* Other state */
uint32_t clocks; /* Clocks until next sample */
uint32_t clocks; /* Clocks until next wave or noise sample */
uint8_t freqmod; /* Frequency modifications are active */
uint32_t until; /* Clocks until channel component update */
} Channel;
/* VIP character (hflp << 1 | vflp) */
typedef uint8_t Character[4][64];
/* VIP object */
typedef struct {
Cell cell; /* BG map-like attributes */
int16_t jp; /* Parallax */
int16_t jx; /* Screen base X */
int16_t jy; /* Screen Y */
uint8_t lron; /* Visible in left and right images */
} Object;
/* Output image */
typedef uint8_t Pixels[2][384*224];
/* VIP world */
typedef struct {
/* Attributes */
uint8_t bgm; /* Mode */
uint8_t end; /* Control world */
int16_t gp; /* World parallax */
int16_t gx; /* World base X */
int16_t gy; /* World Y */
int16_t mx; /* Scroll base X */
int16_t mp; /* Scroll parallax */
int16_t my; /* Scroll Y */
uint8_t over; /* Use overplane */
Cell *overplane; /* Overplane cell */
/* Non-attributes */
Cell *bg[64]; /* Background arrangement */
int32_t bgHeight; /* Height of background in pixels */
uint32_t bgMaskX; /* Horizontal background mask */
uint32_t bgMaskY; /* Vertical background mask */
uint32_t bgShift; /* Bits to shift when selecting a BG map */
int32_t bgWidth; /* Width of background in pixels */
uint32_t hAffine; /* H for affine mode */
uint32_t hNaffine; /* H for not affine mode */
uint32_t height; /* Height of world in pixels */
uint8_t lron; /* Visible in left and right images */
uint32_t paramBase; /* H-bias and affine parameter address */
uint32_t wAffine; /* W for affine mode */
uint32_t wNaffine; /* W for not affine mode */
uint32_t width; /* Width of world in pixels */
} World;
/* Simulation state */
struct VB {
@@ -199,6 +251,7 @@ struct VB {
uint32_t clocks; /* Master clocks to wait */
uint16_t counter; /* Current counter value */
uint16_t reload; /* Reload counter value */
uint8_t tick20; /* Current 20-microsecond tick */
uint32_t until; /* Clocks until interrupt condition */
} tmr;
@@ -278,7 +331,11 @@ struct VB {
Pixels shadow; /* Drawing shadow image, column-major */
/* Other state */
uint8_t ram[0x40000]; /* Video memory */
Cell cells[0x10000]; /* Pre-computed BG map attributes */
Character characters[2048]; /* Pre-computed character pixels */
Object objects[1024]; /* Pre-computed object attributes */
uint8_t ram[0x40000]; /* Video memory */
World worlds[32]; /* Pre-computed world attributes */
} vip;
/* VSU */
@@ -643,6 +700,7 @@ VBAPI vbOnWrite vbGetWriteCallback(VB *sim) {
VBAPI VB* vbInit(VB *sim) {
sim->cart.ram = NULL;
sim->cart.rom = NULL;
sim->ph.enabled = 0;
sim->vsu.out.samples = NULL;
sim->onExecute = NULL;
sim->onFetch = NULL;
@@ -652,7 +710,7 @@ VBAPI VB* vbInit(VB *sim) {
sim->onSamples = NULL;
sim->onWrite = NULL;
sim->peer = NULL;
sim->ph.enabled = 0;
sim->tag = NULL;
vbReset(sim);
return sim;
}
@@ -716,28 +774,28 @@ VBAPI int vbSetCartROM(VB *sim, void *rom, uint32_t size) {
/* Specify a new exception callback handler */
VBAPI vbOnException vbSetExceptionCallback(VB *sim, vbOnException callback) {
vbOnException prev = sim->onException;
sim->onException = callback;
sim->onException = callback;
return prev;
}
/* Specify a new execute callback handler */
VBAPI vbOnExecute vbSetExecuteCallback(VB *sim, vbOnExecute callback) {
vbOnExecute prev = sim->onExecute;
sim->onExecute = callback;
sim->onExecute = callback;
return prev;
}
/* Specify a new fetch callback handler */
VBAPI vbOnFetch vbSetFetchCallback(VB *sim, vbOnFetch callback) {
vbOnFetch prev = sim->onFetch;
sim->onFetch = callback;
sim->onFetch = callback;
return prev;
}
/* Specify a new frame callback handler */
VBAPI vbOnFrame vbSetFrameCallback(VB *sim, vbOnFrame callback) {
vbOnFrame prev = sim->onFrame;
sim->onFrame = callback;
sim->onFrame = callback;
return prev;
}
@@ -749,7 +807,7 @@ VBAPI uint16_t vbSetKeys(VB *sim, uint16_t keys) {
/* Specify a new link callback handler */
VBAPI vbOnLink vbSetLinkCallback(VB *sim, vbOnLink callback) {
vbOnLink prev = sim->onLink;
sim->onLink = callback;
sim->onLink = callback;
return prev;
}
@@ -774,6 +832,8 @@ VBAPI int vbSetOption(VB *sim, int key, int value) {
VBAPI void vbSetPeer(VB *sim, VB *peer) {
if (sim->peer == peer)
return;
if (sim->peer != NULL)
sim->peer->peer = NULL;
sim->peer = peer;
if (peer == NULL)
return;
@@ -784,6 +844,7 @@ VBAPI void vbSetPeer(VB *sim, VB *peer) {
/* Specify a new value for the program counter */
VBAPI uint32_t vbSetProgramCounter(VB *sim, uint32_t value) {
sim->cpu.clocks = 0;
sim->cpu.operation = CPU_FETCH;
sim->cpu.pc = sim->cpu.nextPC = value & 0xFFFFFFFE;
sim->cpu.step = 0;
@@ -798,7 +859,7 @@ VBAPI int32_t vbSetProgramRegister(VB *sim, unsigned index, int32_t value) {
/* Specify a new read callback handler */
VBAPI vbOnRead vbSetReadCallback(VB *sim, vbOnRead callback) {
vbOnRead prev = sim->onRead;
sim->onRead = callback;
sim->onRead = callback;
return prev;
}
@@ -829,7 +890,7 @@ VBAPI uint32_t vbSetSystemRegister(VB *sim, unsigned index, uint32_t value) {
/* Specify a new write callback handler */
VBAPI vbOnWrite vbSetWriteCallback(VB *sim, vbOnWrite callback) {
vbOnWrite prev = sim->onWrite;
sim->onWrite = callback;
sim->onWrite = callback;
return prev;
}
@@ -841,7 +902,7 @@ VBAPI size_t vbSizeOf() {
/* Specify a simulation's userdata pointer */
VBAPI void* vbSetUserData(VB *sim, void *tag) {
void *prev = sim->tag;
sim->tag = tag;
sim->tag = tag;
return prev;
}
+18
View File
@@ -84,6 +84,24 @@ extern "C" {
/* Option keys */
#define VB_PSEUDO_HALT 0
/* Controller buttons */
#define VB_PWR 0x0001
#define VB_SGN 0x0002
#define VB_A 0x0004
#define VB_B 0x0008
#define VB_RT 0x0010
#define VB_LT 0x0020
#define VB_RU 0x0040
#define VB_RR 0x0080
#define VB_LR 0x0100
#define VB_LL 0x0200
#define VB_LD 0x0400
#define VB_LU 0x0800
#define VB_STA 0x1000
#define VB_SEL 0x2000
#define VB_RL 0x4000
#define VB_RD 0x8000
/*********************************** Types ***********************************/
+581 -314
View File
@@ -56,37 +56,18 @@ static const uint8_t BRIGHT8[] = {
/*********************************** Types ***********************************/
/* World attribtues per eye */
/* Intersection of the screen and a world rectangle */
typedef struct {
int32_t bp; /* BG source parallax */
int32_t left, right; /* Window bounds */
int32_t wx; /* World horizontal position */
} EyeAttribs;
/* Parsed world attributes */
typedef struct {
int32_t base; /* Base BG map index */
uint8_t *bg; /* Background template */
int32_t bgm; /* World type */
int32_t bgw, bgh; /* Background dimension masks */
int32_t bottom, top; /* Window bounds */
int32_t gx, gp, gy; /* World screen position */
int32_t mx, mp, my; /* Background scroll */
uint8_t *over; /* Overplane character */
uint32_t params; /* Line parameters address */
int32_t scx, scy; /* Background dimensions */
int32_t w, h; /* Window size */
} WorldAttribs;
int32_t x1; /* Left edge inclusive */
int32_t x2; /* Right edge exclusive */
int32_t y1; /* Top edge inclusive */
int32_t y2; /* Bottom edge exclusive */
} Window;
/***************************** Module Functions ******************************/
/* Retrieve a pointer to character data in host memory */
static uint8_t* vipCharacter(VB *sim, uint32_t c) {
return &sim->vip.ram[0x06000 | (c << 6 & 0x18000) | (c << 4 & 0x01FF0)];
}
/* Read a palette */
static int32_t vipReadPalette(uint8_t *entries) {
return entries[3] << 6 | entries[2] << 4 | entries[1] << 2;
@@ -244,6 +225,178 @@ static int32_t vipReadIO(VB *sim, uint32_t address, int type) {
value;
}
/* Write into BG map cell memory */
static void vipWriteCell(VB *sim, uint32_t offset, int type, int32_t value) {
Cell *cell;
/* Adjustments for byte and word writes */
switch (type) {
case VB_S8:
case VB_U8:
value = offset & 1 ?
value << 8 | sim->vip.ram[0x20001 ^ offset] :
value | (int32_t) sim->vip.ram[0x20001 | offset] << 8
;
break;
case VB_S32:
vipWriteCell(sim, offset + 2, VB_U16, value >> 16);
break;
}
/* Update pixel pointer */
cell = &sim->vip.cells[offset >> 1];
cell->palette = sim->vip.gplt[value >> 14 & 3];
cell->pixels = sim->vip.characters[value & 0x07FF][value >> 12 & 3];
}
/* Write into character memory */
static void vipWriteChr(VB *sim, uint32_t offset, int type, int32_t value) {
Character *chr; /* Pixel state */
int b, l, r, t; /* Pixel bounds */
int x, y, X, Y; /* Iterators */
/* Byte advance by data type */
static const uint8_t HORZ[] = { 4, 4, 8, 8, 8 };
static const uint8_t VERT[] = { 8, 8, 8, 8, 16 };
/* Working variables */
chr = &sim->vip.characters[offset >> 4];
l = (offset & 0x1) << 2;
t = (offset & 0xE) << 2;
r = l + HORZ[type];
b = t + VERT[type];
/* Process all pixels */
for (y = t, Y = 56 - y; y < b; y += 8, Y -= 8)
for (x = l, X = 7 - x; x < r; x += 1, X -= 1, value >>= 2) {
(*chr)[0][y | x] = (*chr)[1][Y | x] =
(*chr)[2][y | X] = (*chr)[3][Y | X] = value & 3;
}
}
/* Write into object attribute memory */
static void vipWriteObject(VB *sim, uint32_t offset, int type, int32_t value) {
Object *obj; /* Object state */
/* Adjustments for byte and word writes */
switch (type) {
case VB_S8:
case VB_U8:
value = offset & 1 ?
value << 8 | sim->vip.ram[0x3E001 ^ offset] :
value | (int32_t) sim->vip.ram[0x3E001 | offset] << 8
;
break;
case VB_S32:
vipWriteObject(sim, offset + 2, VB_U16, value >> 16);
break;
}
/* Processing by offset */
obj = &sim->vip.objects[offset >> 3];
switch (offset >> 1 & 3) {
case 0: obj->jx = SignExtend(value, 10); break;
case 1:
obj->jp = SignExtend(value, 10);
obj->lron = value >> 14 & 3;
break;
case 2:
value = (int8_t) value;
obj->jy = value > -8 ? value : value & 0xFF;
break;
case 3:
obj->cell.palette = sim->vip.jplt[value >> 14 & 3];
obj->cell.pixels =
sim->vip.characters[value & 0x07FF][value >> 12 & 3];
}
}
/* Write into world attribute memory */
static void vipWriteWorld(VB *sim, uint32_t offset, int type, int32_t value) {
uint32_t base; /* Base BG map index */
uint8_t *bg; /* Background template */
uint32_t count; /* Number of BG maps in background */
World *world; /* World state */
uint32_t z; /* Iterator */
/* Adjustments for byte and word writes */
switch (type) {
case VB_S8:
case VB_U8:
value = offset & 1 ?
value << 8 | sim->vip.ram[0x3D801 ^ offset] :
value | (int32_t) sim->vip.ram[0x3D801 | offset] << 8
;
break;
case VB_S32:
vipWriteWorld(sim, offset + 2, VB_U16, value >> 16);
break;
}
/* Processing by offset */
world = &sim->vip.worlds[offset >> 5];
switch (offset >> 1 & 15) {
case 0:
/* Parse attributes */
world->lron = value >> 14 & 3;
world->bgm = value >> 12 & 3;
world->over = value >> 7 & 1;
world->end = value >> 6 & 1;
/* Update background fields */
z = value >> 10 & 3;
world->bgMaskX = (1 << z) - 1;
world->bgWidth = 512 << z;
world->bgShift = z;
z = value >> 8 & 3;
world->bgMaskY = (1 << z) - 1;
world->bgHeight = 512 << z;
/* Update world dimensions */
if (world->bgm != 2) {
world->height = world->hNaffine;
world->width = world->wNaffine;
} else {
world->height = world->hAffine;
world->width = world->wAffine;
}
/* Update background arrangement */
count = (world->bgMaskX + 1) * (world->bgMaskY + 1);
base = value & 15 & ~(count > 8 ? 7 : count - 1);
bg = (uint8_t *) BG_TEMPLATES[value >> 8 & 15];
for (z = 0; z < count; z++)
world->bg[z] = &sim->vip.cells[(base + bg[z]) << 12];
break;
case 1: world->gx = SignExtend(value, 10); break;
case 2: world->gp = SignExtend(value, 10); break;
case 3: world->gy = (int16_t) value; break;
case 4: world->mx = SignExtend(value, 13); break;
case 5: world->mp = SignExtend(value, 15); break;
case 6: world->my = SignExtend(value, 13); break;
case 7: /* W */
value = SignExtend(value, 13) + 1;
world->wNaffine = value < 0 ? 0 : value;
value = SignExtend(value, 10) + 1;
world->wAffine = value < 0 ? 0 : value;
world->width = world->bgm != 2 ? world->wNaffine : world->wAffine;
break;
case 8: /* H */
value = (int16_t) value + 1;
world->hNaffine = value < 8 ? 8 : value;
world->hAffine = value < 0 ? 0 : value;
world->height = world->bgm != 2 ? world->hNaffine : world->hAffine;
break;
case 9: world->paramBase = 0x20000|(uint32_t)(uint16_t)value<<1;break;
case 10: world->overplane = &sim->vip.cells[(uint16_t) value]; break;
}
}
/* Write a typed value to an I/O register */
static void vipWriteIO(
VB *sim, uint32_t address, int type, int32_t value, int debug) {
@@ -328,10 +481,12 @@ static void vipWriteIO(
break;
case 0x5F830>>1: /* CTA */
if ((mask & 0xFF00) == 0)
sim->vip.cta.cta_r = value >> 8;
if ((mask & 0x00FF) == 0)
sim->vip.cta.cta_l = value;
if (debug) {
if ((mask & 0xFF00) == 0)
sim->vip.cta.cta_r = value >> 8;
if ((mask & 0x00FF) == 0)
sim->vip.cta.cta_l = value;
}
break;
case 0x5F842>>1: /* XPCTRL */
@@ -627,298 +782,311 @@ static int vipEmulateDisplay(VB *sim, uint32_t clocks) {
/****************************** Pixel Processor ******************************/
/* Parse eye attributes and test window bounds */
static int vipParseEye(WorldAttribs *wttr, EyeAttribs *ettr, int eye) {
/* Draw a cell into shadow memory */
static void vipDrawCell(uint8_t *shadow, Window *wnd,
Cell *cell, int32_t x, int32_t y) {
uint8_t *col, *row; /* Output pixel */
uint8_t pixel; /* Character pixel value */
int32_t px1, px2, py1, py2; /* Pixel bounds in cell */
int32_t px, py; /* Pixel origin in cell */
/* Validate the world isn't right of frame */
ettr->wx = wttr->gx - (eye == 0 ? wttr->gp : -wttr->gp);
if (ettr->wx > 383)
return 1;
ettr->left = ettr->wx < 0 ? 0 : ettr->wx;
/* Identify the visible pixels */
py1 = y > wnd->y1 ? y : wnd->y1;
py2 = y + 8 < wnd->y2 ? y + 8 : wnd->y2;
px1 = x > wnd->x1 ? x : wnd->x1;
px2 = x + 8 < wnd->x2 ? x + 8 : wnd->x2;
/* Validate the world isn't left of frame */
ettr->right = ettr->wx + wttr->w - 1;
if (ettr->right < 0)
return 1;
if (ettr->right > 383)
ettr->right = 383;
/* BG source parallax */
if (wttr->bgm != 2)
ettr->bp = eye == 0 ? -wttr->mp : wttr->mp;
return 0;
}
/* Parse world attributes and test window bounds */
static int vipParseWorld(VB*sim,uint8_t*world,uint16_t bits,WorldAttribs*attr){
int32_t z; /* Scratch */
/* Validate the world isn't below the frame */
attr->gy = busReadBuffer(world + 6, VB_S16);
if (attr->gy > 223)
return 1;
/* Validate the world isn't above the frame, and height is positive */
attr->bgm = bits >> 12 & 3;
attr->h = busReadBuffer(world + 16, VB_S16) + 1;
z = attr->bgm == 2 ? 0 : 8;
if (attr->h < z)
attr->h = z;
if (attr->h == 0)
return 1;
attr->bottom = attr->gy + attr->h - 1;
attr->top = (int32_t) sim->vip.xp.sbcount << 3;
if (attr->bottom < attr->top)
return 1;
if (attr->bottom > 223)
attr->bottom = 223;
if (attr->top < attr->gy)
attr->top = attr->gy;
/* Validate width is positive */
attr->w = SignExtend(busReadBuffer(world + 14, VB_U16),
attr->bgm == 2 ? 10 : 13) + 1;
if (attr->w < 1)
return 1;
/* Parse attributes */
attr->bg = (uint8_t *) BG_TEMPLATES[bits >> 8 & 15];
attr->gx = SignExtend(busReadBuffer(world + 2, VB_U16), 10);
attr->gp = SignExtend(busReadBuffer(world + 4, VB_U16), 10);
attr->over = (bits & 0x0080) == 0 ? NULL :
&sim->vip.ram[0x00020000 | busReadBuffer(world + 10, VB_U16) << 1];
attr->scx = bits >> 10 & 3;
attr->scy = bits >> 8 & 3;
attr->bgw = (1 << attr->scx) - 1;
attr->bgh = (1 << attr->scy) - 1;
z = attr->scx + attr->scy;
attr->base = bits & 15 & ~((1 << (z < 3 ? z : 3)) - 1);
if (attr->bgm != 2) {
attr->mx = SignExtend(busReadBuffer(world + 8, VB_U16), 13);
attr->mp = SignExtend(busReadBuffer(world + 10, VB_U16), 15);
attr->my = SignExtend(busReadBuffer(world + 12, VB_U16), 13);
}
if (attr->bgm != 0) {
attr->params =
0x20000 +
(attr->top - attr->gy) * (attr->bgm == 1 ? 4 : 16) +
((uint32_t) busReadBuffer(world + 18, VB_U16) << 1)
;
/* Process all columns of pixels */
col = &shadow[px1 * 224 + py1];
for (px = px1; px < px2; px++, col += 224)
for (py = py1, row = col; py < py2; py++, row++) {
pixel = cell->pixels[(py - y) << 3 | (px - x)];
if (pixel != 0)
*row = cell->palette[pixel];
}
return 0;
}
/* Draw an object group into shadow memory */
static void vipDrawObjects(VB *sim, int group) {
uint8_t *dest; /* Pointer to object position in shadow memory */
int fx; /* Output horizontal position */
int fy; /* Output vertical position */
uint8_t *obj; /* Pointer to object attributes in host memory */
int32_t ox; /* Eye horizontal position */
int pixel; /* Character input pixel */
int start; /* Index of first object in group */
int stop; /* Index of last object in group */
int sx; /* Input horizontal position */
int sy; /* Input vertical position */
int32_t top; /* Window boundary */
int i, o, x, y; /* Iterators */
/* Draw a BG map into shadow memory */
static void vipDrawBGMap(uint8_t *shadow, Window *wnd,
uint8_t over, Cell *map, int32_t x, int32_t y) {
Cell *col, *row; /* Source cell */
int32_t cx1, cx2, cy1, cy2; /* Cell bounds in BG map pixels */
int32_t cx, cy; /* Cell origin in BG map pixels */
/* Object attributes */
uint32_t attr1; /* Attribute bits */
uint32_t attr2; /* Attribute bits */
int jhflp; /* Is flipped horizontally */
int32_t jp; /* Stereo parallax */
int jvflp; /* Is flipped vetically */
int32_t jx; /* Base horizontal position */
int32_t jy; /* Vertical position */
uint8_t *plt; /* Palette */
uint8_t *src; /* Pointer to character source data in host memory */
/* Identify the visible cells */
cy1 = (wnd->y1-y ) & ~7; if (cy1 < 0) cy1 = 0; if (cy1 > 512) cy1 = 512;
cy2 = (wnd->y2-y+7) & ~7; if (cy2 < 0) cy2 = 0; if (cy2 > 512) cy2 = 512;
cx1 = (wnd->x1-x ) & ~7; if (cx1 < 0) cx1 = 0; if (cx1 > 512) cx1 = 512;
cx2 = (wnd->x2-x+7) & ~7; if (cx2 < 0) cx2 = 0; if (cx2 > 512) cx2 = 512;
/* Process all objects in the group */
start = group == 0 ? 0 : (sim->vip.spt[group - 1] + 1) & 1023;
stop = sim->vip.spt[group];
top = (int32_t) sim->vip.xp.sbcount << 3;
for (o = stop; o != -1; o = o == start ? -1 : (o - 1) & 1023) {
obj = &sim->vip.ram[0x3E000 | o << 3];
/* Process all cells as overplane */
if (over) {
for (cy = cy1; cy < cy2; cy += 8)
for (cx = cx1; cx < cx2; cx += 8)
vipDrawCell(shadow, wnd, map, x + cx, y + cy);
return;
}
/* Validate object is enabled */
attr1 = busReadBuffer(obj + 2, VB_U16);
if ((attr1 & 0xC000) == 0) /* JLON, JRON */
/* Process all cells normally */
row = &map[cy1 << 3 | cx1 >> 3];
for (cy = cy1 ; cy < cy2; cy += 8, row += 64)
for (cx = cx1, col = row; cx < cx2; cx += 8, col++)
vipDrawCell(shadow, wnd, col, x + cx, y + cy);
}
/* Draw a background into shadow memory */
static void vipDrawBackground(uint8_t *shadow, Window *wnd,
World *world, int32_t x, int32_t y) {
int32_t bx1, bx2, by1, by2; /* BG map bounds in background pixels */
int32_t bx, by; /* BG map origin in background pixels */
/* Identify the visible BG maps */
by1 = (wnd->y1 - y ) & ~511;
by2 = (wnd->y2 - y - 1) & ~511;
bx1 = (wnd->x1 - x ) & ~511;
bx2 = (wnd->x2 - x - 1) & ~511;
/* Process all rows of BG maps */
for (by = by1; by <= by2; by += 512) {
/* Row is out of bounds */
if (world->over && (by < 0 || by >= world->bgHeight)) {
for (bx = bx1; bx <= bx2; bx++)
vipDrawBGMap(shadow, wnd, 1, world->overplane, x + bx, y + by);
continue;
}
/* Validate Y position */
jy = (int8_t) obj[4];
if (jy < 7)
jy &= 0xFF;
if (jy < top - 7 || jy > 223)
continue;
/* Process all columns of BG maps */
for (bx = bx1; bx <= bx2; bx += 512) {
/* Parse attributes */
jx = SignExtend(busReadBuffer(obj, VB_U16), 10);
jp = SignExtend(attr1, 10);
attr2 = busReadBuffer(obj + 6, VB_U16);
jhflp = attr2 >> 13 & 1;
jvflp = attr2 >> 12 & 1;
/* Locate palette and character state in host memory */
plt = sim->vip.jplt[attr2 >> 14];
src = vipCharacter(sim, attr2);
/* Draw the character */
for (i = 0; i < 2; i++) {
if ((attr1 >> (15 - i) & 1) == 0) /* J*ON */
/* Column is out of bounds */
if (world->over && (bx < 0 || bx >= world->bgWidth)) {
vipDrawBGMap(shadow, wnd, 1, world->overplane, x + bx, y + by);
continue;
ox = jx - (i == 0 ? jp : -jp);
dest = &sim->vip.shadow[i][ox * 224 + jy];
/* Draw all columns */
for (x = 0; x < 8; x++, dest += 224) {
fx = ox + x;
if (fx < 0 || fx > 383)
continue;
sx = jhflp ? 7 - x : x;
/* Draw all rows */
for (y = 0; y < 8; y++) {
fy = jy + y;
if (fy < top || fy > 223)
continue;
sy = jvflp ? 7 - y : y;
pixel = src[sy << 1 | sx >> 2] >> ((sx & 3) << 1) & 3;
if (pixel != 0)
dest[y] = plt[pixel]; /* TODO: Research clocks */
} /* y */
} /* x */
} /* i */
}
}
/* Draw a background world into shadow memory */
static void vipDrawWorld(VB *sim, uint8_t *world, uint16_t bits) {
uint16_t bgAttr; /* BG map cell attributes */
int32_t bx; /* BG source X */
int32_t by; /* BG source Y */
uint8_t *cell; /* Pointer to BG map cell in host memory */
uint8_t *chr; /* Pointer to character data in host memory */
int32_t cx; /* BG cell/character source X */
int32_t cy; /* BG cell/character source Y */
uint8_t *dest; /* Pointer to output in shadow memory */
EyeAttribs ettr; /* Eye attributes */
uint32_t param; /* Current line parameter address */
int8_t pixel; /* Character pixel value */
int32_t i, x, y; /* Iterators */
/* World attributes */
WorldAttribs wttr; /* Common attributes */
int32_t dx; /* Affine per-pixel X delta */
int32_t dy; /* Affine per-pixel Y delta */
int32_t mp; /* BG source parallax */
int32_t mx; /* BG source X */
int32_t my; /* BG source Y */
int16_t hofst; /* H-bias shift */
/* Working variables */
ettr.bp = wttr.mp = wttr.mx = wttr.my = wttr.params = 0;
/* Parse attributes */
if (vipParseWorld(sim, world, bits, &wttr))
return; /* Window not in frame */
/* Draw the world */
for (i = 0; i < 2; i++) {
/* World is not visible */
if ((bits & (int32_t) 0x8000 >> i) == 0) /* LON, RON */
continue;
/* Process attributes */
if (vipParseEye(&wttr, &ettr, i))
continue;
/* Draw all rows */
for (y = wttr.top, param = wttr.params; y <= wttr.bottom; y++) {
/* Parse line parameters */
hofst = 0;
mp = wttr.mp;
mx = wttr.mx;
my = wttr.my;
switch (wttr.bgm) {
case 1: /* H-bias */
hofst = SignExtend(vipParam(param | i << 1), 13);
param += 4;
break;
case 2: /* Affine */
dx = (int32_t) (int16_t) vipParam(param|6) << 7;
dy = (int32_t) (int16_t) vipParam(param|8) << 7;
mp = (int32_t) (int16_t) vipParam(param|2);
mp = ettr.left - ettr.wx - ((mp < 0) ^ i ? mp : 0);
mx = ((int32_t) (int16_t) vipParam(param ) << 13) + dx*mp;
my = ((int32_t) (int16_t) vipParam(param|4) << 13) + dy*mp;
param += 16;
}
/* Select output in shadow memory */
dest = &sim->vip.shadow[i][ettr.left * 224 + y];
/* Column is in bounds */
vipDrawBGMap(shadow, wnd, 0, world->bg[
(by >> 9 & world->bgMaskY) << world->bgShift |
(bx >> 9 & world->bgMaskX)
], x + bx, y + by);
}
/* Draw all columns */
for (x = ettr.left; x <= ettr.right; x++, dest += 224) {
}
/* Locate the pixel in the background */
if (wttr.bgm != 2) { /* Normal, H-bias */
cx = x - ettr.wx + mx + ettr.bp + hofst;
cy = y - wttr.gy + my;
} else { /* Affine */
cx = (int16_t) (mx >> 16);
cy = (int16_t) (my >> 16);
mx += dx;
my += dy;
}
}
/* Locate the BG map in the background */
bx = SignExtend(cx >> 9, 23);
by = SignExtend(cy >> 9, 23);
cell = NULL;
/* Draw a Normal world into shadow memory */
static void vipDrawNormal(VB *sim, World *world) {
int32_t mx, my; /* Background origin */
Window wnd; /* Window bounds in pixels */
int i; /* Iterator */
/* BG map is out of bounds */
if (
bx < 0 || bx > wttr.bgw ||
by < 0 || by > wttr.bgh
) {
if (wttr.over == NULL) {
bx &= wttr.bgw;
by &= wttr.bgh;
} else cell = wttr.over;
}
/* No visible content */
if (world->width == 0)
return;
/* Locate the cell in the BG map */
if (cell == NULL) {
cell = &sim->vip.ram[
0x20000 | ((
wttr.base +
wttr.bg[by << wttr.scx | bx]
) << 13) |
(cy << 4 & 0x1F80) |
(cx >> 2 & 0x007E)
];
}
/* Vertical window bounds */
wnd.y1 = (int32_t) sim->vip.xp.sbcount << 3;
if (world->gy > wnd.y1)
wnd.y1 = world->gy;
if (wnd.y1 >= 224)
return;
wnd.y2 = world->gy + world->height;
if (wnd.y2 <= 0)
return;
my = world->gy - world->my;
if (wnd.y1 < 0)
wnd.y1 = 0;
if (wnd.y2 > 224)
wnd.y2 = 224;
/* Extract the pixel from the character */
bgAttr = busReadBuffer(cell, VB_U16);
chr = vipCharacter(sim, bgAttr);
cx = (bgAttr & 0x2000 ? 7 - cx : cx) & 7;
cy = (bgAttr & 0x1000 ? 7 - cy : cy) & 7;
pixel = chr[cy << 1 | cx >> 2] >> ((cx & 3) << 1) & 3;
/* Process both eyes */
for (i = 0; i < 2; i++) {
/* Write the pixel into shadow memory */
/* Check visibility */
if (!(world->lron >> (i ^ 1) & 1))
continue;
/* Horizontal window bounds */
wnd.x1 = world->gx + (i == 0 ? -world->gp : world->gp);
if (wnd.x1 >= 384)
continue;
wnd.x2 = wnd.x1 + world->width;
if (wnd.x2 <= 0)
continue;
mx = wnd.x1 - world->mx - (i == 0 ? -world->mp : world->mp);
if (wnd.x1 < 0)
wnd.x1 = 0;
if (wnd.x2 > 384)
wnd.x2 = 384;
/* Draw the background into the window */
vipDrawBackground(sim->vip.shadow[i], &wnd, world, mx, my);
}
}
/* Draw an H-bias world into shadow memory */
static void vipDrawHBias(VB *sim, World *world) {
int32_t mx, my; /* Background origin */
uint8_t *param; /* World parameter memory */
Window wnd; /* Window bounds in pixels */
int32_t y1, y2; /* Vertical window bounds */
int i, y; /* Iterators */
/* No visible content */
if (world->width == 0)
return;
/* Vertical window bounds */
y1 = (int32_t) sim->vip.xp.sbcount << 3;
if (world->gy > y1)
y1 = world->gy;
if (y1 >= 224)
return;
y2 = world->gy + world->height;
if (y2 <= 0)
return;
my = world->gy - world->my;
if (y1 < 0)
y1 = 0;
if (y2 > 224)
y2 = 224;
/* Process both eyes */
for (i = 0; i < 2; i++) {
/* Check visibility */
if (!(world->lron >> (i ^ 1) & 1))
continue;
/* Horizontal window bounds */
wnd.x1 = world->gx + (i == 0 ? -world->gp : world->gp);
if (wnd.x1 >= 384)
continue;
wnd.x2 = wnd.x1 + world->width;
if (wnd.x2 <= 0)
continue;
mx = wnd.x1 - world->mx - (i == 0 ? -world->mp : world->mp);
if (wnd.x1 < 0)
wnd.x1 = 0;
if (wnd.x2 > 384)
wnd.x2 = 384;
/* Process all visible lines */
param = &sim->vip.ram[world->paramBase +
((y1 - world->gy) << 2) + (i << 1)];
for (y = y1; y < y2; y++, param += 2) {
/* Configure window bounds */
wnd.y1 = y;
wnd.y2 = y + 1;
/* Draw the background into the window */
vipDrawBackground(sim->vip.shadow[i], &wnd, world,
mx + busReadBuffer(param, VB_S16), my);
}
}
}
/* Draw an Affine world into shadow memory */
static void vipDrawAffine(VB *sim, World *world) {
Cell *cell; /* Source cell */
uint8_t *col, *row; /* Output pixel */
uint8_t pixel; /* Character pixel value */
uint8_t *param; /* World parameter memory */
int32_t px, py; /* Pixel source coordinates */
int32_t dx, dy, mp, mx, my; /* Affine parameters */
int32_t wx; /* Base world X coordinate */
int32_t x1, x2, y1, y2; /* Window bounds */
int i, x, y; /* Iterators */
/* No visible content */
if (world->width == 0 || world->height == 0)
return;
/* Vertical window bounds */
y1 = (int32_t) sim->vip.xp.sbcount << 3;
if (world->gy > y1)
y1 = world->gy;
if (y1 >= 224)
return;
y2 = world->gy + world->height;
if (y2 <= 0)
return;
if (y1 < 0)
y1 = 0;
if (y2 > 224)
y2 = 224;
/* Process both eyes */
for (i = 0; i < 2; i++) {
/* Check visibility */
if (!(world->lron >> (i ^ 1) & 1))
continue;
/* Horizontal window bounds */
x1 = world->gx + (i == 0 ? -world->gp : world->gp);
if (x1 >= 384)
continue;
x2 = x1 + world->width;
if (x2 <= 0)
continue;
wx = x1;
if (x1 < 0)
x1 = 0;
if (x2 > 384)
x2 = 384;
wx = x1 - wx;
/* Process all visible rows */
param = &sim->vip.ram[world->paramBase | (y1 - world->gy) << 4];
row = &sim->vip.shadow[i][x1 * 224 + y1];
for (y = y1; y < y2; y++, param += 16, row++) {
/* Parse line parameters */
mx = (int32_t) busReadBuffer(param + 0, VB_S16) << 6;
mp = (int32_t) busReadBuffer(param + 2, VB_S16);
my = (int32_t) busReadBuffer(param + 4, VB_S16) << 6;
dx = (int32_t) busReadBuffer(param + 6, VB_S16);
dy = (int32_t) busReadBuffer(param + 8, VB_S16);
/* Adjust left-edge parameters */
if ((mp < 0) ^ i) {
mx += dx * (wx - mp);
my += dy * (wx - mp);
} else {
mx += dx * wx;
my += dy * wx;
}
/* Process all visible columns */
col = row;
for (x = x1; x < x2; x++, mx += dx, my += dy, col += 224) {
px = (int16_t) (mx >> 9);
py = (int16_t) (my >> 9);
/* Overplane */
if (world->over && (
px < 0 || px >= world->bgWidth ||
py < 0 || py >= world->bgHeight
)) cell = world->overplane;
/* Regular */
else cell = &world->bg[
(py >> 9 & world->bgMaskY) << world->bgShift |
(px >> 9 & world->bgMaskX)
][(py << 3 & 0xFC0) | (px >> 3 & 63)];
/* Sample the pixel */
pixel = cell->pixels[(py & 7) << 3 | (px & 7)];
if (pixel != 0)
*dest = sim->vip.gplt[bgAttr >> 14 & 3][pixel];
*col = cell->palette[pixel];
} /* x */
@@ -928,12 +1096,40 @@ static void vipDrawWorld(VB *sim, uint8_t *world, uint16_t bits) {
}
/* Draw an object group into shadow memory */
static void vipDrawObjects(VB *sim, int group) {
Object *obj; /* Object state */
int start; /* First object in the group */
int stop; /* Last object in the group */
Window wnd; /* Window bounds in pixels */
int i, o; /* Iterators */
/* Establish the viewing window */
wnd.x1 = 0;
wnd.y1 = (int32_t) sim->vip.xp.sbcount << 3;
wnd.x2 = 384;
wnd.y2 = 224;
/* Process all objects in the group */
start = group == 0 ? 0 : (sim->vip.spt[group - 1] + 1) & 1023;
stop = sim->vip.spt[group];
for (o = stop; o != -1; o = o == start ? -1 : (o - 1) & 1023) {
obj = &sim->vip.objects[o];
for (i = 0; i < 2; i++) {
if (!(obj->lron >> (i ^ 1) & 1))
continue;
vipDrawCell(sim->vip.shadow[i], &wnd, &obj->cell,
obj->jx + (i == 0 ? -obj->jp : obj->jp), obj->jy);
}
}
}
/* Draw the current graphics configuration into shadow memory */
static void vipRender(VB *sim) {
uint16_t attr; /* World attribute bits */
int group; /* Next object group index */
uint16_t *timing; /* Column timing in host memory */
uint8_t *world; /* World attribute source in host memory */
World *world; /* World attribute source in host memory */
int32_t x, y; /* Iterators */
/* Erase all pixels */
@@ -943,25 +1139,25 @@ static void vipRender(VB *sim) {
/* Process all worlds */
group = 3;
world = &sim->vip.ram[0x3DBE0]; /* World 31 */
for (x = 31; x >= 0; x--, world -= 32) {
attr = busReadBuffer(world, VB_U16);
for (x = 31; x >= 0; x--) {
world = &sim->vip.worlds[x];
/* Non-graphical world */
if (attr & 0x0040) /* END */
if (world->end)
break; /* Control world */
if ((attr & 0xC000) == 0) /* LON, RON */
if (world->lron == 0)
continue; /* Dummy world */
/* Object world */
if ((attr >> 12 & 3) == 3) {
vipDrawObjects(sim, group);
group = (group - 1) & 3;
/* Processing by world type */
switch (world->bgm) {
case 0: vipDrawNormal(sim, world); break;
case 1: vipDrawHBias (sim, world); break;
case 2: vipDrawAffine(sim, world); break;
case 3:
vipDrawObjects(sim, group);
group = (group - 1) & 3;
}
/* Background world */
else if (attr & 0xC000) /* LON, RON */
vipDrawWorld(sim, world, attr);
}
/*
@@ -1134,7 +1330,11 @@ static void vipRead(VB *sim, uint32_t address, int type, int32_t *value) {
/* Simulate a hardware reset */
static void vipReset(VB *sim) {
int x, y; /* Iterators */
Cell *cell; /* BG map cell state */
Character *chr; /* Character state */
Object *obj; /* Object state */
World *world; /* World state */
int x, y; /* Iterators */
/* Normal */
sim->vip.intenb = 0x0000;
@@ -1159,6 +1359,53 @@ static void vipReset(VB *sim) {
sim->vip.jplt[x][y] = 0;
}
}
for (x = 0; x < 0x10000; x++) {
cell = &sim->vip.cells[x];
cell->palette = sim->vip.gplt[0];
cell->pixels = sim->vip.characters[0][0];
}
for (x = 0; x < 2048; x++) {
chr = &sim->vip.characters[x];
for (y = 0; y < 64; y++)
(*chr)[0][y] = (*chr)[1][y] = (*chr)[2][y] = (*chr)[3][y] = 0;
}
for (x = 0; x < 32; x++) {
world = &sim->vip.worlds[x];
world->bgm = 0;
world->end = 0;
world->gp = 0;
world->gx = 0;
world->gy = 0;
world->mx = 0;
world->mp = 0;
world->my = 0;
world->over = 0;
world->overplane = &sim->vip.cells[0];
for (y = 0; y < 64; y++)
world->bg[0] = world->overplane;
world->bgHeight = 512;
world->bgMaskX = 0;
world->bgMaskY = 0;
world->bgShift = 0;
world->bgWidth = 512;
world->hAffine = 0;
world->hNaffine = 0;
world->height = 8;
world->lron = 0;
world->paramBase = 0;
world->wAffine = 0;
world->wNaffine = 0;
world->width = 0;
}
for (x = 0; x < 1024; x++) {
obj = &sim->vip.objects[x];
obj->cell.palette = sim->vip.jplt[0];
obj->cell.pixels = sim->vip.characters[0][0];
obj->jp = 0;
obj->jx = 0;
obj->jy = 0;
obj->lron = 0;
}
/* Display processor extra (the hardware does not do this) */
sim->vip.dp.fclk = 0;
@@ -1206,9 +1453,28 @@ static void vipWrite(VB*sim,uint32_t address,int type,int32_t value,int debug){
address &= 0x0007FFFF;
/* RAM */
if (address < 0x40000)
if (address < 0x40000) {
busWriteBuffer(&sim->vip.ram[address], type, value);
/* Character memory */
if ((address & 0x26000) == 0x06000) {
address = (address & 0x18000) >> 2 | (address & 0x01FFF);
vipWriteChr(sim, address, type, value);
}
/* BG map memory */
if (address >= 0x20000)
vipWriteCell(sim, address & 0x1FFFF, type, value);
/* World memory */
if ((address & 0x3FC00) == 0x3D800)
vipWriteWorld(sim, address &0x003FF, type, value);
/* Object memory */
if (address >= 0x3E000)
vipWriteObject(sim, address & 0x01FFF, type, value);
}
/* Unmapped */
else if (address < 0x5E000)
;
@@ -1223,6 +1489,7 @@ static void vipWrite(VB*sim,uint32_t address,int type,int32_t value,int debug){
/* Mirrors of character memory */
else {
vipWriteChr(sim, address & 0x07FFF, type, value);
address = 0x06000 | (address << 2 & 0x18000) | (address & 0x01FFF);
busWriteBuffer(&sim->vip.ram[address], type, value);
}
+97 -90
View File
@@ -74,100 +74,57 @@ static void vsuNextFreqMod(VB *sim, Channel *chan) {
}
/* Process one channel */
static void vsuEmulateChannel(VB *sim, int index, uint32_t clocks) {
uint32_t bit; /* Pseudorandom bit */
Channel *chan; /* Channel handle */
int freqmod; /* Frequency modifications enabled */
uint32_t until; /* Clocks to process sub-channel components */
static void vsuEmulateChannel(VB *sim, Channel *chan) {
uint32_t bit; /* Pseudorandom bit */
/* Select channel */
chan = &sim->vsu.channels[index];
/* Channel is disabled */
if (!chan->int_.enb)
/* Automatic shutoff */
if (chan->int_.auto_ && chan->int_.clocks == 0) {
chan->int_.enb = 0;
return;
}
/* Process all clocks */
do {
/* Next input sample */
if (chan->clocks == 0) {
/* Frequency modifications are active */
freqmod =
index == 4 && /* Channel 5 */
sim->vsu.freqmod.enb && /* Modifications enabled */
sim->vsu.freqmod.interval != 0 /* Modifications valid */
;
/* Wave */
if (chan != &sim->vsu.channels[5]) {
chan->clocks = 4 * (2048 - (uint32_t) chan->freq.current);
chan->wave.sample = (chan->wave.sample + 1) & 31;
}
/* Clocks until next state change */
until = clocks;
if (chan->clocks < until)
until = chan->clocks;
if (chan->env.enb && chan->env.clocks < until)
until = chan->env.clocks;
if (chan->int_.auto_ && chan->int_.clocks < until)
until = chan->int_.clocks;
if (freqmod && sim->vsu.freqmod.clocks < until)
until = sim->vsu.freqmod.clocks;
/* Noise */
else {
chan->clocks = 40 * (2048 - (uint32_t) chan->freq.current);
bit = ((
sim->vsu.noise.register_ >> NOISE_TAPS[sim->vsu.noise.tap]^
sim->vsu.noise.register_ >> 7
) & 1) ^ 1;
sim->vsu.noise.register_ = bit |
(sim->vsu.noise.register_ << 1 & 0x7FFE);
}
/* Manage clocks */
clocks -= until;
chan->clocks -= until;
if (chan->env.enb)
chan->env.clocks -= until;
if (chan->int_.auto_)
chan->int_.clocks -= until;
if (freqmod)
sim->vsu.freqmod.clocks -= until;
}
/* Automatic shutoff */
if (chan->int_.auto_ && chan->int_.clocks == 0) {
chan->int_.enb = 0;
/* Envelope modification */
if (chan->env.enb && chan->env.clocks == 0) {
if (chan->env.dir == 0 && chan->env.value != 0)
chan->env.value--;
else if (chan->env.dir == 1 && chan->env.value != 15)
chan->env.value++;
else if (chan->env.rep)
chan->env.value = chan->env.reload;
chan->env.clocks = ((uint32_t) chan->env.interval + 1) * 307220;
}
/* Frequency modification */
if (chan->freqmod && sim->vsu.freqmod.clocks == 0) {
chan->freq.current = sim->vsu.freqmod.next;
vsuNextFreqMod(sim, chan);
if (!chan->int_.enb)
return;
}
/* Next sample */
if (chan->clocks == 0) {
/* Wave */
if (index != 5) {
chan->clocks = 4 * (2048 - (uint32_t) chan->freq.current);
chan->wave.sample = (chan->wave.sample + 1) & 31;
}
/* Noise */
else {
chan->clocks = 40 * (2048 - (uint32_t) chan->freq.current);
bit = ((
sim->vsu.noise.register_ >> NOISE_TAPS[sim->vsu.noise.tap]^
sim->vsu.noise.register_ >> 7
) & 1) ^ 1;
sim->vsu.noise.register_ = bit |
(sim->vsu.noise.register_ << 1 & 0x7FFE);
}
}
/* Envelope modification */
if (chan->env.enb && chan->env.clocks == 0) {
if (chan->env.dir == 0 && chan->env.value != 0)
chan->env.value--;
else if (chan->env.dir == 1 && chan->env.value != 15)
chan->env.value++;
else if (chan->env.rep)
chan->env.value = chan->env.reload;
chan->env.clocks = ((uint32_t) chan->env.interval + 1) * 307220;
}
/* Frequency modification */
if (freqmod && sim->vsu.freqmod.clocks == 0) {
chan->freq.current = sim->vsu.freqmod.next;
vsuNextFreqMod(sim, chan);
if (!chan->int_.enb)
return;
sim->vsu.freqmod.clocks = (uint32_t) sim->vsu.freqmod.interval *
(sim->vsu.freqmod.clk == 0 ? 19200 : 153600);
}
} while (clocks != 0);
sim->vsu.freqmod.clocks = (uint32_t) sim->vsu.freqmod.interval *
(sim->vsu.freqmod.clk == 0 ? 19200 : 153600);
}
}
@@ -230,6 +187,8 @@ static void vsuWriteEV1(VB *sim, int index, uint8_t value) {
sim->vsu.freqmod.func = value >> 4 & 1;
sim->vsu.freqmod.rep = value >> 5 & 1;
vsuNextFreqMod(sim, chan);
chan->freqmod =
sim->vsu.freqmod.enb && sim->vsu.freqmod.interval != 0;
break;
case 5: /* Channel 6 */
@@ -313,6 +272,8 @@ static void vsuWriteSWP(VB *sim, uint8_t value) {
(sim->vsu.freqmod.clk == 0 ? 19200 : 153600);
if (clocks < sim->vsu.freqmod.clocks)
sim->vsu.freqmod.clocks = clocks;
sim->vsu.channels[4].freqmod =
sim->vsu.freqmod.enb && sim->vsu.freqmod.interval != 0;
}
@@ -321,6 +282,8 @@ static void vsuWriteSWP(VB *sim, uint8_t value) {
/* Process component */
static void vsuEmulate(VB *sim, uint32_t clocks) {
Channel *chan; /* Input channel */
uint32_t chantil; /* Clocks until next channel state update */
float i0; /* Current analog input sample */
float o0; /* Current analog output sample */
uint16_t output[2]; /* Digital output samples */
@@ -340,12 +303,54 @@ static void vsuEmulate(VB *sim, uint32_t clocks) {
sim->vsu.clocks -= until;
/* Process all channels */
for (x = 0; x < 6; x++)
vsuEmulateChannel(sim, x, until);
for (x = 0; x < 6; x++) {
chan = &sim->vsu.channels[x];
chantil = until;
/* Process all clocks */
while (chan->int_.enb && chantil != 0) {
/* Determine when the next state change will occur */
if (chan->until == 0) {
/* Clocks until next state change */
chan->until = chan->clocks;
if (chan->env.enb && chan->env.clocks < chan->until)
chan->until = chan->env.clocks;
if (chan->int_.auto_ && chan->int_.clocks < chan->until)
chan->until = chan->int_.clocks;
if (chan->freqmod && sim->vsu.freqmod.clocks < chan->until)
chan->until = sim->vsu.freqmod.clocks;
/* Manage clocks */
chan->clocks -= chan->until;
if (chan->env.enb)
chan->env.clocks -= chan->until;
if (chan->int_.auto_)
chan->int_.clocks -= chan->until;
if (chan->freqmod)
sim->vsu.freqmod.clocks -= chan->until;
}
/* Manage clocks */
if (chan->until > chantil) {
chan->until -= chantil;
chantil = 0;
} else {
chantil -= chan->until;
chan->until = 0;
}
/* Update channel state */
if (chan->until == 0)
vsuEmulateChannel(sim, chan);
}
}
/* Wait for the current sample to finish */
if (sim->vsu.clocks != 0)
continue;
return;
/* Compute the output sample */
output[0] = output[1] = 0;
@@ -369,7 +374,7 @@ static void vsuEmulate(VB *sim, uint32_t clocks) {
sim->vsu.out.offset >> 1 < sim->vsu.out.capacity
) {
/* Processing by data type*/
/* Processing by data type */
switch (sim->vsu.out.type) {
case VB_S16:
((int16_t *) sim->vsu.out.samples)
@@ -416,6 +421,8 @@ static void vsuReset(VB *sim) {
for (x = 0; x < 6; x++) {
chan = &sim->vsu.channels[x];
chan->clocks = 0;
chan->freqmod = 0;
chan->until = 0;
chan->env.clocks = 0;
chan->env.enb = 0;
chan->env.dir = 0;
+24 -18
View File
@@ -1,4 +1,4 @@
/* This file is included into vb.c and cannot be compiled on its own. */
/* This file is included into vbu.c and cannot be compiled on its own. */
#ifdef VBUAPI
@@ -253,18 +253,24 @@ static void dasmOpDisp26(char*dest, VBU_DasmConfig*config, VBU_DasmLine*line) {
}
/* Format a 5-bit sign-extended immediate operand */
static void dasmOpImm5S(char *dest, VBU_DasmLine *line) {
static void dasmOpImm5S(char *dest, VBU_DasmConfig*config, VBU_DasmLine *line){
if (config->immediateNotation == VBU_NUMBER)
*dest++ = '#';
sprintf(dest, "%d", SignExtend(line->code[0], 5));
}
/* Format a 5-bit zero-filled immediate operand */
static void dasmOpImm5U(char *dest, VBU_DasmLine *line) {
static void dasmOpImm5U(char *dest, VBU_DasmConfig*config, VBU_DasmLine *line){
if (config->immediateNotation == VBU_NUMBER)
*dest++ = '#';
sprintf(dest, "%d", line->code[0] & 31);
}
/* Format a 16-bit sign-extended immediate operand */
static void dasmOpImm16S(char*dest, VBU_DasmConfig*config, VBU_DasmLine*line) {
int32_t imm = (int16_t) ((int16_t) line->code[3] << 8 | line->code[2]);
int32_t imm = (int16_t) ((int16_t) line->code[3] << 8 | line->code[2]);
if (config->immediateNotation == VBU_NUMBER)
*dest++ = '#';
if (imm >= -256 && imm <= 256) {
sprintf(dest, "%d", imm);
return;
@@ -279,6 +285,8 @@ static void dasmOpImm16S(char*dest, VBU_DasmConfig*config, VBU_DasmLine*line) {
/* Format a 16-bit zero-filled immediate operand */
static void dasmOpImm16U(char*dest, VBU_DasmConfig*config, VBU_DasmLine*line) {
uint16_t imm = (uint16_t) line->code[3] << 8 | line->code[2];
if (config->immediateNotation == VBU_NUMBER)
*dest++ = '#';
dasmToHex(dest, config, 4, imm);
}
@@ -441,12 +449,6 @@ static VBU_DasmLine* dasmGrow(
return *lines == NULL ? NULL : &(*lines)[index];
}
/* Determine the size of an instruction */
static uint32_t dasmInstSize(VB *sim, uint32_t address) {
unsigned opcode = vbRead(sim, address, VB_U16) >> 10 & 63;
return opcode < 0x20 || opcode == 0x32 || opcode == 0x36 ? 2 : 4;
}
/* Format an operand */
static void dasmOperand(char *dest, VBU_DasmConfig *config,
VBU_DasmLine *line, uint8_t type) {
@@ -454,8 +456,8 @@ static void dasmOperand(char *dest, VBU_DasmConfig *config,
case DASM_BCOND : dasmOpBCOND (dest, config, line); break;
case DASM_DISP9 : dasmOpDisp9 (dest, config, line); break;
case DASM_DISP26: dasmOpDisp26(dest, config, line); break;
case DASM_IMM5S : dasmOpImm5S (dest, line); break;
case DASM_IMM5U : dasmOpImm5U (dest, line); break;
case DASM_IMM5S : dasmOpImm5S (dest, config, line); break;
case DASM_IMM5U : dasmOpImm5U (dest, config, line); break;
case DASM_IMM16S: dasmOpImm16S(dest, config, line); break;
case DASM_IMM16U: dasmOpImm16U(dest, config, line); break;
case DASM_JMP : dasmOpJMP (dest, config, line); break;
@@ -507,11 +509,13 @@ static int dasmLine(VB *sim, uint32_t *address, uint32_t pc,
/* Process non-text members */
line = &(*lines)[index];
line->address = *address;
line->codeLength = dasmInstSize(sim, *address);
line->codeLength = vbuCodeSize(sim, *address);
line->isPC = *address == pc;
for (x = 0; x < line->codeLength; x++)
line->code[x] = vbRead(sim, *address + x, VB_U8);
*address += pc - *address < line->codeLength ?
/* Advance to the next instruction or PC, whichever is sooner */
*address += pc != *address && pc - *address < line->codeLength ?
pc - *address : line->codeLength;
/* Do not process text members */
@@ -652,10 +656,12 @@ static VBU_DasmLine* dasmDisassemble(VB *sim, uint32_t address,
}
/* Check if the instruction contains the reference address */
size = dasmInstSize(sim, addr);
size = vbuCodeSize(sim, addr);
if (address - addr < size)
break;
addr += pc - addr < size ? pc - addr : size;
/* Advance to the next instruction or PC, whichever is sooner */
addr += addr != pc && pc - addr < size ? pc - addr : size;
}
/* Address of first line is in the circular buffer */
@@ -666,7 +672,7 @@ static VBU_DasmLine* dasmDisassemble(VB *sim, uint32_t address,
/* Keep decoding until the first line of output */
else for (; line < 0; line++)
addr += dasmInstSize(sim, addr);
addr += vbuCodeSize(sim, addr);
/* Working variables */
size = length * sizeof (VBU_DasmLine);
@@ -680,7 +686,7 @@ static VBU_DasmLine* dasmDisassemble(VB *sim, uint32_t address,
if (dasmLine(sim, &addr, pc, config, &lines, &size, &offset, x))
goto catch;
}
return lines;
return VBU_REALLOC(lines, offset);
/* Exception handler */
catch:
+7
View File
@@ -38,6 +38,12 @@ static int32_t SignExtend(int32_t value, int32_t bits) {
/******************************* API Commands ********************************/
/* Determine the size in bytes of an instruction */
VBUAPI int vbuCodeSize(VB *sim, uint32_t address) {
int opcode = vbRead(sim, address, VB_U16) >> 10 & 63;
return opcode < 0x20 || opcode == 0x32 || opcode == 0x36 ? 2 : 4;
}
/* Initialize disassembler options with default settings */
VBUAPI VBU_DasmConfig* vbuDasmInit(VBU_DasmConfig *config) {
config->bcondNotation = VBU_JOINED;
@@ -47,6 +53,7 @@ VBUAPI VBU_DasmConfig* vbuDasmInit(VBU_DasmConfig *config) {
config->conditionNotation = VBU_NAMES;
config->hexCase = VBU_UPPER;
config->hexNotation = VBU_0X;
config->immediateNotation = VBU_NONE;
config->memoryNotation = VBU_OUTSIDE;
config->mnemonicCase = VBU_UPPER;
config->operandOrder = VBU_DEST_LAST;
+4
View File
@@ -29,6 +29,8 @@ extern "C" {
#define VBU_L 0
#define VBU_LOWER 1
#define VBU_NAMES 1
#define VBU_NONE 0
#define VBU_NUMBER 1
#define VBU_NUMBERS 0
#define VBU_OUTSIDE 0
#define VBU_SPLIT 1
@@ -48,6 +50,7 @@ typedef struct { /* Defaults listed first */
uint8_t conditionNotation; /* NAMES, NUMBERS */
uint8_t hexCase; /* UPPER, LOWER */
uint8_t hexNotation; /* 0X, H, DOLLAR */
uint8_t immediateNotation; /* NONE, NUMBER */
uint8_t memoryNotation; /* OUTSIDE, INSIDE */
uint8_t mnemonicCase; /* UPPER, LOWER */
uint8_t operandOrder; /* DEST_LAST, DEST_FIRST */
@@ -82,6 +85,7 @@ typedef struct {
/******************************* API Commands ********************************/
VBUAPI int vbuCodeSize (VB *sim, uint32_t address);
VBUAPI VBU_DasmConfig* vbuDasmInit (VBU_DasmConfig *config);
VBUAPI VBU_DasmLine* vbuDisassemble(VB *sim, uint32_t address, VBU_DasmConfig *config, unsigned length, int line);
+101
View File
@@ -0,0 +1,101 @@
"use strict";
//////////////////////////////////// Audio ////////////////////////////////////
// Dedicated audio output processor
class Audio extends AudioWorkletProcessor {
// Instance fields
buffers; // Input sample buffer queue
core; // Communications with core thread
dom; // Communications with DOM thread
offset; // Offset into oldest buffer
///////////////////////// Initialization Methods //////////////////////////
constructor() {
super();
this.port.onmessage = async e=>{
await this.#construct(e.data.core);
this.port.postMessage(0);
};
}
// Asynchronous constructor
async #construct(core) {
// Configure instance fields
this.buffers = [];
this.core = core;
this.dom = this.port;
this.offset = 0;
// Configure communications
this.core.onmessage = e=>this.#onCore(e.data);
this.dom .onmessage = e=>this.#onDOM (e.data);
}
///////////////////////////// Public Methods //////////////////////////////
// Produce output samples (called by the user agent)
process(inputs, outputs, parameters) {
let output = outputs[0];
let length = output [0].length;
let empty = null;
// Process all samples
for (let x = 0; x < length;) {
// No bufferfed samples are available
if (this.buffers.length == 0) {
for (; x < length; x++)
output[0][x] = output[1][x] = 0;
break;
}
// Transfer samples from the oldest buffer
let buffer = this.buffers[0];
let y = this.offset;
for (; x < length && y < buffer.length; x++, y+=2) {
output[0][x] = buffer[y ];
output[1][x] = buffer[y + 1];
}
// Advance to the next buffer
if (y == buffer.length) {
if (empty == null)
empty = [];
empty.push(this.buffers.shift().buffer);
this.offset = 0;
}
// Buffer is not empty
else this.offset = y;
}
// Return emptied sample buffers to the core thread
if (empty != null)
this.core.postMessage(empty, empty);
return true;
}
///////////////////////////// Event Handlers //////////////////////////////
// Message received from core thread
#onCore(e) {
this.buffers.push(new Float32Array(e));
}
// Message received from DOM thread
#onDOM(e) {
}
}
registerProcessor("shrooms-vb", Audio);
+89
View File
@@ -0,0 +1,89 @@
let Constants = {
// Core
VB: {
// System registers
ADTRE: 25,
CHCW : 24,
ECR : 4,
EIPC : 0,
EIPSW: 1,
FEPC : 2,
FEPSW: 3,
PIR : 6,
PSW : 5,
TKCW : 7,
// Memory access data types
S8 : 0,
U8 : 1,
S16: 2,
U16: 3,
S32: 4,
F32: 5,
// Option keys
PSEUDO_HALT: 0,
// Controller buttons
PWR: 0x0001,
SGN: 0x0002,
A : 0x0004,
B : 0x0008,
RT : 0x0010,
LT : 0x0020,
RU : 0x0040,
RR : 0x0080,
LR : 0x0100,
LL : 0x0200,
LD : 0x0400,
LU : 0x0800,
STA: 0x1000,
SEL: 0x2000,
RL : 0x4000,
RD : 0x8000
},
// Utility
VBU: {
// Disassembler options
"0X" : 0,
C : 1,
DEST_FIRST: 1,
DEST_LAST : 0,
DOLLAR : 1,
E : 0,
H : 2,
INSIDE : 1,
JOINED : 0,
L : 0,
LOWER : 1,
NAMES : 1,
NONE : 0,
NUMBER : 1,
NUMBERS : 0,
OUTSIDE : 0,
SPLIT : 1,
UPPER : 0,
Z : 1
},
// Web interface
web: {
// Break types
BREAK_FRAME: 1,
BREAK_POINT: 2,
// Anaglyph colors
STEREO_CYAN : 0x00C6F0,
STEREO_GREEN : 0x00B400,
STEREO_MAGENTA: 0xC800FF,
STEREO_RED : 0xFF0000
}
};
export { Constants };
+563
View File
@@ -0,0 +1,563 @@
"use strict";
import { Constants } from "./Constants.js";
//////////////////////////////////// Core /////////////////////////////////////
// Emulation processor
new class Core {
// Instance fields
audio; // Audio communication
automatic; // Automatic emulation state
clocked; // Clocked emulation state
dom; // DOM communication
mallocs; // Memory allocations by pointer
pointerType; // TypedArray for WebAssembly pointers
sims; // Simulations by pointer
///////////////////////// Initialization Methods //////////////////////////
constructor() {
onmessage = async e=>{
await this.#construct(e.data.audio, e.data.wasmUrl);
this.dom.postMessage(0);
};
}
// Asynchronous constructor
async #construct(audio, wasmUrl) {
// Configure instance fields
this.mallocs = new Map();
this.sims = new Map();
// DOM thread communication
this.dom = globalThis;
this.dom.onmessage = e=>this[e.data.command](e.data);
// Instantiate the WebAssembly module
this.wasm = (await WebAssembly.instantiateStreaming(
fetch(wasmUrl), {
env: {
emscripten_notify_memory_growth: ()=>this.#onGrowth()
}
}));
Object.assign(this, this.wasm.instance.exports);
this.pointerType = this.PointerSize() == 8 ?
BigUint64Array : Uint32Array;
// Configure audio state
this.audio = audio;
audio.buffers = [0,0,0].map(v=>new Float32Array(41700 / 50 * 2));
audio.samples =
this.#malloc(41700 / 50 * 2, audio, "samples", Float32Array);
audio.onmessage = e=>this.#onAudio(e.data);
// Configure emulation states
this.automatic = { emulating: false };
this.clocked = {};
for (let s of [ this.automatic, this.clocked ]) {
s.clocks = this.#malloc(1, s, "clocks" , Uint32Array);
s.pointers = this.#malloc(1, s, "pointers", this.pointerType);
s.sims = [];
}
}
////////////////////////////// Core Commands //////////////////////////////
// Instantiate sims
createSims(message) {
let sims = new Array(message.count);
let size = this.vbSizeOf();
// Process all sims
for (let x = 0; x < message.count; x++) {
let sim = {
canvas : null,
keys : Constants.VB.SGN,
pointer : sims[x] = this.CreateSim()
};
this.sims.set(sim.pointer, sim);
// Video
sim.pixels = this.#noalloc(
this.GetExtPixels(sim.pointer),
384*224*4, sim, "pixels", Uint8ClampedArray
);
sim.image = new ImageData(sim.pixels, 384, 224);
// Audio
sim.samples = this.#noalloc(
this.GetExtSamples(sim.pointer),
41700 / 50 * 2, sim, "samples", Float32Array
);
}
this.dom.postMessage({
sims : sims,
promised: true
});
}
// Produce disassembly from a sim
disassemble(message) {
// Disassemble from the simulation
let dasm = message.config == null ?
this.vbuDisassemble(
message.sim,
message.address,
0,
message.length,
message.line
)
:
this.Disassemble(
message.config.bcondNotation,
message.config.conditionCase,
message.config.conditionCL,
message.config.conditionEZ,
message.config.conditionNotation,
message.config.hexCase,
message.config.hexNotation,
message.config.immediateNotation,
message.config.memoryNotation,
message.config.mnemonicCase,
message.config.operandOrder,
message.config.programCase,
message.config.programNotation,
message.config.setfNotation,
message.config.systemCase,
message.config.systemNotation,
message.sim,
message.address,
message.length,
message.line
)
;
// A memory error occurred
if (dasm == 0) {
this.dom.postMessage({
promised: message.promised,
success : false
});
return;
}
// Retrieve all disassembly data into a working buffer
let pointer = this.Realloc(0, message.length * 17 * 4);
let buffer = new Uint32Array(
this.memory.buffer, pointer, message.length * 17);
this.GetDasm(pointer, dasm, message.length);
// Consume output lines
let lines = new Array(message.length);
for (let x = 0, z = 0; x < lines.length; x++) {
let line = lines[x] = { text: {} };
line.address = buffer[z++];
line.code = new Array(buffer[z++]);
for (let y = 0; y < line.code.length; y++)
line.code[y] = buffer[z++];
z += 4 - line.code.length;
line.isPC = buffer[z++] != 0;
line.text.address = this.#string(dasm + buffer[z++], true);
line.text.code = new Array(line.code.length);
for (let y = 0; y < line.code.length; y++)
line.text.code[y] = this.#string(dasm + buffer[z++], true);
z += 4 - line.code.length;
line.text.mnemonic = this.#string(dasm + buffer[z++], true);
line.text.operands = new Array(buffer[z++]);
for (let y = 0; y < line.text.operands.length; y++)
line.text.operands[y] = this.#string(dasm + buffer[z++], true);
z += 3 - line.text.operands.length;
}
// Memory cleanup
this.Realloc(pointer, 0);
this.Realloc(dasm , 0);
// Send response
this.dom.postMessage({
success : true,
lines : lines,
promised: message.promised
});
}
// Emulate automatically
emulateAutomatic(message) {
// Configure sims
this.automatic.pointers = this.#realloc(
this.automatic.pointers, message.sims.length);
for (let x = 0; x < message.sims.length; x++) {
this.automatic.pointers[x] = message.sims[x];
this.automatic.sims [x] = this.sims.get(message.sims[x]);
}
// Notify the DOM thread
this.dom.postMessage({ promised: true });
// Begin automatic emulation
this.automatic.emulating = true;
this.#autoEmulate();
}
// Emulate for a given number of clocks
emulateClocked(message) {
// Configure sims
this.clocked.pointers = this.#realloc(
this.clocked.pointers, message.sims.length);
for (let x = 0; x < message.sims.length; x++) {
this.clocked.pointers[x] = message.sims[x];
this.clocked.sims [x] = this.sims.get(message.sims[x]);
}
// Process simulations
let broke = false;
this.clocked.clocks[0] = message.clocks;
while (!broke && this.clocked.clocks[0] != 0) {
// Process simulations until a suspension
this.Emulate(
this.clocked.pointers.pointer,
message.sims.length,
this.clocked.clocks.pointer
);
// Monitor break conditions
for (let x = 0; x < message.sims.length; x++) {
let sim = this.clocked.sims[x];
sim.breaks = this.GetBreaks(sim.pointer);
if (breaks & Constants.web.BREAK_POINT)
broke = true;
}
}
// Update images
for (let sim of this.clocked.sims) {
if (!(sim.breaks & Constants.web.BREAK_FRAME))
continue;
this.GetPixels(sim.pointer);
sim.context.putImageData(sim.image, 0, 0);
}
// Notify DOM thread
this.dom.postMessage({
promised: true,
broke : broke,
clocks : this.clocked.clocks[0]
});
}
// Specify anaglyph colors
setAnaglyph(message) {
this.SetAnaglyph(message.sim, message.left, message.right);
this.dom.postMessage({ promised: true });
}
// Specify the OffscreenCanvas that goes with a sim
setCanvas(message) {
let sim = this.sims.get(message.sim);
sim.canvas = message.canvas;
sim.context = sim.canvas.getContext("2d");
sim.context.putImageData(sim.image, 0, 0);
this.dom.postMessage({ promised: true });
}
// Specify a game pak RAM buffer
setCartRAM(message) {
this.#setCartMemory(message.sim, message.data,
this.vbGetCartRAM, this.vbSetCartRAM);
}
// Specify a game pak ROM buffer
setCartROM(message) {
this.#setCartMemory(message.sim, message.data,
this.vbGetCartROM, this.vbSetCartROM);
}
// Specify new game pad keys
setKeys(message) {
this.vbSetKeys(message.sim, message.keys);
this.dom.postMessage({ promised: true });
}
// Specify audio panning
setPanning(message) {
this.SetPanning(message.sim, message.panning);
this.dom.postMessage({ promised: true });
}
// Specify a new communication peer
setPeer(message) {
let orphaned = [];
let prev = this.vbGetPeer(message.sim);
if (prev != message.peer) {
if (prev != 0) // Sim's previous peer has been orphaned
orphaned.push(prev);
if (message.peer != 0) {
prev = this.vbGetPeer(message.peer);
if (prev != null) // Peer's previous peer has been orphaned
orphaned.push(prev);
}
this.vbSetPeer(message.sim, message.peer);
}
this.dom.postMessage({
orphaned: orphaned,
promised: true
});
}
// Specify audio volume
setVolume(message) {
this.SetVolume(message.sim, message.volume);
this.dom.postMessage({ promised: true });
}
// Suspend automatic emulation
suspend(message) {
this.automatic.emulating = false;
this.dom.postMessage({ promised: true });
}
///////////////////////////// Event Handlers //////////////////////////////
// Message from audio thread
#onAudio(e) {
// Output staged images
if (this.automatic.emulating && this.audio.buffers.length == 0) {
for (let sim of this.automatic.sims)
sim.context.putImageData(sim.image, 0, 0);
}
// Acquire the emptied buffers and resume emulation
this.audio.buffers.push(... e.map(b=>new Float32Array(b)));
this.#autoEmulate();
}
// WebAssembly memory has grown
#onGrowth() {
for (let prev of this.mallocs.values()) {
let buffer = new prev.constructor(
this.memory.buffer, prev.pointer, prev.size);
Object.assign(buffer, {
assign : prev.assign,
pointer: prev.pointer,
size : prev.size,
target : prev.target
});
this.mallocs.set(buffer.pointer, buffer);
this.#updateTarget(buffer);
}
for (let sim of this.sims)
sim.image = new ImageData(sim.pixels, 384, 224);
}
///////////////////////////// Private Methods /////////////////////////////
// Automatic emulation processing
#autoEmulate() {
// Error checking
if (!this.automatic.emulating)
return;
// Process all remaining audio buffers
while (this.audio.buffers.length != 0) {
// Reset sample output
for (let sim of this.automatic.sims) {
this.vbSetSamples(sim.pointer, sim.samples.pointer,
Constants.VB.F32, 41700 / 50);
}
// Process all clocks
this.automatic.clocks[0] = 400000; // 0.02s
while (this.automatic.clocks[0] != 0) {
this.Emulate(
this.automatic.pointers.pointer,
this.automatic.sims.length,
this.automatic.clocks.pointer
);
// Too many buffers left to output video
if (this.audio.buffers.length > 2)
continue;
// Stage the next video image
for (let sim of this.automatic.sims) {
let breaks = this.GetBreaks(sim.pointer);
if (breaks & Constants.web.BREAK_FRAME)
this.GetPixels(sim.pointer);
}
}
// Mix and output audio samples
let buffer = this.audio.buffers.shift();
this.Mix(
this.audio.samples.pointer,
this.automatic.pointers.pointer,
this.automatic.sims.length
);
for (let x = 0; x < buffer.length; x++)
buffer[x] = this.audio.samples[x];
this.audio.postMessage(buffer.buffer, [ buffer.buffer ]);
// Output staged images if there's one audio buffer to go
if (this.audio.buffers.length != 1)
continue;
for (let sim of this.automatic.sims)
sim.context.putImageData(sim.image, 0, 0);
}
}
// Delete an allocated buffer in WebAssembly memory
#free(buffer) {
this.mallocs.delete(buffer.pointer);
this.Realloc(buffer.pointer, 0);
}
// Allocate memory in WebAssembly and register the buffer
#malloc(count, target = null, assign = null, type = Uint8ClampedArray) {
return this.#noalloc(
this.Realloc(0, count * type.BYTES_PER_ELEMENT),
count, target, assign, type
);
}
// Register a buffer in WebAssembly memory without allocating it
#noalloc(pointer, count, target=null, assign=null, type=Uint8ClampedArray){
let buffer = new type(this.memory.buffer, pointer, count);
Object.assign(buffer, {
assign : assign?.split("."),
count : count,
pointer: pointer,
target : target
});
this.mallocs.set(pointer, buffer);
return buffer;
}
// Resize a previously allocated buffer in WebAssembly memory
#realloc(prev, count) {
this.mallocs.delete(prev.pointer);
let pointer = this.Realloc(prev.pointer,
count * prev.constructor.prototype.BYTES_PER_ELEMENT);
let buffer = new prev.constructor(this.memory.buffer, pointer, count);
Object.assign(buffer, {
assign : prev.assign,
count : count,
pointer: pointer,
target : prev.target
});
this.mallocs.set(pointer, buffer);
this.#updateTarget(buffer);
return buffer;
}
// Compute anaglyph color values
#setAnaglyph(sim, left, right) {
// Split out the RGB channels
let color = left | right;
let stereo = [
color >> 16 & 0xFF,
color >> 8 & 0xFF,
color & 0xFF
];
// Compute scaled RGB values by output level
sim.anaglyph = new Array(256);
for (let x = 0; x < 256; x++) {
let level = sim.anaglyph[x] = new Array(3);
for (let y = 0; y < 3; y++)
level[y] = Math.round(x * stereo[y] / 255.0);
}
// Determine which channels are in each eye
sim.anaglyph.left = [];
sim.anaglyph.right = [];
for (let x = 0, y = 16; x < 3; x++, y -= 8) {
if (left >> y & 0xFF)
sim.anaglyph.left .push(x);
if (right >> y & 0xFF)
sim.anaglyph.right.push(x);
}
}
// Specify a game pak memory buffer
#setCartMemory(sim, mem, getter, setter) {
// Working variables
let cart = new Uint8Array(mem);
let prev = getter(sim);
let cur = this.Realloc(0, cart.length);
mem = new Uint8Array(this.memory.buffer, cur, cart.length);
// Transfer the data into core memory
for (let x = 0; x < mem.length; x++)
mem[x] = cart[x];
// Assign the ROM to the simulation
let success = setter(sim, cur, mem.length) == 0;
if (success) {
if (prev != 0)
this.Realloc(prev, 0);
} else this.Realloc(cur, 0);
// Reply to the DOM thread
this.dom.postMessage({
success : success,
promised: true
});
}
// Read a C string from WebAssembly memory
#string(address, indirect = false) {
if (address == 0)
return null;
if (indirect) {
let next = new this.pointerType(this.memory.buffer, address, 1)[0];
address = next;
}
let length = 0;
let memory = new Uint8Array(this.memory.buffer);
for (let addr = address; memory[addr++] != 0; length++);
return (Array.from(memory.slice(address, address + length))
.map(b=>String.fromCodePoint(b)).join(""));
}
// Update an allocated buffer's assignment in its monitor object
#updateTarget(buffer) {
if (buffer.target == null)
return;
let obj = buffer.target;
let assign = buffer.assign.slice();
while (assign.length > 1)
obj = obj[assign.shift()];
obj[assign[0]] = buffer;
}
}();
+935
View File
@@ -0,0 +1,935 @@
"use strict";
import { Constants } from "./Constants.js";
// Instantiation guard
const GUARD = Symbol();
///////////////////////////////// DasmConfig //////////////////////////////////
// Disassembler option settings
class DasmConfig {
// Instance fields
#bcondNotation;
#conditionCase;
#conditionCL;
#conditionEZ;
#conditionNotation;
#hexCase;
#hexNotation;
#immediateNotation;
#memoryNotation;
#mnemonicCase;
#operandOrder;
#programCase;
#programNotation;
#setfNotation;
#systemCase;
#systemNotation;
///////////////////////// Initialization Methods //////////////////////////
constructor() {
this.#bcondNotation = Constants.VBU.JOINED;
this.#conditionCase = Constants.VBU.LOWER;
this.#conditionCL = Constants.VBU.L;
this.#conditionEZ = Constants.VBU.Z;
this.#conditionNotation = Constants.VBU.NAMES;
this.#hexCase = Constants.VBU.UPPER;
this.#hexNotation = Constants.VBU["0X"];
this.#immediateNotation = Constants.VBU.NONE;
this.#memoryNotation = Constants.VBU.OUTSIDE;
this.#mnemonicCase = Constants.VBU.UPPER;
this.#operandOrder = Constants.VBU.DEST_LAST;
this.#programCase = Constants.VBU.LOWER;
this.#programNotation = Constants.VBU.NAMES;
this.#setfNotation = Constants.VBU.SPLIT;
this.#systemCase = Constants.VBU.LOWER;
this.#systemNotation = Constants.VBU.NAMES;
}
/////////////////////////// Property Accessors ////////////////////////////
get bcondNotation() { return this.#bcondNotation; }
set bcondNotation(value) {
switch (value) {
case Constants.VBU.JOINED:
case Constants.VBU.SPLIT : break;
default: return;
}
this.#bcondNotation = value;
}
get conditionCase() { return this.#conditionCase; }
set conditionCase(value) {
switch (value) {
case Constants.VBU.LOWER:
case Constants.VBU.UPPER: break;
default: return;
}
this.#conditionCase = value;
}
get conditionCL() { return this.#conditionCL; }
set conditionCL(value) {
switch (value) {
case Constants.VBU.C:
case Constants.VBU.L: break;
default: return;
}
this.#conditionCL = value;
}
get conditionEZ() { return this.#conditionEZ; }
set conditionEZ(value) {
switch (value) {
case Constants.VBU.E:
case Constants.VBU.Z: break;
default: return;
}
this.#conditionEZ = value;
}
get conditionNotation() { return this.#conditionNotation; }
set conditionNotation(value) {
switch (value) {
case Constants.VBU.NAMES :
case Constants.VBU.NUMBERS: break;
default: return;
}
this.#conditionNotation = value;
}
get hexCase() { return this.#hexCase; }
set hexCase(value) {
switch (value) {
case Constants.VBU.LOWER:
case Constants.VBU.UPPER: break;
default: return;
}
this.#hexCase = value;
}
get hexNotation() { return this.#hexNotation; }
set hexNotation(value) {
switch (value) {
case Constants.VBU["0X"] :
case Constants.VBU.DOLLAR:
case Constants.VBU.H : break;
default: return;
}
this.#hexNotation = value;
}
get immediateNotation() { return this.#immediateNotation; }
set immediateNotation(value) {
switch (value) {
case Constants.VBU.NONE :
case Constants.VBU.NUMBER: break;
default: return;
}
this.#immediateNotation = value;
}
get memoryNotation() { return this.#memoryNotation; }
set memoryNotation(value) {
switch (value) {
case Constants.VBU.INSIDE :
case Constants.VBU.OUTSIDE: break;
default: return;
}
this.#memoryNotation = value;
}
get mnemonicCase() { return this.#mnemonicCase; }
set mnemonicCase(value) {
switch (value) {
case Constants.VBU.LOWER:
case Constants.VBU.UPPER: break;
default: return;
}
this.#mnemonicCase = value;
}
get operandOrder() { return this.#operandOrder; }
set operandOrder(value) {
switch (value) {
case Constants.VBU.DEST_FIRST:
case Constants.VBU.DEST_LAST : break;
default: return;
}
this.#operandOrder = value;
}
get programCase() { return this.#programCase; }
set programCase(value) {
switch (value) {
case Constants.VBU.LOWER:
case Constants.VBU.UPPER: break;
default: return;
}
this.#programCase = value;
}
get programNotation() { return this.#programNotation; }
set programNotation(value) {
switch (value) {
case Constants.VBU.NAMES :
case Constants.VBU.NUMBERS: break;
default: return;
}
this.#programNotation = value;
}
get setfNotation() { return this.#setfNotation; }
set setfNotation(value) {
switch (value) {
case Constants.VBU.JOINED:
case Constants.VBU.SPLIT : break;
default: return;
}
this.#setfNotation = value;
}
get systemCase() { return this.#systemCase; }
set systemCase(value) {
switch (value) {
case Constants.VBU.LOWER:
case Constants.VBU.UPPER: break;
default: return;
}
this.#systemCase = value;
}
get systemNotation() { return this.#systemNotation; }
set systemNotation(value) {
switch (value) {
case Constants.VBU.NAMES :
case Constants.VBU.NUMBERS: break;
default: return;
}
this.#systemNotation = value;
}
}
////////////////////////////////// DasmLine ///////////////////////////////////
// One line of disassembler output
class DasmLine {
// Instance fields
#address;
#addressText;
#code;
#codeText;
#isPC;
#mnemonicText;
#operandText;
///////////////////////// Initialization Methods //////////////////////////
constructor() {
if (arguments[0] != GUARD)
throw new Error("Cannot be instantiated.");
let line = arguments[1];
this.#address = line.address;
this.#addressText = line.text.address;
this.#code = line.code;
this.#codeText = line.text.code;
this.#isPC = line.isPC;
this.#mnemonicText = line.text.mnemonic;
this.#operandText = line.text.operands;
}
/////////////////////////// Property Accessors ////////////////////////////
get address() { return this.#address; }
get code () { return this.#code.slice(); }
get isPC () { return this.#isPC; }
get text () {
return {
address : this.#addressText,
code : this.#codeText.slice(),
mnemonic: this.#mnemonicText,
operands: this.#operandText.slice()
};
}
///////////////////////////// Public Methods //////////////////////////////
// Express self as a plain object
toObject() {
return {
address: this.address,
code : Array.from(this.#code),
isPC : this.isPC,
text : this.text
};
}
}
///////////////////////////////////// Sim /////////////////////////////////////
// Simulation instance
class Sim extends HTMLElement {
// Instance fields
#anaglyph; // Anaglyph color values
#canvas; // Canvas element
#core; // Core proxy
#emulating; // Current emulation status
#keys; // Controller state
#panning; // Audio stereo balance
#peer; // Communication peer
#pointer; // Pointer in core memory
#volume; // Audio output volume
///////////////////////// Initialization Methods //////////////////////////
constructor() {
if (arguments[0] != GUARD)
throw new Error("Must be created via VB.create()");
super();
this.proxy = {
construct : (core, pointer)=>this.#construct(core, pointer),
isEmulating : ()=>this.#emulating,
setEmulating: e =>this.#emulating = e,
setPeer : p =>this.#peer = p
};
}
// Asynchronous constructor
async #construct(core, pointer) {
// Configure instance fields
this.#anaglyph = [ VB.STEREO_RED, VB.STEREO_CYAN ];
this.#core = core;
this.#emulating = false;
this.#keys = Constants.VB.SGN;
this.#panning = 0.0;
this.#peer = null;
this.#pointer = pointer;
this.#volume = 1.0;
delete this.proxy;
// Create a <canvas> for the video image
let canvas = this.#canvas = document.createElement("canvas");
Object.assign(canvas, { width: 384, height: 224 });
canvas.style.imageRendering = "pixelated";
// Configure elements
Object.assign(this.style, {
display : "inline-block",
height : "224px",
position: "relative",
width : "384px"
});
Object.assign(canvas.style, {
height : "100%",
imageRendering: "pixelated",
left : "0",
position : "absolute",
top : "0",
width : "100%"
});
this.append(canvas);
// Send control of the canvas to the core worker
let offscreen = canvas.transferControlToOffscreen();
await core.toCore({
command : "setCanvas",
promised : true,
sim : pointer,
canvas : offscreen,
transfers: [ offscreen ]
});
return this;
}
/////////////////////////// Property Accessors ////////////////////////////
get anaglyph() { return this.#anaglyph.slice(); }
get core () { return this.#core.core ; }
get keys () { return this.#keys ; }
get panning () { return this.#panning ; }
get peer () { return this.#peer ; }
get volume () { return this.#volume ; }
///////////////////////////// Public Methods //////////////////////////////
// Delete the sim
async delete() {
// Unlink peer
// Deallocate memory
// Unlink core
}
// Disassemble from a simulation
async disassemble(address, config, length, line) {
// Error checking
if (!Number.isSafeInteger(address) ||
address < 0 || address > 0xFFFFFFFF)
throw new RangeError("Address must conform to Uint32.");
if (config != null && !(config instanceof DasmConfig))
throw new TypeError("Config must be an instance of DasmConfig.");
if (!Number.isSafeInteger(address) || length < 0)
throw new RangeError("Length must be nonnegative.");
if (!Number.isSafeInteger(line))
throw new TypeError("Line must be a safe integer.");
// Request disassembly from the core
let response = await this.#core.toCore({
command : "disassemble",
promised: true,
sim : this.#pointer,
address : address,
length : length,
line : line,
config : config == null ? null : {
bcondNotation : config.bcondNotation,
conditionCase : config.conditionCase,
conditionCL : config.conditionCL,
conditionEZ : config.conditionEZ,
conditionNotation: config.conditionNotation,
hexCase : config.hexCase,
hexNotation : config.hexNotation,
immediateNotation: config.immediateNotation,
memoryNotation : config.memoryNotation,
mnemonicCase : config.mnemonicCase,
operandOrder : config.operandOrder,
programCase : config.programCase,
programNotation : config.programNotation,
setfNotation : config.setfNotation,
systemCase : config.systemCase,
systemNotation : config.systemNotation
}
});
// Process the response
return !response.success ? null :
response.lines.map(l=>new DasmLine(GUARD, l));
}
// Specify anaglyph colors
async setAnaglyph(left, right) {
// Error checking
if (!Number.isSafeInteger(left ) || left < 0 || left > 0xFFFFFF)
throw new RangeError("Left must conform to Uint24.");
if (!Number.isSafeInteger(right) || right < 0 || right > 0xFFFFFF)
throw new RangeError("Right must conform to Uint24.");
if (
left & 0xFF0000 && right & 0xFF0000 ||
left & 0x00FF00 && right & 0x00FF00 ||
left & 0x0000FF && right & 0x0000FF
) throw new RangeError("Left and right overlap RGB channels.");
// Configure instance fields
this.#anaglyph[0] = left;
this.#anaglyph[1] = right;
// Send the colors to the core
await this.#core.toCore({
command : "setAnaglyph",
promised: true,
sim : this.#pointer,
left : left,
right : right
});
}
// Specify a game pak RAM buffer
setCartRAM(wram) {
return this.#setCartMemory("setCartRAM", wram);
}
// Specify a game pak ROM buffer
setCartROM(rom) {
return this.#setCartMemory("setCartROM", rom);
}
// Specify new game pad keys
async setKeys(keys) {
// Error checking
if (!Number.isSafeInteger(keys) || keys < 0 || keys > 0xFFFF)
throw new RangeError("Keys must conform to Uint16.");
if (keys == this.#keys)
return;
// Configure instance fields
this.#keys = keys;
// Send the keys to the core
await this.#core.toCore({
command : "setKeys",
promised: true,
sim : this.#pointer,
keys : keys
});
}
// Specify audio panning
async setPanning(panning) {
// Error checking
if (!Number.isFinite(panning) ||panning < -1 || panning > +1) {
throw new RangeError(
"Panning must be a number from -1 to +1.");
}
// Configure instance fields
this.#panning = panning;
// Send the panning to the core
await this.#core.toCore({
command : "setPanning",
promised: true,
sim : this.#pointer,
panning : panning
});
}
// Specify a new communication peer
async setPeer(peer = null) {
// Error checking
if (peer !== null && peer.#core != this.#core)
throw new RangeError("Peer sim must belong to the same core.");
// Configure peers on the core
if (peer != this.#peer)
await this.#core.setPeer(this, peer);
}
// Specify audio volume
async setVolume(volume) {
// Error checking
if (!Number.isFinite(volume) ||volume < 0 || volume > 10) {
throw new RangeError(
"Volume must be a number from 0\u00d7 to 10\u00d7.");
}
// Configure instance fields
this.#volume = volume;
// Send the volume to the core
await this.#core.toCore({
command : "setVolume",
promised: true,
sim : this.#pointer,
volume : volume
});
}
///////////////////////////// Private Methods /////////////////////////////
// Specify a game pak memory buffer
async #setCartMemory(command, mem) {
// Validation
if (mem instanceof ArrayBuffer)
mem = new Uint8Array(mem);
if (
!(mem instanceof Uint8Array) &&
!(mem instanceof Uint8ClampedArray)
) mem = Uint8Array.from(mem);
// Send the memory to the core
let response = await this.#core.toCore({
command : command,
promised : true,
sim : this.#pointer,
data : mem.buffer,
transfers: [ mem.buffer ]
});
return response.success;
}
}
customElements.define("shrooms-vb", Sim);
///////////////////////////////////// VB //////////////////////////////////////
// Emulation core interface
class VB {
// Static fields
static get DasmConfig() { return DasmConfig; }
static get DasmLine () { return DasmLine; }
static get Sim () { return Sim; }
// Instance fields
#audio; // Audio worklet
#automatic; // Current automatic emulation group
#commands; // Computed method table
#core; // Core worker
#proxy; // Self proxy for sim access
#sims; // All sims
#state; // Operations state
//////////////////////////////// Constants ////////////////////////////////
// Operations states
static #SUSPENDED = Symbol();
static #RESUMING = Symbol();
static #EMULATING = Symbol();
static #SUSPENDING = Symbol();
// System registers
static get ADTRE() { return Constants.VB.ADTRE; }
static get CHCW () { return Constants.VB.CHCW ; }
static get ECR () { return Constants.VB.ECR ; }
static get EIPC () { return Constants.VB.EIPC ; }
static get EIPSW() { return Constants.VB.EIPSW; }
static get FEPC () { return Constants.VB.FEPC ; }
static get FEPSW() { return Constants.VB.FEPSW; }
static get PIR () { return Constants.VB.PIR ; }
static get PSW () { return Constants.VB.PSW ; }
static get TKCW () { return Constants.VB.TKCW ; }
// Memory access data types
static get S8 () { return Constants.VB.S8 ; }
static get U8 () { return Constants.VB.U8 ; }
static get S16() { return Constants.VB.S16; }
static get U16() { return Constants.VB.U16; }
static get S32() { return Constants.VB.S32; }
// Option keys
static get PSEUDO_HALT() { return Constants.VB.PSEUDO_HALT; }
// Controller buttons
static get PWR() { return Constants.VB.PWR; }
static get SGN() { return Constants.VB.SGN; }
static get A () { return Constants.VB.A ; }
static get B () { return Constants.VB.B ; }
static get RT () { return Constants.VB.RT ; }
static get LT () { return Constants.VB.LT ; }
static get RU () { return Constants.VB.RU ; }
static get RR () { return Constants.VB.RR ; }
static get LR () { return Constants.VB.LR ; }
static get LL () { return Constants.VB.LL ; }
static get LD () { return Constants.VB.LD ; }
static get LU () { return Constants.VB.LU ; }
static get STA() { return Constants.VB.STA; }
static get SEL() { return Constants.VB.SEL; }
static get RL () { return Constants.VB.RL ; }
static get RD () { return Constants.VB.RD ; }
// Disassembler options
static get ["0X"] () { return Constants.VBU["0X"] ; }
static get ABSOLUTE () { return Constants.VBU.ABSOLUTE ; }
static get C () { return Constants.VBU.C ; }
static get DEST_FIRST() { return Constants.VBU.DEST_FIRST; }
static get DEST_LAST () { return Constants.VBU.DEST_LAST ; }
static get DOLLAR () { return Constants.VBU.DOLLAR ; }
static get E () { return Constants.VBU.E ; }
static get H () { return Constants.VBU.H ; }
static get INSIDE () { return Constants.VBU.INSIDE ; }
static get JOINED () { return Constants.VBU.JOINED ; }
static get L () { return Constants.VBU.L ; }
static get LOWER () { return Constants.VBU.LOWER ; }
static get NAMES () { return Constants.VBU.NAMES ; }
static get NONE () { return Constants.VBU.NONE ; }
static get NUMBER () { return Constants.VBU.NUMBER ; }
static get NUMBERS () { return Constants.VBU.NUMBERS ; }
static get OUTSIDE () { return Constants.VBU.OUTSIDE ; }
static get RELATIVE () { return Constants.VBU.RELATIVE ; }
static get SPLIT () { return Constants.VBU.SPLIT ; }
static get UPPER () { return Constants.VBU.UPPER ; }
static get Z () { return Constants.VBU.Z ; }
// Anaglyph colors
static get STEREO_CYAN () { return Constants.web.STEREO_CYAN ; }
static get STEREO_GREEN () { return Constants.web.STEREO_GREEN ; }
static get STEREO_MAGENTA() { return Constants.web.STEREO_MAGENTA; }
static get STEREO_RED () { return Constants.web.STEREO_RED ; }
///////////////////////////// Static Methods //////////////////////////////
// Create a core instance
static async create(options) {
return await new VB(GUARD).#construct(options);
}
///////////////////////// Initialization Methods //////////////////////////
constructor() {
if (arguments[0] != GUARD)
throw new Error("Must be created via VB.create()");
}
// Asynchronous constructor
async #construct(options) {
// Configure instance fields
this.#automatic = null;
this.#sims = new Map();
this.#state = VB.#SUSPENDED;
// Ensure default options
options ??= {};
options.audioUrl ??= import.meta.resolve("./Audio.js");
options.coreUrl ??= import.meta.resolve("./Core.js");
options.wasmUrl ??= import.meta.resolve("./core.wasm");
// Core<->audio communications
let channel = new MessageChannel();
// Audio output context
let audio = new AudioContext({
latencyHint: "interactive",
sampleRate : 41700
});
await audio.suspend();
// Audio node
await audio.audioWorklet.addModule(options.audioUrl);
audio = this.#audio = new AudioWorkletNode(audio, "shrooms-vb", {
numberOfInputs : 0,
numberOfOutputs : 1,
outputChannelCount: [2]
});
audio.connect(audio.context.destination);
// Send one message channel port to the audio worklet
await new Promise(resolve=>{
audio.port.onmessage = resolve;
audio.port.postMessage({
core: channel.port1
}, [channel.port1]);
});
audio.port.onmessage = null;//e=>this.#onAudio(e.data);
// Core worker
let core = this.#core = new Worker(options.coreUrl, {type: "module"});
core.promises = [];
// Send the other message channel port to the core worker
await new Promise(resolve=>{
core.onmessage = resolve;
core.postMessage({
audio : channel.port2,
wasmUrl: options.wasmUrl
}, [ channel.port2 ]);
});
core.onmessage = e=>this.#onCore(e.data);
// Establish a concealed proxy for sim objects
this.#proxy = {
core : this,
setPeer: (a,b)=>this.#setPeer(a,b),
toCore : m=>this.#toCore(m)
};
// Configure command table
this.#commands = {
// Will be used with subscriptions
};
return this;
}
///////////////////////////// Public Methods //////////////////////////////
// Create one or more sims
async create(count = null) {
// Error checking
if (count !== null && (!Number.isSafeInteger(count) || count < 1)) {
throw new RangeError(
"Count must be a safe integer and at least 1.");
}
// Allocate memory in the core
let response = await this.#toCore({
command : "createSims",
promised: true,
count : count ?? 1
});
// Produce Sim elements for each instance
let sims = response.sims;
for (let x = 0; x < (count ?? 1); x++) {
let proxy = new Sim(GUARD).proxy;
proxy.pointer = sims[x];
proxy.sim = sims[x] =
await proxy.construct(this.#proxy, sims[x]);
this.#sims.set(sims[x], proxy);
this.#sims.set(proxy.pointer, proxy);
}
return count === null ? sims[0] : sims;
}
// Begin emulation
async emulate(sims, clocks) {
// Error checking
if (sims instanceof Sim)
sims = [sims];
if (
!Array.isArray(sims) ||
sims.length == 0 ||
sims.find(s=>!this.#sims.has(s))
) {
throw new TypeError("Must specify a Sim or array of Sims " +
"that belong to this core.");
}
if (sims.find(s=>this.#sims.get(s).isEmulating()))
throw new Error("Sims cannot already be part of emulation.");
if (
clocks !== true &&
!(Number.isSafeInteger(clocks) && clocks >= 0)
) {
throw new RangeError(
"Clocks must be true or a nonnegative safe integer.");
}
// Cannot resume automatic emulation
if (clocks === true && this.#state != VB.#SUSPENDED)
return false;
// Manage sims
let proxies = sims .map(s=>this.#sims.get(s));
let pointers = proxies.map(p=>p.pointer);
for (let sim of proxies)
sim.setEmulating(true);
// Clocked emulation
if (clocks !== true) {
let response = await this.#toCore({
command : "emulateClocked",
promised: true,
sims : pointers,
clocks : clocks
});
for (let sim of proxies)
sim.setEmulating(false);
return {
broke : response.broke,
clocks: response.clocks
};
}
// Resume automatic emulation
this.#automatic = proxies;
this.#state = VB.#RESUMING;
if (this.#audio.context.state == "suspended")
await this.#audio.context.resume();
await this.#toCore({
command : "emulateAutomatic",
promised: true,
sims : pointers
});
this.#state = VB.#EMULATING;
return true;
}
// Suspend automatic emulation
async suspend() {
// Error checking
if (this.#state != VB.#EMULATING)
return false;
// Tell the core to stop emulating
this.#state = VB.#SUSPENDING;
await this.#toCore({
command : "suspend",
promised: true
});
// Configure state
this.#state = VB.#SUSPENDED;
for (let sim of this.#automatic)
sim.setEmulating(false);
return true;
}
///////////////////////////// Private Methods /////////////////////////////
// Message received from core worker
#onCore(message) {
if (message.promised)
this.#core.promises.shift()(message);
if ("command" in message)
this.#commands[message.command](message);
}
// Specify a new communication peer
async #setPeer(sim, peer) {
// Associate the peers on the core
let response = await this.#toCore({
command : "setPeer",
promised: true,
sim : this.#sims.get(sim).pointer,
peer : peer == null ? 0 : this.#sims.get(peer).pointer
});
// Link sims
this.#sims.get(sim).setPeer(peer);
if (peer != null)
this.#sims.get(peer).setPeer(sim);
// Unlink orphaned sims
for (let pointer of response.orphaned)
this.#sims.get(pointer).setPeer(null);
}
// Send a message to the core worker
async #toCore(message) {
let transfers = message.transfers;
if (transfers != null)
delete message.transfers;
return await new Promise(resolve=>{
if (message.promised)
this.#core.promises.push(resolve);
this.#core.postMessage(message, transfers ?? []);
});
}
}
export { VB };
+273
View File
@@ -0,0 +1,273 @@
#include <stdlib.h>
#include <stdio.h>
#include <emscripten/emscripten.h>
#include <vb.h>
#include <vbu.h>
////////////////////////////////// Constants //////////////////////////////////
// Break conditions
#define BREAK_FRAME 1
#define BREAK_POINT 2
// Anaglyph colors
#define STEREO_CYAN 0x00C6F0
#define STEREO_GREEN 0x00B400
#define STEREO_MAGENTA 0xC800FF
#define STEREO_RED 0xFF0000
// Element counts
#define NUM_SAMPLES (41700 / 50 * 2)
//////////////////////////////////// Types ////////////////////////////////////
// Additional monitor state for simulations
typedef struct {
int32_t breaks;
uint32_t left[256];
float panning;
uint8_t pixels[384 * 224 * 4];
uint32_t right[256];
float samples[NUM_SAMPLES];
float volume;
} Ext;
////////////////////////////////// Callbacks //////////////////////////////////
// Frame callback
int wasmOnFrame(VB *sim) {
((Ext *) vbGetUserData(sim))->breaks |= BREAK_FRAME;
return 1;
}
/////////////////////////////// Module Exports ////////////////////////////////
// Specify anaglyph colors
EMSCRIPTEN_KEEPALIVE void SetAnaglyph(VB *sim, uint32_t left, uint32_t right) {
Ext *ext = (Ext *) vbGetUserData(sim);
// Erase all RGB values
for (int x = 0; x < 256; x++)
ext->left[x] = ext->right[x] = 0xFF000000;
// Process all RGB channels
for (int c = 0, shift = 16; c < 3; c++, shift -= 8) {
double max; // Magnitude of channel value
uint32_t *dest; // Lookup data
// Select the magnitude and lookup channel
dest = ext->left;
max = (left >> shift & 0xFF) / 255.0;
if (max == 0) {
dest = ext->right;
max = (right >> shift & 0xFF) / 255.0;
if (max == 0)
continue;
}
// Compute the resulting RGB values
for (int x = 0; x < 256; x++)
*dest++ |= (uint32_t) (x * max + 0.5) << (16 - shift);
}
}
// Instantiate a simulation
EMSCRIPTEN_KEEPALIVE void* CreateSim() {
size_t sizeOfSim = vbSizeOf();
uint8_t *pointer = malloc(sizeOfSim + sizeof (Ext));
// Configure sim
VB *sim = vbInit((VB *) pointer);
vbSetFrameCallback(sim, &wasmOnFrame);
vbSetOption(sim, VB_PSEUDO_HALT, 1);
// Configure extra
Ext *ext = (Ext *) (pointer + sizeOfSim);
ext->breaks = 0;
ext->panning = 0.0f;
ext->volume = 1.0f;
vbSetUserData(sim, ext);
SetAnaglyph(sim, STEREO_RED, STEREO_CYAN);
// Initialize pixels with opaque black
for (unsigned x = 0; x < 384 * 224; x++)
((uint32_t *) ext->pixels)[x] = 0xFF000000;
return sim;
}
// Disassemble from a simulation
EMSCRIPTEN_KEEPALIVE void* Disassemble(
int bcondNotation, int conditionCase, int conditionCL, int conditionEZ,
int conditionNotation, int hexCase, int hexNotation, int immediateNotation,
int memoryNotation, int mnemonicCase, int operandOrder, int programCase,
int programNotation, int setfNotation, int systemCase, int systemNotation,
VB *sim, uint32_t address, unsigned length, int line
) {
VBU_DasmConfig config;
config.bcondNotation = bcondNotation;
config.conditionCase = conditionCase;
config.conditionCL = conditionCL;
config.conditionEZ = conditionEZ;
config.conditionNotation = conditionNotation;
config.hexCase = hexCase;
config.hexNotation = hexNotation;
config.immediateNotation = immediateNotation;
config.memoryNotation = memoryNotation;
config.mnemonicCase = mnemonicCase;
config.operandOrder = operandOrder;
config.programCase = programCase;
config.programNotation = programNotation;
config.setfNotation = setfNotation;
config.systemCase = systemCase;
config.systemNotation = systemNotation;
return vbuDisassemble(sim, address, &config, length, line);
}
// Process simulations
EMSCRIPTEN_KEEPALIVE int Emulate(VB **sims, unsigned count, uint32_t *clocks) {
for (unsigned x = 0; x < count; x++)
((Ext *) vbGetUserData(sims[x]))->breaks = 0;
return vbEmulateEx(sims, count, clocks);
}
// Retrieve a sim's pixel pointer
EMSCRIPTEN_KEEPALIVE void* GetExtPixels(VB *sim) {
return ((Ext *) vbGetUserData(sim))->pixels;
}
// Retrieve a sim's sample pointer
EMSCRIPTEN_KEEPALIVE void* GetExtSamples(VB *sim) {
return ((Ext *) vbGetUserData(sim))->samples;
}
// Retrieve the break condition flags for a sim
EMSCRIPTEN_KEEPALIVE int32_t GetBreaks(VB *sim) {
return ((Ext *) vbGetUserData(sim))->breaks;
}
// Serialize disassembled lines into a linear buffer
EMSCRIPTEN_KEEPALIVE void GetDasm(uint32_t *buffer, void *dasm, int count) {
for (int x = 0; x < count; x++) {
VBU_DasmLine *line = &((VBU_DasmLine *) dasm)[x];
// Numeric data
*buffer++ = line->address;
*buffer++ = line->codeLength;
for (int y = 0; y < 4; y++)
*buffer++ = line->code[y];
*buffer++ = line->isPC;
// Text data -- Store offset of string pointer from start of dasm
*buffer++ = (uint32_t) ((void *) &line->text.address - dasm);
for (int y = 0; y < 4; y++)
*buffer++ = (uint32_t) ((void *) &line->text.code[y] - dasm);
*buffer++ = (uint32_t) ((void *) &line->text.mnemonic - dasm);
*buffer++ = line->text.operandsLength;
for (int y = 0; y < 3; y++)
*buffer++ = (uint32_t) ((void *) &line->text.operands[y] - dasm);
}
}
// Retrieve anaglyph-tinted pixels from a sim
EMSCRIPTEN_KEEPALIVE void GetPixels(VB *sim) {
Ext *ext = (Ext *) vbGetUserData(sim);
uint8_t *pixels = ext->pixels;
vbGetPixels(sim, pixels, 4, 384 * 4, pixels + 1, 4, 384 * 4);
for (unsigned x = 0; x < 384 * 224 * 4; x += 4, pixels += 4)
*(uint32_t *) pixels = ext->left[pixels[0]] | ext->right[pixels[1]];
}
// Mix audio samples for output
EMSCRIPTEN_KEEPALIVE void Mix(float *buffer, VB **sims, int count) {
// Process all sims
for (int x = 0; x < count; x++) {
Ext *ext = (Ext *) vbGetUserData(sims[x]);
float *samples = ext->samples;
float v = ext->volume;
// First sim initializes buffer
if (x == 0) {
if (ext->panning < 0) {
float l = -ext->panning * v;
float r = (1.0f + ext->panning) * v;
for (unsigned y = 0; y < NUM_SAMPLES; y += 2) {
buffer[y ] = samples[y] * v + samples[y + 1] * l;
buffer[y + 1] = samples[y + 1] * r;
}
} else if (ext->panning > 0) {
float r = ext->panning * v;
float l = (1.0f - ext->panning) * v;
for (unsigned y = 0; y < NUM_SAMPLES; y += 2) {
buffer[y ] = samples[y] * l;
buffer[y + 1] = samples[y + 1] * v + samples[y] * r;
}
} else {
for (unsigned y = 0; y < NUM_SAMPLES; y++)
buffer[y] = samples[y] * v;
}
}
// Subsequent sims add to buffer
else {
if (ext->panning < 0) {
float l = -ext->panning * v;
float r = (1.0f + ext->panning) * v;
for (unsigned y = 0; y < NUM_SAMPLES; y += 2) {
buffer[y ] += samples[y] * v + samples[y + 1] * l;
buffer[y + 1] += samples[y + 1] * r;
}
} else if (ext->panning > 0) {
float r = ext->panning * v;
float l = (1.0f - ext->panning) * v;
for (unsigned y = 0; y < NUM_SAMPLES; y += 2) {
buffer[y ] += samples[y] * l;
buffer[y + 1] += samples[y + 1] * v + samples[y] * r;
}
} else {
for (unsigned y = 0; y < NUM_SAMPLES; y++)
buffer[y] += samples[y] * v;
}
}
}
// Clipping
for (unsigned y = 0; y < NUM_SAMPLES; y++) {
if (buffer[y] < -1.0f)
buffer[y] = -1.0f;
else if (buffer[y] > +1.0f)
buffer[y] = +1.0f;
}
}
// Determine the size in bytes of a pointer
EMSCRIPTEN_KEEPALIVE int PointerSize() {
return sizeof (void *);
}
// Memory management
EMSCRIPTEN_KEEPALIVE void* Realloc(void *data, size_t size) {
return realloc(data, size);
}
// Specify audio panning
EMSCRIPTEN_KEEPALIVE void SetPanning(VB *sim, float panning) {
((Ext *) vbGetUserData(sim))->panning = panning;
}
// Specify audio volume
EMSCRIPTEN_KEEPALIVE void SetVolume(VB *sim, float volume) {
((Ext *) vbGetUserData(sim))->volume = volume;
}