diff options
author | Ben Bridle <ben@derelict.engineering> | 2025-09-13 07:20:51 +1200 |
---|---|---|
committer | Ben Bridle <ben@derelict.engineering> | 2025-09-13 07:50:11 +1200 |
commit | 8de8a05ffbd622810cc23f6ae818c3bd6cfcf8a0 (patch) | |
tree | 90f09ee857d6696f6ea62f6487e730868fcc2008 | |
parent | 39af042fbcc7cf900493df33f69281a39dee4a51 (diff) | |
download | bedrock-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.
-rw-r--r-- | bedrock.js | 64 |
1 files changed, 36 insertions, 28 deletions
@@ -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++) { |