Newer
Older
pub fn frame_buffer_rgb565_u16(&self) -> [u16; FRAME_BUFFER_SIZE] {
let mut buffer = [0u16; FRAME_BUFFER_SIZE];
for (index, pixel) in buffer.iter_mut().enumerate().take(DISPLAY_SIZE) {
let (r, g, b) = (
self.frame_buffer[index * RGB_SIZE],
self.frame_buffer[index * RGB_SIZE + 1],
self.frame_buffer[index * RGB_SIZE + 2],
);
}
buffer
}
pub fn vram(&self) -> &[u8; VRAM_SIZE] {
&self.vram
}
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
pub fn vram_dmg(&self) -> &[u8] {
&self.vram[0..VRAM_SIZE_DMG]
}
pub fn vram_cgb(&self) -> &[u8] {
&self.vram[0..VRAM_SIZE_CGB]
}
pub fn vram_device(&self) -> &[u8] {
match self.gb_mode {
GameBoyMode::Dmg => self.vram_dmg(),
GameBoyMode::Cgb => self.vram_cgb(),
GameBoyMode::Sgb => self.vram_dmg(),
}
}
pub fn set_vram(&mut self, value: &[u8]) {
self.vram[0..value.len()].copy_from_slice(value);
self.update_vram();
}
pub fn hram(&self) -> &[u8; HRAM_SIZE] {
&self.hram
}
pub fn set_hram(&mut self, value: [u8; HRAM_SIZE]) {
self.hram = value;
}
pub fn tiles(&self) -> &[Tile; TILE_COUNT] {
&self.tiles
pub fn set_palette_colors(&mut self, value: &Palette) {
self.palette_colors = *value;
pub fn palette_bg(&self) -> Palette {
self.palette_bg
}
pub fn palette_obj_0(&self) -> Palette {
self.palette_obj_0
}
pub fn palette_obj_1(&self) -> Palette {
self.palette_obj_1
}
pub fn palettes_color(&self) -> &[[u8; 64]; 2] {
&self.palettes_color
}
pub fn set_palettes_color(&mut self, palettes_color: [[u8; 64]; 2]) {
self.palettes_color = palettes_color;
Self::compute_palettes_color(
&mut [&mut self.palettes_color_bg, &mut self.palettes_color_obj],
&self.palettes_color,
);
}
pub fn ly(&self) -> u8 {
self.ly
}
pub fn mode(&self) -> PpuMode {
self.mode
}
pub fn frame_index(&self) -> u16 {
self.frame_index
}
pub fn int_vblank(&self) -> bool {
self.int_vblank
}
pub fn set_int_vblank(&mut self, value: bool) {
self.int_vblank = value;
}
pub fn int_stat(&self) -> bool {
self.int_stat
}
pub fn set_int_stat(&mut self, value: bool) {
self.int_stat = value;
}
pub fn ack_stat(&mut self) {
self.set_int_stat(false);
}
pub fn dmg_compat(&self) -> bool {
self.dmg_compat
}
pub fn set_dmg_compat(&mut self, value: bool) {
self.dmg_compat = value;
// if we're switching to the DMG compat mode
// then we need to recompute the palettes so
// the compat palettes set by the Boot ROM
if value {
self.compute_palettes();
}
pub fn gb_mode(&self) -> GameBoyMode {
self.gb_mode
}
pub fn set_gb_mode(&mut self, value: GameBoyMode) {
self.gb_mode = value;
}
pub fn set_gbc(&mut self, value: Rc<RefCell<GameBoyConfig>>) {
self.gbc = value;
}
/// Fills the frame buffer with pixels of the provided color,
/// this method must represent the fastest way of achieving
/// the fill background with color operation.
pub fn fill_frame_buffer(&mut self, color: Pixel) {
for index in (0..self.frame_buffer.len()).step_by(RGB_SIZE) {
self.frame_buffer[index] = color[0];
self.frame_buffer[index + 1] = color[1];
self.frame_buffer[index + 2] = color[2];
}
}
/// Clears the current frame buffer, setting the background color
/// for all the pixels in the frame buffer.
pub fn clear_frame_buffer(&mut self) {
self.fill_frame_buffer(self.palette_colors[0]);
/// Prints the tile data information to the stdout, this is
/// useful for debugging purposes.
pub fn print_tile_stdout(&self, tile_index: usize) {
println!("{}", self.tiles[tile_index]);
/// Updates the internal PPU state (calculated values) according
/// to the VRAM values, this should be called whenever the VRAM
/// is replaced.
pub fn update_vram(&mut self) {
// "saves" the old values of the VRAM bank and offset
// as they are going to be needed later, this is required
// as we're going to trick the PPU into switching banks
// over the update of the calculated values for the new VRAM,
// essentially required for the `update_tile()` method
let (vram_bank_old, vram_offset_old) = (self.vram_bank, self.vram_offset);
// determines the number of VRAM banks available according
// to the running Game Boy running mode (CGB vs DMG)
let vram_banks = if self.gb_mode == GameBoyMode::Cgb {
// goes over all the VRAM banks, and over all the VRAM addresses
// in those banks to update the internal tiles and background map
// attributes structures accordingly
for vram_bank in 0..vram_banks {
self.vram_bank = vram_bank;
self.vram_offset = self.vram_bank as u16 * 0x2000;
let value = self.vram[(self.vram_offset + (addr & 0x1fff)) as usize];
if addr < 0x9800 {
self.update_tile(addr, value);
} else if self.vram_bank == 0x1 {
self.update_bg_map_attrs(addr, value);
}
}
}
// restores the "old" values for VRAM bank and offset
(self.vram_bank, self.vram_offset) = (vram_bank_old, vram_offset_old);
/// Updates the tile structure with the value that has
/// just been written to a location on the VRAM associated
/// with tiles.
fn update_tile(&mut self, addr: u16, _value: u8) {
let addr = (self.vram_offset + (addr & 0x1ffe)) as usize;
let tile_index = ((addr >> 4) & 0x01ff) + (self.vram_bank as usize * TILE_COUNT_DMG);
let tile = self.tiles[tile_index].borrow_mut();
mask = 1 << (TILE_WIDTH_I - x);
x,
y,
if self.vram[addr] & mask > 0 { 0x1 } else { 0x0 }
| if self.vram[addr + 1] & mask > 0 {
0x2
} else {
0x0
},
);
fn update_object(&mut self, addr: u16, value: u8) {
let addr = (addr & 0x01ff) as usize;
let obj_index = addr >> 2;
if obj_index >= OBJ_COUNT {
return;
}
0x00 => obj.y = value as i16 - 16,
0x01 => obj.x = value as i16 - 8,
0x02 => obj.tile = value,
obj.palette_cgb = value & 0x07;
obj.tile_bank = (value & 0x08 == 0x08) as u8;
obj.xflip = value & 0x20 == 0x20;
obj.yflip = value & 0x40 == 0x40;
obj.bg_over = value & 0x80 == 0x80;
}
_ => (),
}
}
fn update_bg_map_attrs(&mut self, addr: u16, value: u8) {
let tile_index = if bg_map { addr - 0x9c00 } else { addr - 0x9800 };
let bg_map_attrs = if bg_map {
&mut self.bg_map_attrs_1
let tile_data: &mut TileData = bg_map_attrs[tile_index as usize].borrow_mut();
tile_data.vram_bank = (value & 0x08 == 0x08) as u8;
tile_data.xflip = value & 0x20 == 0x20;
tile_data.yflip = value & 0x40 == 0x40;
tile_data.priority = value & 0x80 == 0x80;
pub fn registers(&self) -> PpuRegisters {
PpuRegisters {
scy: self.scy,
scx: self.scx,
wy: self.wy,
wx: self.wx,
ly: self.ly,
lyc: self.lyc,
}
}
if self.gb_mode == GameBoyMode::Dmg {
self.render_line_dmg();
} else {
self.render_line_cgb();
}
}
fn render_line_dmg(&mut self) {
if self.switch_bg {
self.render_map_dmg(self.bg_map, self.scx, self.scy, 0, 0, self.ly);
}
if self.switch_bg && self.switch_window {
self.render_map_dmg(self.window_map, 0, 0, self.wx, self.wy, self.window_counter);
}
if self.switch_obj {
self.render_objects();
}
}
fn render_line_cgb(&mut self) {
if self.first_frame {
return;
}
let switch_bg_window = (self.gb_mode.is_cgb() && !self.dmg_compat) || self.switch_bg;
self.render_map(self.bg_map, self.scx, self.scy, 0, 0, self.ly);
if switch_bg_window && self.switch_window {
self.render_map(self.window_map, 0, 0, self.wx, self.wy, self.window_counter);
}
if self.switch_obj {
self.render_objects();
}
}
fn render_map(&mut self, map: bool, scx: u8, scy: u8, wx: u8, wy: u8, ld: u8) {
// in case the target window Y position has not yet been reached
// then there's nothing to be done, returns control flow immediately
if self.ly < wy {
return;
}
// selects the correct background attributes map based on the bg map flag
// because the attributes are separated according to the map they represent
// this is only relevant for CGB mode
let bg_map_attrs = if map {
self.bg_map_attrs_1
} else {
self.bg_map_attrs_0
};
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
// obtains the base address of the background map using the bg map flag
// that control which background map is going to be used
let map_offset: usize = if map { 0x1c00 } else { 0x1800 };
// calculates the map row index for the tile by using the current line
// index and the DY (scroll Y) divided by 8 (as the tiles are 8x8 pixels),
// on top of that ensures that the result is modulus 32 meaning that the
// drawing wraps around the Y axis
let row_index = (((ld as usize + scy as usize) & 0xff) >> 3) % 32;
// calculates the map offset by the row offset multiplied by the number
// of tiles in each row (32)
let row_offset = row_index * 32;
// calculates the sprite line offset by using the SCX register
// shifted by 3 meaning that the tiles are 8x8
let mut line_offset = (scx >> 3) as usize;
// calculates the index of the initial tile in drawing,
// if the tile data set in use is #1, the indexes are
// signed, then calculates a real tile offset
let mut tile_index = self.vram[map_offset + row_offset + line_offset] as usize;
if !self.bg_tile && tile_index < 128 {
tile_index += 256;
}
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
// obtains the reference to the attributes of the new tile in
// drawing for meta processing (CGB only)
let mut tile_attr = if self.dmg_compat {
&DEFAULT_TILE_ATTR
} else {
&bg_map_attrs[row_offset + line_offset]
};
// retrieves the proper palette for the current tile in drawing
// taking into consideration if we're running in CGB mode or not
let mut palette = if self.gb_mode == GameBoyMode::Cgb {
if self.dmg_compat {
&self.palette_bg
} else {
&self.palettes_color_bg[tile_attr.palette as usize]
}
} else {
&self.palette_bg
};
// obtains the values of both X and Y flips for the current tile
// they will be applied by the get tile pixel method
let mut xflip = tile_attr.xflip;
let mut yflip = tile_attr.yflip;
// obtains the value the BG-to-OAM priority to be used in the computation
// of the final pixel value (CGB only)
let mut priority = tile_attr.priority;
// increments the tile index value by the required offset for the VRAM
// bank in which the tile is stored, this is only required for CGB mode
tile_index += tile_attr.vram_bank as usize * TILE_COUNT_DMG;
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
// obtains the reference to the tile that is going to be drawn
let mut tile = &self.tiles[tile_index];
// calculates the offset that is going to be used in the update of the color buffer
// which stores Game Boy colors from 0 to 3
let mut color_offset = self.ly as usize * DISPLAY_WIDTH;
// calculates the frame buffer offset position assuming the proper
// Game Boy screen width and RGB pixel (3 bytes) size
let mut frame_offset = self.ly as usize * DISPLAY_WIDTH * RGB_SIZE;
// calculates both the current Y and X positions within the tiles
// using the bitwise and operation as an effective modulus 8
let y = (ld as usize + scy as usize) & 0x07;
let mut x = (scx & 0x07) as usize;
// calculates the initial tile X position in drawing, doing this
// allows us to position the background map properly in the display
let initial_index = max(wx as i16 - 7, 0) as usize;
color_offset += initial_index;
frame_offset += initial_index * RGB_SIZE;
// iterates over all the pixels in the current line of the display
// to draw the background map, note that the initial index is used
// to skip the drawing of the tiles that are not visible (WX)
for _ in initial_index..DISPLAY_WIDTH {
// obtains the current pixel data from the tile and
// re-maps it according to the current palette
let pixel = tile.get_flipped(x, y, xflip, yflip);
let color = &palette[pixel as usize];
// updates the pixel in the color buffer, which stores
// the raw pixel color information (unmapped)
self.color_buffer[color_offset] = pixel;
// set the color pixel in the frame buffer
self.frame_buffer[frame_offset] = color[0];
self.frame_buffer[frame_offset + 1] = color[1];
self.frame_buffer[frame_offset + 2] = color[2];
// updates the priority buffer with the current pixel
// the priority is only set in case the priority of
// the background (over OAM) is set in the attributes
// and the pixel is not transparent
self.priority_buffer[color_offset] = priority && pixel != 0;
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
// increments the current tile X position in drawing
x += 1;
// in case the end of tile width has been reached then
// a new tile must be retrieved for rendering
if x == TILE_WIDTH {
// resets the tile X position to the base value
// as a new tile is going to be drawn
x = 0;
// calculates the new line tile offset making sure that
// the maximum of 32 is not overflown
line_offset = (line_offset + 1) % 32;
// calculates the tile index and makes sure the value
// takes into consideration the bg tile value
tile_index = self.vram[map_offset + row_offset + line_offset] as usize;
if !self.bg_tile && tile_index < 128 {
tile_index += 256;
}
// in case the current mode is CGB and the DMG compatibility
// flag is not set then a series of tile values must be
// updated according to the tile attributes field
if self.gb_mode == GameBoyMode::Cgb && !self.dmg_compat {
tile_attr = &bg_map_attrs[row_offset + line_offset];
palette = &self.palettes_color_bg[tile_attr.palette as usize];
xflip = tile_attr.xflip;
yflip = tile_attr.yflip;
priority = tile_attr.priority;
tile_index += tile_attr.vram_bank as usize * TILE_COUNT_DMG;
}
// obtains the reference to the new tile in drawing
tile = &self.tiles[tile_index];
}
// increments the color offset by one, representing
// the drawing of one pixel
color_offset += 1;
// increments the offset of the frame buffer by the
// size of an RGB pixel (which is 3 bytes)
frame_offset += RGB_SIZE;
}
}
fn render_map_dmg(&mut self, map: bool, scx: u8, scy: u8, wx: u8, wy: u8, ld: u8) {
// in case the target window Y position has not yet been reached
// then there's nothing to be done, returns control flow immediately
if self.ly < wy {
return;
}
// obtains the base address of the background map using the bg map flag
// that control which background map is going to be used
let map_offset: usize = if map { 0x1c00 } else { 0x1800 };
// calculates the map row index for the tile by using the current line
// index and the DY (scroll Y) divided by 8 (as the tiles are 8x8 pixels),
// on top of that ensures that the result is modulus 32 meaning that the
// drawing wraps around the Y axis
let row_index = (((ld as usize + scy as usize) & 0xff) >> 3) % 32;
// calculates the map offset by the row offset multiplied by the number
// calculates the sprite line offset by using the SCX register
// shifted by 3 meaning that the tiles are 8x8
// calculates the index of the initial tile in drawing,
// if the tile data set in use is #1, the indexes are
// signed, then calculates a real tile offset
let mut tile_index = self.vram[map_offset + row_offset + line_offset] as usize;
if !self.bg_tile && tile_index < 128 {
tile_index += 256;
}
// obtains the reference to the tile that is going to be drawn
let mut tile = &self.tiles[tile_index];
// calculates the offset that is going to be used in the update of the color buffer
// which stores Game Boy colors from 0 to 3
let mut color_offset = self.ly as usize * DISPLAY_WIDTH;
// calculates the frame buffer offset position assuming the proper
// Game Boy screen width and RGB pixel (3 bytes) size
let mut frame_offset = self.ly as usize * DISPLAY_WIDTH * RGB_SIZE;
// calculates both the current Y and X positions within the tiles
// using the bitwise and operation as an effective modulus 8
let y = (ld as usize + scy as usize) & 0x07;
let mut x = (scx & 0x07) as usize;
// calculates the initial tile X position in drawing, doing this
// allows us to position the background map properly in the display
let initial_index = max(wx as i16 - 7, 0) as usize;
color_offset += initial_index;
frame_offset += initial_index * RGB_SIZE;
// iterates over all the pixels in the current line of the display
// to draw the background map, note that the initial index is used
// to skip the drawing of the tiles that are not visible (WX)
for _ in initial_index..DISPLAY_WIDTH {
// obtains the current pixel data from the tile and
// re-maps it according to the current palette
let pixel = tile.get(x, y);
let color = &self.palette_bg[pixel as usize];
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
// updates the pixel in the color buffer, which stores
// the raw pixel color information (unmapped)
self.color_buffer[color_offset] = pixel;
// set the color pixel in the frame buffer
self.frame_buffer[frame_offset] = color[0];
self.frame_buffer[frame_offset + 1] = color[1];
self.frame_buffer[frame_offset + 2] = color[2];
// increments the current tile X position in drawing
x += 1;
// in case the end of tile width has been reached then
// a new tile must be retrieved for rendering
if x == TILE_WIDTH {
// resets the tile X position to the base value
// as a new tile is going to be drawn
x = 0;
// calculates the new line tile offset making sure that
// the maximum of 32 is not overflown
line_offset = (line_offset + 1) % 32;
// calculates the tile index and makes sure the value
// takes into consideration the bg tile value
tile_index = self.vram[map_offset + row_offset + line_offset] as usize;
if !self.bg_tile && tile_index < 128 {
tile_index += 256;
}
// obtains the reference to the new tile in drawing
tile = &self.tiles[tile_index];
// increments the color offset by one, representing
// increments the offset of the frame buffer by the
// size of an RGB pixel (which is 3 bytes)
frame_offset += RGB_SIZE;
// the mode in which the object priority should be computed
// if true this means that the X coordinate priority mode will
// be used otherwise the object priority will be defined according
// to the object's index in the OAM memory, notice that this
// control of priority is only present in the CGB and to be able
// to offer retro-compatibility with DMG
let obj_priority_mode = self.gb_mode != GameBoyMode::Cgb || self.obj_priority;
// creates a local counter object to count the total number
// of object that were drawn in the current line, this will
// be used for instance to limit the total number of objects
// to 10 per line (Game Boy limitation)
let mut draw_count = 0u8;
// allocates the buffer that is going to be used to determine
// drawing priority for overlapping pixels between different
// objects, in MBR mode the object that has the smallest X
// coordinate takes priority in drawing the pixel
let mut index_buffer = [-256i16; DISPLAY_WIDTH];
// determines if the object should always be placed over the
// possible background, this is only required for CGB mode
let always_over = if self.gb_mode == GameBoyMode::Cgb && !self.dmg_compat {
!self.switch_bg
} else {
false
};
// iterates over the complete set of available object to check
// the ones that require drawing and draws them
// in case the limit on the number of objects to be draw per
// line has been reached breaks the loop avoiding more draws
if draw_count == 10 {
break;
}
// obtains the meta data of the object that is currently
// under iteration to be checked for drawing
let obj_height = if self.obj_size {
TILE_DOUBLE_HEIGHT
} else {
TILE_HEIGHT
};
// verifies if the sprite is currently located at the
// current line that is going to be drawn and skips it
// in case it's not
(obj.y <= self.ly as i16) && ((obj.y + obj_height as i16) > self.ly as i16);
if !is_contained {
continue;
}
let palette = if self.gb_mode == GameBoyMode::Cgb {
if self.dmg_compat {
if obj.palette == 0 {
&self.palette_obj_0
} else if obj.palette == 1 {
&self.palette_obj_1
} else {
panic!("Invalid object palette: {:02x}", obj.palette);
}
} else {
&self.palettes_color_obj[obj.palette_cgb as usize]
}
} else if obj.palette == 0 {
&self.palette_obj_0
} else if obj.palette == 1 {
&self.palette_obj_1
panic!("Invalid object palette: {:02x}", obj.palette);
// calculates the offset in the color buffer (raw color information
// from 0 to 3) for the sprit that is going to be drawn, this value
// is kept as a signed integer to allow proper negative number math
let mut color_offset = self.ly as i32 * DISPLAY_WIDTH as i32 + obj.x as i32;
// calculates the offset in the frame buffer for the sprite
// that is going to be drawn, this is going to be the starting
// point for the draw operation to be performed
let mut frame_offset =
(self.ly as i32 * DISPLAY_WIDTH as i32 + obj.x as i32) * RGB_SIZE as i32;
// the relative title offset should range from 0 to 7 in 8x8
// objects and from 0 to 15 in 8x16 objects
let mut tile_offset = self.ly as i16 - obj.y;
// in case we're flipping the object we must recompute the
// tile offset as an inverted value using the object's height
if obj.yflip {
tile_offset = obj_height as i16 - tile_offset - 1;
}
// saves some space for the reference to the tile that
// is going to be used in the current operation
let tile: &Tile;
// "calculates" the index offset that is going to be applied
// to the tile index to retrieve the proper tile taking into
// consideration the VRAM in which the tile is stored
let tile_bank_offset = if self.dmg_compat {
0
} else {
obj.tile_bank as usize * TILE_COUNT_DMG
};
// in case we're facing a 8x16 object then we must
// differentiate between the handling of the top tile
// and the bottom tile through bitwise manipulation
// of the tile index
if self.obj_size {
if tile_offset < 8 {
let tile_index = (obj.tile as usize & 0xfe) + tile_bank_offset;
tile = &self.tiles[tile_index];
let tile_index = (obj.tile as usize | 0x01) + tile_bank_offset;
tile = &self.tiles[tile_index];
}
}
// otherwise we're facing a 8x8 sprite and we should grab
// the tile directly from the object's tile index
else {
let tile_index = obj.tile as usize + tile_bank_offset;
let tile_row = tile.get_row(tile_offset as usize);
// determines if the object should always be placed over the
// previously placed background or window pixels
let obj_over = always_over || !obj.bg_over;
for tile_x in 0..TILE_WIDTH {
let x = obj.x + tile_x as i16;
let is_contained = (x >= 0) && (x < DISPLAY_WIDTH as i16);
// the object is only considered visible if no background or
// window should be drawn over or if the underlying pixel
// is transparent (zero value) meaning there's no background
// or window for the provided pixel
let mut is_visible = obj_over || self.color_buffer[color_offset as usize] == 0;
// additionally (in CCG mode) the object is only considered to
// be visible if the priority buffer is not set for the current
// pixel, this means that the background is capturing priority
// by having the BG-to-OAM priority bit set in the bg map attributes
is_visible &= always_over || !self.priority_buffer[color_offset as usize];
// determines if the current pixel has priority over a possible
// one that has been drawn by a previous object, this happens
// in case the current object has a small X coordinate according
// to the MBR algorithm
let has_priority = index_buffer[x as usize] == -256
|| (obj_priority_mode && obj.x < index_buffer[x as usize]);
let pixel = tile_row[if obj.xflip {
TILE_WIDTH_I - tile_x
} else {
tile_x
}];
if is_visible && has_priority && pixel != 0 {
// marks the current pixel in iteration as "owned"
// by the object with the defined X base position,
// to be used in priority calculus
index_buffer[x as usize] = obj.x;
// obtains the current pixel data from the tile row and
// re-maps it according to the object palette
let color = palette[pixel as usize];
// updates the pixel in the color buffer, which stores
// the raw pixel color information (unmapped)
self.color_buffer[color_offset as usize] = pixel;
// sets the color pixel in the frame buffer
self.frame_buffer[frame_offset as usize] = color[0];
self.frame_buffer[frame_offset as usize + 1] = color[1];
self.frame_buffer[frame_offset as usize + 2] = color[2];
// increment the color offset by one as this represents
// the advance of one color pixel
color_offset += 1;
// increments the offset of the frame buffer by the
// size of an RGB pixel (which is 3 bytes)
frame_offset += RGB_SIZE as i32;
// increments the counter so that we're able to keep
// track on the number of object drawn
draw_count += 1;
/// Runs an update operation on the LCD STAT interrupt meaning
/// that the flag that control will be updated in case the conditions
/// required for the LCD STAT interrupt to be triggered are met.
self.int_stat = self.stat_level();
}
/// Obtains the current level of the LCD STAT interrupt by
/// checking the current PPU state in various sections.
self.stat_lyc && self.lyc == self.ly
|| self.stat_oam && self.mode == PpuMode::OamRead
|| self.stat_vblank && self.mode == PpuMode::VBlank
|| self.stat_hblank && self.mode == PpuMode::HBlank
/// Computes the values for all of the palettes, this method
/// is useful to "flush" color computation whenever the base
/// palette colors are changed.
/// Notice that this is only applicable to the DMG running mode
/// either in the original DMG or in CGB with DMG compatibility.
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
if self.dmg_compat {
Self::compute_palette(
&mut self.palette_bg,
&self.palettes_color_bg[0],
self.palettes[0],
);
Self::compute_palette(
&mut self.palette_obj_0,
&self.palettes_color_obj[0],
self.palettes[1],
);
Self::compute_palette(
&mut self.palette_obj_1,
&self.palettes_color_obj[1],
self.palettes[2],
);
} else {
// re-computes the complete set of palettes according to
// the currently set palette colors (that may have changed)
Self::compute_palette(&mut self.palette_bg, &self.palette_colors, self.palettes[0]);
Self::compute_palette(
&mut self.palette_obj_0,
&self.palette_colors,
self.palettes[1],
);
Self::compute_palette(
&mut self.palette_obj_1,
&self.palette_colors,
self.palettes[2],
);
}
// clears the frame buffer to allow the new background
// color to be used
self.clear_frame_buffer();
}
/// Static method used for the base logic of computation of RGB
/// based palettes from the internal Game Boy color indexes.
/// This method should be called whenever the palette indexes
/// are changed.
fn compute_palette(palette: &mut Palette, palette_colors: &Palette, value: u8) {
for (index, palette_item) in palette.iter_mut().enumerate() {
let color_index: usize = (value as usize >> (index * 2)) & 3;
match color_index {
0..=3 => *palette_item = palette_colors[color_index],
color_index => panic!("Invalid palette color index {:04x}", color_index),
}
}
}
/// Static method that computes an RGB888 color palette ready to
/// be used for frame buffer operations from 4 (colors) x 2 bytes (RGB555)
/// that represent an RGB555 set of colors. This method should be
/// used for CGB mode where colors are represented using RGB555.
fn compute_palette_color(
palette: &mut Palette,
palette_color: &[u8; 64],
palette_index: u8,
color_index: u8,
) {
let palette_offset = (palette_index * 4 * 2) as usize;
let color_offset = (color_index * 2) as usize;
palette[color_index as usize] = Self::rgb555_to_rgb888(
palette_color[palette_offset + color_offset],
palette_color[palette_offset + color_offset + 1],
);
/// Re-computes the complete set of CGB only color palettes using the
/// raw `palettes_color` information and computing the `Palette` structure
/// for both background and objects palettes.
fn compute_palettes_color(
palettes: &mut [&mut [Palette; 8]; 2],
palettes_color: &[[u8; 64]; 2],
) {
for index in 0..2 {
let palette = &mut palettes[index];
let palette_color = &palettes_color[index];
for palette_index in 0..palette.len() {
&mut palette[palette_index],
&palette_color[palette_index * 8..(palette_index + 1) * 8]
.try_into()
.unwrap(),
);
}
}
}
/// Computes an individual structured CGB color palette from 8 raw bytes
/// coming from the raw `palette_color` information, this 8 bytes should
/// represent the 4 colors of the palette in the RGB555 format.
fn compute_color_palette(palette: &mut Palette, palette_color: &[u8; 8]) {
for color_index in 0..palette.len() {
palette[color_index] = Self::rgb555_to_rgb888(
palette_color[color_index * 2],
palette_color[color_index * 2 + 1],
);
}
}
fn rgb555_to_rgb888(first: u8, second: u8) -> Pixel {
let r = (first & 0x1f) << 3;
let g = (((first & 0xe0) >> 5) | ((second & 0x03) << 3)) << 3;
let b = ((second & 0x7c) >> 2) << 3;
fn rgb888_to_rgb1555(first: u8, second: u8, third: u8) -> PixelRgb1555 {
let pixel = Self::rgb888_to_rgb1555_u16(first, second, third);
[pixel as u8, (pixel >> 8) as u8]
}
fn rgb888_to_rgb1555_u16(first: u8, second: u8, third: u8) -> u16 {
let r = (first as u16 >> 3) & 0x1f;
let g = (second as u16 >> 3) & 0x1f;
let b = (third as u16 >> 3) & 0x1f;
(a << 15) | (r << 10) | (g << 5) | b
}
fn rgb888_to_rgb565(first: u8, second: u8, third: u8) -> PixelRgb565 {
let pixel = Self::rgb888_to_rgb565_u16(first, second, third);
[pixel as u8, (pixel >> 8) as u8]
}
fn rgb888_to_rgb565_u16(first: u8, second: u8, third: u8) -> u16 {
let r = (first as u16 >> 3) & 0x1f;
let g = (second as u16 >> 2) & 0x3f;
let b = (third as u16 >> 3) & 0x1f;
(r << 11) | (g << 5) | b
impl Default for Ppu {
fn default() -> Self {
Self::new(
GameBoyMode::Dmg,
Rc::new(RefCell::new(GameBoyConfig::default())),
)
ppu.vram[0x0000] = 0xff;
ppu.vram[0x0001] = 0xff;
let result = ppu.tiles()[0].get(0, 0);
assert_eq!(result, 0);
ppu.update_tile(0x8000, 0x00);
let result = ppu.tiles()[0].get(0, 0);
assert_eq!(result, 3);
}
#[test]
fn test_update_tile_upper() {
ppu.vram[0x1000] = 0xff;
ppu.vram[0x1001] = 0xff;
let result = ppu.tiles()[256].get(0, 0);
assert_eq!(result, 0);