Add a draw sprint feature for reducing flicker
This commit is contained in:
parent
f8de8aa2cf
commit
5504050d56
28
README.md
28
README.md
|
@ -34,11 +34,33 @@ Emulation speed
|
||||||
---------------
|
---------------
|
||||||
Instructions are run 500 times a second
|
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
|
Issues
|
||||||
------
|
------
|
||||||
* Player sprites tend to flicker. (See e.g. BRIX) Why? Also, this seems to
|
* Keyboard can't be changed
|
||||||
happen in the haxe-chip-8-emulator too, and lowering the FPS doesn't seem
|
* FPS can't be changed
|
||||||
to help, which suggests something fishy with the programs
|
* Interpreter speed can't be changed
|
||||||
|
* Window size can't be changed
|
||||||
|
* No error handing on opening a file
|
||||||
|
|
||||||
Requirements
|
Requirements
|
||||||
------------
|
------------
|
||||||
|
|
45
sipsi-8.py
45
sipsi-8.py
|
@ -79,9 +79,14 @@ def step():
|
||||||
global delay_timer, sound_timer
|
global delay_timer, sound_timer
|
||||||
global keys_pressed, waiting_for_keypress, keypress_arrived
|
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
|
# Don't execute any code in a waitstate, as it'd only end up
|
||||||
# busylooping
|
# 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]
|
high_byte = ram[ip]
|
||||||
ip = (ip + 1) & 0xfff
|
ip = (ip + 1) & 0xfff
|
||||||
|
@ -100,6 +105,7 @@ def step():
|
||||||
# 00EE ret
|
# 00EE ret
|
||||||
elif high_byte == 0x00 and low_byte == 0xEE:
|
elif high_byte == 0x00 and low_byte == 0xEE:
|
||||||
ip = stack.pop()
|
ip = stack.pop()
|
||||||
|
did_jump = True
|
||||||
|
|
||||||
# 0nnn call_machine
|
# 0nnn call_machine
|
||||||
elif high_byte >> 4 == 0:
|
elif high_byte >> 4 == 0:
|
||||||
|
@ -109,11 +115,13 @@ def step():
|
||||||
# 1nnn jmp nnn
|
# 1nnn jmp nnn
|
||||||
elif high_byte >> 4 == 1:
|
elif high_byte >> 4 == 1:
|
||||||
ip = ((high_byte & 0xf) << 8) | low_byte
|
ip = ((high_byte & 0xf) << 8) | low_byte
|
||||||
|
did_jump = True
|
||||||
|
|
||||||
# 2nnn call nnn
|
# 2nnn call nnn
|
||||||
elif high_byte >> 4 == 2:
|
elif high_byte >> 4 == 2:
|
||||||
stack.append(ip)
|
stack.append(ip)
|
||||||
ip = ((high_byte & 0xf) << 8) | low_byte
|
ip = ((high_byte & 0xf) << 8) | low_byte
|
||||||
|
did_jump = True
|
||||||
|
|
||||||
# 3xnn skipeq vx, nn
|
# 3xnn skipeq vx, nn
|
||||||
elif high_byte >> 4 == 3:
|
elif high_byte >> 4 == 3:
|
||||||
|
@ -216,6 +224,7 @@ def step():
|
||||||
elif high_byte >> 4 == 0xB:
|
elif high_byte >> 4 == 0xB:
|
||||||
ip = ((high_byte & 0xf) << 8) | low_byte
|
ip = ((high_byte & 0xf) << 8) | low_byte
|
||||||
ip = (ip + data_registers[0]) & 0xfff
|
ip = (ip + data_registers[0]) & 0xfff
|
||||||
|
did_jump = True
|
||||||
|
|
||||||
# Cxnn maskedrandom vx, nn
|
# Cxnn maskedrandom vx, nn
|
||||||
elif high_byte >> 4 == 0xC:
|
elif high_byte >> 4 == 0xC:
|
||||||
|
@ -242,6 +251,8 @@ def step():
|
||||||
# have yet to find any games that break with it, I'm just
|
# have yet to find any games that break with it, I'm just
|
||||||
# skipping pixels that fall outside the screen.
|
# skipping pixels that fall outside the screen.
|
||||||
|
|
||||||
|
did_draw = True
|
||||||
|
|
||||||
x_start = data_registers[high_byte & 0xf]
|
x_start = data_registers[high_byte & 0xf]
|
||||||
y_start = data_registers[low_byte >> 4]
|
y_start = data_registers[low_byte >> 4]
|
||||||
|
|
||||||
|
@ -362,6 +373,8 @@ def step():
|
||||||
else:
|
else:
|
||||||
print('%03x: Unrecognized!' % (ip-2))
|
print('%03x: Unrecognized!' % (ip-2))
|
||||||
|
|
||||||
|
return did_draw, did_jump
|
||||||
|
|
||||||
def tick_timers():
|
def tick_timers():
|
||||||
global delay_timer, sound_timer
|
global delay_timer, sound_timer
|
||||||
|
|
||||||
|
@ -391,9 +404,37 @@ def advance_interpreter(dt):
|
||||||
tick_timers()
|
tick_timers()
|
||||||
|
|
||||||
while cpu_cycles_to_go >= 1:
|
while cpu_cycles_to_go >= 1:
|
||||||
step()
|
did_draw, _ = step()
|
||||||
cpu_cycles_to_go -= 1
|
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:
|
if frames_to_go >= 1:
|
||||||
# Only draw the latest frame. This means FPS >60 won't work
|
# Only draw the latest frame. This means FPS >60 won't work
|
||||||
# but why would you ever want to run chip-8 faster than
|
# but why would you ever want to run chip-8 faster than
|
||||||
|
|
Loading…
Reference in New Issue