Skip to content

Commit 7482151

Browse files
committed
Started rewrite, with correct graphical cycle counts
1 parent 4416390 commit 7482151

File tree

13 files changed

+649
-209
lines changed

13 files changed

+649
-209
lines changed
File renamed without changes.

core/graphics-old/graphics.ts

Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
// Main Class and funcitons for rendering the gameboy display
2+
import { FRAME_LOCATION, GAMEBOY_INTERNAL_MEMORY_LOCATION } from '../constants';
3+
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';
10+
import { Cpu } from '../cpu/index';
11+
import { Config } from '../config';
12+
import { Memory, eightBitLoadFromGBMemory, eightBitStoreIntoGBMemory } from '../memory/index';
13+
14+
export class Graphics {
15+
// Current cycles
16+
// This will be used for batch processing
17+
static currentCycles: i32 = 0;
18+
19+
// Number of cycles to run in each batch process
20+
// This number should be in sync so that graphics doesn't run too many cyles at once
21+
// and does not exceed the minimum number of cyles for either scanlines, or
22+
// How often we change the frame, or a channel's update process
23+
static batchProcessCycles(): i32 {
24+
return Graphics.MAX_CYCLES_PER_SCANLINE();
25+
}
26+
27+
// Count the number of cycles to keep synced with cpu cycles
28+
// Found GBC cycles by finding clock speed from Gb Cycles
29+
// See TCAGBD For cycles
30+
static scanlineCycleCounter: i32 = 0x00;
31+
32+
// TCAGBD says 456 per scanline, but 153 only a handful
33+
static MAX_CYCLES_PER_SCANLINE(): i32 {
34+
if (Graphics.scanlineRegister === 153) {
35+
return 4 << (<i32>Cpu.GBCDoubleSpeed);
36+
} else {
37+
return 456 << (<i32>Cpu.GBCDoubleSpeed);
38+
}
39+
}
40+
41+
static MIN_CYCLES_SPRITES_LCD_MODE(): i32 {
42+
// TODO: Confirm these clock cyles, double similar to scanline, which TCAGBD did
43+
return 376 << (<i32>Cpu.GBCDoubleSpeed);
44+
}
45+
46+
static MIN_CYCLES_TRANSFER_DATA_LCD_MODE(): i32 {
47+
// TODO: Confirm these clock cyles, double similar to scanline, which TCAGBD did
48+
return 249 << (<i32>Cpu.GBCDoubleSpeed);
49+
}
50+
51+
// LCD
52+
// scanlineRegister also known as LY
53+
// See: http://bgb.bircd.org/pandocs.txt , and search " LY "
54+
static readonly memoryLocationScanlineRegister: i32 = 0xff44;
55+
static scanlineRegister: i32 = 0;
56+
static readonly memoryLocationDmaTransfer: i32 = 0xff46;
57+
58+
// Scroll and Window
59+
static readonly memoryLocationScrollX: i32 = 0xff43;
60+
static scrollX: i32 = 0;
61+
static readonly memoryLocationScrollY: i32 = 0xff42;
62+
static scrollY: i32 = 0;
63+
static readonly memoryLocationWindowX: i32 = 0xff4b;
64+
static windowX: i32 = 0;
65+
static readonly memoryLocationWindowY: i32 = 0xff4a;
66+
static windowY: i32 = 0;
67+
68+
// Tile Maps And Data
69+
static readonly memoryLocationTileMapSelectZeroStart: i32 = 0x9800;
70+
static readonly memoryLocationTileMapSelectOneStart: i32 = 0x9c00;
71+
static readonly memoryLocationTileDataSelectZeroStart: i32 = 0x8800;
72+
static readonly memoryLocationTileDataSelectOneStart: i32 = 0x8000;
73+
74+
// Sprites
75+
static readonly memoryLocationSpriteAttributesTable: i32 = 0xfe00;
76+
77+
// Palettes
78+
static readonly memoryLocationBackgroundPalette: i32 = 0xff47;
79+
static readonly memoryLocationSpritePaletteOne: i32 = 0xff48;
80+
static readonly memoryLocationSpritePaletteTwo: i32 = 0xff49;
81+
82+
// Screen data needs to be stored in wasm memory
83+
84+
// Save States
85+
86+
static readonly saveStateSlot: i32 = 1;
87+
88+
// Function to save the state of the class
89+
static saveState(): void {
90+
store<i32>(getSaveStateMemoryOffset(0x00, Graphics.saveStateSlot), Graphics.scanlineCycleCounter);
91+
store<u8>(getSaveStateMemoryOffset(0x04, Graphics.saveStateSlot), <u8>Lcd.currentLcdMode);
92+
93+
eightBitStoreIntoGBMemory(Graphics.memoryLocationScanlineRegister, Graphics.scanlineRegister);
94+
}
95+
96+
// Function to load the save state from memory
97+
static loadState(): void {
98+
Graphics.scanlineCycleCounter = load<i32>(getSaveStateMemoryOffset(0x00, Graphics.saveStateSlot));
99+
Lcd.currentLcdMode = load<u8>(getSaveStateMemoryOffset(0x04, Graphics.saveStateSlot));
100+
101+
Graphics.scanlineRegister = eightBitLoadFromGBMemory(Graphics.memoryLocationScanlineRegister);
102+
Lcd.updateLcdControl(eightBitLoadFromGBMemory(Lcd.memoryLocationLcdControl));
103+
}
104+
}
105+
106+
// Batch Process Graphics
107+
// http://gameboy.mongenel.com/dmg/asmmemmap.html and http://gbdev.gg8.se/wiki/articles/Video_Display
108+
// Function to batch process our graphics after we skipped so many cycles
109+
// This is not currently checked in memory read/write
110+
export function batchProcessGraphics(): void {
111+
var batchProcessCycles = Graphics.batchProcessCycles();
112+
while (Graphics.currentCycles >= batchProcessCycles) {
113+
updateGraphics(batchProcessCycles);
114+
Graphics.currentCycles -= batchProcessCycles;
115+
}
116+
}
117+
118+
// Inlined because closure compiler inlines
119+
export function initializeGraphics(): void {
120+
// Reset Stateful Variables
121+
Graphics.currentCycles = 0;
122+
Graphics.scanlineCycleCounter = 0x00;
123+
Graphics.scanlineRegister = 0;
124+
Graphics.scrollX = 0;
125+
Graphics.scrollY = 0;
126+
Graphics.windowX = 0;
127+
Graphics.windowY = 0;
128+
129+
Graphics.scanlineRegister = 0x90;
130+
131+
if (Cpu.GBCEnabled) {
132+
eightBitStoreIntoGBMemory(0xff41, 0x81);
133+
// 0xFF42 -> 0xFF43 = 0x00
134+
eightBitStoreIntoGBMemory(0xff44, 0x90);
135+
// 0xFF45 -> 0xFF46 = 0x00
136+
eightBitStoreIntoGBMemory(0xff47, 0xfc);
137+
// 0xFF48 -> 0xFF4B = 0x00
138+
} else {
139+
eightBitStoreIntoGBMemory(0xff41, 0x85);
140+
// 0xFF42 -> 0xFF45 = 0x00
141+
eightBitStoreIntoGBMemory(0xff46, 0xff);
142+
eightBitStoreIntoGBMemory(0xff47, 0xfc);
143+
eightBitStoreIntoGBMemory(0xff48, 0xff);
144+
eightBitStoreIntoGBMemory(0xff49, 0xff);
145+
// 0xFF4A -> 0xFF4B = 0x00
146+
// GBC VRAM Banks (Handled by Memory, initializeCartridge)
147+
}
148+
149+
// Scanline
150+
// Bgb says LY is 90 on boot
151+
Graphics.scanlineRegister = 0x90;
152+
eightBitStoreIntoGBMemory(0xff40, 0x90);
153+
154+
// GBC VRAM Banks
155+
eightBitStoreIntoGBMemory(0xff4f, 0x00);
156+
eightBitStoreIntoGBMemory(0xff70, 0x01);
157+
158+
// Override/reset some variables if the boot ROM is enabled
159+
if (Cpu.BootROMEnabled) {
160+
if (Cpu.GBCEnabled) {
161+
// GBC
162+
Graphics.scanlineRegister = 0x00;
163+
eightBitStoreIntoGBMemory(0xff40, 0x00);
164+
eightBitStoreIntoGBMemory(0xff41, 0x80);
165+
eightBitStoreIntoGBMemory(0xff44, 0x00);
166+
} else {
167+
// GB
168+
Graphics.scanlineRegister = 0x00;
169+
eightBitStoreIntoGBMemory(0xff40, 0x00);
170+
eightBitStoreIntoGBMemory(0xff41, 0x84);
171+
}
172+
}
173+
174+
initializeColors();
175+
}
176+
177+
export function updateGraphics(numberOfCycles: i32): void {
178+
if (Lcd.enabled) {
179+
Graphics.scanlineCycleCounter += numberOfCycles;
180+
181+
let graphicsDisableScanlineRendering = Config.graphicsDisableScanlineRendering;
182+
183+
while (Graphics.scanlineCycleCounter >= Graphics.MAX_CYCLES_PER_SCANLINE()) {
184+
// Reset the scanlineCycleCounter
185+
// Don't set to zero to catch extra cycles
186+
Graphics.scanlineCycleCounter -= Graphics.MAX_CYCLES_PER_SCANLINE();
187+
188+
// Move to next scanline
189+
// let scanlineRegister: i32 = eightBitLoadFromGBMemory(Graphics.memoryLocationScanlineRegister);
190+
let scanlineRegister = Graphics.scanlineRegister;
191+
192+
// Check if we've reached the last scanline
193+
if (scanlineRegister === 144) {
194+
// Draw the scanline
195+
if (!graphicsDisableScanlineRendering) {
196+
_drawScanline(scanlineRegister);
197+
} else {
198+
_renderEntireFrame();
199+
}
200+
201+
// Clear the priority map
202+
clearPriorityMap();
203+
204+
// Reset the tile cache
205+
resetTileCache();
206+
} else if (scanlineRegister < 144) {
207+
// Draw the scanline
208+
if (!graphicsDisableScanlineRendering) {
209+
_drawScanline(scanlineRegister);
210+
}
211+
}
212+
213+
// Post increment the scanline register after drawing
214+
// TODO: Need to fix graphics timing
215+
if (scanlineRegister > 153) {
216+
// Check if we overflowed scanlines
217+
// if so, reset our scanline number
218+
scanlineRegister = 0;
219+
} else {
220+
scanlineRegister += 1;
221+
}
222+
223+
// Store our new scanline value
224+
Graphics.scanlineRegister = scanlineRegister;
225+
// eightBitStoreIntoGBMemory(Graphics.memoryLocationScanlineRegister, scanlineRegister);
226+
}
227+
}
228+
229+
// Games like Pokemon crystal want the vblank right as it turns to the value, and not have it increment after
230+
// It will break and lead to an infinite loop in crystal
231+
// Therefore, we want to be checking/Setting our LCD status after the scanline updates
232+
setLcdStatus();
233+
}
234+
235+
// TODO: Make this a _drawPixelOnScanline, as values can be updated while drawing a scanline
236+
function _drawScanline(scanlineRegister: i32): void {
237+
// Get our seleted tile data memory location
238+
let tileDataMemoryLocation = Graphics.memoryLocationTileDataSelectZeroStart;
239+
if (Lcd.bgWindowTileDataSelect) {
240+
tileDataMemoryLocation = Graphics.memoryLocationTileDataSelectOneStart;
241+
}
242+
243+
// Check if the background is enabled
244+
// NOTE: On Gameboy color, Pandocs says this does something completely different
245+
// LCDC.0 - 2) CGB in CGB Mode: BG and Window Master Priority
246+
// When Bit 0 is cleared, the background and window lose their priority -
247+
// the sprites will be always displayed on top of background and window,
248+
// independently of the priority flags in OAM and BG Map attributes.
249+
// TODO: Enable this different feature for GBC
250+
if (Cpu.GBCEnabled || Lcd.bgDisplayEnabled) {
251+
// Get our map memory location
252+
let tileMapMemoryLocation = Graphics.memoryLocationTileMapSelectZeroStart;
253+
if (Lcd.bgTileMapDisplaySelect) {
254+
tileMapMemoryLocation = Graphics.memoryLocationTileMapSelectOneStart;
255+
}
256+
257+
// Finally, pass everything to draw the background
258+
renderBackground(scanlineRegister, tileDataMemoryLocation, tileMapMemoryLocation);
259+
}
260+
261+
// Check if the window is enabled, and we are currently
262+
// Drawing lines on the window
263+
if (Lcd.windowDisplayEnabled) {
264+
// Get our map memory location
265+
let tileMapMemoryLocation = Graphics.memoryLocationTileMapSelectZeroStart;
266+
if (Lcd.windowTileMapDisplaySelect) {
267+
tileMapMemoryLocation = Graphics.memoryLocationTileMapSelectOneStart;
268+
}
269+
270+
// Finally, pass everything to draw the background
271+
renderWindow(scanlineRegister, tileDataMemoryLocation, tileMapMemoryLocation);
272+
}
273+
274+
if (Lcd.spriteDisplayEnable) {
275+
// Sprites are enabled, render them!
276+
renderSprites(scanlineRegister, Lcd.tallSpriteSize);
277+
}
278+
}
279+
280+
// Function to render everything for a frame at once
281+
// This is to improve performance
282+
// See above for comments on how things are donw
283+
function _renderEntireFrame(): void {
284+
// Scanline needs to be in sync while we draw, thus, we can't shortcut anymore than here
285+
for (let i = 0; i <= 144; ++i) {
286+
_drawScanline(<u8>i);
287+
}
288+
}
289+
290+
// Function to get the start of a RGB pixel (R, G, B)
291+
// Inlined because closure compiler inlines
292+
export function getRgbPixelStart(x: i32, y: i32): i32 {
293+
// Get the pixel number
294+
// let pixelNumber: i32 = (y * 160) + x;
295+
// Each pixel takes 3 slots, therefore, multiply by 3!
296+
return (y * 160 + x) * 3;
297+
}
298+
299+
// Also need to store current frame in memory to be read by JS
300+
export function setPixelOnFrame(x: i32, y: i32, colorId: i32, color: i32): void {
301+
// Currently only supports 160x144
302+
// Storing in X, then y
303+
// So need an offset
304+
store<u8>(FRAME_LOCATION + getRgbPixelStart(x, y) + colorId, color);
305+
}
306+
307+
// Function to shortcut the memory map, and load directly from the VRAM Bank
308+
export function loadFromVramBank(gameboyOffset: i32, vramBankId: i32): u8 {
309+
let wasmBoyAddress = gameboyOffset - Memory.videoRamLocation + GAMEBOY_INTERNAL_MEMORY_LOCATION + 0x2000 * (vramBankId & 0x01);
310+
return load<u8>(wasmBoyAddress);
311+
}

core/graphics-old/index.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
export { Graphics, batchProcessGraphics, initializeGraphics, updateGraphics, loadFromVramBank } from './graphics';
2+
3+
export { Lcd } from './lcd';
4+
5+
export { getRedFromHexColor, getGreenFromHexColor, getBlueFromHexColor } from './colors';
6+
7+
export {
8+
Palette,
9+
initializePalette,
10+
writeColorPaletteToMemory,
11+
getMonochromeColorFromPalette,
12+
getColorizedGbHexColorFromPalette,
13+
getRgbColorFromPalette,
14+
getColorComponentFromRgb
15+
} from './palette';
16+
17+
export { getTileDataAddress, drawPixelsFromLineOfTile } from './tiles';

0 commit comments

Comments
 (0)