NESe (pronounced "Nessie") is a NES emulator based on the e6502 emulator, also written in C with a focus on speed and portability for use on embedded platforms, especially ARM.
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

297 行
9.0KB

  1. #include <stdio.h>
  2. #include "ppu.h"
  3. #include "mapper.h"
  4. #include "cart.h"
  5. // TODO: Retain open bus bits?
  6. #define ppu_reg_ctrl (0x2000U)
  7. #define ppu_reg_mask (0x2001U)
  8. #define ppu_reg_status (0x2002U)
  9. #define oam_reg_addr (0x2003U)
  10. #define oam_reg_data (0x2004U)
  11. #define ppu_reg_scroll (0x2005U)
  12. #define ppu_reg_addr (0x2006U)
  13. #define ppu_reg_data (0x2007U)
  14. #define oam_reg_dma (0x4014U)
  15. uint8_t nes_ppu_read(nes_ppu* ppu, uint16_t addr) {
  16. uint8_t val = 0;
  17. if (ppu_reg_status == addr) {
  18. val = ppu->status;
  19. ppu->latch = 0;
  20. } else if (oam_reg_data == addr) {
  21. OAM_LOG("PPU: OAM READ %02x > %02x\n", ppu->oam_addr, val);
  22. val = ((uint8_t*)ppu->oam)[ppu->oam_addr];
  23. } else if (ppu_reg_data == addr) {
  24. if (ppu->addr >= nes_ppu_mem_pal_start) {
  25. PPU_LOG("PPU: PAL READ %04x > %02x\n", ppu->addr, val);
  26. uint8_t pal_addr =
  27. (ppu->addr - nes_ppu_mem_pal_start) &
  28. (nes_ppu_mem_pal_size - 1);
  29. val = ppu->palette[pal_addr];
  30. } else {
  31. val = ppu->data;
  32. if (ppu->addr < nes_ppu_mem_vram_start) {
  33. PPU_LOG("PPU: CHR MEM READ %04x > %02x\n", ppu->addr, val);
  34. ppu->data = nes_chr_read(ppu->mapper, ppu->addr);
  35. } else if (ppu->addr < nes_ppu_mem_vram_start +
  36. nes_ppu_mem_vram_size) {
  37. VRAM_LOG("PPU: VRAM READ %04x > %02x\n", ppu->addr, val);
  38. ppu->data = nes_vram_read(
  39. ppu->mapper,
  40. ppu->addr - nes_ppu_mem_vram_start
  41. );
  42. } else if (ppu->addr < nes_ppu_mem_pal_start) {
  43. PPU_LOG("PPU: BLANK READ %04x > %02x\n", ppu->addr, val);
  44. /*
  45. ppu->data = *(nes_map_chr_addr(ppu->mapper,
  46. ppu->addr));
  47. */
  48. }
  49. }
  50. ppu->addr += (ppu->control & ppu_Control_VRAM_Inc) ?
  51. 32 : 1;
  52. }
  53. PPU_LOG("PPU: <-R $%04x %02x\n", addr, val);
  54. return val;
  55. }
  56. // Copy t to v (decoded into scroll_x, control)
  57. static inline void nes_ppu_internal_copy_x(nes_ppu* ppu) {
  58. ppu->control &= ~(1U);
  59. ppu->control |= ((ppu->t >> 10) & 1U);
  60. ppu->scroll_x = ((ppu->t & 0b11111U) << 3) |
  61. (ppu->x & 0b111U);
  62. }
  63. // Copy t to v (decoded into scroll_y, control)
  64. static inline void nes_ppu_internal_copy_y(nes_ppu* ppu) {
  65. ppu->control &= ~(0b10U);
  66. ppu->control |= ((ppu->t >> 10) & 0b10U);
  67. ppu->scroll_y = ((ppu->t & 0x03E0U) >> 2) |
  68. ((ppu->t & 0x7000U) >> 12);
  69. }
  70. void nes_ppu_write(nes_ppu* ppu, uint16_t addr, uint8_t val) {
  71. PPU_LOG("PPU: W-> $%04x %02x\n", addr, val);
  72. if (ppu_reg_ctrl == addr) {
  73. PPU_LOG("PPU: CTRL %02x\n", val);
  74. ppu->control &= ppu_Control_Nametable_Mask;
  75. ppu->control |= (val & ~ppu_Control_Nametable_Mask);
  76. ppu->t &= ~(0xC00U);
  77. ppu->t |= (uint16_t)(val & ppu_Control_Nametable_Mask) << 10;
  78. } else if (oam_reg_addr == addr) {
  79. OAM_LOG("PPU: OAM ADDR %02x\n", val);
  80. ppu->oam_addr = val;
  81. } else if (oam_reg_data == addr) {
  82. OAM_LOG("PPU: OAM %02x < %02x\n", ppu->oam_addr, val);
  83. ((uint8_t*)ppu->oam)[ppu->oam_addr++] = val;
  84. } else if (ppu_reg_mask == addr) {
  85. PPU_LOG("PPU: Mask %02x\n", val);
  86. ppu->mask = val;
  87. } else if (ppu_reg_scroll == addr) {
  88. if (ppu->latch) {
  89. PPU_LOG("PPU: Scroll Y %02x\n", val);
  90. ppu->t &= 0b0000110000011111U;
  91. ppu->t |= (uint16_t)(val & 0b00000111U) << 12;
  92. ppu->t |= (uint16_t)(val & 0b11111000U) << 2;
  93. } else {
  94. PPU_LOG("PPU: Scroll X %02x\n", val);
  95. ppu->t &= ~(0b11111U);
  96. ppu->t |= (val & 0b11111000U) >> 3;
  97. ppu->x = (val & 0b111U);
  98. }
  99. ppu->latch = !ppu->latch;
  100. } else if (ppu_reg_addr == addr) {
  101. VRAM_LOG("PPU: ADDR %02x\n", val);
  102. if (ppu->latch) {
  103. ppu->t &= 0xFF00U;
  104. ppu->t |= val;
  105. ppu->addr = (ppu->t & 0x3FFFU);
  106. VRAM_LOG("PPU: VRAM ADDR %04x\n", ppu->addr);
  107. nes_ppu_internal_copy_x(ppu);
  108. nes_ppu_internal_copy_y(ppu);
  109. PPU_LOG("PPU: Scroll N, X, Y = %d, %d, %d\n",
  110. ppu->control & ppu_Control_Nametable_Mask,
  111. ppu->scroll_x, ppu->scroll_y);
  112. } else {
  113. ppu->t &= 0x00FFU;
  114. ppu->t |= (uint16_t)(0x3FU & val) << 8;
  115. }
  116. ppu->latch = !ppu->latch;
  117. } else if (ppu_reg_data == addr) {
  118. if (ppu->addr >= nes_ppu_mem_size) {
  119. printf("!!! PPU: MEM OOB: %04x\n", ppu->addr);
  120. } else if (ppu->addr >= nes_ppu_mem_pal_start) {
  121. uint8_t pal_addr =
  122. (ppu->addr - nes_ppu_mem_pal_start) &
  123. (nes_ppu_mem_pal_size - 1);
  124. PPU_LOG("PPU: PAL %02x < %02x\n", pal_addr, val);
  125. ppu->palette[pal_addr] = val;
  126. if ((pal_addr & 0b11) == 0) {
  127. ppu->palette[pal_addr & 0xF] = val;
  128. }
  129. } else if (ppu->addr >= nes_ppu_mem_vram_start) {
  130. uint16_t vram_addr = ppu->addr -
  131. nes_ppu_mem_vram_start;
  132. if (vram_addr >= nes_ppu_mem_vram_size) {
  133. printf("!!! PPU: VRAM OOB: %04x\n", vram_addr);
  134. vram_addr &= (nes_ppu_mem_vram_size - 1);
  135. }
  136. #ifdef DEBUG_VRAM
  137. {
  138. int page = vram_addr >> 10;
  139. int loc = vram_addr & 0x3FFU;
  140. if (loc < 960) {
  141. int y = loc / 32;
  142. int x = loc % 32;
  143. printf("PPU: VRAM Page %d @ %2d,%2d : %02x\n",
  144. page, y, x, val);
  145. } else {
  146. printf("PPU: VRAM Attr %2d : %02x\n",
  147. loc - 960, val);
  148. }
  149. }
  150. #endif // DEBUG_VRAM
  151. // printf("PPU: VRAM %04x < %02x\n", vram_addr, val);
  152. nes_vram_write(ppu->mapper, vram_addr, val);
  153. } else {
  154. // PPU_LOG("PPU: CHR MEM WRITE %04x > %02x\n", ppu->addr, val);
  155. nes_chr_write(ppu->mapper, ppu->addr, val);
  156. }
  157. ppu->addr += (ppu->control & ppu_Control_VRAM_Inc) ?
  158. 32 : 1;
  159. }
  160. }
  161. void nes_ppu_reset(nes_ppu* ppu) {
  162. ppu->control = 0;
  163. ppu->mask = 0;
  164. ppu->latch = 0;
  165. ppu->scroll_x = 0;
  166. ppu->scroll_y = 0;
  167. ppu->data = 0;
  168. ppu->frame = 0;
  169. ppu->scanline = 0;
  170. ppu->cycle = 0;
  171. }
  172. int nes_ppu_init(nes_ppu* ppu, const nes_cart* cart) {
  173. ppu->mapper = cart->mapper;
  174. ppu->status = 0;
  175. ppu->oam_addr = 0;
  176. ppu->addr = 0;
  177. ppu->t = 0;
  178. nes_ppu_reset(ppu);
  179. return 0;
  180. }
  181. int nes_ppu_run(nes_ppu* ppu, int cycles) {
  182. nes_ppu_Result result = ppu_Result_Running;
  183. int next_cycle = ppu->cycle + cycles;
  184. if ( ppu->scanline < nes_ppu_render &&
  185. ppu->cycle < 257 && next_cycle >= 257) {
  186. nes_ppu_internal_copy_x(ppu);
  187. if (ppu->scanline == 0) {
  188. nes_ppu_internal_copy_y(ppu);
  189. }
  190. }
  191. if ( NULL != ppu->mapper->scanline &&
  192. ppu->scanline < nes_ppu_render &&
  193. (ppu->mask & (ppu_Mask_Back | ppu_Mask_Sprite)) &&
  194. ppu->cycle < 260 && next_cycle >= 260) {
  195. ppu->mapper->scanline(ppu->mapper);
  196. }
  197. ppu->cycle = next_cycle;
  198. while (ppu->cycle >= nes_ppu_dots) {
  199. ppu->cycle -= nes_ppu_dots;
  200. if ( ppu->scanline < nes_ppu_prerender &&
  201. (ppu->frame & 1)) {
  202. // Prerender line is one dot shorter in odd frames
  203. // Fake it by incrementing the cycle in this case
  204. // TODO: Only if actually rendering
  205. ppu->cycle++;
  206. }
  207. ppu->scanline++;
  208. if (ppu->scanline >= nes_ppu_frame) {
  209. ppu->status &= ~(ppu_Status_VBlank | ppu_Status_Hit);
  210. ppu->hit_line = 0;
  211. ppu->hit_dot = 0;
  212. ppu->scanline = 0;
  213. ppu->frame++;
  214. result = ppu_Result_VBlank_Off;
  215. } else if (ppu->scanline >= nes_ppu_active) {
  216. ppu->status |= ppu_Status_VBlank;
  217. if (ppu->control & ppu_Control_VBlank) {
  218. result = ppu_Result_VBlank_On;
  219. }
  220. } else {
  221. if ( ppu->scanline > nes_ppu_prerender &&
  222. ppu->scanline < nes_ppu_render) {
  223. ppu->scroll_y++;
  224. if (ppu->scroll_y >= nes_ppu_render_h) {
  225. ppu->scroll_y -= nes_ppu_render_h;
  226. ppu->control ^= 0b10;
  227. }
  228. }
  229. result = ppu_Result_Ready;
  230. }
  231. }
  232. if ( ppu->hit_line > 0 &&
  233. ppu->scanline > ppu->hit_line &&
  234. ppu->cycle >= ppu->hit_dot) {
  235. if (!(ppu->status & ppu_Status_Hit)) {
  236. PPU_LOG("PPU: Hit @ %d, %d\n", ppu->hit_line + 1, ppu->hit_dot);
  237. PPU_LOG("(Currently @ %d, %d)\n", ppu->scanline, ppu->cycle);
  238. }
  239. ppu->status |= ppu_Status_Hit;
  240. }
  241. return result;
  242. }