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.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

189 lines
5.5KB

  1. #include <stdio.h>
  2. #include "ppu.h"
  3. // TODO: Retain open bus bits?
  4. #define ppu_reg_ctrl (0x2000U)
  5. #define ppu_reg_mask (0x2001U)
  6. #define ppu_reg_status (0x2002U)
  7. #define oam_reg_addr (0x2003U)
  8. #define oam_reg_data (0x2004U)
  9. #define ppu_reg_scroll (0x2005U)
  10. #define ppu_reg_addr (0x2006U)
  11. #define ppu_reg_data (0x2007U)
  12. #define oam_reg_dma (0x4014U)
  13. uint8_t nes_ppu_read(nes_ppu* ppu, uint16_t addr) {
  14. uint8_t val = 0;
  15. if (ppu_reg_status == addr) {
  16. val = ppu->status;
  17. ppu->latch = 0;
  18. } else if (oam_reg_data == addr) {
  19. val = ppu->oam[ppu->oam_addr];
  20. } else if (ppu_reg_data == addr) {
  21. val = ppu->data;
  22. if (ppu->addr < nes_ppu_mem_vram_start) {
  23. ppu->data = ppu->chr_mem[ppu->addr];
  24. } else if (ppu->addr < nes_ppu_mem_vram_start +
  25. nes_ppu_mem_vram_size) {
  26. ppu->data = ppu->vram[ppu->addr -
  27. nes_ppu_mem_vram_start];
  28. } else if (ppu->addr < nes_ppu_mem_pal_start) {
  29. ppu->data = ppu->chr_mem[ppu->addr];
  30. } else if (ppu->addr < nes_ppu_mem_size) {
  31. uint8_t pal_addr =
  32. (ppu->addr - nes_ppu_mem_pal_start) &
  33. (nes_ppu_mem_pal_size - 1);
  34. ppu->data = ppu->palette[pal_addr];
  35. }
  36. ppu->addr += (ppu->status & ppu_Control_VRAM_Inc) ?
  37. 32 : 1;
  38. }
  39. // fprintf(stdout, "PPU: <-R $%04x %02x\n", addr, val);
  40. return val;
  41. }
  42. void nes_ppu_write(nes_ppu* ppu, uint16_t addr, uint8_t val) {
  43. // fprintf(stdout, "PPU: W-> $%04x %02x\n", addr, val);
  44. if (ppu_reg_ctrl == addr) {
  45. ppu->control = val;
  46. // TODO: Trigger NMI if it's enabled during VBlank?
  47. } else if (oam_reg_addr == addr) {
  48. ppu->oam_addr = val;
  49. } else if (oam_reg_data == addr) {
  50. ppu->oam[ppu->oam_addr++] = val;
  51. } else if (ppu_reg_mask == addr) {
  52. ppu->mask = val;
  53. } else if (ppu_reg_scroll == addr) {
  54. if (ppu->latch) {
  55. ppu->scroll &= 0xFF00U;
  56. ppu->scroll |= val;
  57. } else {
  58. ppu->scroll &= 0x00FFU;
  59. ppu->scroll |= (uint16_t)val << 8;
  60. }
  61. ppu->latch = !ppu->latch;
  62. } else if (ppu_reg_addr == addr) {
  63. if (ppu->latch) {
  64. ppu->addr &= 0x3F00U;
  65. ppu->addr |= val;
  66. // printf("PPU: VRAM ADDR %04x\n", ppu->addr);
  67. } else {
  68. ppu->addr &= 0x00FFU;
  69. ppu->addr |= (uint16_t)val << 8;
  70. }
  71. ppu->latch = !ppu->latch;
  72. } else if (ppu_reg_data == addr) {
  73. if (ppu->addr >= nes_ppu_mem_size) {
  74. printf("!!! PPU: MEM OOB: %04x", ppu->addr);
  75. } else if (ppu->addr >= nes_ppu_mem_pal_start) {
  76. uint8_t pal_addr =
  77. (ppu->addr - nes_ppu_mem_pal_start) &
  78. (nes_ppu_mem_pal_size - 1);
  79. // fprintf(stderr, "PPU PAL %02x < %02x\n", pal_addr, val);
  80. ppu->palette[pal_addr] = val;
  81. } else if (ppu->addr >= nes_ppu_mem_vram_start) {
  82. uint16_t vram_addr = ppu->addr - nes_ppu_mem_vram_start;
  83. if (vram_addr >= nes_ppu_mem_vram_size) {
  84. printf("!!! PPU: VRAM OOB: %04x", vram_addr);
  85. vram_addr &= (nes_ppu_mem_vram_size - 1);
  86. }
  87. ppu->vram[vram_addr] = val;
  88. }
  89. ppu->addr += (ppu->status & ppu_Control_VRAM_Inc) ?
  90. 32 : 1;
  91. }
  92. }
  93. void nes_ppu_reset(nes_ppu* ppu) {
  94. ppu->control = 0;
  95. ppu->mask = 0;
  96. ppu->latch = 0;
  97. ppu->scroll = 0;
  98. ppu->data = 0;
  99. ppu->frame = 0;
  100. ppu->scanline = 0;
  101. ppu->cycle = 0;
  102. }
  103. void nes_ppu_init(nes_ppu* ppu, uint8_t* chr_mem) {
  104. ppu->chr_mem = chr_mem;
  105. ppu->status = 0;
  106. ppu->oam_addr = 0;
  107. ppu->addr = 0;
  108. nes_ppu_reset(ppu);
  109. }
  110. int nes_ppu_run(nes_ppu* ppu, int cycles) {
  111. int vblank = 0;
  112. ppu->cycle += cycles;
  113. if ( 0 != ppu->hit_line &&
  114. ppu->scanline > ppu->hit_line &&
  115. ppu->cycle > ppu->hit_dot) {
  116. ppu->status |= ppu_Status_Hit;
  117. }
  118. while (ppu->cycle >= nes_ppu_dots) {
  119. ppu->cycle -= nes_ppu_dots;
  120. if ( ppu->scanline <= nes_ppu_prerender &&
  121. (ppu->frame & 1)) {
  122. // Prerender line is one dot shorter in odd frames
  123. // Fake it by incrementing the cycle in this case
  124. ppu->cycle++;
  125. }
  126. ppu->scanline++;
  127. if (ppu->scanline >= nes_ppu_prerender +
  128. nes_ppu_height +
  129. nes_ppu_postrender +
  130. nes_ppu_vblank) {
  131. ppu->status &= ~(ppu_Status_VBlank | ppu_Status_Hit);
  132. ppu->hit_line = 0;
  133. ppu->hit_dot = 0;
  134. ppu->scanline = 0;
  135. ppu->frame++;
  136. // TODO: Render callback if vblank was previously set
  137. } else if (ppu->scanline >= nes_ppu_prerender +
  138. nes_ppu_height +
  139. nes_ppu_postrender) {
  140. ppu->status |= ppu_Status_VBlank;
  141. if (ppu->control & ppu_Control_VBlank) {
  142. vblank = 1;
  143. }
  144. }
  145. }
  146. return vblank;
  147. }
  148. int nes_ppu_cycles_til_vblank(nes_ppu* ppu) {
  149. int cycles_til_vblank = nes_ppu_active_cycles - (
  150. ppu->cycle + (ppu->scanline * nes_ppu_dots));
  151. return (cycles_til_vblank > 0 ? cycles_til_vblank :
  152. nes_ppu_vblank_cycles + cycles_til_vblank);
  153. }