src/gb/video.c (view raw)
1/* Copyright (c) 2013-2016 Jeffrey Pfau
2 *
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6#include "video.h"
7
8#include "core/sync.h"
9#include "core/thread.h"
10#include "gb/gb.h"
11#include "gb/io.h"
12
13#include "util/memory.h"
14
15static void GBVideoDummyRendererInit(struct GBVideoRenderer* renderer, enum GBModel model);
16static void GBVideoDummyRendererDeinit(struct GBVideoRenderer* renderer);
17static uint8_t GBVideoDummyRendererWriteVideoRegister(struct GBVideoRenderer* renderer, uint16_t address, uint8_t value);
18static void GBVideoDummyRendererWritePalette(struct GBVideoRenderer* renderer, int index, uint16_t value);
19static void GBVideoDummyRendererDrawRange(struct GBVideoRenderer* renderer, int startX, int endX, int y, struct GBObj** obj, size_t oamMax);
20static void GBVideoDummyRendererFinishScanline(struct GBVideoRenderer* renderer, int y);
21static void GBVideoDummyRendererFinishFrame(struct GBVideoRenderer* renderer);
22static void GBVideoDummyRendererGetPixels(struct GBVideoRenderer* renderer, unsigned* stride, const void** pixels);
23
24static void _cleanOAM(struct GBVideo* video, int y);
25
26static struct GBVideoRenderer dummyRenderer = {
27 .init = GBVideoDummyRendererInit,
28 .deinit = GBVideoDummyRendererDeinit,
29 .writeVideoRegister = GBVideoDummyRendererWriteVideoRegister,
30 .writePalette = GBVideoDummyRendererWritePalette,
31 .drawRange = GBVideoDummyRendererDrawRange,
32 .finishScanline = GBVideoDummyRendererFinishScanline,
33 .finishFrame = GBVideoDummyRendererFinishFrame,
34 .getPixels = GBVideoDummyRendererGetPixels
35};
36
37void GBVideoInit(struct GBVideo* video) {
38 video->renderer = &dummyRenderer;
39 video->vram = 0;
40 video->frameskip = 0;
41}
42
43void GBVideoReset(struct GBVideo* video) {
44 video->ly = 0;
45 video->mode = 1;
46 video->stat = 1;
47
48 video->nextEvent = INT_MAX;
49 video->eventDiff = 0;
50
51 video->nextMode = INT_MAX;
52 video->dotCounter = INT_MIN;
53 video->nextFrame = INT_MAX;
54
55 video->frameCounter = 0;
56 video->frameskipCounter = 0;
57
58 if (video->vram) {
59 mappedMemoryFree(video->vram, GB_SIZE_VRAM);
60 }
61 video->vram = anonymousMemoryMap(GB_SIZE_VRAM);
62 GBVideoSwitchBank(video, 0);
63 video->renderer->vram = video->vram;
64 memset(&video->oam, 0, sizeof(video->oam));
65 video->renderer->oam = &video->oam;
66 memset(&video->palette, 0, sizeof(video->palette));
67
68 video->renderer->deinit(video->renderer);
69 video->renderer->init(video->renderer, video->p->model);
70}
71
72void GBVideoDeinit(struct GBVideo* video) {
73 GBVideoAssociateRenderer(video, &dummyRenderer);
74 mappedMemoryFree(video->vram, GB_SIZE_VRAM);
75}
76
77void GBVideoAssociateRenderer(struct GBVideo* video, struct GBVideoRenderer* renderer) {
78 video->renderer->deinit(video->renderer);
79 video->renderer = renderer;
80 renderer->vram = video->vram;
81 video->renderer->init(video->renderer, video->p->model);
82}
83
84int32_t GBVideoProcessEvents(struct GBVideo* video, int32_t cycles) {
85 video->eventDiff += cycles;
86 if (video->nextEvent != INT_MAX) {
87 video->nextEvent -= cycles;
88 }
89 if (video->nextEvent <= 0) {
90 if (video->nextEvent != INT_MAX) {
91 video->nextMode -= video->eventDiff;
92 video->nextFrame -= video->eventDiff;
93 }
94 video->nextEvent = INT_MAX;
95 GBVideoProcessDots(video);
96 if (video->nextMode <= 0) {
97 int lyc = video->p->memory.io[REG_LYC];
98 switch (video->mode) {
99 case 0:
100 if (video->frameskipCounter <= 0) {
101 video->renderer->finishScanline(video->renderer, video->ly);
102 }
103 ++video->ly;
104 video->p->memory.io[REG_LY] = video->ly;
105 video->stat = GBRegisterSTATSetLYC(video->stat, lyc == video->ly);
106 if (video->ly < GB_VIDEO_VERTICAL_PIXELS) {
107 video->nextMode = GB_VIDEO_MODE_2_LENGTH;
108 video->mode = 2;
109 if (!GBRegisterSTATIsHblankIRQ(video->stat) && GBRegisterSTATIsOAMIRQ(video->stat)) {
110 video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT);
111 }
112 } else {
113 video->nextMode = GB_VIDEO_HORIZONTAL_LENGTH;
114 video->mode = 1;
115 --video->frameskipCounter;
116 if (video->frameskipCounter < 0) {
117 video->renderer->finishFrame(video->renderer);
118 mCoreSyncPostFrame(video->p->sync);
119 video->frameskipCounter = video->frameskip;
120 }
121 ++video->frameCounter;
122
123 if (video->nextFrame != 0) {
124 video->nextFrame = 0;
125 }
126
127 if (video->p->stream && video->p->stream->postVideoFrame) {
128 const color_t* pixels;
129 unsigned stride;
130 video->renderer->getPixels(video->renderer, &stride, (const void**) &pixels);
131 video->p->stream->postVideoFrame(video->p->stream, pixels, stride);
132 }
133
134 if (GBRegisterSTATIsVblankIRQ(video->stat) || GBRegisterSTATIsOAMIRQ(video->stat)) {
135 video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT);
136 }
137 video->p->memory.io[REG_IF] |= (1 << GB_IRQ_VBLANK);
138 }
139 if (GBRegisterSTATIsLYCIRQ(video->stat) && lyc == video->ly) {
140 video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT);
141 }
142 GBUpdateIRQs(video->p);
143 break;
144 case 1:
145 // TODO: One M-cycle delay
146 ++video->ly;
147 if (video->ly == GB_VIDEO_VERTICAL_TOTAL_PIXELS + 1) {
148 video->ly = 0;
149 video->p->memory.io[REG_LY] = video->ly;
150 video->nextMode = GB_VIDEO_MODE_2_LENGTH;
151 video->mode = 2;
152 if (GBRegisterSTATIsOAMIRQ(video->stat)) {
153 video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT);
154 GBUpdateIRQs(video->p);
155 }
156 break;
157 } else if (video->ly == GB_VIDEO_VERTICAL_TOTAL_PIXELS) {
158 video->p->memory.io[REG_LY] = 0;
159 video->nextMode = GB_VIDEO_HORIZONTAL_LENGTH - 8;
160 } else if (video->ly == GB_VIDEO_VERTICAL_TOTAL_PIXELS - 1) {
161 video->p->memory.io[REG_LY] = video->ly;
162 video->nextMode = 8;
163 } else {
164 video->p->memory.io[REG_LY] = video->ly;
165 video->nextMode = GB_VIDEO_HORIZONTAL_LENGTH;
166 }
167
168 video->stat = GBRegisterSTATSetLYC(video->stat, lyc == video->p->memory.io[REG_LY]);
169 if (GBRegisterSTATIsLYCIRQ(video->stat) && lyc == video->p->memory.io[REG_LY]) {
170 video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT);
171 GBUpdateIRQs(video->p);
172 }
173 if (video->p->memory.mbcType == GB_MBC7 && video->p->memory.rotation && video->p->memory.rotation->sample) {
174 video->p->memory.rotation->sample(video->p->memory.rotation);
175 }
176 break;
177 case 2:
178 _cleanOAM(video, video->ly);
179 video->dotCounter = 0;
180 video->nextEvent = GB_VIDEO_HORIZONTAL_LENGTH;
181 video->x = 0;
182 video->nextMode = GB_VIDEO_MODE_3_LENGTH_BASE + video->objMax * 12;
183 video->mode = 3;
184 break;
185 case 3:
186 video->nextMode = GB_VIDEO_MODE_0_LENGTH_BASE - video->objMax * 12;
187 video->mode = 0;
188 if (GBRegisterSTATIsHblankIRQ(video->stat)) {
189 video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT);
190 GBUpdateIRQs(video->p);
191 }
192 if (video->ly < GB_VIDEO_VERTICAL_PIXELS && video->p->memory.isHdma && video->p->memory.io[REG_HDMA5] != 0xFF) {
193 video->p->memory.hdmaRemaining = 0x10;
194 video->p->memory.hdmaNext = video->p->cpu->cycles;
195 }
196 break;
197 }
198 video->stat = GBRegisterSTATSetMode(video->stat, video->mode);
199 video->p->memory.io[REG_STAT] = video->stat;
200 }
201 if (video->nextFrame <= 0) {
202 if (video->p->cpu->executionState == LR35902_CORE_FETCH) {
203 GBFrameEnded(video->p);
204 struct mCoreThread* thread = mCoreThreadGet();
205 if (thread && thread->frameCallback) {
206 thread->frameCallback(thread);
207 }
208 video->nextFrame = GB_VIDEO_TOTAL_LENGTH;
209 } else {
210 video->nextFrame = 4 - ((video->p->cpu->executionState + 1) & 3);
211 if (video->nextFrame < video->nextEvent) {
212 video->nextEvent = video->nextFrame;
213 }
214 }
215 }
216 if (video->nextMode < video->nextEvent) {
217 video->nextEvent = video->nextMode;
218 }
219 video->eventDiff = 0;
220 }
221 return video->nextEvent;
222}
223
224static void _cleanOAM(struct GBVideo* video, int y) {
225 // TODO: GBC differences
226 // TODO: Optimize
227 video->objMax = 0;
228 int spriteHeight = 8;
229 if (GBRegisterLCDCIsObjSize(video->p->memory.io[REG_LCDC])) {
230 spriteHeight = 16;
231 }
232 int o = 0;
233 int i;
234 for (i = 0; i < 40; ++i) {
235 uint8_t oy = video->oam.obj[i].y;
236 if (y < oy - 16 || y >= oy - 16 + spriteHeight) {
237 continue;
238 }
239 // TODO: Sort
240 video->objThisLine[o] = &video->oam.obj[i];
241 ++o;
242 if (o == 10) {
243 break;
244 }
245 }
246 video->objMax = o;
247}
248
249void GBVideoProcessDots(struct GBVideo* video) {
250 if (video->mode != 3 || video->dotCounter < 0) {
251 return;
252 }
253 int oldX = video->x;
254 video->x = video->dotCounter + video->eventDiff + (video->p->cpu->cycles >> video->p->doubleSpeed);
255 if (video->x > GB_VIDEO_HORIZONTAL_PIXELS) {
256 video->x = GB_VIDEO_HORIZONTAL_PIXELS;
257 }
258 if (video->x == GB_VIDEO_HORIZONTAL_PIXELS) {
259 video->dotCounter = INT_MIN;
260 }
261 if (video->frameskipCounter <= 0) {
262 video->renderer->drawRange(video->renderer, oldX, video->x, video->ly, video->objThisLine, video->objMax);
263 }
264}
265
266void GBVideoWriteLCDC(struct GBVideo* video, GBRegisterLCDC value) {
267 if (!GBRegisterLCDCIsEnable(video->p->memory.io[REG_LCDC]) && GBRegisterLCDCIsEnable(value)) {
268 video->mode = 2;
269 video->nextMode = GB_VIDEO_MODE_2_LENGTH - 5; // TODO: Why is this fudge factor needed? Might be related to T-cycles for load/store differing
270 video->nextEvent = video->nextMode;
271 video->eventDiff = -video->p->cpu->cycles >> video->p->doubleSpeed;
272 video->ly = 0;
273 video->p->memory.io[REG_LY] = 0;
274 // TODO: Does this read as 0 for 4 T-cycles?
275 video->stat = GBRegisterSTATSetMode(video->stat, 2);
276 video->stat = GBRegisterSTATSetLYC(video->stat, video->ly == video->p->memory.io[REG_LYC]);
277 if (GBRegisterSTATIsLYCIRQ(video->stat) && video->ly == video->p->memory.io[REG_LYC]) {
278 video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT);
279 GBUpdateIRQs(video->p);
280 }
281 video->p->memory.io[REG_STAT] = video->stat;
282
283 if (video->p->cpu->cycles + (video->nextEvent << video->p->doubleSpeed) < video->p->cpu->nextEvent) {
284 video->p->cpu->nextEvent = video->p->cpu->cycles + (video->nextEvent << video->p->doubleSpeed);
285 }
286 return;
287 }
288 if (GBRegisterLCDCIsEnable(video->p->memory.io[REG_LCDC]) && !GBRegisterLCDCIsEnable(value)) {
289 video->mode = 0;
290 video->nextMode = INT_MAX;
291 video->nextEvent = INT_MAX;
292 video->stat = GBRegisterSTATSetMode(video->stat, video->mode);
293 video->p->memory.io[REG_STAT] = video->stat;
294 video->ly = 0;
295 video->p->memory.io[REG_LY] = 0;
296 }
297}
298
299void GBVideoWriteSTAT(struct GBVideo* video, GBRegisterSTAT value) {
300 video->stat = (video->stat & 0x7) | (value & 0x78);
301 if (video->p->model == GB_MODEL_DMG && video->mode == 1) {
302 video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT);
303 GBUpdateIRQs(video->p);
304 }
305}
306
307void GBVideoWritePalette(struct GBVideo* video, uint16_t address, uint8_t value) {
308 static const uint16_t dmgPalette[4] = { 0x7FFF, 0x56B5, 0x294A, 0x0000};
309 if (video->p->model < GB_MODEL_CGB) {
310 switch (address) {
311 case REG_BGP:
312 video->palette[0] = dmgPalette[value & 3];
313 video->palette[1] = dmgPalette[(value >> 2) & 3];
314 video->palette[2] = dmgPalette[(value >> 4) & 3];
315 video->palette[3] = dmgPalette[(value >> 6) & 3];
316 video->renderer->writePalette(video->renderer, 0, video->palette[0]);
317 video->renderer->writePalette(video->renderer, 1, video->palette[1]);
318 video->renderer->writePalette(video->renderer, 2, video->palette[2]);
319 video->renderer->writePalette(video->renderer, 3, video->palette[3]);
320 break;
321 case REG_OBP0:
322 video->palette[8 * 4 + 0] = dmgPalette[value & 3];
323 video->palette[8 * 4 + 1] = dmgPalette[(value >> 2) & 3];
324 video->palette[8 * 4 + 2] = dmgPalette[(value >> 4) & 3];
325 video->palette[8 * 4 + 3] = dmgPalette[(value >> 6) & 3];
326 video->renderer->writePalette(video->renderer, 8 * 4 + 0, video->palette[8 * 4 + 0]);
327 video->renderer->writePalette(video->renderer, 8 * 4 + 1, video->palette[8 * 4 + 1]);
328 video->renderer->writePalette(video->renderer, 8 * 4 + 2, video->palette[8 * 4 + 2]);
329 video->renderer->writePalette(video->renderer, 8 * 4 + 3, video->palette[8 * 4 + 3]);
330 break;
331 case REG_OBP1:
332 video->palette[9 * 4 + 0] = dmgPalette[value & 3];
333 video->palette[9 * 4 + 1] = dmgPalette[(value >> 2) & 3];
334 video->palette[9 * 4 + 2] = dmgPalette[(value >> 4) & 3];
335 video->palette[9 * 4 + 3] = dmgPalette[(value >> 6) & 3];
336 video->renderer->writePalette(video->renderer, 9 * 4 + 0, video->palette[9 * 4 + 0]);
337 video->renderer->writePalette(video->renderer, 9 * 4 + 1, video->palette[9 * 4 + 1]);
338 video->renderer->writePalette(video->renderer, 9 * 4 + 2, video->palette[9 * 4 + 2]);
339 video->renderer->writePalette(video->renderer, 9 * 4 + 3, video->palette[9 * 4 + 3]);
340 break;
341 }
342 } else {
343 switch (address) {
344 case REG_BCPD:
345 if (video->bcpIndex & 1) {
346 video->palette[video->bcpIndex >> 1] &= 0x00FF;
347 video->palette[video->bcpIndex >> 1] |= value << 8;
348 } else {
349 video->palette[video->bcpIndex >> 1] &= 0xFF00;
350 video->palette[video->bcpIndex >> 1] |= value;
351 }
352 video->renderer->writePalette(video->renderer, video->bcpIndex >> 1, video->palette[video->bcpIndex >> 1]);
353 if (video->bcpIncrement) {
354 ++video->bcpIndex;
355 video->bcpIndex &= 0x3F;
356 video->p->memory.io[REG_BCPD] = video->palette[video->bcpIndex >> 1];
357 }
358 break;
359 case REG_OCPD:
360 if (video->ocpIndex & 1) {
361 video->palette[8 * 4 + (video->ocpIndex >> 1)] &= 0x00FF;
362 video->palette[8 * 4 + (video->ocpIndex >> 1)] |= value << 8;
363 } else {
364 video->palette[8 * 4 + (video->ocpIndex >> 1)] &= 0xFF00;
365 video->palette[8 * 4 + (video->ocpIndex >> 1)] |= value;
366 }
367 video->renderer->writePalette(video->renderer, 8 * 4 + (video->ocpIndex >> 1), video->palette[8 * 4 + (video->ocpIndex >> 1)]);
368 if (video->ocpIncrement) {
369 ++video->ocpIndex;
370 video->ocpIndex &= 0x3F;
371 video->p->memory.io[REG_OCPD] = video->palette[8 * 4 + (video->ocpIndex >> 1)];
372 }
373 break;
374 }
375 }
376}
377
378void GBVideoSwitchBank(struct GBVideo* video, uint8_t value) {
379 value &= 1;
380 video->vramBank = &video->vram[value * GB_SIZE_VRAM_BANK0];
381 video->vramCurrentBank = value;
382}
383
384static void GBVideoDummyRendererInit(struct GBVideoRenderer* renderer, enum GBModel model) {
385 UNUSED(renderer);
386 UNUSED(model);
387 // Nothing to do
388}
389
390static void GBVideoDummyRendererDeinit(struct GBVideoRenderer* renderer) {
391 UNUSED(renderer);
392 // Nothing to do
393}
394
395static uint8_t GBVideoDummyRendererWriteVideoRegister(struct GBVideoRenderer* renderer, uint16_t address, uint8_t value) {
396 UNUSED(renderer);
397 UNUSED(address);
398 return value;
399}
400
401static void GBVideoDummyRendererWritePalette(struct GBVideoRenderer* renderer, int index, uint16_t value) {
402 UNUSED(renderer);
403 UNUSED(index);
404 UNUSED(value);
405}
406
407static void GBVideoDummyRendererDrawRange(struct GBVideoRenderer* renderer, int startX, int endX, int y, struct GBObj** obj, size_t oamMax) {
408 UNUSED(renderer);
409 UNUSED(endX);
410 UNUSED(startX);
411 UNUSED(y);
412 UNUSED(obj);
413 UNUSED(oamMax);
414 // Nothing to do
415}
416
417static void GBVideoDummyRendererFinishScanline(struct GBVideoRenderer* renderer, int y) {
418 UNUSED(renderer);
419 UNUSED(y);
420 // Nothing to do
421}
422
423static void GBVideoDummyRendererFinishFrame(struct GBVideoRenderer* renderer) {
424 UNUSED(renderer);
425 // Nothing to do
426}
427
428static void GBVideoDummyRendererGetPixels(struct GBVideoRenderer* renderer, unsigned* stride, const void** pixels) {
429 UNUSED(renderer);
430 UNUSED(stride);
431 UNUSED(pixels);
432 // Nothing to do
433}