Skip to content

Commit 53febd5

Browse files
committed
perf: display optimization
1 parent 3fa5a48 commit 53febd5

File tree

6 files changed

+447
-241
lines changed

6 files changed

+447
-241
lines changed

.gas-snapshot

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
EmuTest:testExecuteADD_I_VX() (gas: 112007)
2+
EmuTest:testExecuteADD_VX_NN() (gas: 87897)
3+
EmuTest:testExecuteADD_VX_VY() (gas: 92135)
4+
EmuTest:testExecuteAND_VX_VY() (gas: 89401)
5+
EmuTest:testExecuteCALL_ADDR() (gas: 112333)
6+
EmuTest:testExecuteCLS() (gas: 86080)
7+
EmuTest:testExecuteDRW_VX_VY_N() (gas: 342323)
8+
EmuTest:testExecuteJP_ADDR() (gas: 63925)
9+
EmuTest:testExecuteJP_V0_ADDR() (gas: 88069)
10+
EmuTest:testExecuteLD_B_VX() (gas: 139587)
11+
EmuTest:testExecuteLD_DT_VX() (gas: 110533)
12+
EmuTest:testExecuteLD_F_VX() (gas: 111000)
13+
EmuTest:testExecuteLD_I_ADDR() (gas: 86549)
14+
EmuTest:testExecuteLD_I_VX() (gas: 183907)
15+
EmuTest:testExecuteLD_ST_VX() (gas: 110648)
16+
EmuTest:testExecuteLD_VX_DT() (gas: 110262)
17+
EmuTest:testExecuteLD_VX_I() (gas: 183325)
18+
EmuTest:testExecuteLD_VX_K() (gas: 125347)
19+
EmuTest:testExecuteLD_VX_NN() (gas: 86280)
20+
EmuTest:testExecuteLD_VX_VY() (gas: 87927)
21+
EmuTest:testExecuteNOP() (gas: 47714)
22+
EmuTest:testExecuteOR_VX_VY() (gas: 89529)
23+
EmuTest:testExecuteRET() (gas: 95008)
24+
EmuTest:testExecuteRND_VX_NN() (gas: 87591)
25+
EmuTest:testExecuteSE_VX_NN() (gas: 89440)
26+
EmuTest:testExecuteSE_VX_VY() (gas: 91089)
27+
EmuTest:testExecuteSHL_VX_VY() (gas: 90803)
28+
EmuTest:testExecuteSHR_VX_VY() (gas: 90570)
29+
EmuTest:testExecuteSKNP_VX() (gas: 92714)
30+
EmuTest:testExecuteSKP_VX() (gas: 113723)
31+
EmuTest:testExecuteSNE_VX_NN() (gas: 89513)
32+
EmuTest:testExecuteSNE_VX_VY() (gas: 91675)
33+
EmuTest:testExecuteSUBN_VX_VY() (gas: 92777)
34+
EmuTest:testExecuteSUB_VX_VY() (gas: 92393)
35+
EmuTest:testExecuteUnknownOpcode() (gas: 64227)
36+
EmuTest:testExecuteXOR_VX_VY() (gas: 89448)
37+
EmuTest:testReset() (gas: 130454)

desktop/Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

desktop/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ version = "0.1.0"
44
edition = "2021"
55

66
[dependencies]
7+
bitvec = "1.0.1"
78
sdl2 = "^0.34.3"
89
alloy = { version = "=0.5.4", features = [
910
"full",

desktop/src/main.rs

+116-76
Large diffs are not rendered by default.

src/Emu.sol

+145-18
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,105 @@ contract Emu {
2727
// Display
2828
// -------------------------------------------------------------------------
2929

30+
/// @notice fontset size
31+
uint8 constant FONTSET_SIZE = 80;
32+
33+
/// @notice fontset
34+
/// @dev Most modern emulators will use that space to store the sprite data for font characters of all the
35+
/// hexadecimal digits, that is characters of 0-9 and A-F. We could store this data at any fixed position in RAM, but this
36+
/// space is already defined as empty anyway. Each character is made up of eight rows of five pixels, with each row using
37+
/// a byte of data, meaning that each letter altogether takes up five bytes of data. The following diagram illustrates how
38+
/// a character is stored as bytes
39+
uint8[FONTSET_SIZE] FONTSET = [
40+
0xF0,
41+
0x90,
42+
0x90,
43+
0x90,
44+
0xF0, // 0
45+
0x20,
46+
0x60,
47+
0x20,
48+
0x20,
49+
0x70, // 1
50+
0xF0,
51+
0x10,
52+
0xF0,
53+
0x80,
54+
0xF0, // 2
55+
0xF0,
56+
0x10,
57+
0xF0,
58+
0x10,
59+
0xF0, // 3
60+
0x90,
61+
0x90,
62+
0xF0,
63+
0x10,
64+
0x10, // 4
65+
0xF0,
66+
0x80,
67+
0xF0,
68+
0x10,
69+
0xF0, // 5
70+
0xF0,
71+
0x80,
72+
0xF0,
73+
0x90,
74+
0xF0, // 6
75+
0xF0,
76+
0x10,
77+
0x20,
78+
0x40,
79+
0x40, // 7
80+
0xF0,
81+
0x90,
82+
0xF0,
83+
0x90,
84+
0xF0, // 8
85+
0xF0,
86+
0x90,
87+
0xF0,
88+
0x10,
89+
0xF0, // 9
90+
0xF0,
91+
0x90,
92+
0xF0,
93+
0x90,
94+
0x90, // A
95+
0xE0,
96+
0x90,
97+
0xE0,
98+
0x90,
99+
0xE0, // B
100+
0xF0,
101+
0x80,
102+
0x80,
103+
0x80,
104+
0xF0, // C
105+
0xE0,
106+
0x90,
107+
0x90,
108+
0x90,
109+
0xE0, // D
110+
0xF0,
111+
0x80,
112+
0xF0,
113+
0x80,
114+
0xF0, // E
115+
0xF0,
116+
0x80,
117+
0xF0,
118+
0x80,
119+
0x80 // F
120+
];
121+
30122
struct Emulator {
31123
/// @notice 16-bit program counter
32124
uint16 pc;
33125
/// @notice 4KB RAM
34126
uint8[RAM_SIZE] ram;
35-
/// @notice A 64x32 monochrome display
36-
bool[SCREEN_WIDTH * SCREEN_HEIGHT] screen;
127+
/// @notice A 64x32 monochrome display = 2048 bit = 256 * 8 bits
128+
uint256[8] screen;
37129
/// @notice Sixteen 8-bit general purpose registers, referred to as V0 thru VF
38130
uint8[NUM_REGS] v_reg;
39131
/// @notice Single 16-bit register used as a pointer for memory access, called the I Register
@@ -63,13 +155,16 @@ contract Emu {
63155

64156
constructor() {
65157
emu.pc = START_ADDR;
158+
for (uint256 i = 0; i < FONTSET_SIZE; i++) {
159+
emu.ram[i] = FONTSET[i];
160+
}
66161
}
67162

68163
/// @notice Reset the emulator
69164
function reset() public {
70165
emu.pc = START_ADDR;
71-
for (uint256 i = 0; i < SCREEN_WIDTH * SCREEN_HEIGHT; i++) {
72-
emu.screen[i] = false;
166+
for (uint256 i = 0; i < 8; i++) {
167+
emu.screen[i] = 0;
73168
}
74169
for (uint256 i = 0; i < NUM_REGS; i++) {
75170
emu.v_reg[i] = 0;
@@ -84,6 +179,10 @@ contract Emu {
84179
}
85180
emu.dt = 0;
86181
emu.st = 0;
182+
// Copy FONTSET
183+
for (uint256 i = 0; i < FONTSET_SIZE; i++) {
184+
emu.ram[i] = FONTSET[i];
185+
}
87186
}
88187

89188
// -------------------------------------------------------------------------
@@ -159,8 +258,8 @@ contract Emu {
159258

160259
// 00E0 - CLS
161260
if (digit1 == 0x0 && digit2 == 0x0 && digit3 == 0xE && digit4 == 0) {
162-
for (uint256 i = 0; i < SCREEN_WIDTH * SCREEN_HEIGHT; i++) {
163-
emu.screen[i] = false;
261+
for (uint256 i = 0; i < 8; i++) {
262+
emu.screen[i] = 0;
164263
}
165264
return;
166265
}
@@ -321,17 +420,39 @@ contract Emu {
321420
for (uint8 row = 0; row < height; row++) {
322421
uint8 sprite_byte = emu.ram[emu.i_reg + row];
323422
for (uint8 col = 0; col < 8; col++) {
423+
// Get the sprite pixel (bit) at the current column
324424
uint8 sprite_pixel = (sprite_byte >> (7 - col)) & 0x1;
425+
426+
// Calculate the screen coordinates, wrapping around if necessary
325427
uint32 screen_x = uint32((x + col) % SCREEN_WIDTH);
326428
uint32 screen_y = uint32((y + row) % SCREEN_HEIGHT);
327-
uint256 index = screen_y * SCREEN_WIDTH + screen_x;
328429

329-
bool pixel_before = emu.screen[index];
430+
// Calculate the index in the display buffer
431+
uint32 pixel_index = screen_y * SCREEN_WIDTH + screen_x; // Range: 0 to 2047
432+
433+
// Calculate the display array index and bit position
434+
uint256 display_index = pixel_index / 256; // Index in emu.screen[]
435+
uint256 bit_position = pixel_index % 256; // Bit position within emu.screen[display_index]
436+
437+
// Get the current pixel value from the display
438+
bool pixel_before = ((emu.screen[display_index] >> (255 - bit_position)) & 0x1) != 0;
439+
440+
// Calculate the new pixel value using XOR (as per CHIP-8 drawing behavior)
330441
bool new_pixel = pixel_before != (sprite_pixel == 1);
442+
443+
// Update the collision flag VF if a pixel is erased
331444
if (pixel_before && !new_pixel) {
332445
emu.v_reg[0xF] = 1;
333446
}
334-
emu.screen[index] = new_pixel;
447+
448+
// Update the display with the new pixel value
449+
if (new_pixel) {
450+
// Set the bit to 1
451+
emu.screen[display_index] |= (1 << (255 - bit_position));
452+
} else {
453+
// Set the bit to 0
454+
emu.screen[display_index] &= ~(1 << (255 - bit_position));
455+
}
335456
}
336457
}
337458
return;
@@ -438,7 +559,7 @@ contract Emu {
438559
// -------------------------------------------------------------------------
439560

440561
/// @notice Get display
441-
function getDisplay() public view returns (bool[SCREEN_WIDTH * SCREEN_HEIGHT] memory) {
562+
function getDisplay() public view returns (uint256[8] memory) {
442563
return emu.screen;
443564
}
444565

@@ -530,16 +651,22 @@ contract Emu {
530651
}
531652

532653
function setScreenPixel(uint256 index, bool value) public {
533-
require(index < SCREEN_WIDTH * SCREEN_HEIGHT, "Index out of bounds");
534-
emu.screen[index] = value;
654+
unchecked {
655+
require(index < SCREEN_WIDTH * SCREEN_HEIGHT, "Index out of bounds");
656+
if (value) {
657+
// Set the bit to 1
658+
emu.screen[index >> 8] |= 1 << (index & 255);
659+
} else {
660+
// Set the bit to 0
661+
emu.screen[index >> 8] &= ~(1 << (index & 255));
662+
}
663+
}
535664
}
536665

537666
function isDisplayCleared() public view returns (bool) {
538-
for (uint256 i = 0; i < SCREEN_WIDTH * SCREEN_HEIGHT; i++) {
539-
if (emu.screen[i]) {
540-
return false;
541-
}
542-
}
543-
return true;
667+
return (
668+
emu.screen[0] == 0 && emu.screen[1] == 0 && emu.screen[2] == 0 && emu.screen[3] == 0 && emu.screen[4] == 0
669+
&& emu.screen[5] == 0 && emu.screen[6] == 0 && emu.screen[7] == 0
670+
);
544671
}
545672
}

0 commit comments

Comments
 (0)