Mercurial > repos > simple16
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 } |