yaze 0.2.0
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
ppu.cc
Go to the documentation of this file.
1#include "app/emu/video/ppu.h"
2
3#include <cstdint>
4#include <iostream>
5#include <vector>
6
8
9namespace yaze {
10namespace app {
11namespace emu {
12namespace video {
13
14using namespace PpuRegisters;
15
16// array for layer definitions per mode:
17// 0-7: mode 0-7; 8: mode 1 + l3prio; 9: mode 7 + extbg
18
19// 0-3; layers 1-4; 4: sprites; 5: nonexistent
20static const int kLayersPerMode[10][12] = {
21 {4, 0, 1, 4, 0, 1, 4, 2, 3, 4, 2, 3}, {4, 0, 1, 4, 0, 1, 4, 2, 4, 2, 5, 5},
22 {4, 0, 4, 1, 4, 0, 4, 1, 5, 5, 5, 5}, {4, 0, 4, 1, 4, 0, 4, 1, 5, 5, 5, 5},
23 {4, 0, 4, 1, 4, 0, 4, 1, 5, 5, 5, 5}, {4, 0, 4, 1, 4, 0, 4, 1, 5, 5, 5, 5},
24 {4, 0, 4, 4, 0, 4, 5, 5, 5, 5, 5, 5}, {4, 4, 4, 0, 4, 5, 5, 5, 5, 5, 5, 5},
25 {2, 4, 0, 1, 4, 0, 1, 4, 4, 2, 5, 5}, {4, 4, 1, 4, 0, 4, 1, 5, 5, 5, 5, 5}};
26
27static const int kPrioritysPerMode[10][12] = {
28 {3, 1, 1, 2, 0, 0, 1, 1, 1, 0, 0, 0}, {3, 1, 1, 2, 0, 0, 1, 1, 0, 0, 5, 5},
29 {3, 1, 2, 1, 1, 0, 0, 0, 5, 5, 5, 5}, {3, 1, 2, 1, 1, 0, 0, 0, 5, 5, 5, 5},
30 {3, 1, 2, 1, 1, 0, 0, 0, 5, 5, 5, 5}, {3, 1, 2, 1, 1, 0, 0, 0, 5, 5, 5, 5},
31 {3, 1, 2, 1, 0, 0, 5, 5, 5, 5, 5, 5}, {3, 2, 1, 0, 0, 5, 5, 5, 5, 5, 5, 5},
32 {1, 3, 1, 1, 2, 0, 0, 1, 0, 0, 5, 5}, {3, 2, 1, 1, 0, 0, 0, 5, 5, 5, 5, 5}};
33
34static const int kLayerCountPerMode[10] = {12, 10, 8, 8, 8, 8, 6, 5, 10, 7};
35
36static const int kBitDepthsPerMode[10][4] = {
37 {2, 2, 2, 2}, {4, 4, 2, 5}, {4, 4, 5, 5}, {8, 4, 5, 5}, {8, 2, 5, 5},
38 {4, 2, 5, 5}, {4, 5, 5, 5}, {8, 5, 5, 5}, {4, 4, 2, 5}, {8, 7, 5, 5}};
39
40static const int kSpriteSizes[8][2] = {{8, 16}, {8, 32}, {8, 64}, {16, 32},
41 {16, 64}, {32, 64}, {16, 32}, {16, 32}};
42
43void Ppu::Update() {}
44
45void Ppu::Reset() {
46 memset(vram, 0, sizeof(vram));
47 vram_pointer = 0;
52 memset(cgram, 0, sizeof(cgram));
54 cgram_second_write_ = false;
55 cgram_buffer_ = 0;
56 memset(oam, 0, sizeof(oam));
57 memset(high_oam_, 0, sizeof(high_oam_));
58 oam_adr_ = 0;
60 oam_in_high_ = false;
62 oam_second_write_ = false;
63 oam_buffer_ = 0;
64 obj_priority_ = false;
67 obj_size_ = 0;
68 obj_pixel_buffer_.fill(0);
70 time_over_ = false;
71 range_over_ = false;
72 obj_interlace_ = false;
73 for (int i = 0; i < 4; i++) {
74 bg_layer_[i].hScroll = 0;
75 bg_layer_[i].vScroll = 0;
76 bg_layer_[i].tilemapWider = false;
77 bg_layer_[i].tilemapHigher = false;
78 bg_layer_[i].tilemapAdr = 0;
79 bg_layer_[i].tileAdr = 0;
80 bg_layer_[i].bigTiles = false;
81 bg_layer_[i].mosaicEnabled = false;
82 }
83 scroll_prev_ = 0;
84 scroll_prev2_ = 0;
85 mosaic_size_ = 1;
87 for (int i = 0; i < 5; i++) {
88 layer_[i].mainScreenEnabled = false;
89 layer_[i].subScreenEnabled = false;
90 layer_[i].mainScreenWindowed = false;
91 layer_[i].subScreenWindowed = false;
92 }
93 memset(m7matrix, 0, sizeof(m7matrix));
94 m7prev = 0;
95 m7largeField = false;
96 m7charFill = false;
97 m7xFlip = false;
98 m7yFlip = false;
99 m7extBg = false;
100 m7startX = 0;
101 m7startY = 0;
102 for (int i = 0; i < 6; i++) {
103 windowLayer[i].window1enabled = false;
104 windowLayer[i].window2enabled = false;
105 windowLayer[i].window1inversed = false;
106 windowLayer[i].window2inversed = false;
107 windowLayer[i].maskLogic = 0;
108 }
109 window1left = 0;
110 window1right = 0;
111 window2left = 0;
112 window2right = 0;
113 clip_mode_ = 0;
115 add_subscreen_ = false;
116 subtract_color_ = false;
117 half_color_ = false;
118 memset(math_enabled_array_, 0, sizeof(math_enabled_array_));
119 fixed_color_r_ = 0;
120 fixed_color_g_ = 0;
121 fixed_color_b_ = 0;
122 forced_blank_ = true;
123 brightness = 0;
124 mode = 0;
125 bg3priority = false;
126 even_frame = false;
127 pseudo_hires_ = false;
128 overscan_ = false;
129 frame_overscan_ = false;
130 interlace = false;
131 frame_interlace = false;
132 direct_color_ = false;
133 h_count_ = 0;
134 v_count_ = 0;
135 h_count_second_ = false;
136 v_count_second_ = false;
137 counters_latched_ = false;
138 ppu1_open_bus_ = 0;
139 ppu2_open_bus_ = 0;
140 memset(pixelBuffer, 0, sizeof(pixelBuffer));
141}
142
144 // called at (0, 0)
146 range_over_ = false;
147 time_over_ = false;
149}
150
151void Ppu::RunLine(int line) {
152 // called for lines 1-224/239
153 // evaluate sprites
154 obj_pixel_buffer_.fill(0);
155 if (!forced_blank_) EvaluateSprites(line - 1);
156 // actual line
157 if (mode == 7) CalculateMode7Starts(line);
158 for (int x = 0; x < 256; x++) {
159 HandlePixel(x, line);
160 }
161}
162
163void Ppu::HandlePixel(int x, int y) {
164 int r = 0, r2 = 0;
165 int g = 0, g2 = 0;
166 int b = 0, b2 = 0;
167 if (!forced_blank_) {
168 int mainLayer = GetPixel(x, y, false, &r, &g, &b);
169 bool colorWindowState = GetWindowState(5, x);
170 if (clip_mode_ == 3 || (clip_mode_ == 2 && colorWindowState) ||
171 (clip_mode_ == 1 && !colorWindowState)) {
172 r = 0;
173 g = 0;
174 b = 0;
175 }
176 int secondLayer = 5; // backdrop
177 bool mathEnabled = mainLayer < 6 && math_enabled_array_[mainLayer] &&
178 !(prevent_math_mode_ == 3 ||
179 (prevent_math_mode_ == 2 && colorWindowState) ||
180 (prevent_math_mode_ == 1 && !colorWindowState));
181 if ((mathEnabled && add_subscreen_) || pseudo_hires_ || mode == 5 ||
182 mode == 6) {
183 secondLayer = GetPixel(x, y, true, &r2, &g2, &b2);
184 }
185 // TODO: subscreen pixels can be clipped to black as well
186 // TODO: math for subscreen pixels (add/sub sub to main)
187 if (mathEnabled) {
188 if (subtract_color_) {
189 r -= (add_subscreen_ && secondLayer != 5) ? r2 : fixed_color_r_;
190 g -= (add_subscreen_ && secondLayer != 5) ? g2 : fixed_color_g_;
191 b -= (add_subscreen_ && secondLayer != 5) ? b2 : fixed_color_b_;
192 } else {
193 r += (add_subscreen_ && secondLayer != 5) ? r2 : fixed_color_r_;
194 g += (add_subscreen_ && secondLayer != 5) ? g2 : fixed_color_g_;
195 b += (add_subscreen_ && secondLayer != 5) ? b2 : fixed_color_b_;
196 }
197 if (half_color_ && (secondLayer != 5 || !add_subscreen_)) {
198 r >>= 1;
199 g >>= 1;
200 b >>= 1;
201 }
202 if (r > 31) r = 31;
203 if (g > 31) g = 31;
204 if (b > 31) b = 31;
205 if (r < 0) r = 0;
206 if (g < 0) g = 0;
207 if (b < 0) b = 0;
208 }
209 if (!(pseudo_hires_ || mode == 5 || mode == 6)) {
210 r2 = r;
211 g2 = g;
212 b2 = b;
213 }
214 }
215 int row = (y - 1) + (even_frame ? 0 : 239);
216 pixelBuffer[row * 2048 + x * 8 + 0 + pixelOutputFormat] =
217 ((b2 << 3) | (b2 >> 2)) * brightness / 15;
218 pixelBuffer[row * 2048 + x * 8 + 1 + pixelOutputFormat] =
219 ((g2 << 3) | (g2 >> 2)) * brightness / 15;
220 pixelBuffer[row * 2048 + x * 8 + 2 + pixelOutputFormat] =
221 ((r2 << 3) | (r2 >> 2)) * brightness / 15;
222 pixelBuffer[row * 2048 + x * 8 + 4 + pixelOutputFormat] =
223 ((b << 3) | (b >> 2)) * brightness / 15;
224 pixelBuffer[row * 2048 + x * 8 + 5 + pixelOutputFormat] =
225 ((g << 3) | (g >> 2)) * brightness / 15;
226 pixelBuffer[row * 2048 + x * 8 + 6 + pixelOutputFormat] =
227 ((r << 3) | (r >> 2)) * brightness / 15;
228}
229
230int Ppu::GetPixel(int x, int y, bool subscreen, int* r, int* g, int* b) {
231 // figure out which color is on this location on main- or subscreen, sets it
232 // in r, g, b
233 // returns which layer it is: 0-3 for bg layer, 4 or 6 for sprites (depending
234 // on palette), 5 for backdrop
235 int actMode = mode == 1 && bg3priority ? 8 : mode;
236 actMode = mode == 7 && m7extBg ? 9 : actMode;
237 int layer = 5;
238 int pixel = 0;
239 for (int i = 0; i < kLayerCountPerMode[actMode]; i++) {
240 int curLayer = kLayersPerMode[actMode][i];
241 int curPriority = kPrioritysPerMode[actMode][i];
242 bool layerActive = false;
243 if (!subscreen) {
244 layerActive = layer_[curLayer].mainScreenEnabled &&
245 (!layer_[curLayer].mainScreenWindowed ||
246 !GetWindowState(curLayer, x));
247 } else {
248 layerActive =
249 layer_[curLayer].subScreenEnabled &&
250 (!layer_[curLayer].subScreenWindowed || !GetWindowState(curLayer, x));
251 }
252 if (layerActive) {
253 if (curLayer < 4) {
254 // bg layer
255 int lx = x;
256 int ly = y;
257 if (bg_layer_[curLayer].mosaicEnabled && mosaic_size_ > 1) {
258 lx -= lx % mosaic_size_;
259 ly -= (ly - mosaic_startline_) % mosaic_size_;
260 }
261 if (mode == 7) {
262 pixel = GetPixelForMode7(lx, curLayer, curPriority);
263 } else {
264 lx += bg_layer_[curLayer].hScroll;
265 if (mode == 5 || mode == 6) {
266 lx *= 2;
267 lx += (subscreen || bg_layer_[curLayer].mosaicEnabled) ? 0 : 1;
268 if (interlace) {
269 ly *= 2;
270 ly += (even_frame || bg_layer_[curLayer].mosaicEnabled) ? 0 : 1;
271 }
272 }
273 ly += bg_layer_[curLayer].vScroll;
274 if (mode == 2 || mode == 4 || mode == 6) {
275 HandleOPT(curLayer, &lx, &ly);
276 }
277 pixel =
278 GetPixelForBgLayer(lx & 0x3ff, ly & 0x3ff, curLayer, curPriority);
279 }
280 } else {
281 // get a pixel from the sprite buffer
282 pixel = 0;
283 if (obj_priority_buffer_[x] == curPriority)
284 pixel = obj_pixel_buffer_[x];
285 }
286 }
287 if (pixel > 0) {
288 layer = curLayer;
289 break;
290 }
291 }
292 if (direct_color_ && layer < 4 && kBitDepthsPerMode[actMode][layer] == 8) {
293 *r = ((pixel & 0x7) << 2) | ((pixel & 0x100) >> 7);
294 *g = ((pixel & 0x38) >> 1) | ((pixel & 0x200) >> 8);
295 *b = ((pixel & 0xc0) >> 3) | ((pixel & 0x400) >> 8);
296 } else {
297 uint16_t color = cgram[pixel & 0xff];
298 *r = color & 0x1f;
299 *g = (color >> 5) & 0x1f;
300 *b = (color >> 10) & 0x1f;
301 }
302 if (layer == 4 && pixel < 0xc0)
303 layer = 6; // sprites with palette color < 0xc0
304 return layer;
305}
306
307int Ppu::GetPixelForMode7(int x, int layer, bool priority) {
308 uint8_t rx = m7xFlip ? 255 - x : x;
309 int xPos = (m7startX + m7matrix[0] * rx) >> 8;
310 int yPos = (m7startY + m7matrix[2] * rx) >> 8;
311 bool outsideMap = xPos < 0 || xPos >= 1024 || yPos < 0 || yPos >= 1024;
312 xPos &= 0x3ff;
313 yPos &= 0x3ff;
314 if (!m7largeField) outsideMap = false;
315 uint8_t tile = outsideMap ? 0 : vram[(yPos >> 3) * 128 + (xPos >> 3)] & 0xff;
316 uint8_t pixel = outsideMap && !m7charFill
317 ? 0
318 : vram[tile * 64 + (yPos & 7) * 8 + (xPos & 7)] >> 8;
319 if (layer == 1) {
320 if (((bool)(pixel & 0x80)) != priority) return 0;
321 return pixel & 0x7f;
322 }
323 return pixel;
324}
325
326bool Ppu::GetWindowState(int layer, int x) {
327 if (!windowLayer[layer].window1enabled &&
328 !windowLayer[layer].window2enabled) {
329 return false;
330 }
331 if (windowLayer[layer].window1enabled && !windowLayer[layer].window2enabled) {
332 bool test = x >= window1left && x <= window1right;
333 return windowLayer[layer].window1inversed ? !test : test;
334 }
335 if (!windowLayer[layer].window1enabled && windowLayer[layer].window2enabled) {
336 bool test = x >= window2left && x <= window2right;
337 return windowLayer[layer].window2inversed ? !test : test;
338 }
339 bool test1 = x >= window1left && x <= window1right;
340 bool test2 = x >= window2left && x <= window2right;
341 if (windowLayer[layer].window1inversed) test1 = !test1;
342 if (windowLayer[layer].window2inversed) test2 = !test2;
343 switch (windowLayer[layer].maskLogic) {
344 case 0:
345 return test1 || test2;
346 case 1:
347 return test1 && test2;
348 case 2:
349 return test1 != test2;
350 case 3:
351 return test1 == test2;
352 }
353 return false;
354}
355
356void Ppu::HandleOPT(int layer, int* lx, int* ly) {
357 int x = *lx;
358 int y = *ly;
359 int column = 0;
360 if (mode == 6) {
361 column = ((x - (x & 0xf)) - ((bg_layer_[layer].hScroll * 2) & 0xfff0)) >> 4;
362 } else {
363 column = ((x - (x & 0x7)) - (bg_layer_[layer].hScroll & 0xfff8)) >> 3;
364 }
365 if (column > 0) {
366 // fetch offset values from layer 3 tilemap
367 int valid = layer == 0 ? 0x2000 : 0x4000;
368 uint16_t hOffset = GetOffsetValue(column - 1, 0);
369 uint16_t vOffset = 0;
370 if (mode == 4) {
371 if (hOffset & 0x8000) {
372 vOffset = hOffset;
373 hOffset = 0;
374 }
375 } else {
376 vOffset = GetOffsetValue(column - 1, 1);
377 }
378 if (mode == 6) {
379 // TODO: not sure if correct
380 if (hOffset & valid)
381 *lx = (((hOffset & 0x3f8) + (column * 8)) * 2) | (x & 0xf);
382 } else {
383 if (hOffset & valid) *lx = ((hOffset & 0x3f8) + (column * 8)) | (x & 0x7);
384 }
385 // TODO: not sure if correct for interlace
386 if (vOffset & valid)
387 *ly = (vOffset & 0x3ff) + (y - bg_layer_[layer].vScroll);
388 }
389}
390
391uint16_t Ppu::GetOffsetValue(int col, int row) {
392 int x = col * 8 + bg_layer_[2].hScroll;
393 int y = row * 8 + bg_layer_[2].vScroll;
394 int tileBits = bg_layer_[2].bigTiles ? 4 : 3;
395 int tileHighBit = bg_layer_[2].bigTiles ? 0x200 : 0x100;
396 uint16_t tilemapAdr =
398 (((y >> tileBits) & 0x1f) << 5 | ((x >> tileBits) & 0x1f));
399 if ((x & tileHighBit) && bg_layer_[2].tilemapWider) tilemapAdr += 0x400;
400 if ((y & tileHighBit) && bg_layer_[2].tilemapHigher)
401 tilemapAdr += bg_layer_[2].tilemapWider ? 0x800 : 0x400;
402 return vram[tilemapAdr & 0x7fff];
403}
404
405int Ppu::GetPixelForBgLayer(int x, int y, int layer, bool priority) {
406 // figure out address of tilemap word and read it
407 bool wideTiles = bg_layer_[layer].bigTiles || mode == 5 || mode == 6;
408 int tileBitsX = wideTiles ? 4 : 3;
409 int tileHighBitX = wideTiles ? 0x200 : 0x100;
410 int tileBitsY = bg_layer_[layer].bigTiles ? 4 : 3;
411 int tileHighBitY = bg_layer_[layer].bigTiles ? 0x200 : 0x100;
412 uint16_t tilemapAdr =
413 bg_layer_[layer].tilemapAdr +
414 (((y >> tileBitsY) & 0x1f) << 5 | ((x >> tileBitsX) & 0x1f));
415 if ((x & tileHighBitX) && bg_layer_[layer].tilemapWider) tilemapAdr += 0x400;
416 if ((y & tileHighBitY) && bg_layer_[layer].tilemapHigher)
417 tilemapAdr += bg_layer_[layer].tilemapWider ? 0x800 : 0x400;
418 uint16_t tile = vram[tilemapAdr & 0x7fff];
419 // check priority, get palette
420 if (((bool)(tile & 0x2000)) != priority) return 0; // wrong priority
421 int paletteNum = (tile & 0x1c00) >> 10;
422 // figure out position within tile
423 int row = (tile & 0x8000) ? 7 - (y & 0x7) : (y & 0x7);
424 int col = (tile & 0x4000) ? (x & 0x7) : 7 - (x & 0x7);
425 int tileNum = tile & 0x3ff;
426 if (wideTiles) {
427 // if unflipped right half of tile, or flipped left half of tile
428 if (((bool)(x & 8)) ^ ((bool)(tile & 0x4000))) tileNum += 1;
429 }
430 if (bg_layer_[layer].bigTiles) {
431 // if unflipped bottom half of tile, or flipped upper half of tile
432 if (((bool)(y & 8)) ^ ((bool)(tile & 0x8000))) tileNum += 0x10;
433 }
434 // read tiledata, ajust palette for mode 0
435 int bitDepth = kBitDepthsPerMode[mode][layer];
436 if (mode == 0) paletteNum += 8 * layer;
437 // plane 1 (always)
438 int paletteSize = 4;
439 uint16_t plane1 = vram[(bg_layer_[layer].tileAdr +
440 ((tileNum & 0x3ff) * 4 * bitDepth) + row) &
441 0x7fff];
442 int pixel = (plane1 >> col) & 1;
443 pixel |= ((plane1 >> (8 + col)) & 1) << 1;
444 // plane 2 (for 4bpp, 8bpp)
445 if (bitDepth > 2) {
446 paletteSize = 16;
447 uint16_t plane2 = vram[(bg_layer_[layer].tileAdr +
448 ((tileNum & 0x3ff) * 4 * bitDepth) + 8 + row) &
449 0x7fff];
450 pixel |= ((plane2 >> col) & 1) << 2;
451 pixel |= ((plane2 >> (8 + col)) & 1) << 3;
452 }
453 // plane 3 & 4 (for 8bpp)
454 if (bitDepth > 4) {
455 paletteSize = 256;
456 uint16_t plane3 = vram[(bg_layer_[layer].tileAdr +
457 ((tileNum & 0x3ff) * 4 * bitDepth) + 16 + row) &
458 0x7fff];
459 pixel |= ((plane3 >> col) & 1) << 4;
460 pixel |= ((plane3 >> (8 + col)) & 1) << 5;
461 uint16_t plane4 = vram[(bg_layer_[layer].tileAdr +
462 ((tileNum & 0x3ff) * 4 * bitDepth) + 24 + row) &
463 0x7fff];
464 pixel |= ((plane4 >> col) & 1) << 6;
465 pixel |= ((plane4 >> (8 + col)) & 1) << 7;
466 }
467 // return cgram index, or 0 if transparent, palette number in bits 10-8 for
468 // 8-color layers
469 return pixel == 0 ? 0 : paletteSize * paletteNum + pixel;
470}
471
472void Ppu::EvaluateSprites(int line) {
473 // TODO: rectangular sprites, wierdness with sprites at -256
474 uint8_t index = obj_priority_ ? (oam_adr_ & 0xfe) : 0;
475 int spritesFound = 0;
476 int tilesFound = 0;
477 uint8_t foundSprites[32] = {};
478 // iterate over oam to find sprites in range
479 for (int i = 0; i < 128; i++) {
480 uint8_t y = oam[index] >> 8;
481 // check if the sprite is on this line and get the sprite size
482 uint8_t row = line - y;
483 int spriteSize =
484 kSpriteSizes[obj_size_]
485 [(high_oam_[index >> 3] >> ((index & 7) + 1)) & 1];
486 int spriteHeight = obj_interlace_ ? spriteSize / 2 : spriteSize;
487 if (row < spriteHeight) {
488 // in y-range, get the x location, using the high bit as well
489 int x = oam[index] & 0xff;
490 x |= ((high_oam_[index >> 3] >> (index & 7)) & 1) << 8;
491 if (x > 255) x -= 512;
492 // if in x-range, record
493 if (x > -spriteSize) {
494 // break if we found 32 sprites already
495 spritesFound++;
496 if (spritesFound > 32) {
497 range_over_ = true;
498 spritesFound = 32;
499 break;
500 }
501 foundSprites[spritesFound - 1] = index;
502 }
503 }
504 index += 2;
505 }
506 // iterate over found sprites backwards to fetch max 34 tile slivers
507 for (int i = spritesFound; i > 0; i--) {
508 index = foundSprites[i - 1];
509 uint8_t y = oam[index] >> 8;
510 uint8_t row = line - y;
511 int spriteSize =
512 kSpriteSizes[obj_size_]
513 [(high_oam_[index >> 3] >> ((index & 7) + 1)) & 1];
514 int x = oam[index] & 0xff;
515 x |= ((high_oam_[index >> 3] >> (index & 7)) & 1) << 8;
516 if (x > 255) x -= 512;
517 if (x > -spriteSize) {
518 // update row according to obj-interlace
519 if (obj_interlace_) row = row * 2 + (even_frame ? 0 : 1);
520 // get some data for the sprite and y-flip row if needed
521 int tile = oam[index + 1] & 0xff;
522 int palette = (oam[index + 1] & 0xe00) >> 9;
523 bool hFlipped = oam[index + 1] & 0x4000;
524 if (oam[index + 1] & 0x8000) row = spriteSize - 1 - row;
525 // fetch all tiles in x-range
526 for (int col = 0; col < spriteSize; col += 8) {
527 if (col + x > -8 && col + x < 256) {
528 // break if we found > 34 8*1 slivers already
529 tilesFound++;
530 if (tilesFound > 34) {
531 time_over_ = true;
532 break;
533 }
534 // figure out which tile this uses, looping within 16x16 pages, and
535 // get it's data
536 int usedCol = hFlipped ? spriteSize - 1 - col : col;
537 uint8_t usedTile = (((tile >> 4) + (row / 8)) << 4) |
538 (((tile & 0xf) + (usedCol / 8)) & 0xf);
539 uint16_t objAdr =
540 (oam[index + 1] & 0x100) ? obj_tile_adr2_ : obj_tile_adr1_;
541 uint16_t plane1 =
542 vram[(objAdr + usedTile * 16 + (row & 0x7)) & 0x7fff];
543 uint16_t plane2 =
544 vram[(objAdr + usedTile * 16 + 8 + (row & 0x7)) & 0x7fff];
545 // go over each pixel
546 for (int px = 0; px < 8; px++) {
547 int shift = hFlipped ? px : 7 - px;
548 int pixel = (plane1 >> shift) & 1;
549 pixel |= ((plane1 >> (8 + shift)) & 1) << 1;
550 pixel |= ((plane2 >> shift) & 1) << 2;
551 pixel |= ((plane2 >> (8 + shift)) & 1) << 3;
552 // draw it in the buffer if there is a pixel here
553 int screenCol = col + x + px;
554 if (pixel > 0 && screenCol >= 0 && screenCol < 256) {
555 obj_pixel_buffer_[screenCol] = 0x80 + 16 * palette + pixel;
556 obj_priority_buffer_[screenCol] = (oam[index + 1] & 0x3000) >> 12;
557 }
558 }
559 }
560 }
561 if (tilesFound > 34)
562 break; // break out of sprite-loop if max tiles found
563 }
564 }
565}
566
568 // expand 13-bit values to signed values
569 int hScroll = ((int16_t)(m7matrix[6] << 3)) >> 3;
570 int vScroll = ((int16_t)(m7matrix[7] << 3)) >> 3;
571 int xCenter = ((int16_t)(m7matrix[4] << 3)) >> 3;
572 int yCenter = ((int16_t)(m7matrix[5] << 3)) >> 3;
573 // do calculation
574 int clippedH = hScroll - xCenter;
575 int clippedV = vScroll - yCenter;
576 clippedH = (clippedH & 0x2000) ? (clippedH | ~1023) : (clippedH & 1023);
577 clippedV = (clippedV & 0x2000) ? (clippedV | ~1023) : (clippedV & 1023);
578 if (bg_layer_[0].mosaicEnabled && mosaic_size_ > 1) {
579 y -= (y - mosaic_startline_) % mosaic_size_;
580 }
581 uint8_t ry = m7yFlip ? 255 - y : y;
582 m7startX = (((m7matrix[0] * clippedH) & ~63) + ((m7matrix[1] * ry) & ~63) +
583 ((m7matrix[1] * clippedV) & ~63) + (xCenter << 8));
584 m7startY = (((m7matrix[2] * clippedH) & ~63) + ((m7matrix[3] * ry) & ~63) +
585 ((m7matrix[3] * clippedV) & ~63) + (yCenter << 8));
586}
587
589 // called either right after CheckOverscan at (0,225), or at (0,240)
590 if (!forced_blank_) {
593 oam_second_write_ = false;
594 }
595 frame_interlace = interlace; // set if we have a interlaced frame
596}
597
598uint8_t Ppu::Read(uint8_t adr, bool latch) {
599 switch (adr) {
600 case 0x04:
601 case 0x14:
602 case 0x24:
603 case 0x05:
604 case 0x15:
605 case 0x25:
606 case 0x06:
607 case 0x16:
608 case 0x26:
609 case 0x08:
610 case 0x18:
611 case 0x28:
612 case 0x09:
613 case 0x19:
614 case 0x29:
615 case 0x0a:
616 case 0x1a:
617 case 0x2a: {
618 return ppu1_open_bus_;
619 }
620 case 0x34:
621 case 0x35:
622 case 0x36: {
623 int result = m7matrix[0] * (m7matrix[1] >> 8);
624 ppu1_open_bus_ = (result >> (8 * (adr - 0x34))) & 0xff;
625 return ppu1_open_bus_;
626 }
627 case 0x37: {
628 // TODO: only when ppulatch is set
629 if (latch) {
630 LatchHV();
631 }
632 return memory_.open_bus();
633 }
634 case 0x38: {
635 uint8_t ret = 0;
636 if (oam_in_high_) {
637 ret = high_oam_[((oam_adr_ & 0xf) << 1) | oam_second_write_];
638 if (oam_second_write_) {
639 oam_adr_++;
640 if (oam_adr_ == 0) oam_in_high_ = false;
641 }
642 } else {
643 if (!oam_second_write_) {
644 ret = oam[oam_adr_] & 0xff;
645 } else {
646 ret = oam[oam_adr_++] >> 8;
647 if (oam_adr_ == 0) oam_in_high_ = true;
648 }
649 }
651 ppu1_open_bus_ = ret;
652 return ret;
653 }
654 case 0x39: {
655 uint16_t val = vram_read_buffer_;
657 vram_read_buffer_ = vram[GetVramRemap() & 0x7fff];
659 }
660 ppu1_open_bus_ = val & 0xff;
661 return val & 0xff;
662 }
663 case 0x3a: {
664 uint16_t val = vram_read_buffer_;
666 vram_read_buffer_ = vram[GetVramRemap() & 0x7fff];
668 }
669 ppu1_open_bus_ = val >> 8;
670 return val >> 8;
671 }
672 case 0x3b: {
673 uint8_t ret = 0;
674 if (!cgram_second_write_) {
675 ret = cgram[cgram_pointer_] & 0xff;
676 } else {
677 ret = ((cgram[cgram_pointer_++] >> 8) & 0x7f) | (ppu2_open_bus_ & 0x80);
678 }
680 ppu2_open_bus_ = ret;
681 return ret;
682 }
683 case 0x3c: {
684 uint8_t val = 0;
685 if (h_count_second_) {
686 val = ((h_count_ >> 8) & 1) | (ppu2_open_bus_ & 0xfe);
687 } else {
688 val = h_count_ & 0xff;
689 }
691 ppu2_open_bus_ = val;
692 return val;
693 }
694 case 0x3d: {
695 uint8_t val = 0;
696 if (v_count_second_) {
697 val = ((v_count_ >> 8) & 1) | (ppu2_open_bus_ & 0xfe);
698 } else {
699 val = v_count_ & 0xff;
700 }
702 ppu2_open_bus_ = val;
703 return val;
704 }
705 case 0x3e: {
706 uint8_t val = 0x1; // ppu1 version (4 bit)
707 val |= ppu1_open_bus_ & 0x10;
708 val |= range_over_ << 6;
709 val |= time_over_ << 7;
710 ppu1_open_bus_ = val;
711 return val;
712 }
713 case 0x3f: {
714 uint8_t val = 0x3; // ppu2 version (4 bit)
715 val |= memory_.pal_timing() << 4; // ntsc/pal
716 val |= ppu2_open_bus_ & 0x20;
717 val |= counters_latched_ << 6;
718 val |= even_frame << 7;
719 if (latch) {
720 counters_latched_ = false;
721 h_count_second_ = false;
722 v_count_second_ = false;
723 }
724 ppu2_open_bus_ = val;
725 return val;
726 }
727 default: {
728 return memory_.open_bus();
729 }
730 }
731}
732
733void Ppu::Write(uint8_t adr, uint8_t val) {
734 switch (adr) {
735 case 0x00: {
736 // TODO: oam address reset when written on first line of vblank, (and when
737 // forced blank is disabled?)
738 brightness = val & 0xf;
739 forced_blank_ = val & 0x80;
740 break;
741 }
742 case 0x01: {
743 obj_size_ = val >> 5;
744 obj_tile_adr1_ = (val & 7) << 13;
745 obj_tile_adr2_ = obj_tile_adr1_ + (((val & 0x18) + 8) << 9);
746 break;
747 }
748 case 0x02: {
749 oam_adr_ = val;
752 oam_second_write_ = false;
753 break;
754 }
755 case 0x03: {
756 obj_priority_ = val & 0x80;
757 oam_in_high_ = val & 1;
760 oam_second_write_ = false;
761 break;
762 }
763 case 0x04: {
764 if (oam_in_high_) {
765 high_oam_[((oam_adr_ & 0xf) << 1) | oam_second_write_] = val;
766 if (oam_second_write_) {
767 oam_adr_++;
768 if (oam_adr_ == 0) oam_in_high_ = false;
769 }
770 } else {
771 if (!oam_second_write_) {
772 oam_buffer_ = val;
773 } else {
774 oam[oam_adr_++] = (val << 8) | oam_buffer_;
775 if (oam_adr_ == 0) oam_in_high_ = true;
776 }
777 }
779 break;
780 }
781 case 0x05: {
782 mode = val & 0x7;
783 bg3priority = val & 0x8;
784 bg_layer_[0].bigTiles = val & 0x10;
785 bg_layer_[1].bigTiles = val & 0x20;
786 bg_layer_[2].bigTiles = val & 0x40;
787 bg_layer_[3].bigTiles = val & 0x80;
788 break;
789 }
790 case 0x06: {
791 // TODO: mosaic line reset specifics
792 bg_layer_[0].mosaicEnabled = val & 0x1;
793 bg_layer_[1].mosaicEnabled = val & 0x2;
794 bg_layer_[2].mosaicEnabled = val & 0x4;
795 bg_layer_[3].mosaicEnabled = val & 0x8;
796 mosaic_size_ = (val >> 4) + 1;
798 break;
799 }
800 case 0x07:
801 case 0x08:
802 case 0x09:
803 case 0x0a: {
804 bg_layer_[adr - 7].tilemapWider = val & 0x1;
805 bg_layer_[adr - 7].tilemapHigher = val & 0x2;
806 bg_layer_[adr - 7].tilemapAdr = (val & 0xfc) << 8;
807 break;
808 }
809 case 0x0b: {
810 bg_layer_[0].tileAdr = (val & 0xf) << 12;
811 bg_layer_[1].tileAdr = (val & 0xf0) << 8;
812 break;
813 }
814 case 0x0c: {
815 bg_layer_[2].tileAdr = (val & 0xf) << 12;
816 bg_layer_[3].tileAdr = (val & 0xf0) << 8;
817 break;
818 }
819 case 0x0d: {
820 m7matrix[6] = ((val << 8) | m7prev) & 0x1fff;
821 m7prev = val;
822 // fallthrough to normal layer BG-HOFS
823 }
824 case 0x0f:
825 case 0x11:
826 case 0x13: {
827 bg_layer_[(adr - 0xd) / 2].hScroll =
828 ((val << 8) | (scroll_prev_ & 0xf8) | (scroll_prev2_ & 0x7)) & 0x3ff;
829 scroll_prev_ = val;
830 scroll_prev2_ = val;
831 break;
832 }
833 case 0x0e: {
834 m7matrix[7] = ((val << 8) | m7prev) & 0x1fff;
835 m7prev = val;
836 // fallthrough to normal layer BG-VOFS
837 }
838 case 0x10:
839 case 0x12:
840 case 0x14: {
841 bg_layer_[(adr - 0xe) / 2].vScroll = ((val << 8) | scroll_prev_) & 0x3ff;
842 scroll_prev_ = val;
843 break;
844 }
845 case 0x15: {
846 if ((val & 3) == 0) {
847 vram_increment_ = 1;
848 } else if ((val & 3) == 1) {
849 vram_increment_ = 32;
850 } else {
851 vram_increment_ = 128;
852 }
853 vram_remap_mode_ = (val & 0xc) >> 2;
854 vram_increment_on_high_ = val & 0x80;
855 break;
856 }
857 case 0x16: {
858 vram_pointer = (vram_pointer & 0xff00) | val;
859 vram_read_buffer_ = vram[GetVramRemap() & 0x7fff];
860 break;
861 }
862 case 0x17: {
863 vram_pointer = (vram_pointer & 0x00ff) | (val << 8);
864 vram_read_buffer_ = vram[GetVramRemap() & 0x7fff];
865 break;
866 }
867 case 0x18: {
868 // TODO: vram access during rendering (also cgram and oam)
869 uint16_t vramAdr = GetVramRemap();
870 vram[vramAdr & 0x7fff] = (vram[vramAdr & 0x7fff] & 0xff00) | val;
872 break;
873 }
874 case 0x19: {
875 uint16_t vramAdr = GetVramRemap();
876 vram[vramAdr & 0x7fff] = (vram[vramAdr & 0x7fff] & 0x00ff) | (val << 8);
878 break;
879 }
880 case 0x1a: {
881 m7largeField = val & 0x80;
882 m7charFill = val & 0x40;
883 m7yFlip = val & 0x2;
884 m7xFlip = val & 0x1;
885 break;
886 }
887 case 0x1b:
888 case 0x1c:
889 case 0x1d:
890 case 0x1e: {
891 m7matrix[adr - 0x1b] = (val << 8) | m7prev;
892 m7prev = val;
893 break;
894 }
895 case 0x1f:
896 case 0x20: {
897 m7matrix[adr - 0x1b] = ((val << 8) | m7prev) & 0x1fff;
898 m7prev = val;
899 break;
900 }
901 case 0x21: {
902 cgram_pointer_ = val;
903 cgram_second_write_ = false;
904 break;
905 }
906 case 0x22: {
907 if (!cgram_second_write_) {
908 cgram_buffer_ = val;
909 } else {
910 cgram[cgram_pointer_++] = (val << 8) | cgram_buffer_;
911 }
913 break;
914 }
915 case 0x23:
916 case 0x24:
917 case 0x25: {
918 windowLayer[(adr - 0x23) * 2].window1inversed = val & 0x1;
919 windowLayer[(adr - 0x23) * 2].window1enabled = val & 0x2;
920 windowLayer[(adr - 0x23) * 2].window2inversed = val & 0x4;
921 windowLayer[(adr - 0x23) * 2].window2enabled = val & 0x8;
922 windowLayer[(adr - 0x23) * 2 + 1].window1inversed = val & 0x10;
923 windowLayer[(adr - 0x23) * 2 + 1].window1enabled = val & 0x20;
924 windowLayer[(adr - 0x23) * 2 + 1].window2inversed = val & 0x40;
925 windowLayer[(adr - 0x23) * 2 + 1].window2enabled = val & 0x80;
926 break;
927 }
928 case 0x26: {
929 window1left = val;
930 break;
931 }
932 case 0x27: {
933 window1right = val;
934 break;
935 }
936 case 0x28: {
937 window2left = val;
938 break;
939 }
940 case 0x29: {
941 window2right = val;
942 break;
943 }
944 case 0x2a: {
945 windowLayer[0].maskLogic = val & 0x3;
946 windowLayer[1].maskLogic = (val >> 2) & 0x3;
947 windowLayer[2].maskLogic = (val >> 4) & 0x3;
948 windowLayer[3].maskLogic = (val >> 6) & 0x3;
949 break;
950 }
951 case 0x2b: {
952 windowLayer[4].maskLogic = val & 0x3;
953 windowLayer[5].maskLogic = (val >> 2) & 0x3;
954 break;
955 }
956 case 0x2c: {
957 layer_[0].mainScreenEnabled = val & 0x1;
958 layer_[1].mainScreenEnabled = val & 0x2;
959 layer_[2].mainScreenEnabled = val & 0x4;
960 layer_[3].mainScreenEnabled = val & 0x8;
961 layer_[4].mainScreenEnabled = val & 0x10;
962 break;
963 }
964 case 0x2d: {
965 layer_[0].subScreenEnabled = val & 0x1;
966 layer_[1].subScreenEnabled = val & 0x2;
967 layer_[2].subScreenEnabled = val & 0x4;
968 layer_[3].subScreenEnabled = val & 0x8;
969 layer_[4].subScreenEnabled = val & 0x10;
970 break;
971 }
972 case 0x2e: {
973 layer_[0].mainScreenWindowed = val & 0x1;
974 layer_[1].mainScreenWindowed = val & 0x2;
975 layer_[2].mainScreenWindowed = val & 0x4;
976 layer_[3].mainScreenWindowed = val & 0x8;
977 layer_[4].mainScreenWindowed = val & 0x10;
978 break;
979 }
980 case 0x2f: {
981 layer_[0].subScreenWindowed = val & 0x1;
982 layer_[1].subScreenWindowed = val & 0x2;
983 layer_[2].subScreenWindowed = val & 0x4;
984 layer_[3].subScreenWindowed = val & 0x8;
985 layer_[4].subScreenWindowed = val & 0x10;
986 break;
987 }
988 case 0x30: {
989 direct_color_ = val & 0x1;
990 add_subscreen_ = val & 0x2;
991 prevent_math_mode_ = (val & 0x30) >> 4;
992 clip_mode_ = (val & 0xc0) >> 6;
993 break;
994 }
995 case 0x31: {
996 subtract_color_ = val & 0x80;
997 half_color_ = val & 0x40;
998 for (int i = 0; i < 6; i++) {
999 math_enabled_array_[i] = val & (1 << i);
1000 }
1001 break;
1002 }
1003 case 0x32: {
1004 if (val & 0x80) fixed_color_b_ = val & 0x1f;
1005 if (val & 0x40) fixed_color_g_ = val & 0x1f;
1006 if (val & 0x20) fixed_color_r_ = val & 0x1f;
1007 break;
1008 }
1009 case 0x33: {
1010 interlace = val & 0x1;
1011 obj_interlace_ = val & 0x2;
1012 overscan_ = val & 0x4;
1013 pseudo_hires_ = val & 0x8;
1014 m7extBg = val & 0x40;
1015 break;
1016 }
1017 default: {
1018 break;
1019 }
1020 }
1021}
1022
1024 uint16_t adr = vram_pointer;
1025 switch (vram_remap_mode_) {
1026 case 0:
1027 return adr;
1028 case 1:
1029 return (adr & 0xff00) | ((adr & 0xe0) >> 5) | ((adr & 0x1f) << 3);
1030 case 2:
1031 return (adr & 0xfe00) | ((adr & 0x1c0) >> 6) | ((adr & 0x3f) << 3);
1032 case 3:
1033 return (adr & 0xfc00) | ((adr & 0x380) >> 7) | ((adr & 0x7f) << 3);
1034 }
1035 return adr;
1036}
1037
1038void Ppu::PutPixels(uint8_t* pixels) {
1039 for (int y = 0; y < (frame_overscan_ ? 239 : 224); y++) {
1040 int dest = y * 2 + (frame_overscan_ ? 2 : 16);
1041 int y1 = y, y2 = y + 239;
1042 if (!frame_interlace) {
1043 y1 = y + (even_frame ? 0 : 239);
1044 y2 = y1;
1045 }
1046 memcpy(pixels + (dest * 2048), &pixelBuffer[y1 * 2048], 2048);
1047 memcpy(pixels + ((dest + 1) * 2048), &pixelBuffer[y2 * 2048], 2048);
1048 }
1049 // clear top 2 lines, and following 14 and last 16 lines if not overscanning
1050 memset(pixels, 0, 2048 * 2);
1051 if (!frame_overscan_) {
1052 memset(pixels + (2 * 2048), 0, 2048 * 14);
1053 memset(pixels + (464 * 2048), 0, 2048 * 16);
1054 }
1055}
1056
1057} // namespace video
1058} // namespace emu
1059} // namespace app
1060} // namespace yaze
virtual auto v_pos() const -> uint16_t=0
virtual uint8_t open_bus() const =0
virtual auto pal_timing() const -> bool=0
int GetPixelForBgLayer(int x, int y, int layer, bool priority)
Definition ppu.cc:405
WindowLayer windowLayer[6]
Definition ppu.h:406
uint8_t pixelBuffer[512 *4 *239 *2]
Definition ppu.h:423
uint8_t prevent_math_mode_
Definition ppu.h:380
void HandlePixel(int x, int y)
Definition ppu.cc:163
uint16_t obj_tile_adr1_
Definition ppu.h:372
void EvaluateSprites(int line)
Definition ppu.cc:472
bool math_enabled_array_[6]
Definition ppu.h:381
uint8_t mosaic_startline_
Definition ppu.h:414
uint8_t Read(uint8_t adr, bool latch)
Definition ppu.cc:598
std::array< uint8_t, 256 > obj_priority_buffer_
Definition ppu.h:376
void Write(uint8_t adr, uint8_t val)
Definition ppu.cc:733
void HandleOPT(int layer, int *lx, int *ly)
Definition ppu.cc:356
uint16_t GetVramRemap()
Definition ppu.cc:1023
void CalculateMode7Starts(int y)
Definition ppu.cc:567
uint16_t cgram[0x100]
Definition ppu.h:352
uint16_t obj_tile_adr2_
Definition ppu.h:373
uint8_t pixelOutputFormat
Definition ppu.h:424
int GetPixel(int x, int y, bool sub, int *r, int *g, int *b)
Definition ppu.cc:230
uint16_t vram_read_buffer_
Definition ppu.h:349
uint16_t vram_increment_
Definition ppu.h:347
void RunLine(int line)
Definition ppu.cc:151
uint16_t oam[0x100]
Definition ppu.h:358
BgLayer bg_layer_[4]
Definition ppu.h:416
bool GetWindowState(int layer, int x)
Definition ppu.cc:326
void PutPixels(uint8_t *pixel_data)
Definition ppu.cc:1038
int GetPixelForMode7(int x, int layer, bool priority)
Definition ppu.cc:307
std::array< uint8_t, 256 > obj_pixel_buffer_
Definition ppu.h:375
int16_t m7matrix[8]
Definition ppu.h:393
uint16_t GetOffsetValue(int col, int row)
Definition ppu.cc:391
uint8_t high_oam_[0x20]
Definition ppu.h:359
uint16_t vram[0x8000]
Definition ppu.h:344
Definition common.cc:21