src/gba/video.c (view raw)
1/* Copyright (c) 2013-2015 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 <mgba/internal/gba/video.h>
7
8#include <mgba/core/sync.h>
9#include <mgba/core/tile-cache.h>
10#include <mgba/internal/arm/macros.h>
11#include <mgba/internal/gba/dma.h>
12#include <mgba/internal/gba/gba.h>
13#include <mgba/internal/gba/io.h>
14#include <mgba/internal/gba/serialize.h>
15
16#include <mgba-util/memory.h>
17
18mLOG_DEFINE_CATEGORY(GBA_VIDEO, "GBA Video", "gba.video");
19
20static void GBAVideoDummyRendererInit(struct GBAVideoRenderer* renderer);
21static void GBAVideoDummyRendererReset(struct GBAVideoRenderer* renderer);
22static void GBAVideoDummyRendererDeinit(struct GBAVideoRenderer* renderer);
23static uint16_t GBAVideoDummyRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
24static void GBAVideoDummyRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address);
25static void GBAVideoDummyRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
26static void GBAVideoDummyRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam);
27static void GBAVideoDummyRendererDrawScanline(struct GBAVideoRenderer* renderer, int y);
28static void GBAVideoDummyRendererFinishFrame(struct GBAVideoRenderer* renderer);
29static void GBAVideoDummyRendererGetPixels(struct GBAVideoRenderer* renderer, size_t* stride, const void** pixels);
30static void GBAVideoDummyRendererPutPixels(struct GBAVideoRenderer* renderer, size_t stride, const void* pixels);
31
32static void _startHblank(struct mTiming*, void* context, uint32_t cyclesLate);
33static void _startHdraw(struct mTiming*, void* context, uint32_t cyclesLate);
34
35const int GBAVideoObjSizes[16][2] = {
36 { 8, 8 },
37 { 16, 16 },
38 { 32, 32 },
39 { 64, 64 },
40 { 16, 8 },
41 { 32, 8 },
42 { 32, 16 },
43 { 64, 32 },
44 { 8, 16 },
45 { 8, 32 },
46 { 16, 32 },
47 { 32, 64 },
48 { 0, 0 },
49 { 0, 0 },
50 { 0, 0 },
51 { 0, 0 },
52};
53
54static struct GBAVideoRenderer dummyRenderer = {
55 .init = GBAVideoDummyRendererInit,
56 .reset = GBAVideoDummyRendererReset,
57 .deinit = GBAVideoDummyRendererDeinit,
58 .writeVideoRegister = GBAVideoDummyRendererWriteVideoRegister,
59 .writeVRAM = GBAVideoDummyRendererWriteVRAM,
60 .writePalette = GBAVideoDummyRendererWritePalette,
61 .writeOAM = GBAVideoDummyRendererWriteOAM,
62 .drawScanline = GBAVideoDummyRendererDrawScanline,
63 .finishFrame = GBAVideoDummyRendererFinishFrame,
64 .getPixels = GBAVideoDummyRendererGetPixels,
65 .putPixels = GBAVideoDummyRendererPutPixels,
66};
67
68void GBAVideoInit(struct GBAVideo* video) {
69 video->renderer = &dummyRenderer;
70 video->renderer->cache = NULL;
71 video->vram = 0;
72 video->frameskip = 0;
73 video->event.name = "GBA Video";
74 video->event.callback = NULL;
75 video->event.context = video;
76 video->event.priority = 8;
77}
78
79void GBAVideoReset(struct GBAVideo* video) {
80 if (video->p->memory.fullBios) {
81 video->vcount = 0;
82 } else {
83 // TODO: Verify exact scanline hardware
84 video->vcount = 0x7E;
85 }
86 video->p->memory.io[REG_VCOUNT >> 1] = video->vcount;
87
88 video->event.callback = _startHblank;
89 mTimingSchedule(&video->p->timing, &video->event, VIDEO_HDRAW_LENGTH);
90
91 video->frameCounter = 0;
92 video->frameskipCounter = 0;
93
94 if (video->vram) {
95 mappedMemoryFree(video->vram, SIZE_VRAM);
96 }
97 video->vram = anonymousMemoryMap(SIZE_VRAM);
98 memset(video->renderer->vramBG, 0, sizeof(video->renderer->vramBG));
99 video->renderer->vramBG[0] = &video->vram[0x0000];
100 video->renderer->vramBG[1] = &video->vram[0x2000];
101 video->renderer->vramBG[2] = &video->vram[0x4000];
102 video->renderer->vramBG[3] = &video->vram[0x6000];
103 memset(video->renderer->vramOBJ, 0, sizeof(video->renderer->vramOBJ));
104 video->renderer->vramOBJ[0] = &video->vram[0x8000];
105 video->renderer->vramOBJ[1] = &video->vram[0xA000];
106
107 memset(video->palette, 0, sizeof(video->palette));
108 memset(video->oam.raw, 0, sizeof(video->oam.raw));
109
110 video->renderer->deinit(video->renderer);
111 video->renderer->init(video->renderer);
112}
113
114void GBAVideoDeinit(struct GBAVideo* video) {
115 GBAVideoAssociateRenderer(video, &dummyRenderer);
116 mappedMemoryFree(video->vram, SIZE_VRAM);
117}
118
119void GBAVideoAssociateRenderer(struct GBAVideo* video, struct GBAVideoRenderer* renderer) {
120 video->renderer->deinit(video->renderer);
121 renderer->cache = video->renderer->cache;
122 video->renderer = renderer;
123 renderer->palette = video->palette;
124 memset(renderer->vramBG, 0, sizeof(renderer->vramBG));
125 renderer->vramBG[0] = &video->vram[0x0000];
126 renderer->vramBG[1] = &video->vram[0x2000];
127 renderer->vramBG[2] = &video->vram[0x4000];
128 renderer->vramBG[3] = &video->vram[0x6000];
129 memset(renderer->vramOBJ, 0, sizeof(renderer->vramOBJ));
130 renderer->vramOBJ[0] = &video->vram[0x8000];
131 renderer->vramOBJ[1] = &video->vram[0xA000];
132 renderer->oam = &video->oam;
133 video->renderer->init(video->renderer);
134}
135
136void _startHdraw(struct mTiming* timing, void* context, uint32_t cyclesLate) {
137 struct GBAVideo* video = context;
138 GBARegisterDISPSTAT dispstat = video->p->memory.io[REG_DISPSTAT >> 1];
139 dispstat = GBARegisterDISPSTATClearInHblank(dispstat);
140 video->event.callback = _startHblank;
141 mTimingSchedule(timing, &video->event, VIDEO_HDRAW_LENGTH - cyclesLate);
142
143 ++video->vcount;
144 if (video->vcount == VIDEO_VERTICAL_TOTAL_PIXELS) {
145 video->vcount = 0;
146 }
147 video->p->memory.io[REG_VCOUNT >> 1] = video->vcount;
148
149 if (video->vcount == GBARegisterDISPSTATGetVcountSetting(dispstat)) {
150 dispstat = GBARegisterDISPSTATFillVcounter(dispstat);
151 if (GBARegisterDISPSTATIsVcounterIRQ(dispstat)) {
152 GBARaiseIRQ(video->p, IRQ_VCOUNTER);
153 }
154 } else {
155 dispstat = GBARegisterDISPSTATClearVcounter(dispstat);
156 }
157 video->p->memory.io[REG_DISPSTAT >> 1] = dispstat;
158
159 // Note: state may be recorded during callbacks, so ensure it is consistent!
160 switch (video->vcount) {
161 case 0:
162 GBAFrameStarted(video->p);
163 break;
164 case VIDEO_VERTICAL_PIXELS:
165 video->p->memory.io[REG_DISPSTAT >> 1] = GBARegisterDISPSTATFillInVblank(dispstat);
166 if (video->frameskipCounter <= 0) {
167 video->renderer->finishFrame(video->renderer);
168 }
169 GBADMARunVblank(video->p, -cyclesLate);
170 if (GBARegisterDISPSTATIsVblankIRQ(dispstat)) {
171 GBARaiseIRQ(video->p, IRQ_VBLANK);
172 }
173 GBAFrameEnded(video->p);
174 --video->frameskipCounter;
175 if (video->frameskipCounter < 0) {
176 mCoreSyncPostFrame(video->p->sync);
177 video->frameskipCounter = video->frameskip;
178 }
179 ++video->frameCounter;
180 break;
181 case VIDEO_VERTICAL_TOTAL_PIXELS - 1:
182 video->p->memory.io[REG_DISPSTAT >> 1] = GBARegisterDISPSTATClearInVblank(dispstat);
183 break;
184 }
185}
186
187void _startHblank(struct mTiming* timing, void* context, uint32_t cyclesLate) {
188 struct GBAVideo* video = context;
189 GBARegisterDISPSTAT dispstat = video->p->memory.io[REG_DISPSTAT >> 1];
190 dispstat = GBARegisterDISPSTATFillInHblank(dispstat);
191 video->event.callback = _startHdraw;
192 mTimingSchedule(timing, &video->event, VIDEO_HBLANK_LENGTH - cyclesLate);
193
194 // Begin Hblank
195 dispstat = GBARegisterDISPSTATFillInHblank(dispstat);
196 if (video->vcount < VIDEO_VERTICAL_PIXELS && video->frameskipCounter <= 0) {
197 video->renderer->drawScanline(video->renderer, video->vcount);
198 }
199
200 if (video->vcount < VIDEO_VERTICAL_PIXELS) {
201 GBADMARunHblank(video->p, -cyclesLate);
202 }
203 if (GBARegisterDISPSTATIsHblankIRQ(dispstat)) {
204 GBARaiseIRQ(video->p, IRQ_HBLANK);
205 }
206 video->p->memory.io[REG_DISPSTAT >> 1] = dispstat;
207}
208
209void GBAVideoWriteDISPSTAT(struct GBAVideo* video, uint16_t value) {
210 video->p->memory.io[REG_DISPSTAT >> 1] &= 0x7;
211 video->p->memory.io[REG_DISPSTAT >> 1] |= value;
212 // TODO: Does a VCounter IRQ trigger on write?
213}
214
215static void GBAVideoDummyRendererInit(struct GBAVideoRenderer* renderer) {
216 UNUSED(renderer);
217 // Nothing to do
218}
219
220static void GBAVideoDummyRendererReset(struct GBAVideoRenderer* renderer) {
221 UNUSED(renderer);
222 // Nothing to do
223}
224
225static void GBAVideoDummyRendererDeinit(struct GBAVideoRenderer* renderer) {
226 UNUSED(renderer);
227 // Nothing to do
228}
229
230static uint16_t GBAVideoDummyRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) {
231 UNUSED(renderer);
232 switch (address) {
233 case REG_BG0CNT:
234 case REG_BG1CNT:
235 value &= 0xDFFF;
236 break;
237 case REG_BG2CNT:
238 case REG_BG3CNT:
239 value &= 0xFFFF;
240 break;
241 case REG_BG0HOFS:
242 case REG_BG0VOFS:
243 case REG_BG1HOFS:
244 case REG_BG1VOFS:
245 case REG_BG2HOFS:
246 case REG_BG2VOFS:
247 case REG_BG3HOFS:
248 case REG_BG3VOFS:
249 value &= 0x01FF;
250 break;
251 case REG_BLDCNT:
252 value &= 0x3FFF;
253 break;
254 case REG_BLDALPHA:
255 value &= 0x1F1F;
256 break;
257 case REG_WININ:
258 case REG_WINOUT:
259 value &= 0x3F3F;
260 break;
261 default:
262 break;
263 }
264 return value;
265}
266
267static void GBAVideoDummyRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address) {
268 if (renderer->cache) {
269 mTileCacheWriteVRAM(renderer->cache, address);
270 }
271}
272
273static void GBAVideoDummyRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) {
274 UNUSED(value);
275 if (renderer->cache) {
276 mTileCacheWritePalette(renderer->cache, address);
277 }
278}
279
280static void GBAVideoDummyRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam) {
281 UNUSED(renderer);
282 UNUSED(oam);
283 // Nothing to do
284}
285
286static void GBAVideoDummyRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) {
287 UNUSED(renderer);
288 UNUSED(y);
289 // Nothing to do
290}
291
292static void GBAVideoDummyRendererFinishFrame(struct GBAVideoRenderer* renderer) {
293 UNUSED(renderer);
294 // Nothing to do
295}
296
297static void GBAVideoDummyRendererGetPixels(struct GBAVideoRenderer* renderer, size_t* stride, const void** pixels) {
298 UNUSED(renderer);
299 UNUSED(stride);
300 UNUSED(pixels);
301 // Nothing to do
302}
303
304static void GBAVideoDummyRendererPutPixels(struct GBAVideoRenderer* renderer, size_t stride, const void* pixels) {
305 UNUSED(renderer);
306 UNUSED(stride);
307 UNUSED(pixels);
308 // Nothing to do
309}
310
311void GBAVideoSerialize(const struct GBAVideo* video, struct GBASerializedState* state) {
312 memcpy(state->vram, video->vram, SIZE_VRAM);
313 memcpy(state->oam, video->oam.raw, SIZE_OAM);
314 memcpy(state->pram, video->palette, SIZE_PALETTE_RAM);
315 STORE_32(video->event.when - mTimingCurrentTime(&video->p->timing), 0, &state->video.nextEvent);
316 STORE_32(video->frameCounter, 0, &state->video.frameCounter);
317}
318
319void GBAVideoDeserialize(struct GBAVideo* video, const struct GBASerializedState* state) {
320 memcpy(video->vram, state->vram, SIZE_VRAM);
321 uint16_t value;
322 int i;
323 for (i = 0; i < SIZE_OAM; i += 2) {
324 LOAD_16(value, i, state->oam);
325 GBAStore16(video->p->cpu, BASE_OAM | i, value, 0);
326 }
327 for (i = 0; i < SIZE_PALETTE_RAM; i += 2) {
328 LOAD_16(value, i, state->pram);
329 GBAStore16(video->p->cpu, BASE_PALETTE_RAM | i, value, 0);
330 }
331 LOAD_32(video->frameCounter, 0, &state->video.frameCounter);
332
333 uint32_t when;
334 LOAD_32(when, 0, &state->video.nextEvent);
335 GBARegisterDISPSTAT dispstat = video->p->memory.io[REG_DISPSTAT >> 1];
336 if (GBARegisterDISPSTATIsInHblank(dispstat)) {
337 video->event.callback = _startHdraw;
338 } else {
339 video->event.callback = _startHblank;
340 }
341 mTimingSchedule(&video->p->timing, &video->event, when);
342
343 LOAD_16(video->vcount, REG_VCOUNT, state->io);
344 video->renderer->reset(video->renderer);
345}