comparison src/vdp.c @ 43:6e7bfe83d2b0

Changed the design to vastly simplify the video hardware and support a 23-bit address space on the CPU
author Michael Pavone <pavone@retrodev.com>
date Sat, 27 Aug 2016 22:38:31 -0700
parents 083347ccd508
children
comparison
equal deleted inserted replaced
42:a64e11e48a41 43:6e7bfe83d2b0
1 #include <stdint.h> 1 #include <stdint.h>
2 #include <string.h> 2 #include <string.h>
3 #include <stdio.h> 3 #include <stdio.h>
4 #include "vdp.h" 4 #include "vdp.h"
5 #include "system.h" 5 #include "system.h"
6
7 #define MAX_ACTIVE_LINES 240
8 #define TOTAL_LINES 262
9 #define ACTIVE_WIDTH 320
10 #define TOTAL_WIDTH 416
11
12 #define VDP_STATUS_FB_SELECT 1
13 #define VDP_STATUS_PENDING_VINT 2
14 #define VDP_STATUS_VBLANK 4
15 #define VDP_STATUS_CRAM_PENDING 8
16 #define VDP_STATUS_VINT_ENABLED 0x2000
17 #define VDP_STATUS_DEPTH 0x4000
18 #define VDP_STATUS_ENABLED 0x8000
6 19
7 void vdp_init(vdp *context, uint32_t clock_div) 20 void vdp_init(vdp *context, uint32_t clock_div)
8 { 21 {
9 memset(context, 0, sizeof(vdp)); 22 memset(context, 0, sizeof(vdp));
10 //clock div specifies the pixel clock divider 23 //clock div specifies the pixel clock divider
11 //but our emulation step is half that fast 24 //but our emulation step is half that fast
12 context->clock_inc = clock_div*2; 25 context->clock_inc = clock_div*2;
13 context->drawbuffer = context->linebuffers;
14 context->readbuffer = context->linebuffers+320;
15 } 26 }
16 27
17 void vdp_run(vdp *context, uint32_t target) 28 void vdp_run(vdp *context, uint32_t target)
18 { 29 {
30 uint8_t *current_fb = context->status & VDP_STATUS_FB_SELECT ? context->vram + 64*1024 : context->vram;
31
19 while (context->cycles < target) 32 while (context->cycles < target)
20 { 33 {
21 context->hcounter+=2; 34 context->hcounter+=2;
22 if (context->hcounter == 416) { 35 if (context->hcounter == TOTAL_WIDTH) {
23 context->hcounter = 0; 36 context->hcounter = 0;
24 context->vcounter++; 37 context->vcounter++;
25 if (context->vcounter == 262) { 38 if (context->vcounter == TOTAL_LINES) {
26 context->vcounter = 0; 39 context->vcounter = 0;
27 } 40 }
28 } 41 }
29 context->status &= ~(VDP_STATUS_VRAM|VDP_STATUS_SRAM);
30 //Render to linebuffer
31 if ((context->status & VDP_STATUS_ENABLED) && context->vcounter > 15 && context->vcounter < 240 && context->hcounter < 406) {
32 if (context->hcounter < 246) {
33 context->status |= VDP_STATUS_VRAM;
34 if (!context->hcounter) {
35 //flip linebuffers
36 if (context->drawbuffer == context->linebuffers) {
37 context->drawbuffer = context->linebuffers + 328;
38 context->readbuffer = context->linebuffers;
39 } else {
40 context->drawbuffer = context->linebuffers;
41 context->readbuffer = context->linebuffers + 328;
42 }
43 context->draw_dest = 0;
44 //enable sprite scanning
45 context->status |= VDP_STATUS_SPRITE_SCAN;
46 context->current_draw = 0;
47 }
48 if (context->draw_counter) {
49 context->draw_counter--;
50 uint16_t pixels = context->vram[context->draw_source++];
51 for (int i = context->hflip ? 0 : 12; i >= 0 && i < 16; i+= context->hflip ? 4 : -4)
52 {
53 uint8_t pixel = ((pixels >> i) & 0xF) | context->palpriority;
54 context->drawbuffer[context->draw_dest ^ (context->hflip << 2)] = pixel;
55 context->draw_dest++;
56 }
57 } else {
58 //00VV VVVV VVHH HHHH
59 uint16_t vpos = (context->vscroll & 0x7FF) + context->vcounter - 16;
60 uint16_t vmask = (context->vscroll >> 2) & 0x3E00;
61 uint16_t vcoarse = (vpos << 3) & 0x3FC0;
62 uint16_t vfine = vpos & 7;
63 uint16_t hcoarse = ((context->hscroll >> 3) + context->hcounter/6) & 0x3F;
64 uint16_t tableaddress = hcoarse | (vcoarse & ~vmask) | ((context->vscroll << 3) & vmask);
65 //printf("VCounter: %X, VScroll: %X, HCounter: %X, Table: %X\n", context->vcounter, context->vscroll, context->hcounter, tableaddress);
66 uint16_t entry = context->vram[tableaddress];
67 context->draw_source = (entry & 0x3FF) * 16;
68 if (entry & 0x1000) {
69 context->draw_source += 14 - vfine * 2;
70 } else {
71 context->draw_source += vfine * 2;
72 }
73 context->palpriority = entry >> 9 & 0x70;
74 context->draw_counter = 2;
75 context->hflip = (entry & 0x800) != 0;
76 }
77 if (context->status & VDP_STATUS_SPRITE_SCAN) {
78 context->status |= VDP_STATUS_SRAM;
79 uint16_t pos = context->sram[context->hcounter];
80 uint16_t y = pos & 0xFF;
81 uint16_t x = pos >> 8;
82 uint16_t atts = context->sram[context->hcounter+1];
83 x |= atts << 2 & 0x100;
84 if (x | y) {
85 uint16_t size = atts & 0x400 ? 16 : 8;
86 if (context->vcounter >= y && context->vcounter < y + size) {
87 uint16_t address = (atts & 0x3F) * 16;
88 if (atts & 0x1000) {
89 address += (size-1) * 2 - (context->vcounter - y) * 2;
90 } else {
91 address += (context->vcounter - y) * 2;
92 }
93 context->sprite_draws[context->current_draw].source = address;
94 context->sprite_draws[context->current_draw].x = x;
95 context->sprite_draws[context->current_draw].hflip = (atts & 0x800) != 0;
96 context->sprite_draws[context->current_draw].palpriority = 0x80 | (atts >> 9 & 0x50);
97 context->current_draw++;
98 if (size == 16) {
99 context->sprite_draws[context->current_draw].source = address + 32;
100 context->sprite_draws[context->current_draw].x = x + 8;
101 context->sprite_draws[context->current_draw].hflip = (atts & 0x800) != 0;
102 context->sprite_draws[context->current_draw].palpriority = 0x80 | (atts >> 9 & 0x50);
103 if (context->sprite_draws[context->current_draw].hflip) {
104 context->sprite_draws[context->current_draw].x -= 8;
105 context->sprite_draws[context->current_draw-1].x += 8;
106 }
107 }
108 context->current_draw++;
109 if (context->current_draw == 40) {
110 //no more rendering capacity
111 context->status &= ~VDP_STATUS_SPRITE_SCAN;
112 context->current_draw = 0;
113 }
114 }
115 } else {
116 //hit sprite list terminator
117 context->status &= ~VDP_STATUS_SPRITE_SCAN;
118 context->current_draw = 0;
119 }
120 }
121 } else {
122 sprite_draw *draw = context->sprite_draws + (context->current_draw >> 1);
123 if (draw->palpriority) {
124 context->status |= VDP_STATUS_VRAM;
125 uint16_t pixels = context->vram[draw->source + (context->current_draw & 1)];
126 uint16_t x = draw->x - 16 + (context->hscroll & 7);
127 for (int i = draw->hflip ? 0 : 12; i >= 0 && i < 16; i+= draw->hflip ? 4 : -4, x++)
128 {
129 uint8_t pixel = (pixels >> i) & 0xF;
130 if (pixel && x < 328 && ((draw->palpriority & 0x40) || !(context->drawbuffer[x] & 0x40))) {
131 context->drawbuffer[x ^ (draw->hflip << 2)] = pixel | draw->palpriority;
132 }
133 }
134 if (context->current_draw & 1) {
135 draw->palpriority = 0;
136 } else {
137 draw->x += 4;
138 }
139 }
140 context->current_draw++;
141 }
142 }
143 //Draw to framebuffer 42 //Draw to framebuffer
144 if (context->vcounter > 8 && context->vcounter < 249 && context->hcounter < 320) { 43 if (context->vcounter < MAX_ACTIVE_LINES && context->hcounter < ACTIVE_WIDTH) {
145 if (!context->hcounter && context->vcounter == 9) { 44 if (!context->framebuffer) {
146 context->framebuffer = system_get_framebuffer(&context->pitch); 45 context->framebuffer = system_get_framebuffer(&context->pitch);
147 //pitch is in terms of bytes, but we want it in terms of pixels 46 //pitch is in terms of bytes, but we want it in terms of pixels
148 context->pitch /= sizeof(uint16_t); 47 context->pitch /= sizeof(uint16_t);
149 //clear pending interrupt flag since VBlank is over
150 context->status &= ~VDP_STATUS_PENDING_VINT;
151 } 48 }
152 uint16_t *dest = context->framebuffer + (context->vcounter - 9) * context->pitch + context->hcounter; 49 uint16_t *dest = context->framebuffer + context->vcounter * context->pitch + context->hcounter;
153 if (context->status & VDP_STATUS_ENABLED && context->vcounter > 16 && context->vcounter < 241) { 50 if (
154 *dest = context->cram[0x3F & context->readbuffer[context->hcounter]]; 51 context->status & VDP_STATUS_ENABLED
155 dest++; 52 && context->vcounter >= context->top_skip
156 *dest = context->cram[0x3F & context->readbuffer[context->hcounter+1]]; 53 && context->vcounter < MAX_ACTIVE_LINES - context->bottom_skip
54 ) {
55 if (context->status & VDP_STATUS_DEPTH) {
56 uint16_t offset = context->start_offset + (context->vcounter - context->top_skip) * ACTIVE_WIDTH + context->hcounter;
57 //8bpp
58 *(dest++) = context->cram[current_fb[offset++]];
59 *dest = context->cram[current_fb[offset]];
60 } else {
61 //4bpp
62 uint8_t pixels = current_fb[context->start_offset + (context->vcounter - context->top_skip) * ACTIVE_WIDTH + context->hcounter >> 1];
63 *(dest++) = context->cram[context->pal_select | pixels >> 4];
64 *dest = context->cram[context->pal_select | (pixels & 0xF)];
65 }
157 } else { 66 } else {
158 //Display is disabled or we're in the border area, draw the background color 67 *(dest++) = context->cram[0];
159 *dest = *context->cram; 68 *dest = context->cram[0];
160 dest++;
161 *dest = *context->cram;
162 } 69 }
163 } else if(!context->hcounter && context->vcounter == 249) { 70 } else if (context->framebuffer && context->hcounter < ACTIVE_WIDTH) {
164 if (context->status & VDP_STATUS_ENABLED) {
165 context->status |= VDP_STATUS_PENDING_VINT;
166 }
167 system_framebuffer_updated(); 71 system_framebuffer_updated();
168 context->framebuffer = NULL; 72 context->framebuffer = NULL;
169 } 73 }
170 //Handle the FIFO 74 if (!context->hcounter) {
171 if (context->status & VDP_STATUS_FIFO) { 75 if (context->vcounter == (context->vcounter - context->bottom_skip)) {
172 switch (context->fifo_dest) 76 context->status |= VDP_STATUS_PENDING_VINT | VDP_STATUS_VBLANK;
173 { 77 } else if (context->vcounter == context->top_skip) {
174 case FIFO_DEST_VRAM: 78 //clear pending interrupt flag since VBlank is over
175 if (!(context->status & VDP_STATUS_VRAM)) { 79 context->status &= ~(VDP_STATUS_PENDING_VINT | VDP_STATUS_VBLANK);
176 context->vram[context->dest_offset++] = context->fifo;
177 context->dest_offset &= sizeof(context->vram)/2-1;
178 context->status &= ~VDP_STATUS_FIFO;
179 }
180 break;
181 case FIFO_DEST_SRAM:
182 if (!(context->status & VDP_STATUS_SRAM)) {
183 context->sram[context->dest_offset++] = context->fifo;
184 context->dest_offset &= sizeof(context->sram)/2-1;
185 context->status &= ~VDP_STATUS_FIFO;
186 }
187 break;
188 case FIFO_DEST_CRAM:
189 context->cram[context->dest_offset++] = context->fifo;
190 context->dest_offset &= sizeof(context->cram)/2-1;
191 context->status &= ~VDP_STATUS_FIFO;
192 break;
193 } 80 }
194 } 81 }
195 context->cycles += context->clock_inc; 82 context->cycles += context->clock_inc;
196 } 83 }
197 } 84 }
198 void vdp_write_address(vdp *context, uint16_t value) 85 void vdp_write_mode(vdp *context, uint16_t value)
199 { 86 {
200 context->status &= ~VDP_STATUS_FIFO; 87 uint16_t status_bits = VDP_STATUS_ENABLED | VDP_STATUS_DEPTH | VDP_STATUS_VINT_ENABLED | VDP_STATUS_FB_SELECT;
201 if (!(value & 0x8000)) { 88 context->status &= ~status_bits;
202 context->fifo_dest = FIFO_DEST_VRAM; 89 context->status |= value & status_bits;
203 context->dest_offset = (value & (sizeof(context->vram) -1))/2; 90 context->pal_select = value >> 7 & 0x30;
204 } else if ((value & 0xFF00) == 0xFE00) { 91 context->top_skip = value >> 6 & 0x1F;
205 context->fifo_dest = FIFO_DEST_SRAM; 92 context->bottom_skip = value >> 1 & 0x1F;
206 context->dest_offset = (value & (sizeof(context->sram) -1))/2;
207 } else if ((value & 0xFF00) == 0xFF00) {
208 context->fifo_dest = FIFO_DEST_CRAM;
209 context->dest_offset = (value & (sizeof(context->cram) -1))/2;
210 }
211 } 93 }
212 94
213 void vdp_write_data(vdp *context, uint16_t value) 95 void vdp_write_cram(vdp *context, uint16_t value)
214 { 96 {
215 context->fifo = value; 97 if (context->status & VDP_STATUS_CRAM_PENDING) {
216 context->status |= VDP_STATUS_FIFO; 98 context->cram[context->pal_write_index++] = value;
217 } 99 if (!(--context->pal_write_count)) {
218 100 context->status &= ~VDP_STATUS_CRAM_PENDING;
219 void vdp_write_hscroll(vdp *context, uint16_t value) 101 }
220 {
221 context->hscroll = value & 0x1FF;
222 if (value & 0x8000) {
223 context->status |= VDP_STATUS_ENABLED;
224 } else { 102 } else {
225 context->status &= ~VDP_STATUS_ENABLED; 103 context->pal_write_count = value;
104 context->pal_write_index = value >> 8;
105 context->status |= VDP_STATUS_CRAM_PENDING;
226 } 106 }
227 } 107 }
228 108
229 uint32_t vdp_next_interrupt(vdp *context) 109 uint32_t vdp_next_interrupt(vdp *context)
230 { 110 {
231 if (context->status & VDP_STATUS_PENDING_VINT) { 111 if (context->status & VDP_STATUS_PENDING_VINT) {
232 return 0; 112 return 0;
233 } else if (context->status & VDP_STATUS_ENABLED) { 113 } else if (context->status & VDP_STATUS_ENABLED) {
234 uint32_t next_line = context->vcounter + 1; 114 uint32_t next_line = context->vcounter + 1;
235 uint32_t next_line_cyc = context->cycles + ((416 - context->hcounter) >> 1) * context->clock_inc; 115 uint32_t next_line_cyc = context->cycles + ((TOTAL_WIDTH - context->hcounter) >> 1) * context->clock_inc;
236 if (context->vcounter < 249) { 116 uint32_t vint_line = (MAX_ACTIVE_LINES - context->bottom_skip);
237 return next_line_cyc + (249 - next_line) * 832; 117 if (context->vcounter < vint_line) {
118 return next_line_cyc + (vint_line - next_line) * (TOTAL_WIDTH >> 1) * context->clock_inc;
238 } else { 119 } else {
239 return next_line_cyc + (249 + 262 - next_line) * 832; 120 return next_line_cyc + (vint_line + TOTAL_LINES - next_line) * (TOTAL_WIDTH >> 1) * context->clock_inc;
240 } 121 }
241 } else { 122 } else {
242 return 0xFFFFFFFF; 123 return 0xFFFFFFFF;
243 } 124 }
244 } 125 }
250 131
251 uint8_t vdp_interrupt_pending(vdp *context) 132 uint8_t vdp_interrupt_pending(vdp *context)
252 { 133 {
253 return (context->status & VDP_STATUS_PENDING_VINT) != 0; 134 return (context->status & VDP_STATUS_PENDING_VINT) != 0;
254 } 135 }
136
137 uint8_t *vdp_get_back_buffer(vdp *context)
138 {
139 return context->status & VDP_STATUS_FB_SELECT ? context->vram : context->vram + 64*1024;
140 }