summaryrefslogtreecommitdiff
path: root/bedrock.js
diff options
context:
space:
mode:
authorBen Bridle <ben@derelict.engineering>2025-09-13 07:20:51 +1200
committerBen Bridle <ben@derelict.engineering>2025-09-13 07:50:11 +1200
commit8de8a05ffbd622810cc23f6ae818c3bd6cfcf8a0 (patch)
tree90f09ee857d6696f6ea62f6487e730868fcc2008 /bedrock.js
parent39af042fbcc7cf900493df33f69281a39dee4a51 (diff)
downloadbedrock-js-8de8a05ffbd622810cc23f6ae818c3bd6cfcf8a0.zip
Fix draw operations failing at the start of a program
The emulator screen panel is designed to stay hidden until it's needed, so that headless programs don't have a large black canvas taking up space on the page. The issue was that the screen content won't be rendered to the canvas if the screen isn't 'dirty', and the screen will only be marked dirty if there's been a draw operation in-bounds, and the size of the canvas determines what counts as in-bounds, and the size of the canvas remains zero until the layout engine has a chance to resize it during the next browser render tick. This is all fixed by breaking the current evaluation loop, yielding control back to the browser, and having the updateScreenSize function queued up to act on the newly-resized canvas element. Evaluation will then continue as normal.
Diffstat (limited to 'bedrock.js')
-rw-r--r--bedrock.js64
1 files changed, 36 insertions, 28 deletions
diff --git a/bedrock.js b/bedrock.js
index d9811c1..aae3977 100644
--- a/bedrock.js
+++ b/bedrock.js
@@ -909,11 +909,17 @@ function Bedrock(emulatorElement, wasm=defaultToWasm) {
}
}
+ // Reveal and resize the canvas.
+ this.showScreen = () => {
+ this.e.showScreenPanel();
+ // HACK: The screen panel isn't given a size until the next render tick,
+ // so we have to queue this function call for when Bedrock next yields
+ // control back to the browser.
+ setTimeout(() => { this.e.updateScreenSize() }, 0);
+ }
+
this.render = () => {
if (this.e) {
- if (this.dev.input.accessed || this.dev.screen.accessed) {
- this.e.showScreenPanel();
- }
let scr = this.dev.screen;
if (scr.dirty()) {
scr.render();
@@ -1426,7 +1432,6 @@ function InputDevice(br) {
this.accessed = false;
}
this.read = function(p) {
- this.accessed = true;
switch (p) {
case 0x0: this.xR = this.x; return getH(this.xR);
case 0x1: return getL(this.xR);
@@ -1445,10 +1450,12 @@ function InputDevice(br) {
}
}
this.write = function(p,v) {
- this.accessed = true;
+ // HACK: Interrupt the emulator loop to give canvas a chance to resize.
+ let s = 0;
+ if (!this.accessed) { br.showScreen(); s = -1; this.accessed = true; }
switch (p) {
- case 0xA: this.characterBytes.clear(); return;
- default: return;
+ case 0xA: this.characterBytes.clear(); return s;
+ default: return s;
}
}
this.wake = function() {
@@ -1580,13 +1587,12 @@ function ScreenDevice(br) {
this.sprite = new SpriteBuffer();
let initialWidth = Math.trunc(br.e.el.clientWidth / initialScreenScale);
let initialHeight = Math.trunc(initialWidth * 9/16);
- this.width = -1; // screen won't resize if dimensions aren't different
+ this.width = -1; // screen won't resize if dimensions haven't changed
this.resize(initialWidth, initialHeight, initialScreenScale);
this.wakeFlag = false;
this.accessed = false;
}
this.read = function(p) {
- this.accessed = true;
switch (p) {
case 0x0: return getH(this.x);
case 0x1: return getL(this.x);
@@ -1600,25 +1606,27 @@ function ScreenDevice(br) {
}
}
this.write = function(p,v) {
- this.accessed = true;
+ // HACK: Interrupt the emulator loop to give canvas a chance to resize.
+ let s = 0;
+ if (!this.accessed) { br.showScreen(); s = -1; this.accessed = true; }
switch (p) {
- case 0x0: this.x = setH(this.x, v); return;
- case 0x1: this.x = setL(this.x, v); return;
- case 0x2: this.y = setH(this.y, v); return;
- case 0x3: this.y = setL(this.y, v); return;
- case 0x4: this.widthW = setH(this.widthW, v); return;
- case 0x5: this.widthW = setL(this.widthW, v); this.commitWidth(); return;
- case 0x6: this.heightW = setH(this.heightW, v); return;
- case 0x7: this.heightW = setL(this.heightW, v); this.commitHeight(); return;
- case 0x8: this.paletteW = setH(this.paletteW, v); return;
- case 0x9: this.paletteW = setL(this.paletteW, v); this.commitPalette(); return;
- case 0xA: this.selection[0] = v >> 4; this.selection[1] = v & 0xF; return;
- case 0xB: this.selection[2] = v >> 4; this.selection[3] = v & 0xF; return;
+ case 0x0: this.x = setH(this.x, v); return s;
+ case 0x1: this.x = setL(this.x, v); return s;
+ case 0x2: this.y = setH(this.y, v); return s;
+ case 0x3: this.y = setL(this.y, v); return s;
+ case 0x4: this.widthW = setH(this.widthW, v); return s;
+ case 0x5: this.widthW = setL(this.widthW, v); this.commitWidth(); return s;
+ case 0x6: this.heightW = setH(this.heightW, v); return s;
+ case 0x7: this.heightW = setL(this.heightW, v); this.commitHeight(); return s;
+ case 0x8: this.paletteW = setH(this.paletteW, v); return s;
+ case 0x9: this.paletteW = setL(this.paletteW, v); this.commitPalette(); return s;
+ case 0xA: this.selection[0] = v >> 4; this.selection[1] = v & 0xF; return s;
+ case 0xB: this.selection[2] = v >> 4; this.selection[3] = v & 0xF; return s;
case 0xC:
- case 0xD: this.sprite.push(v); return;
- case 0xE: this.draw(v); return;
- case 0xF: this.move(v); return;
- default: return;
+ case 0xD: this.sprite.push(v); return s;
+ case 0xE: this.draw(v); return s;
+ case 0xF: this.move(v); return s;
+ default: return s;
}
}
this.wake = function() {
@@ -1642,14 +1650,14 @@ function ScreenDevice(br) {
}
// Check if the screen has a visible dirty region.
this.dirty = function() {
- // TODO: Does clamp pull a distant dirty region into the viewport?
+ // TODO: Does clamp pull distant dirty regions into the viewport?
this.dx0 = clamp(this.dx0, 0, this.width);
this.dx1 = clamp(this.dx1, 0, this.width);
this.dy0 = clamp(this.dy0, 0, this.height);
this.dy1 = clamp(this.dy1, 0, this.height);
return this.dx1 > this.dx0 && this.dy1 > this.dy0;
}
- // Update the dirty region of this.pixels from fg and bg.
+ // Update the dirty region of this.pixels using fg and bg.
this.render = function() {
let dw = this.dx1 - this.dx0;
for (let y=this.dy0; y<this.dy1; y++) {