-
Notifications
You must be signed in to change notification settings - Fork 0
/
PPU_Gameboy.cc
387 lines (362 loc) · 12 KB
/
PPU_Gameboy.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
#include "PPU_Gameboy.h"
#include "Bus.h"
#include <algorithm>
#include <iostream>
#include <SDL2/SDL.h>
PPU_Gameboy::PPU_Gameboy(){}
PPU_Gameboy::~PPU_Gameboy()
{
SDL_FreeSurface(surf);
SDL_DestroyTexture(tex);
}
bool PPU_Gameboy::tick()
{
//test();
bool newFrame = false;
if(((lcdc>>7)&1)==0) //TODO: manage this properly, and make it cycle accurate
{
stat = 0;
scanline = 0;
dots = 4;
mode = 2;
cycles = 0;
pixelcount = 0;
}
else
{
if(mode != VBLANK)
{
if(mode == 2 || dots == 0)
{
mode = 2;
if(mode == 2 && dots == 0) //Enter OAM scan
{
if((stat >> 5)&1)
bus->write(0xFF0F,bus->read(0xFF0F)|2);
tileY = (((scy + scanline)) >> 3)%32;
tileYOffset = ((scy + scanline)) & 0x07;
tileX = scx >> 3;
tileXOffset = scx & 0x07;
ComputeTileLine();
//Preprocess sprites:
uint8_t oamLine[10][4] = {0};
int nbSpritesOnLine = 0;
uint8_t spriteHeight = 8;
bool y16Mode = ((lcdc>>2)&1) == 1;
if(y16Mode) //If 8*16 mode
spriteHeight = 16;
for(uint16_t objectIndex = 0xFE00; objectIndex < 0xFE9F; objectIndex+= 4) //Parcours l'oam
{
if(read(objectIndex) <= scanline+16 && scanline+16 < read(objectIndex)+spriteHeight)
{
for(uint16_t j = 0; j < 4; j++)
oamLine[nbSpritesOnLine][j] = read(objectIndex+j); //Load 10 first objects of the line
nbSpritesOnLine++;
if(nbSpritesOnLine == 10)
break;
}
}
memset(screenLine,8,160); //Bit 3 is 1 when no pixel is drawn
for(int i = 0; i < nbSpritesOnLine; i++)
{
uint8_t spriteY = scanline+16 - oamLine[i][0];
uint8_t charCode = oamLine[i][2];
if(y16Mode)
{
charCode &= (~1);
if(spriteY >= 8)
{
charCode |= 1;
spriteY -= 8;
}
}
if((oamLine[i][3]>>6)&1) //Y-flip
{
spriteY = 7 - spriteY;
if(y16Mode)
{
uint8_t tmp = !(charCode & 1);
charCode &= (~1);
charCode |= tmp;
}
}
uint16_t sprite = GetTileLineFromCode(charCode,spriteY,true);
bool drawAllSprite = false;
for(int j = (oamLine[i][1] < 8 ? (8-oamLine[i][1]) : 0); j < 8; j++) //Ternary here to have x sprite scroling
{
uint8_t spriteXDot= oamLine[i][1]+j-8; //-8 here for x scrolling
if(spriteXDot >= 160) break;
if(!drawAllSprite && screenLine[spriteXDot] >= 8) //>= 8 means the notDrawn bit is set for that pixel
{
drawAllSprite = true;
}
if(drawAllSprite)
{
int spriteIndex = j;
if((oamLine[i][3] >> 5)&1) //X-flip
{
spriteIndex = 7 - spriteIndex;
}
uint8_t pixel = (((sprite >> (15-spriteIndex))<<1)&2) | ((sprite >> (7-spriteIndex))&1); //Extra careful with the bit order
if(pixel == 0) continue;
screenLine[spriteXDot] = pixel;
if((oamLine[i][3] >> 4)&1 == 1)//OAM can't be accessed during scanline however palette can (some game rely on this although they are rare)
screenLine[spriteXDot] |= (1<<2); //We set bit 2 to 1 to indicate obp1
}
}
}
}
//Probably implement OAM Bug or something
//For now, nothing happens
if(dots == 79)
{
mode = 3; //When we reach dots == 79, we ticked 80 times so we switch to mode 3(for next tick)
}
}
else if(mode == 3)
{
if(cycles == 0) //Mode 3 n'est pas régulier, ce if sert à temporiser
{
if(dots == 80) //pixelcount == 0) //TODO: Add sprites, bg scrolling and window as causes of cycles
{
cycles = 8; //Initial 8 pixels (just before the left edge of the screen)
}
else
{
//1 tick = 1 dot = 1 pixel:
generatePixel(scanline,pixelcount);
pixelcount++;
}
}
cycles = std::max(0,cycles-1);
}
if(pixelcount == 159)//ENTER HBLANCK:
{
mode = HBLANK;
if((stat>>3)&1)
bus->write(0xFF0F,bus->read(0xFF0F)|2);
}
}
dots++;
if(dots == 456) //Can't change mode here (may still be in VBLANK)
{
dots = 0;
scanline++;
pixelcount = 0;
}
if(scanline == 144 && dots == 0) //ENTER VBLANK
{
//std::cout<<"VBLANK\n";
mode = VBLANK;
//if((stat >> 4)&1) //Trigger VBLANK Interupt
{
bus->write(0xFF0F,bus->read(0xFF0F)|1);
}
DrawScreen();
newFrame = true;
}
if(scanline == 153 + 1) //EXIT VBLANK
{
mode = 2;
scanline = 0;
dots = 0;
pixelcount = 0;
}
stat &= 0b1'1111'0'11;//reset the lyc=ly flag
if(scanline == lyc) //trigger it only once
{
stat |= 4;
if(dots == 0 && (stat >> 6)&1) //Trigger LYC Interupt
{
bus->write(0xFF0F,bus->read(0xFF0F)|2);
}
}
stat = (stat&0b1'1111'1'00) + mode;
}
return newFrame;
}
void PPU_Gameboy::generatePixel(uint8_t y, uint8_t x)
{
uint8_t pixelData = 0;
uint8_t palette = bgp;
if(lcdc & 1) //Draw the background if enabled
{
pixelData = (lower_tile >> 7) | ((higher_tile >> 6)&2);
lower_tile <<=1; higher_tile <<=1;
tileXOffset++;
if(tileXOffset == 8)
{
tileXOffset = 0;
tileX++;
//tileX%=32;
tileX &= 0x1F; //Modulo for scrolling
ComputeTileLine();
}
palette = bgp;
}
if(lcdc & 2) //Display Sprites
{
if(screenLine[x] < 8) //Means that bit3 is unset which means we draw the pixel
{
//std::cout << (int) screenLine[x] << std::endl; //BIG PROBLEM: TODO: encode palette info in upper bits
pixelData = screenLine[x] & 3; //Take the lower 2 bits
palette = obp0;
if((screenLine[x] >> 2)&1)
palette = obp1;
}
}
UpdateScreen(pixelData,y,x,palette);
}
void PPU_Gameboy::ComputeTileLine()
{
uint8_t tileCode = read((tileY*32+tileX) + GetBGMapOffset());
uint16_t line = GetTileLineFromCode(tileCode,tileYOffset);
higher_tile = line>>8;
lower_tile = line & 0xFF;
}
void PPU_Gameboy::UpdateScreen(uint8_t pixelData,uint8_t y, uint8_t x, uint8_t palette)
{
//int colors[4][4] = {{255,255,255,255},{180,180,180,255},{60,60,60,255},{0,0,0,255}}; //Origjnal colors
int colors[4][4] = {{239,239,239,255},{160,160,160,255},{90,90,90,255},{16,16,16,255}};
uint8_t colorPalette = (palette >> (pixelData*2))&3;
screen[(y*160+x)*4+0] = colors[colorPalette][0];
screen[(y*160+x)*4+1] = colors[colorPalette][1];
screen[(y*160+x)*4+2] = colors[colorPalette][2];
screen[(y*160+x)*4+3] = colors[colorPalette][3];
}
void PPU_Gameboy::DrawScreen()
{
SDL_SetRenderDrawColor(ren, 0, 0, 0, SDL_ALPHA_OPAQUE );
SDL_RenderClear(ren);
/*for(int i = 0; i < 2; i++)
{
for(int j=0; j < 160; j++)
{
screen[(i*160+j)*4+0]=255;
}
}*/
/*for( unsigned int i = 0; i < 160*144; i++ )
{
const unsigned int x = rand() % 160;
const unsigned int y = rand() % 144;
const unsigned int offset = ( 160 * 4 * y ) + x * 4;
screen[ offset + 0 ] = rand() % 256; // b
screen[ offset + 1 ] = rand() % 256; // g
screen[ offset + 2 ] = rand() % 256; // r
screen[ offset + 3 ] = SDL_ALPHA_OPAQUE; // a
}*/
SDL_UpdateTexture(tex,NULL,&screen[0],160*4);
SDL_RenderCopy(ren, tex, NULL, NULL);//&dst);
SDL_RenderPresent(ren);
}
uint16_t PPU_Gameboy::GetTileLineFromCode(uint8_t tileCode,uint8_t line, bool spriteMode)
{
uint16_t index = tileCode*16 + 2*line;
if(((lcdc >> 4)&1)==1 || spriteMode)
{
index += 0x8000;
}
else
{
if(tileCode >= 0x80)
{
index += 0x8000;
}
else
{
index += 0x9000;
}
}
return read(index) + (read(index+1)<<8);
}
uint16_t PPU_Gameboy::GetBGMapOffset()
{
return (((lcdc >> 3)&1) ? 0x9C00 : 0x9800);//- 0x8000;
}
void PPU_Gameboy::DMA_Transfer() //TODO: wait 160 cycles
{
uint16_t starting_adress = ((uint16_t)dma_starting_adress)<<8;
for(uint16_t i = 0xFE00; i <= 0xFE9F;i++) //Exactly 160 iterations... => manage cycle count with this..
{
write(i,bus->read(starting_adress));
starting_adress++;
}
}
void PPU_Gameboy::write(uint16_t adr, uint8_t data)
{
if(adr >= 0x8000 && adr <= 0x9FFF)
{
vram[adr-0x8000] = data;
return;
}
if(adr >= 0xFE00 && adr <= 0xFE9F)
{
oam[adr-0xFE00] = data;
return;
}
switch(adr)
{
case 0xFF40:
lcdc = data; //std::cout<<"lcdc:"<<(int)lcdc<<"|"<<((lcdc>>7)&1)<<std::endl;break;
case 0xFF41:
stat = data&0b0111'1000; break;
case 0xFF42:
scy = data; break;
case 0xFF43:
scx = data; break;
case 0xFF44: //LY
//not writable
break;
case 0xFF45:
lyc = data; break;
case 0xFF46: //DMA Transfer
dma_starting_adress = data;
DMA_Transfer(); break;
case 0xFF47:
bgp = data; break;
case 0xFF48:
obp0 = data; break;
case 0xFF49:
obp1 = data; break;
case 0xFF4A:
wy = data; break;
case 0xFF4B:
wx = data; break;
}
}
uint8_t PPU_Gameboy::read(uint16_t adr)
{
//TODO: implement blocking depending on current mode
if(adr >= 0x8000 && adr <= 0x9FFF)
return vram[adr-0x8000];
if(adr >= 0xFE00 && adr <= 0xFE9F)
return oam[adr-0xFE00];
switch(adr)
{
case 0xFF40:
return lcdc;
case 0xFF41:
return stat;
case 0xFF42:
return scy;
case 0xFF43:
return scx;
case 0xFF44: //LY
return scanline;
case 0xFF45:
return lyc;
case 0xFF46:
return dma_starting_adress;
case 0xFF47:
return bgp;
case 0xFF48:
return obp0;
case 0xFF49:
return obp1;
case 0xFF4A:
return wy;
case 0xFF4B:
return wx;
}
return 0xFF;
}