Skip to content

Commit af27ed3

Browse files
committed
Did a ton of work on the graphics rewrite, specifically Pixel Pipeline.
Almost done with the pixel fetcher (need to do sprite mixing.) Need to do the Pixel Fifo (Pixel Pusher) Need to do overall cycles and overarching Pixel Pipeline Class.
1 parent 7482151 commit af27ed3

File tree

11 files changed

+456
-137
lines changed

11 files changed

+456
-137
lines changed

core/constants.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,13 @@ export const GBC_PALETTE_SIZE: i32 = 0x000080;
3636
export const BG_PRIORITY_MAP_LOCATION: i32 = GBC_PALETTE_LOCATION + GBC_PALETTE_SIZE;
3737
export const BG_PRIORITY_MAP_SIZE: i32 = 0x005c00;
3838

39-
export const FRAME_LOCATION: i32 = BG_PRIORITY_MAP_LOCATION + BG_PRIORITY_MAP_SIZE;
39+
export const OAM_VISIBLE_SPRITES_LOCATION: i32 = BG_PRIORITY_MAP_LOCATION + BG_PRIORITY_MAP_SIZE;
40+
export const OAM_VISIBLE_SPRITES_SIZE: i32 = 0x0b;
41+
42+
export const PIXEL_PIPELINE_ENTIRE_SCANLINE_FIFO_LOCATION: i32 = OAM_VISIBLE_SPRITES_LOCATION + OAM_VISIBLE_SPRITES_SIZE;
43+
export const PIXEL_PIPELINE_ENTIRE_SCANLINE_FIFO_SIZE: i32 = 0x3c;
44+
45+
export const FRAME_LOCATION: i32 = PIXEL_PIPELINE_ENTIRE_SCANLINE_FIFO_LOCATION + PIXEL_PIPELINE_ENTIRE_SCANLINE_FIFO_SIZE;
4046
export const FRAME_SIZE: i32 = 0x016c00;
4147

4248
export const BACKGROUND_MAP_LOCATION: i32 = FRAME_LOCATION + FRAME_SIZE;

core/graphics-old/sprites.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export function renderSprites(scanlineRegister: i32, useLargerSprites: boolean):
2828
let spriteXPosition = eightBitLoadFromGBMemory(index + 1);
2929
let spriteTileId = eightBitLoadFromGBMemory(index + 2);
3030

31-
// Pan docs of sprite attirbute table
31+
// Pan docs of sprite attribute table
3232
// Bit7 OBJ-to-BG Priority (0=OBJ Above BG, 1=OBJ Behind BG color 1-3)
3333
// (Used for both BG and Window. BG color 0 is always behind OBJ)
3434
// Bit6 Y flip (0=Normal, 1=Vertically mirrored)

core/graphics/graphics.ts

Lines changed: 51 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
// Main Class and funcitons for rendering the gameboy display
22
import { FRAME_LOCATION, GAMEBOY_INTERNAL_MEMORY_LOCATION } from '../constants';
33
import { getSaveStateMemoryOffset } from '../core';
4-
import { Lcd, setLcdStatus } from './lcd';
5-
import { renderBackground, renderWindow } from './backgroundWindow';
6-
import { renderSprites } from './sprites';
7-
import { clearPriorityMap } from './priority';
8-
import { resetTileCache } from './tiles';
9-
import { initializeColors } from './colors';
104
import { Cpu } from '../cpu/index';
11-
import { Config } from '../config';
12-
import { Memory, eightBitLoadFromGBMemory, eightBitStoreIntoGBMemory } from '../memory/index';
5+
import {
6+
Memory,
7+
eightBitLoadFromGBMemory,
8+
eightBitStoreIntoGBMemory,
9+
loadBooleanDirectlyFromWasmMemory,
10+
storeBooleanDirectlyToWasmMemory
11+
} from '../memory/index';
12+
import { Lcd } from './lcd';
13+
import { initializeColors } from './colors';
14+
import { oamSearchForVisibleSprites } from './sprites';
1315

1416
export class Graphics {
1517
// Current cycles
@@ -57,7 +59,6 @@ export class Graphics {
5759
// 0x00 - 0x24 Graphics, 0x25 - 0x50 LCD
5860
static saveState(): void {
5961
// Graphics
60-
6162
store<i32>(getSaveStateMemoryOffset(0x00, Graphics.saveStateSlot), Graphics.scanlineCycles);
6263
eightBitStoreIntoGBMemory(Graphics.memoryLocationScanlineRegister, Graphics.scanlineRegister);
6364

@@ -158,29 +159,48 @@ export function updateGraphics(numberOfCycles: i32): void {
158159
// Update our cycles
159160
Graphics.scanlineCycles += numberOfCycles;
160161

161-
// Update our LCD
162-
updateLcd();
163-
}
162+
// See if we need to go from OAM Search mode to Pixel Transfer
163+
let cyclesPerOamSearch = 80 << (<i32>Cpu.GBCDoubleSpeed);
164+
if (Lcd.mode === 2 && Graphics.scanlineCycles > oamSearchCycles) {
165+
// Do the actual OAM Search
166+
oamSearchForVisibleSprites();
164167

165-
// Function to get the start of a RGB pixel (R, G, B)
166-
// Inlined because closure compiler inlines
167-
export function getRgbPixelStart(x: i32, y: i32): i32 {
168-
// Get the pixel number
169-
// let pixelNumber: i32 = (y * 160) + x;
170-
// Each pixel takes 3 slots, therefore, multiply by 3!
171-
return (y * 160 + x) * 3;
172-
}
168+
// Switch to pixel transfer mode
169+
Lcd.setMode(3);
173170

174-
// Also need to store current frame in memory to be read by JS
175-
export function setPixelOnFrame(x: i32, y: i32, colorId: i32, color: i32): void {
176-
// Currently only supports 160x144
177-
// Storing in X, then y
178-
// So need an offset
179-
store<u8>(FRAME_LOCATION + getRgbPixelStart(x, y) + colorId, color);
180-
}
171+
// Continue to do the pixel Transfer
172+
}
181173

182-
// Function to shortcut the memory map, and load directly from the VRAM Bank
183-
export function loadFromVramBank(gameboyOffset: i32, vramBankId: i32): u8 {
184-
let wasmBoyAddress = gameboyOffset - Memory.videoRamLocation + GAMEBOY_INTERNAL_MEMORY_LOCATION + 0x2000 * (vramBankId & 0x01);
185-
return load<u8>(wasmBoyAddress);
174+
// Check if we are in pixel transfer mode
175+
// If so, do the pixel transfer!
176+
if (Lcd.mode === 3) {
177+
PixelPipeline.update(numberOfCycles);
178+
}
179+
180+
// Check if we need to increment the scanline
181+
// 80 << (<i32>Cpu.GBCDoubleSpeed) is the number of OAM Search cycles
182+
let cyclesPerScanline = 456 << (<i32>Cpu.GBCDoubleSpeed);
183+
if (Graphics.scanlineCycles >= cyclesPerScanline) {
184+
// Remove the cycles, start a new scanline
185+
Graphics.scanlineCycles -= cyclesPerScanline;
186+
Graphics.scanlineRegister++;
187+
188+
if (Lcd.mode === 3) {
189+
// Clear our Pixel Pipeline
190+
PixelPipeline.reset();
191+
}
192+
193+
// Check if we need to enter a new mode
194+
if (Graphics.scanlineRegister === 144) {
195+
// Enter VBlank mode
196+
Lcd.setMode(1);
197+
} else if (Graphics.scanlineRegister === 154) {
198+
// Wrap the scanline, go back to OAM Search
199+
Graphics.scanlineRegister = 0;
200+
Lcd.setMode(2);
201+
}
202+
203+
// Lastly, check the LCD Coincidence since we changed scanlines
204+
Lcd.checkCoincidence();
205+
}
186206
}

core/graphics/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
export { Graphics, batchProcessGraphics, initializeGraphics, updateGraphics, loadFromVramBank } from './graphics';
1+
export { Graphics, batchProcessGraphics, initializeGraphics, updateGraphics } from './graphics';
2+
3+
export { loadFromVramBank } from './util';
24

35
export { Lcd } from './lcd';
46

core/graphics/lcd.ts

Lines changed: 57 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -74,110 +74,26 @@ export class Lcd {
7474

7575
if (wasLcdEnabled && !Lcd.enabled) {
7676
// Disable the LCD
77-
resetLcd(true);
77+
_resetLcd(true);
7878
}
7979

8080
if (!wasLcdEnabled && Lcd.enabled) {
8181
// Re-enable the LCD
82-
resetLcd(false);
82+
_resetLcd(false);
8383
}
8484
}
8585

86-
// Cycle getters for scanlines
87-
// (NOTE: One scanline is 456 cycles. Thus, they should add to 456)
88-
// TODO: Optimize this, so that double speed updates all cycle constants
89-
// Rather than re-calculating every time
90-
91-
// Hblank
92-
static MODE_0_CYCLES(): i32 {
93-
return 204 << (<i32>Cpu.GBCDoubleSpeed);
94-
}
95-
96-
// OAM Search
97-
static MODE_2_CYCLES(): i32 {
98-
return 80 << (<i32>Cpu.GBCDoubleSpeed);
99-
}
100-
101-
// Pixel Transfer
102-
static MODE_3_CYCLES(): i32 {
103-
return 172 << (<i32>Cpu.GBCDoubleSpeed);
104-
}
105-
106-
static;
107-
}
108-
109-
function resetLcd(shouldBlankScreen: boolean): void {
110-
// Reset scanline cycle counter
111-
Graphics.scanlineCycles = 0;
112-
Graphics.scanlineRegister = 0;
113-
eightBitStoreIntoGBMemory(Graphics.memoryLocationScanlineRegister, 0);
114-
115-
// Set to mode 0
116-
// https://www.reddit.com/r/EmuDev/comments/4w6479/gb_dr_mario_level_generation_issues/
117-
let lcdStatus: i32 = eightBitLoadFromGBMemory(Lcd.memoryLocationLcdStatus);
118-
lcdStatus = resetBitOnByte(1, lcdStatus);
119-
lcdStatus = resetBitOnByte(0, lcdStatus);
120-
Lcd.mode = 0;
121-
122-
// Store the status in memory
123-
eightBitStoreIntoGBMemory(Lcd.memoryLocationLcdStatus, lcdStatus);
124-
125-
// Blank the screen
126-
if (shouldBlankScreen) {
127-
for (let i = 0; i < FRAME_SIZE; ++i) {
128-
store<u8>(FRAME_LOCATION + i, 255);
129-
}
130-
}
131-
}
132-
133-
function checkCoincidence(lcdMode: i32, lcdStatus: i32): i32 {
134-
// Check for the coincidence flag
135-
// Need to check on every mode, and not just HBLANK, as checking on hblank breaks shantae, which checks on vblank
136-
if ((lcdMode === 0 || lcdMode === 1) && Graphics.scanlineRegister === Lcd.coincidenceCompare) {
137-
lcdStatus = setBitOnByte(2, lcdStatus);
138-
if (checkBitOnByte(6, lcdStatus)) {
139-
requestLcdInterrupt();
140-
}
141-
} else {
142-
lcdStatus = resetBitOnByte(2, lcdStatus);
143-
}
144-
145-
return lcdStatus;
146-
}
147-
148-
export function updateLcd(): void {
149-
// Get our current scanline, and lcd mode
150-
let scanlineRegister: i32 = Graphics.scanlineRegister;
151-
let scanlineCycles: i32 = Graphics.scanlineCycles;
152-
let lcdMode: i32 = Lcd.mode;
153-
154-
// Get our new LCD mode (if it is new)
155-
let newLcdMode: i32 = 0;
156-
157-
// First check if we are in V-Blank
158-
if (scanlineRegister >= 144) {
159-
// VBlank mode
160-
newLcdMode = 1;
161-
} else {
162-
// We are drawing scanlines
163-
// Get all of our cycles
164-
let mode2Cycles = Lcd.MODE_2_CYCLES();
165-
let mode3Cycles = Lcd.MODE_3_CYCLES();
166-
let mode0Cycles = Lcd.MODE_0_CYCLES();
167-
168-
if (scanlineCycles > mode2Cycles + mode3Cycles) {
169-
// We are in mode 0 Hblank
170-
newLcdMode = 0;
171-
} else if (scanlineCycles > mode2Cycles) {
172-
// We are in mode 3 Pixel Transfer
173-
newLcdMode = 3;
174-
} else {
175-
// We are in mode 2, OAM Search
176-
newLcdMode = 2;
86+
// Function to set the LCD Mode, and do all the neccessary checks
87+
// Modes:
88+
// 0 or 00: H-Blank
89+
// 1 or 01: V-Blank
90+
// 2 or 10: Searching Sprites Atts
91+
// 3 or 11: Transfering Data to LCD Driver
92+
static setMode(newLcdMode: i32): void {
93+
if (Lcd.mode === newLcdMode) {
94+
return;
17795
}
178-
}
17996

180-
if (lcdMode !== newLcdMode) {
18197
// Get our lcd status
18298
let lcdStatus: i32 = eightBitLoadFromGBMemory(Lcd.memoryLocationLcdStatus);
18399

@@ -221,17 +137,55 @@ export function updateLcd(): void {
221137
if (shouldRequestInterrupt) {
222138
requestLcdInterrupt();
223139
}
140+
}
141+
142+
// Function to check the coincidence flag for every scanline
143+
// NOTE: Need to check on every mode, and not just HBLANK, as checking on hblank breaks shantae, which checks on vblank
144+
// NOTE: Games like Pokemon crystal want the vblank right as it turns to the value, and not have it increment after
145+
// It will break and lead to an infinite loop in crystal
146+
// Therefore, we want to be checking/Setting our LCD status after the scanline updates
147+
// NOTE: Special Case, need to check LYC
148+
// Fix prehistorik man freeze
149+
static checkCoincidence(): void {
150+
// Get our Lcd Mode and status
151+
let lcdMode: i32 = Lcd.mode;
152+
let lcdStatus: i32 = eightBitLoadFromGBMemory(Lcd.memoryLocationLcdStatus);
224153

225154
// Check for the coincidence
226-
lcdStatus = checkCoincidence(newLcdMode, lcdStatus);
155+
if (Graphics.scanlineRegister === Lcd.coincidenceCompare) {
156+
lcdStatus = setBitOnByte(2, lcdStatus);
157+
if (checkBitOnByte(6, lcdStatus)) {
158+
requestLcdInterrupt();
159+
}
160+
} else {
161+
lcdStatus = resetBitOnByte(2, lcdStatus);
162+
}
227163

228-
// Finally, save our status
229-
eightBitStoreIntoGBMemory(Lcd.memoryLocationLcdStatus, lcdStatus);
230-
} else if (scanlineRegister === 153) {
231-
// Special Case, need to check LYC
232-
// Fix prehistorik man freeze
233-
let lcdStatus: i32 = eightBitLoadFromGBMemory(Lcd.memoryLocationLcdStatus);
234-
lcdStatus = checkCoincidence(newLcdMode, lcdStatus);
164+
// Store our LCD status after the check
235165
eightBitStoreIntoGBMemory(Lcd.memoryLocationLcdStatus, lcdStatus);
236166
}
237167
}
168+
169+
function _resetLcd(shouldBlankScreen: boolean): void {
170+
// Reset scanline cycle counter
171+
Graphics.scanlineCycles = 0;
172+
Graphics.scanlineRegister = 0;
173+
eightBitStoreIntoGBMemory(Graphics.memoryLocationScanlineRegister, 0);
174+
175+
// Set to mode 0
176+
// https://www.reddit.com/r/EmuDev/comments/4w6479/gb_dr_mario_level_generation_issues/
177+
let lcdStatus: i32 = eightBitLoadFromGBMemory(Lcd.memoryLocationLcdStatus);
178+
lcdStatus = resetBitOnByte(1, lcdStatus);
179+
lcdStatus = resetBitOnByte(0, lcdStatus);
180+
Lcd.mode = 0;
181+
182+
// Store the status in memory
183+
eightBitStoreIntoGBMemory(Lcd.memoryLocationLcdStatus, lcdStatus);
184+
185+
// Blank the screen
186+
if (shouldBlankScreen) {
187+
for (let i = 0; i < FRAME_SIZE; ++i) {
188+
store<u8>(FRAME_LOCATION + i, 255);
189+
}
190+
}
191+
}

0 commit comments

Comments
 (0)