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