1
/***************************************************************************
5
Functions to emulate the video hardware of the ZX Spectrum.
9
DJR 08/02/00 - Added support for FLASH 1.
10
DJR 16/05/00 - Support for TS2068/TC2048 hires and 64 column modes.
11
DJR 19/05/00 - Speeded up Spectrum 128 screen refresh.
12
DJR 23/05/00 - Preliminary support for border colour emulation.
14
***************************************************************************/
17
#include "includes/spectrum.h"
19
/***************************************************************************
20
Start the video hardware emulation.
21
***************************************************************************/
22
VIDEO_START( spectrum )
24
spectrum_state *state = machine.driver_data<spectrum_state>();
25
state->m_LastDisplayedBorderColor = -1;
26
state->m_frame_invert_count = 25;
27
state->m_frame_number = 0;
28
state->m_flash_invert = 0;
30
spectrum_EventList_Initialise(machine, 30000);
32
state->m_retrace_cycles = SPEC_RETRACE_CYCLES;
34
state->m_screen_location = state->m_video_ram;
37
VIDEO_START( spectrum_128 )
39
spectrum_state *state = machine.driver_data<spectrum_state>();
40
state->m_LastDisplayedBorderColor = -1;
41
state->m_frame_invert_count = 25;
42
state->m_frame_number = 0;
43
state->m_flash_invert = 0;
45
spectrum_EventList_Initialise(machine, 30000);
47
state->m_retrace_cycles = SPEC128_RETRACE_CYCLES;
51
/* return the color to be used inverting FLASHing colors if necessary */
52
INLINE unsigned char get_display_color (unsigned char color, int invert)
54
if (invert && (color & 0x80))
55
return (color & 0xc0) + ((color & 0x38) >> 3) + ((color & 0x07) << 3);
60
/* Code to change the FLASH status every 25 frames. Note this must be
61
independent of frame skip etc. */
62
SCREEN_EOF( spectrum )
64
spectrum_state *state = machine.driver_data<spectrum_state>();
65
EVENT_LIST_ITEM *pItem;
68
state->m_frame_number++;
69
if (state->m_frame_number >= state->m_frame_invert_count)
71
state->m_frame_number = 0;
72
state->m_flash_invert = !state->m_flash_invert;
75
/* Empty event buffer for undisplayed frames noting the last border
76
colour (in case colours are not changed in the next frame). */
77
NumItems = spectrum_EventList_NumEvents(machine);
80
pItem = spectrum_EventList_GetFirstItem(machine);
81
spectrum_border_set_last_color ( machine, pItem[NumItems-1].Event_Data );
82
spectrum_EventList_Reset(machine);
83
spectrum_EventList_SetOffsetStartTime ( machine, machine.firstcpu->attotime_to_cycles(machine.primary_screen->scan_period() * machine.primary_screen->vpos()) );
84
logerror ("Event log reset in callback fn.\n");
90
/***************************************************************************
91
Update the spectrum screen display.
93
The screen consists of 312 scanlines as follows:
94
64 border lines (the last 48 are actual border lines; the others may be
95
border lines or vertical retrace)
99
Each screen line has 48 left border pixels, 256 screen pixels and 48 right
102
Each scanline takes 224 T-states divided as follows:
103
128 Screen (reads a screen and ATTR byte [8 pixels] every 4 T states)
105
48 Horizontal retrace
108
The 128K Spectrums have only 63 scanlines before the TV picture (311 total)
109
and take 228 T-states per scanline.
111
***************************************************************************/
113
INLINE void spectrum_plot_pixel(bitmap_t *bitmap, int x, int y, UINT32 color)
115
*BITMAP_ADDR16(bitmap, y, x) = (UINT16)color;
118
SCREEN_UPDATE( spectrum )
120
/* for now do a full-refresh */
121
spectrum_state *state = screen->machine().driver_data<spectrum_state>();
122
int x, y, b, scrx, scry;
123
unsigned short ink, pap;
124
unsigned char *attr, *scr;
125
int full_refresh = 1;
127
scr=state->m_screen_location;
129
for (y=0; y<192; y++)
131
scrx=SPEC_LEFT_BORDER;
132
scry=((y&7) * 8) + ((y&0x38)>>3) + (y&0xC0);
133
attr=state->m_screen_location + ((scry>>3)*32) + 0x1800;
137
/* Get ink and paper colour with bright */
138
if (state->m_flash_invert && (*attr & 0x80))
140
ink=((*attr)>>3) & 0x0f;
141
pap=((*attr) & 0x07) + (((*attr)>>3) & 0x08);
145
ink=((*attr) & 0x07) + (((*attr)>>3) & 0x08);
146
pap=((*attr)>>3) & 0x0f;
149
for (b=0x80;b!=0;b>>=1)
152
spectrum_plot_pixel(bitmap,scrx++,SPEC_TOP_BORDER+scry,ink);
154
spectrum_plot_pixel(bitmap,scrx++,SPEC_TOP_BORDER+scry,pap);
162
spectrum_border_draw(screen->machine(), bitmap, full_refresh,
163
SPEC_TOP_BORDER, SPEC_DISPLAY_YSIZE, SPEC_BOTTOM_BORDER,
164
SPEC_LEFT_BORDER, SPEC_DISPLAY_XSIZE, SPEC_RIGHT_BORDER,
165
SPEC_LEFT_BORDER_CYCLES, SPEC_DISPLAY_XSIZE_CYCLES,
166
SPEC_RIGHT_BORDER_CYCLES, state->m_retrace_cycles, 200, 0xfe);
171
static const rgb_t spectrum_palette[16] = {
172
MAKE_RGB(0x00, 0x00, 0x00),
173
MAKE_RGB(0x00, 0x00, 0xbf),
174
MAKE_RGB(0xbf, 0x00, 0x00),
175
MAKE_RGB(0xbf, 0x00, 0xbf),
176
MAKE_RGB(0x00, 0xbf, 0x00),
177
MAKE_RGB(0x00, 0xbf, 0xbf),
178
MAKE_RGB(0xbf, 0xbf, 0x00),
179
MAKE_RGB(0xbf, 0xbf, 0xbf),
180
MAKE_RGB(0x00, 0x00, 0x00),
181
MAKE_RGB(0x00, 0x00, 0xff),
182
MAKE_RGB(0xff, 0x00, 0x00),
183
MAKE_RGB(0xff, 0x00, 0xff),
184
MAKE_RGB(0x00, 0xff, 0x00),
185
MAKE_RGB(0x00, 0xff, 0xff),
186
MAKE_RGB(0xff, 0xff, 0x00),
187
MAKE_RGB(0xff, 0xff, 0xff)
189
/* Initialise the palette */
190
PALETTE_INIT( spectrum )
192
palette_set_colors(machine, 0, spectrum_palette, ARRAY_LENGTH(spectrum_palette));
195
/***************************************************************************
198
Functions for drawing multi-coloured screen borders using the
199
Event List processing.
203
28/05/2000 DJR - Initial implementation.
204
08/06/2000 DJR - Now only uses events with the correct ID value.
205
28/06/2000 DJR - draw_border now uses full_refresh flag.
207
***************************************************************************/
209
/* Force the border to be redrawn on the next frame */
210
void spectrum_border_force_redraw (running_machine &machine)
212
spectrum_state *state = machine.driver_data<spectrum_state>();
213
state->m_LastDisplayedBorderColor = -1;
216
/* Set the last border colour to have been displayed. Used when loading snap
217
shots and to record the last colour change in a frame that was skipped. */
218
void spectrum_border_set_last_color(running_machine &machine, int NewColor)
220
spectrum_state *state = machine.driver_data<spectrum_state>();
221
state->m_CurrBorderColor = NewColor;
224
void spectrum_border_draw(running_machine &machine, bitmap_t *bitmap,
225
int full_refresh, /* Full refresh flag */
226
int TopBorderLines, /* Border lines before actual screen */
227
int ScreenLines, /* Screen height in pixels */
228
int BottomBorderLines, /* Border lines below screen */
229
int LeftBorderPixels, /* Border pixels to the left of each screen line */
230
int ScreenPixels, /* Width of actual screen in pixels */
231
int RightBorderPixels, /* Border pixels to the right of each screen line */
232
int LeftBorderCycles, /* Cycles taken to draw left border of each scan line */
233
int ScreenCycles, /* Cycles taken to draw screen data part of each scan line */
234
int RightBorderCycles, /* Cycles taken to draw right border of each scan line */
235
int HorizontalRetraceCycles, /* Cycles taken to return to LHS of CRT after each scan line */
236
int VRetraceTime, /* Cycles taken before start of first border line */
237
int EventID) /* Event ID of border messages */
239
spectrum_state *state = machine.driver_data<spectrum_state>();
240
EVENT_LIST_ITEM *pItem;
241
int TotalScreenHeight = TopBorderLines+ScreenLines+BottomBorderLines;
242
int TotalScreenWidth = LeftBorderPixels+ScreenPixels+RightBorderPixels;
243
int DisplayCyclesPerLine = LeftBorderCycles+ScreenCycles+RightBorderCycles;
244
int CyclesPerLine = DisplayCyclesPerLine+HorizontalRetraceCycles;
246
int NumItems, CurrItem = 0, NextItem;
247
int Count, ScrX, NextScrX, ScrY;
250
pItem = spectrum_EventList_GetFirstItem(machine);
251
NumItems = spectrum_EventList_NumEvents(machine);
253
for (Count = 0; Count < NumItems; Count++)
255
// logerror ("Event no %05d, ID = %04x, data = %04x, time = %ld\n", Count, pItem[Count].Event_ID, pItem[Count].Event_Data, (long) pItem[Count].Event_Time);
258
/* Find the first and second events with the correct ID */
259
while ((CurrItem < NumItems) && (pItem[CurrItem].Event_ID != EventID))
261
NextItem = CurrItem + 1;
262
while ((NextItem < NumItems) && (pItem[NextItem].Event_ID != EventID))
265
/* Single border colour */
266
if ((CurrItem < NumItems) && (NextItem >= NumItems))
267
state->m_CurrBorderColor = pItem[CurrItem].Event_Data;
269
if ((NextItem >= NumItems) && (state->m_CurrBorderColor==state->m_LastDisplayedBorderColor) && !full_refresh)
271
/* Do nothing if border colour has not changed */
273
else if (NextItem >= NumItems)
275
/* Single border colour - this is not strictly correct as the
276
colour change may have occurred midway through the frame
277
or after the last visible border line however the whole
278
border would be redrawn in the correct colour during the
279
next frame anyway! */
281
r.max_x = TotalScreenWidth-1;
283
r.max_y = TopBorderLines-1;
284
bitmap_fill(bitmap, &r, machine.pens[state->m_CurrBorderColor]);
287
r.max_x = LeftBorderPixels-1;
288
r.min_y = TopBorderLines;
289
r.max_y = TopBorderLines+ScreenLines-1;
290
bitmap_fill(bitmap, &r, machine.pens[state->m_CurrBorderColor]);
292
r.min_x = LeftBorderPixels+ScreenPixels;
293
r.max_x = TotalScreenWidth-1;
294
bitmap_fill(bitmap, &r, machine.pens[state->m_CurrBorderColor]);
297
r.max_x = TotalScreenWidth-1;
298
r.min_y = TopBorderLines+ScreenLines;
299
r.max_y = TotalScreenHeight-1;
300
bitmap_fill(bitmap, &r, machine.pens[state->m_CurrBorderColor]);
302
// logerror ("Setting border colour to %d (Last = %d, Full Refresh = %d)\n", state->m_CurrBorderColor, state->m_LastDisplayedBorderColor, full_refresh);
303
state->m_LastDisplayedBorderColor = state->m_CurrBorderColor;
307
/* Multiple border colours */
309
/* Process entries before first displayed line */
310
while ((CurrItem < NumItems) && (pItem[CurrItem].Event_Time <= VRetraceTime))
312
state->m_CurrBorderColor = pItem[CurrItem].Event_Data;
315
} while ((CurrItem < NumItems) && (pItem[CurrItem].Event_ID != EventID));
318
/* Draw top border */
319
CyclesSoFar = VRetraceTime;
320
for (ScrY = 0; ScrY < TopBorderLines; ScrY++)
323
r.min_y = r.max_y = ScrY;
324
if ((CurrItem >= NumItems) || (pItem[CurrItem].Event_Time >= (CyclesSoFar+DisplayCyclesPerLine)))
326
/* Single colour on line */
327
r.max_x = TotalScreenWidth-1;
328
bitmap_fill(bitmap, &r, machine.pens[state->m_CurrBorderColor]);
332
/* Multiple colours on a line */
333
ScrX = (int)(pItem[CurrItem].Event_Time - CyclesSoFar) * (float)TotalScreenWidth / (float)DisplayCyclesPerLine;
335
bitmap_fill(bitmap, &r, machine.pens[state->m_CurrBorderColor]);
336
state->m_CurrBorderColor = pItem[CurrItem].Event_Data;
339
} while ((CurrItem < NumItems) && (pItem[CurrItem].Event_ID != EventID));
341
while ((CurrItem < NumItems) && (pItem[CurrItem].Event_Time < (CyclesSoFar+DisplayCyclesPerLine)))
343
NextScrX = (int)(pItem[CurrItem].Event_Time - CyclesSoFar) * (float)TotalScreenWidth / (float)DisplayCyclesPerLine;
345
r.max_x = NextScrX-1;
346
bitmap_fill(bitmap, &r, machine.pens[state->m_CurrBorderColor]);
348
state->m_CurrBorderColor = pItem[CurrItem].Event_Data;
351
} while ((CurrItem < NumItems) && (pItem[CurrItem].Event_ID != EventID));
354
r.max_x = TotalScreenWidth-1;
355
bitmap_fill(bitmap, &r, machine.pens[state->m_CurrBorderColor]);
358
/* Process colour changes during horizontal retrace */
359
CyclesSoFar+= CyclesPerLine;
360
while ((CurrItem < NumItems) && (pItem[CurrItem].Event_Time <= CyclesSoFar))
362
state->m_CurrBorderColor = pItem[CurrItem].Event_Data;
365
} while ((CurrItem < NumItems) && (pItem[CurrItem].Event_ID != EventID));
369
/* Draw left and right borders next to screen lines */
370
for (ScrY = TopBorderLines; ScrY < (TopBorderLines+ScreenLines); ScrY++)
372
/* Draw left hand border */
374
r.min_y = r.max_y = ScrY;
376
if ((CurrItem >= NumItems) || (pItem[CurrItem].Event_Time >= (CyclesSoFar+LeftBorderCycles)))
379
r.max_x = LeftBorderPixels-1;
380
bitmap_fill(bitmap, &r, machine.pens[state->m_CurrBorderColor]);
384
/* Multiple colours */
385
ScrX = (int)(pItem[CurrItem].Event_Time - CyclesSoFar) * (float)LeftBorderPixels / (float)LeftBorderCycles;
387
bitmap_fill(bitmap, &r, machine.pens[state->m_CurrBorderColor]);
388
state->m_CurrBorderColor = pItem[CurrItem].Event_Data;
391
} while ((CurrItem < NumItems) && (pItem[CurrItem].Event_ID != EventID));
393
while ((CurrItem < NumItems) && (pItem[CurrItem].Event_Time < (CyclesSoFar+LeftBorderCycles)))
395
NextScrX = (int)(pItem[CurrItem].Event_Time - CyclesSoFar) * (float)LeftBorderPixels / (float)LeftBorderCycles;
397
r.max_x = NextScrX-1;
398
bitmap_fill(bitmap, &r, machine.pens[state->m_CurrBorderColor]);
400
state->m_CurrBorderColor = pItem[CurrItem].Event_Data;
403
} while ((CurrItem < NumItems) && (pItem[CurrItem].Event_ID != EventID));
406
r.max_x = LeftBorderPixels-1;
407
bitmap_fill(bitmap, &r, machine.pens[state->m_CurrBorderColor]);
410
/* Process colour changes during screen draw */
411
while ((CurrItem < NumItems) && (pItem[CurrItem].Event_Time <= (CyclesSoFar+LeftBorderCycles+ScreenCycles)))
413
state->m_CurrBorderColor = pItem[CurrItem].Event_Data;
416
} while ((CurrItem < NumItems) && (pItem[CurrItem].Event_ID != EventID));
419
/* Draw right hand border */
420
r.min_x = LeftBorderPixels+ScreenPixels;
421
if ((CurrItem >= NumItems) || (pItem[CurrItem].Event_Time >= (CyclesSoFar+DisplayCyclesPerLine)))
424
r.max_x = TotalScreenWidth-1;
425
bitmap_fill(bitmap, &r, machine.pens[state->m_CurrBorderColor]);
429
/* Multiple colours */
430
ScrX = LeftBorderPixels + ScreenPixels + (int)(pItem[CurrItem].Event_Time - CyclesSoFar) * (float)RightBorderPixels / (float)RightBorderCycles;
432
bitmap_fill(bitmap, &r, machine.pens[state->m_CurrBorderColor]);
433
state->m_CurrBorderColor = pItem[CurrItem].Event_Data;
436
} while ((CurrItem < NumItems) && (pItem[CurrItem].Event_ID != EventID));
438
while ((CurrItem < NumItems) && (pItem[CurrItem].Event_Time < (CyclesSoFar+DisplayCyclesPerLine)))
440
NextScrX = LeftBorderPixels + ScreenPixels + (int)(pItem[CurrItem].Event_Time - CyclesSoFar) * (float)RightBorderPixels / (float)RightBorderCycles;
442
r.max_x = NextScrX-1;
443
bitmap_fill(bitmap, &r, machine.pens[state->m_CurrBorderColor]);
445
state->m_CurrBorderColor = pItem[CurrItem].Event_Data;
448
} while ((CurrItem < NumItems) && (pItem[CurrItem].Event_ID != EventID));
451
r.max_x = TotalScreenWidth-1;
452
bitmap_fill(bitmap, &r, machine.pens[state->m_CurrBorderColor]);
455
/* Process colour changes during horizontal retrace */
456
CyclesSoFar+= CyclesPerLine;
457
while ((CurrItem < NumItems) && (pItem[CurrItem].Event_Time <= CyclesSoFar))
459
state->m_CurrBorderColor = pItem[CurrItem].Event_Data;
462
} while ((CurrItem < NumItems) && (pItem[CurrItem].Event_ID != EventID));
466
/* Draw bottom border */
467
for (ScrY = TopBorderLines+ScreenLines; ScrY < TotalScreenHeight; ScrY++)
470
r.min_y = r.max_y = ScrY;
471
if ((CurrItem >= NumItems) || (pItem[CurrItem].Event_Time >= (CyclesSoFar+DisplayCyclesPerLine)))
473
/* Single colour on line */
474
r.max_x = TotalScreenWidth-1;
475
bitmap_fill(bitmap, &r, machine.pens[state->m_CurrBorderColor]);
479
/* Multiple colours on a line */
480
ScrX = (int)(pItem[CurrItem].Event_Time - CyclesSoFar) * (float)TotalScreenWidth / (float)DisplayCyclesPerLine;
482
bitmap_fill(bitmap, &r, machine.pens[state->m_CurrBorderColor]);
483
state->m_CurrBorderColor = pItem[CurrItem].Event_Data;
486
} while ((CurrItem < NumItems) && (pItem[CurrItem].Event_ID != EventID));
488
while ((CurrItem < NumItems) && (pItem[CurrItem].Event_Time < (CyclesSoFar+DisplayCyclesPerLine)))
490
NextScrX = (int)(pItem[CurrItem].Event_Time - CyclesSoFar) * (float)TotalScreenWidth / (float)DisplayCyclesPerLine;
492
r.max_x = NextScrX-1;
493
bitmap_fill(bitmap, &r, machine.pens[state->m_CurrBorderColor]);
495
state->m_CurrBorderColor = pItem[CurrItem].Event_Data;
498
} while ((CurrItem < NumItems) && (pItem[CurrItem].Event_ID != EventID));
501
r.max_x = TotalScreenWidth-1;
502
bitmap_fill(bitmap, &r, machine.pens[state->m_CurrBorderColor]);
505
/* Process colour changes during horizontal retrace */
506
CyclesSoFar+= CyclesPerLine;
507
while ((CurrItem < NumItems) && (pItem[CurrItem].Event_Time <= CyclesSoFar))
509
state->m_CurrBorderColor = pItem[CurrItem].Event_Data;
512
} while ((CurrItem < NumItems) && (pItem[CurrItem].Event_ID != EventID));
516
/* Process colour changes after last displayed line */
517
while (CurrItem < NumItems)
519
if (pItem[CurrItem].Event_ID == EventID)
520
state->m_CurrBorderColor = pItem[CurrItem].Event_Data;
524
/* Set value to ensure redraw on next frame */
525
state->m_LastDisplayedBorderColor = -1;
527
// logerror ("Multi coloured border drawn (last colour = %d)\n", CurrBorderColor);
530
/* Assume all other routines have processed their data from the list */
531
spectrum_EventList_Reset(machine);
532
spectrum_EventList_SetOffsetStartTime ( machine, machine.firstcpu->attotime_to_cycles(machine.primary_screen->scan_period() * machine.primary_screen->vpos()));
538
/* if the CPU is the controlling factor, the size of the buffer
541
Number_of_CPU_Cycles_In_A_Frame/Minimum_Number_Of_Cycles_Per_Instruction */
542
void spectrum_EventList_Initialise(running_machine &machine, int NumEntries)
544
spectrum_state *state = machine.driver_data<spectrum_state>();
545
state->m_pEventListBuffer = auto_alloc_array(machine, char, NumEntries);
546
state->m_TotalEvents = NumEntries;
547
state->m_CyclesPerFrame = 0;
548
spectrum_EventList_Reset(machine);
551
/* reset the change list */
552
void spectrum_EventList_Reset(running_machine &machine)
554
spectrum_state *state = machine.driver_data<spectrum_state>();
555
state->m_NumEvents = 0;
556
state->m_pCurrentItem = (EVENT_LIST_ITEM *)state->m_pEventListBuffer;
560
#ifdef UNUSED_FUNCTION
561
/* add an event to the buffer */
562
void EventList_AddItem(running_machine &machine, int ID, int Data, int Time)
564
spectrum_state *state = machine.driver_data<spectrum_state>();
565
if (state->m_NumEvents < state->m_TotalEvents)
567
/* setup item only if there is space in the buffer */
568
state->m_pCurrentItem->Event_ID = ID;
569
state->m_pCurrentItem->Event_Data = Data;
570
state->m_pCurrentItem->Event_Time = Time;
572
state->m_pCurrentItem++;
573
state->m_NumEvents++;
578
/* set the start time for use with EventList_AddItemOffset usually this will
579
be cpu_getcurrentcycles() at the time that the screen is being refreshed */
580
void spectrum_EventList_SetOffsetStartTime(running_machine &machine, int StartTime)
582
spectrum_state *state = machine.driver_data<spectrum_state>();
583
state->m_LastFrameStartTime = StartTime;
586
/* add an event to the buffer with a time index offset from a specified time */
587
void spectrum_EventList_AddItemOffset(running_machine &machine, int ID, int Data, int Time)
589
spectrum_state *state = machine.driver_data<spectrum_state>();
591
if (!state->m_CyclesPerFrame)
592
state->m_CyclesPerFrame = (int)(machine.firstcpu->unscaled_clock() / machine.primary_screen->frame_period().attoseconds); //totalcycles(); //_(int)(cpunum_get_clock(0) / machine.config()->frames_per_second);
594
if (state->m_NumEvents < state->m_TotalEvents)
596
/* setup item only if there is space in the buffer */
597
state->m_pCurrentItem->Event_ID = ID;
598
state->m_pCurrentItem->Event_Data = Data;
600
Time -= state->m_LastFrameStartTime;
601
if ((Time < 0) || ((Time == 0) && state->m_NumEvents))
602
Time += state->m_CyclesPerFrame;
603
state->m_pCurrentItem->Event_Time = Time;
605
state->m_pCurrentItem++;
606
state->m_NumEvents++;
610
/* get number of events */
611
int spectrum_EventList_NumEvents(running_machine &machine)
613
spectrum_state *state = machine.driver_data<spectrum_state>();
614
return state->m_NumEvents;
617
/* get first item in buffer */
618
EVENT_LIST_ITEM *spectrum_EventList_GetFirstItem(running_machine &machine)
620
spectrum_state *state = machine.driver_data<spectrum_state>();
621
return (EVENT_LIST_ITEM *)state->m_pEventListBuffer;