From 5504050d5654b06951b0c561c3bc109645bf334f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juhani=20Krekel=C3=A4?= Date: Sun, 23 Sep 2018 12:03:07 +0300 Subject: [PATCH] Add a draw sprint feature for reducing flicker --- README.md | 28 +++++++++++++++++++++++++--- sipsi-8.py | 45 +++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 68 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a0effc3..7bb294d 100644 --- a/README.md +++ b/README.md @@ -34,11 +34,33 @@ Emulation speed --------------- Instructions are run 500 times a second +Draw sprint +----------- +To reduce sprite flicker, sipsi-8 can run upto two ticks' worth of cycles +after a draw call. This is since chip-8 often does drawing in pairs, where +the first in the pair erases the old sprite and the next one draws it back. +If they happen on different frames, this results in very back flickering. + +To avoid an issue of pairing one sprite's draw to other's erase, a sprint +is invalidated if it hits a jump, call, or a return. + +Games improved by draw sprint: +* Brix and its descendants +* UFO + +Games unaffected by draw sprint: +* Pong and Pong2 (Draws are too far from each other) +* Tetris (Does jumps inbetween) +* Blitz (Does jumps inbetween. If invalidation upon jump is disabled, ends + up pairing wrong draw calls) + Issues ------ -* Player sprites tend to flicker. (See e.g. BRIX) Why? Also, this seems to - happen in the haxe-chip-8-emulator too, and lowering the FPS doesn't seem - to help, which suggests something fishy with the programs +* Keyboard can't be changed +* FPS can't be changed +* Interpreter speed can't be changed +* Window size can't be changed +* No error handing on opening a file Requirements ------------ diff --git a/sipsi-8.py b/sipsi-8.py index 25f58e3..f4e0d47 100644 --- a/sipsi-8.py +++ b/sipsi-8.py @@ -79,9 +79,14 @@ def step(): global delay_timer, sound_timer global keys_pressed, waiting_for_keypress, keypress_arrived + # Keep track of whether we did a draw or a jump, for the draw + # sprint feature + did_draw = False + did_jump = False + # Don't execute any code in a waitstate, as it'd only end up # busylooping - if waiting_for_keypress and not keypress_arrived: return + if waiting_for_keypress and not keypress_arrived: return did_draw, did_jump high_byte = ram[ip] ip = (ip + 1) & 0xfff @@ -100,6 +105,7 @@ def step(): # 00EE ret elif high_byte == 0x00 and low_byte == 0xEE: ip = stack.pop() + did_jump = True # 0nnn call_machine elif high_byte >> 4 == 0: @@ -109,11 +115,13 @@ def step(): # 1nnn jmp nnn elif high_byte >> 4 == 1: ip = ((high_byte & 0xf) << 8) | low_byte + did_jump = True # 2nnn call nnn elif high_byte >> 4 == 2: stack.append(ip) ip = ((high_byte & 0xf) << 8) | low_byte + did_jump = True # 3xnn skipeq vx, nn elif high_byte >> 4 == 3: @@ -216,6 +224,7 @@ def step(): elif high_byte >> 4 == 0xB: ip = ((high_byte & 0xf) << 8) | low_byte ip = (ip + data_registers[0]) & 0xfff + did_jump = True # Cxnn maskedrandom vx, nn elif high_byte >> 4 == 0xC: @@ -242,6 +251,8 @@ def step(): # have yet to find any games that break with it, I'm just # skipping pixels that fall outside the screen. + did_draw = True + x_start = data_registers[high_byte & 0xf] y_start = data_registers[low_byte >> 4] @@ -362,6 +373,8 @@ def step(): else: print('%03x: Unrecognized!' % (ip-2)) + return did_draw, did_jump + def tick_timers(): global delay_timer, sound_timer @@ -391,9 +404,37 @@ def advance_interpreter(dt): tick_timers() while cpu_cycles_to_go >= 1: - step() + did_draw, _ = step() cpu_cycles_to_go -= 1 + # If we did a draw, run upto two ticks' cycles to reach + # the next draw. Chip-8 draws are often paired, an erase + # and a draw. If we don't hit both in the same frame, it + # will cause flicker. + # Since this will put the number of cycles to go below zero + # if we run out of our own cycles, on average we will run + # the right number of cycles per tick + # Also, to avoid pairing draw half of one pair and erase + # half of another, the sprint is invalidated by a jump, a + # call or a ret + if did_draw: + #sprint_run_out = True#debg + for cycle in range(2*cpu_clock // 60): + did_draw, did_jump = step() + cpu_cycles_to_go -= 1 + + # Exit as soon as we hit the other draw + if did_draw: + #sprint_run_out = False#debg + #print('Sprint succeeded! %i' % cycle)#debg + break + + if did_jump: + #sprint_run_out = False#debg + #print('Sprint failed! (jump) %i' % cycle)#debg + break + #if sprint_run_out: print('Sprint failed! (ran out)')#debg + if frames_to_go >= 1: # Only draw the latest frame. This means FPS >60 won't work # but why would you ever want to run chip-8 faster than