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 "gb/gb.h"
9#include "gb/io.h"
10
11#include "util/memory.h"
12
13static void GBVideoDummyRendererInit(struct GBVideoRenderer* renderer);
14static void GBVideoDummyRendererReset(struct GBVideoRenderer* renderer);
15static void GBVideoDummyRendererDeinit(struct GBVideoRenderer* renderer);
16static uint8_t GBVideoDummyRendererWriteVideoRegister(struct GBVideoRenderer* renderer, uint16_t address, uint8_t value);
17static void GBVideoDummyRendererWriteVRAM(struct GBVideoRenderer* renderer, uint16_t address);
18static void GBVideoDummyRendererWriteOAM(struct GBVideoRenderer* renderer, uint8_t oam);
19static void GBVideoDummyRendererDrawScanline(struct GBVideoRenderer* renderer, int y);
20static void GBVideoDummyRendererFinishFrame(struct GBVideoRenderer* renderer);
21static void GBVideoDummyRendererGetPixels(struct GBVideoRenderer* renderer, unsigned* stride, const void** pixels);
22
23static struct GBVideoRenderer dummyRenderer = {
24 .init = GBVideoDummyRendererInit,
25 .reset = GBVideoDummyRendererReset,
26 .deinit = GBVideoDummyRendererDeinit,
27 .writeVideoRegister = GBVideoDummyRendererWriteVideoRegister,
28 .writeVRAM = GBVideoDummyRendererWriteVRAM,
29 .writeOAM = GBVideoDummyRendererWriteOAM,
30 .drawScanline = GBVideoDummyRendererDrawScanline,
31 .finishFrame = GBVideoDummyRendererFinishFrame,
32 .getPixels = GBVideoDummyRendererGetPixels
33};
34
35void GBVideoInit(struct GBVideo* video) {
36 video->renderer = &dummyRenderer;
37 video->vram = 0;
38 video->frameskip = 0;
39}
40
41void GBVideoReset(struct GBVideo* video) {
42 video->ly = 0;
43 video->mode = 1;
44
45 video->nextEvent = INT_MAX;
46 video->eventDiff = 0;
47
48 video->nextMode = INT_MAX;
49
50 video->frameCounter = 0;
51 video->frameskipCounter = 0;
52
53 if (video->vram) {
54 mappedMemoryFree(video->vram, GB_SIZE_VRAM);
55 }
56 video->vram = anonymousMemoryMap(GB_SIZE_VRAM);
57 video->renderer->vram = video->vram;
58 memset(&video->oam, 0, sizeof(video->oam));
59 video->renderer->oam = &video->oam;
60
61 video->renderer->deinit(video->renderer);
62 video->renderer->init(video->renderer);
63}
64
65void GBVideoDeinit(struct GBVideo* video) {
66 GBVideoAssociateRenderer(video, &dummyRenderer);
67 mappedMemoryFree(video->vram, GB_SIZE_VRAM);
68}
69
70void GBVideoAssociateRenderer(struct GBVideo* video, struct GBVideoRenderer* renderer) {
71 video->renderer->deinit(video->renderer);
72 video->renderer = renderer;
73 renderer->vram = video->vram;
74 video->renderer->init(video->renderer);
75}
76
77int32_t GBVideoProcessEvents(struct GBVideo* video, int32_t cycles) {
78 video->eventDiff += cycles;
79 if (video->nextEvent != INT_MAX) {
80 video->nextEvent -= cycles;
81 }
82 if (video->nextEvent <= 0) {
83 if (video->nextEvent != INT_MAX) {
84 video->nextMode -= video->eventDiff;
85 }
86 if (video->nextMode <= 0) {
87 switch (video->mode) {
88 case 0:
89 ++video->ly;
90 video->p->memory.io[REG_LY] = video->ly;
91 if (video->ly < GB_VIDEO_VERTICAL_PIXELS) {
92 video->renderer->drawScanline(video->renderer, video->ly);
93 video->nextMode = GB_VIDEO_MODE_2_LENGTH;
94 video->mode = 2;
95 if (GBRegisterSTATIsOAMIRQ(video->stat)) {
96 video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT);
97 GBUpdateIRQs(video->p);
98 }
99 } else {
100 video->nextMode = GB_VIDEO_HORIZONTAL_LENGTH;
101 video->mode = 1;
102 ++video->frameCounter;
103 video->renderer->finishFrame(video->renderer);
104 if (GBRegisterSTATIsVblankIRQ(video->stat)) {
105 video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT);
106 }
107 video->p->memory.io[REG_IF] |= (1 << GB_IRQ_VBLANK);
108 GBUpdateIRQs(video->p);
109 }
110 break;
111 case 1:
112 ++video->ly;
113 if (video->ly >= GB_VIDEO_VERTICAL_TOTAL_PIXELS) {
114 video->ly = 0;
115 video->renderer->drawScanline(video->renderer, video->ly);
116 video->nextMode = GB_VIDEO_MODE_2_LENGTH;
117 video->mode = 2;
118 if (GBRegisterSTATIsOAMIRQ(video->stat)) {
119 video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT);
120 GBUpdateIRQs(video->p);
121 }
122 } else {
123 video->nextMode = GB_VIDEO_HORIZONTAL_LENGTH;
124 }
125 video->p->memory.io[REG_LY] = video->ly;
126 break;
127 case 2:
128 video->nextMode = GB_VIDEO_MODE_3_LENGTH;
129 video->mode = 3;
130 break;
131 case 3:
132 video->nextMode = GB_VIDEO_MODE_0_LENGTH;
133 video->mode = 0;
134 if (GBRegisterSTATIsHblankIRQ(video->stat)) {
135 video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT);
136 GBUpdateIRQs(video->p);
137 }
138 break;
139 }
140 video->stat = GBRegisterSTATSetMode(video->stat, video->mode);
141 video->p->memory.io[REG_STAT] = video->stat;
142 }
143
144 video->nextEvent = video->nextMode;
145 video->eventDiff = 0;
146 }
147 return video->nextEvent;
148}
149
150void GBVideoWriteLCDC(struct GBVideo* video, GBRegisterLCDC value) {
151 if (!GBRegisterLCDCIsEnable(video->p->memory.io[REG_LCDC]) && GBRegisterLCDCIsEnable(value)) {
152 // TODO: Does enabling the LCD start in vblank?
153 video->mode = 2;
154 video->nextMode = GB_VIDEO_MODE_2_LENGTH;
155 video->nextEvent = video->nextMode;
156 video->eventDiff = 0;
157 video->stat = GBRegisterSTATSetMode(video->stat, video->mode);
158 video->p->memory.io[REG_STAT] = video->stat;
159 video->eventDiff = 0;
160 if (video->p->cpu->cycles + video->nextEvent < video->p->cpu->nextEvent) {
161 video->p->cpu->nextEvent = video->p->cpu->cycles + video->nextEvent;
162 }
163 return;
164 }
165 if (GBRegisterLCDCIsEnable(video->p->memory.io[REG_LCDC]) && !GBRegisterLCDCIsEnable(value)) {
166 video->mode = 0;
167 video->nextMode = INT_MAX;
168 video->nextEvent = INT_MAX;
169 video->stat = GBRegisterSTATSetMode(video->stat, video->mode);
170 video->p->memory.io[REG_STAT] = video->stat;
171 video->ly = 0;
172 video->p->memory.io[REG_LY] = 0;
173 }
174}
175
176void GBVideoWriteSTAT(struct GBVideo* video, GBRegisterSTAT value) {
177 video->stat = (video->stat & 0x7) | (value & 0x78);
178}
179
180static void GBVideoDummyRendererInit(struct GBVideoRenderer* renderer) {
181 UNUSED(renderer);
182 // Nothing to do
183}
184
185static void GBVideoDummyRendererReset(struct GBVideoRenderer* renderer) {
186 UNUSED(renderer);
187 // Nothing to do
188}
189
190static void GBVideoDummyRendererDeinit(struct GBVideoRenderer* renderer) {
191 UNUSED(renderer);
192 // Nothing to do
193}
194
195static uint8_t GBVideoDummyRendererWriteVideoRegister(struct GBVideoRenderer* renderer, uint16_t address, uint8_t value) {
196 UNUSED(renderer);
197 UNUSED(address);
198 return value;
199}
200
201static void GBVideoDummyRendererWriteVRAM(struct GBVideoRenderer* renderer, uint16_t address) {
202 UNUSED(renderer);
203 UNUSED(address);
204 // Nothing to do
205}
206
207static void GBVideoDummyRendererWriteOAM(struct GBVideoRenderer* renderer, uint8_t oam) {
208 UNUSED(renderer);
209 UNUSED(oam);
210 // Nothing to do
211}
212
213static void GBVideoDummyRendererDrawScanline(struct GBVideoRenderer* renderer, int y) {
214 UNUSED(renderer);
215 UNUSED(y);
216 // Nothing to do
217}
218
219static void GBVideoDummyRendererFinishFrame(struct GBVideoRenderer* renderer) {
220 UNUSED(renderer);
221 // Nothing to do
222}
223
224static void GBVideoDummyRendererGetPixels(struct GBVideoRenderer* renderer, unsigned* stride, const void** pixels) {
225 UNUSED(renderer);
226 UNUSED(stride);
227 UNUSED(pixels);
228 // Nothing to do
229}