Skip to content

Commit 238986b

Browse files
trekawekclaude
andcommitted
Don't flash a blank frame on a sub-frame LCD-off
The emulator pushed a fresh frame the instant the LCD was disabled, and Display.disableLcd() cleared the buffer and pushed one too. Games that blank the LCD for only a fraction of a frame - a common way to fit a large VRAM rewrite in, e.g. the A Bug's Life intro toggles it off for under a frame every ~9 frames - therefore emitted a full blank frame, which the display showed as a hard flicker between the picture and a blank screen (issue #67). The LCD-off frame is now emitted only after the LCD has stayed off for a whole frame, at the normal refresh cadence: a brief toggle keeps the last picture (matching SameBoy and hardware persistence) while a sustained LCD-off still blanks the screen. disableLcd() clears the buffer without emitting; the emulation decides when a blank frame is shown. The A Bug's Life intro now plays the Disney starfield animation smoothly instead of flickering. Full battery, mealybug and controller suites stay green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J1wLWscyUGS7CFwc3CjJR8
1 parent 5ea429d commit 238986b

2 files changed

Lines changed: 41 additions & 20 deletions

File tree

core/src/main/java/eu/rekawek/coffeegb/core/Gameboy.java

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ public class Gameboy implements Runnable, Serializable, Originator<Gameboy>, Clo
8787

8888
private boolean lcdDisabled;
8989

90+
private int lcdOffTicks;
91+
9092
private transient volatile boolean doPause;
9193

9294
private transient volatile boolean paused;
@@ -249,23 +251,34 @@ public boolean tick() {
249251
hdma.onGpuUpdate(newMode);
250252
}
251253

252-
if (!lcdDisabled && !gpu.isLcdEnabled()) {
253-
lcdDisabled = true;
254-
display.frameIsReady();
255-
hdma.onLcdSwitch(false);
256-
result = true;
257-
} else if (newMode == Mode.VBlank) {
258-
requestedScreenRefresh = true;
259-
display.frameIsReady();
260-
vRamTransfer.frameIsReady();
261-
result = true;
262-
}
263-
264-
if (lcdDisabled && gpu.isLcdEnabled()) {
265-
lcdDisabled = false;
266-
hdma.onLcdSwitch(true);
267-
} else if (requestedScreenRefresh && newMode == Mode.OamSearch) {
268-
requestedScreenRefresh = false;
254+
if (!gpu.isLcdEnabled()) {
255+
if (!lcdDisabled) {
256+
lcdDisabled = true;
257+
hdma.onLcdSwitch(false);
258+
lcdOffTicks = 0;
259+
}
260+
// a game that only blanks the LCD for a fraction of a frame (a common way to
261+
// squeeze a big VRAM rewrite in, e.g. the A Bug's Life intro) must not flash a
262+
// blank frame - the display keeps the last picture. Only a sustained LCD-off
263+
// blanks the screen, at the normal refresh cadence.
264+
if (++lcdOffTicks >= TICKS_PER_FRAME) {
265+
lcdOffTicks = 0;
266+
display.blankFrame();
267+
result = true;
268+
}
269+
} else {
270+
if (lcdDisabled) {
271+
lcdDisabled = false;
272+
hdma.onLcdSwitch(true);
273+
}
274+
if (newMode == Mode.VBlank) {
275+
requestedScreenRefresh = true;
276+
display.frameIsReady();
277+
vRamTransfer.frameIsReady();
278+
result = true;
279+
} else if (requestedScreenRefresh && newMode == Mode.OamSearch) {
280+
requestedScreenRefresh = false;
281+
}
269282
}
270283
if (console != null) {
271284
console.tick();

core/src/main/java/eu/rekawek/coffeegb/core/gpu/Display.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import eu.rekawek.coffeegb.core.memento.Originator;
77

88
import java.io.Serializable;
9+
import java.util.Arrays;
910

1011
public class Display implements Serializable, Originator<Display> {
1112

@@ -62,10 +63,17 @@ public void enableLcd() {
6263
}
6364

6465
public void disableLcd() {
66+
// clear the framebuffer but do not emit a frame here: the emulator decides when
67+
// (and whether) a blank frame is shown, so a sub-frame LCD toggle doesn't flicker
6568
enabled = false;
66-
for (i = 0; i < buffer.length; i++) {
67-
buffer[i] = 0;
68-
}
69+
Arrays.fill(buffer, 0);
70+
i = 0;
71+
}
72+
73+
/** Emits a blank frame while the LCD stays off (a sustained off blanks the screen). */
74+
public void blankFrame() {
75+
Arrays.fill(buffer, 0);
76+
i = 0;
6977
frameIsReady();
7078
}
7179

0 commit comments

Comments
 (0)