// license:BSD-3-Clause // copyright-holders:Nathan Woods /********************************************************************* mc6847.h Implementation of Motorola 6847 video hardware chip ***************************************************************************/ #ifndef MAME_VIDEO_MC6847_H #define MAME_VIDEO_MC6847_H #pragma once #include "screen.h" //************************************************************************** // MC6847 CONFIGURATION / INTERFACE //************************************************************************** #define MCFG_SCREEN_MC6847_NTSC_ADD(_tag, _mctag) \ MCFG_SCREEN_ADD(_tag, RASTER) \ MCFG_SCREEN_UPDATE_DEVICE(_mctag, mc6847_base_device, screen_update) \ MCFG_SCREEN_REFRESH_RATE(60) \ MCFG_SCREEN_SIZE(320, 243) \ MCFG_SCREEN_VISIBLE_AREA(0, 320-1, 1, 241-1) \ MCFG_SCREEN_VBLANK_TIME(0) #define MCFG_SCREEN_MC6847_PAL_ADD(_tag, _mctag) \ MCFG_SCREEN_ADD(_tag, RASTER) \ MCFG_SCREEN_UPDATE_DEVICE(_mctag, mc6847_base_device, screen_update) \ MCFG_SCREEN_REFRESH_RATE(50) \ MCFG_SCREEN_SIZE(320, 243) \ MCFG_SCREEN_VISIBLE_AREA(0, 320-1, 1, 241-1) \ MCFG_SCREEN_VBLANK_TIME(0) #define MCFG_MC6847_HSYNC_CALLBACK(_write) \ devcb = &downcast(*device).set_hsync_wr_callback(DEVCB_##_write); #define MCFG_MC6847_FSYNC_CALLBACK(_write) \ devcb = &downcast(*device).set_fsync_wr_callback(DEVCB_##_write); #define MCFG_MC6847_CHARROM_CALLBACK(_class, _method) \ downcast(*device).set_get_char_rom(mc6847_friend_device::get_char_rom_delegate(&_class::_method, #_class "::" #_method, this)); #define MCFG_MC6847_INPUT_CALLBACK(_read) \ devcb = &downcast(*device).set_input_callback(DEVCB_##_read); #define MCFG_MC6847_FIXED_MODE(_mode) \ downcast(*device).set_get_fixed_mode(_mode); #define MCFG_MC6847_BW(_bw) \ downcast(*device).set_black_and_white(_bw); #define MC6847_GET_CHARROM_MEMBER(_name) uint8_t _name(uint8_t ch, int line) #define ARTIFACTING_TAG "artifacting" INPUT_PORTS_EXTERN(mc6847_artifacting); //************************************************************************** // MC6847 CORE //************************************************************************** // base class so that the GIME emulation can access mc6847 stuff class mc6847_friend_device : public device_t { public: // video mode constants static constexpr uint8_t MODE_AG = 0x80; static constexpr uint8_t MODE_GM2 = 0x40; static constexpr uint8_t MODE_GM1 = 0x20; static constexpr uint8_t MODE_GM0 = 0x10; static constexpr uint8_t MODE_CSS = 0x08; static constexpr uint8_t MODE_AS = 0x04; static constexpr uint8_t MODE_INTEXT = 0x02; static constexpr uint8_t MODE_INV = 0x01; typedef device_delegate get_char_rom_delegate; // inlines bool hs_r() const { return m_horizontal_sync; } bool fs_r() const { return m_field_sync; } template devcb_base &set_hsync_wr_callback(Object &&cb) { return m_write_hsync.set_callback(std::forward(cb)); } template devcb_base &set_fsync_wr_callback(Object &&cb) { return m_write_fsync.set_callback(std::forward(cb)); } template void set_get_char_rom(Object &&cb) { m_charrom_cb = std::forward(cb); } protected: mc6847_friend_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock, const uint8_t *fontdata, bool is_mc6847t1, double tpfs, int field_sync_falling_edge_scanline, int divider, bool supports_partial_body_scanlines); // timer constants static constexpr device_timer_id TIMER_FRAME = 0; static constexpr device_timer_id TIMER_HSYNC_OFF = 1; static constexpr device_timer_id TIMER_HSYNC_ON = 2; static constexpr device_timer_id TIMER_FSYNC = 3; // fonts static const uint8_t pal_round_fontdata8x12[]; static const uint8_t pal_square_fontdata8x12[]; static const uint8_t ntsc_round_fontdata8x12[]; static const uint8_t ntsc_square_fontdata8x12[]; static const uint8_t semigraphics4_fontdata8x12[]; static const uint8_t semigraphics6_fontdata8x12[]; static const uint8_t s68047_fontdata8x12[]; static const uint8_t stripes[]; // pixel definitions typedef uint32_t pixel_t; pixel_t *bitmap_addr(bitmap_rgb32 &bitmap, int y, int x) { return &bitmap.pix32(y, x); } static uint8_t simplify_mode(uint8_t data, uint8_t mode) { // simplifies MC6847 modes to drop mode flags that are not significant return mode & ~((mode & MODE_AG) ? (MODE_AS | MODE_INV) : 0); } // internal class that represents a MC6847 character map class character_map { public: // constructor that sets up the font data character_map(const uint8_t *fontdata, bool is_mc6847t1); // optimized template function that emits a single character template ATTR_FORCE_INLINE void emit_character(uint8_t mode, const uint8_t *data, int length, pixel_t *RESTRICT pixels, int y, const pixel_t *palette) { for (int i = 0; i < length; i++) { // get the character uint8_t character = data[i]; // based on the mode, determine which entry to use const entry *e = &m_entries[mode % ARRAY_LENGTH(m_entries)]; // identify the character in the font data const uint8_t *font_character = e->m_fontdata + (character & e->m_character_mask) * 12; // get the particular slice out uint8_t font_character_slice = font_character[y % 12]; // get the two colors uint16_t color_base_0 = e->m_color_base_0 + ((character >> e->m_color_shift_0) & e->m_color_mask_0); uint16_t color_base_1 = e->m_color_base_1 + ((character >> e->m_color_shift_1) & e->m_color_mask_1); pixel_t color_0 = palette[color_base_0]; pixel_t color_1 = palette[color_base_1]; // emit the bits for (int j = 0; j < 8; j++) { for (int k = 0; k < xscale; k++) { pixels[(i * 8 + j) * xscale + k] = bit_test(font_character_slice, j, color_0, color_1); } } } } private: struct entry { const uint8_t *m_fontdata; uint8_t m_character_mask; uint8_t m_color_shift_0; uint8_t m_color_shift_1; uint8_t m_color_mask_0; uint8_t m_color_mask_1; uint16_t m_color_base_0; uint16_t m_color_base_1; }; // lookup table for MC6847 modes to determine font data and color entry m_entries[128]; // text font data calculated on startup uint8_t m_text_fontdata_inverse[64*12]; uint8_t m_text_fontdata_lower_case[64*12]; uint8_t m_text_fontdata_lower_case_inverse[64*12]; uint8_t m_stripes[128*12]; // optimized function that tests a single bit ATTR_FORCE_INLINE pixel_t bit_test(uint8_t data, int shift, pixel_t color_0, pixel_t color_1) { return data & (0x80 >> shift) ? color_1 : color_0; } }; // artificater internal class class artifacter { public: artifacter(); // artifacting config void setup_config(device_t *device); void poll_config() { m_artifacting = (m_config!=nullptr) ? m_config->read() : 0; } // artifacting application template ATTR_FORCE_INLINE void process_artifacts(pixel_t *pixels, uint8_t mode, const pixel_t *palette) { if (((mode & (MODE_AG|MODE_GM2|MODE_GM1|MODE_GM0)) == (MODE_AG|MODE_GM2|MODE_GM1|MODE_GM0)) && (m_artifacting != 0)) { // identify the new colors and update pixel_t c0 = palette[(mode & MODE_CSS) ? 10 : 8]; pixel_t c1 = palette[(mode & MODE_CSS) ? 11 : 9]; update_colors(c0, c1); // generate the new line pixel_t new_line[256]; int x, i; for (x = 0; x < 256; x += 2) { uint8_t val = ((pixels[(x - 2) * xscale] == c1) ? 0x20 : 0x00) | ((pixels[(x - 1) * xscale] == c1) ? 0x10 : 0x00) | ((pixels[(x + 0) * xscale] == c1) ? 0x08 : 0x00) | ((pixels[(x + 1) * xscale] == c1) ? 0x04 : 0x00) | ((pixels[(x + 2) * xscale] == c1) ? 0x02 : 0x00) | ((pixels[(x + 3) * xscale] == c1) ? 0x01 : 0x00); new_line[x + 0] = m_expanded_colors[val * 2 + 0]; new_line[x + 1] = m_expanded_colors[val * 2 + 1]; } // and copy it in for (x = 0; x < 256; x++) { for (i = 0; i < xscale; i++) pixels[x * xscale + i] = new_line[x]; } } } private: ioport_port *m_config; ioport_value m_artifacting; ioport_value m_saved_artifacting; pixel_t m_saved_c0, m_saved_c1; pixel_t m_expanded_colors[128]; void update_colors(pixel_t c0, pixel_t c1); static pixel_t mix_color(double factor, uint8_t c0, uint8_t c1); }; enum border_color_t { BORDER_COLOR_BLACK, BORDER_COLOR_GREEN, BORDER_COLOR_WHITE, BORDER_COLOR_ORANGE }; // callbacks devcb_write_line m_write_hsync; devcb_write_line m_write_fsync; /* if specified, this reads the external char rom off of the driver state */ // moved here from mc6847_base_device so to be useable in GIME get_char_rom_delegate m_charrom_cb; // incidentals character_map m_character_map; artifacter m_artifacter; // device-level overrides virtual void device_start() override; virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) override; virtual void device_reset() override; virtual void device_post_load() override; // other overridables virtual void new_frame(); virtual void horizontal_sync_changed(bool line); virtual void field_sync_changed(bool line); virtual void enter_bottom_border(); virtual void record_border_scanline(uint16_t physical_scanline); virtual void record_body_scanline(uint16_t physical_scanline, uint16_t logical_scanline) = 0; virtual void record_partial_body_scanline(uint16_t physical_scanline, uint16_t logical_scanline, int32_t start_clock, int32_t end_clock) = 0; // miscellaneous void video_flush(); std::string describe_context() const; // setup functions emu_timer *setup_timer(device_timer_id id, double offset, double period); // converts to B&W static pixel_t black_and_white(rgb_t color) { uint8_t average_color = (color.r() + color.g() + color.b()) / 3; return rgb_t(average_color, average_color, average_color); } // changes the geometry ATTR_FORCE_INLINE void set_geometry(uint16_t top_border_scanlines, uint16_t body_scanlines, bool wide) { if (UNEXPECTED((m_top_border_scanlines != top_border_scanlines) || (m_body_scanlines != body_scanlines) || (m_wide != wide))) { m_top_border_scanlines = top_border_scanlines; m_body_scanlines = body_scanlines; m_wide = wide; update_field_sync_timer(); } } // calculates the border color static ATTR_FORCE_INLINE border_color_t border_value(uint8_t mode, bool is_mc6847t1) { border_color_t result; if (mode & MODE_AG) { // graphics result = mode & MODE_CSS ? BORDER_COLOR_WHITE : BORDER_COLOR_GREEN; } else if (!is_mc6847t1 || ((mode & MODE_GM2) == 0)) { // text, black border result = BORDER_COLOR_BLACK; } else { // text, green or orange border result = mode & MODE_CSS ? BORDER_COLOR_ORANGE : BORDER_COLOR_GREEN; } return result; } // checks to see if the video has changed ATTR_FORCE_INLINE bool has_video_changed() { /* poll the artifacting config */ m_artifacter.poll_config(); /* if the video didn't change, indicate as much */ bool video_changed = m_video_changed; m_video_changed = false; return video_changed; } // updates a byte in the video state template ATTR_FORCE_INLINE bool update_value(T *ptr, T byte) { bool result = false; if (*ptr != byte) { *ptr = byte; m_video_changed = true; result = true; } return result; } // template function for emitting graphics bytes template ATTR_FORCE_INLINE void emit_graphics(const uint8_t *data, int length, pixel_t *RESTRICT pixels, uint16_t color_base, const pixel_t *RESTRICT palette) { for (int i = 0; i < length; i++) { for (int j = 0; j < (8 / bits_per_pixel); j++) { for (int k = 0; k < xscale; k++) { uint16_t color = color_base + ((data[i] >> (8 - (j + 1) * bits_per_pixel)) & ((1 << bits_per_pixel) - 1)); pixels[(i * (8 / bits_per_pixel) + j) * xscale + k] = palette[color]; } } } } // template function for external bytes template ATTR_FORCE_INLINE void emit_extbytes(const uint8_t *data, int length, pixel_t *RESTRICT pixels, uint16_t color_base, const pixel_t *RESTRICT palette) { for (int i = 0; i < length; i++) { for (int j = 0; j < (8 / bits_per_pixel); j++) { for (int k = 0; k < xscale; k++) { uint16_t color = color_base + BIT(data[i], 7-j); pixels[(i * (8 / bits_per_pixel) + j) * xscale + k] = palette[color]; } } } } // template function for emitting samples template uint32_t emit_mc6847_samples(uint8_t mode, const uint8_t *data, int length, pixel_t *RESTRICT pixels, const pixel_t *RESTRICT palette, get_char_rom_delegate const &get_char_rom, int x, int y) { uint32_t result; if (mode & MODE_AG) { /* graphics */ switch(mode & (MODE_GM2|MODE_GM1|MODE_GM0)) { case 0: emit_graphics<2, xscale * 4>(data, length, pixels, (mode & MODE_CSS) ? 4 : 0, palette); result = length * 8 * xscale * 2; break; case MODE_GM0: case MODE_GM1|MODE_GM0: case MODE_GM2|MODE_GM0: emit_graphics<1, xscale * 2>(data, length, pixels, (mode & MODE_CSS) ? 10 : 8, palette); result = length * 8 * xscale * 2; break; case MODE_GM1: case MODE_GM2: case MODE_GM2|MODE_GM1: emit_graphics<2, xscale * 2>(data, length, pixels, (mode & MODE_CSS) ? 4 : 0, palette); result = length * 8 * xscale; break; case MODE_GM2|MODE_GM1|MODE_GM0: emit_graphics<1, xscale * 1>(data, length, pixels, (mode & MODE_CSS) ? 10 : 8, palette); result = length * 8 * xscale; break; default: /* should not get here */ fatalerror("Should not get here\n"); break; } } else if (!m_charrom_cb.isnull() && ((mode & (MODE_AG|MODE_AS|MODE_INTEXT)) == MODE_INTEXT)) { /* external ROM */ for (int i = 0; i < length; i++) { uint8_t byte = m_charrom_cb(data[i], y % 12) ^ (mode & MODE_INV ? 0xFF : 0x00); emit_extbytes<1, xscale>(&byte, 1, &pixels[i * 8], (mode & MODE_CSS) ? 14 : 12, palette); } result = length * 8 * xscale; } else { /* text/semigraphics */ m_character_map.emit_character(mode, data, length, pixels, y, palette); result = length * 8 * xscale; } return result; } private: enum scanline_zone { SCANLINE_ZONE_TOP_BORDER, SCANLINE_ZONE_BODY, SCANLINE_ZONE_BOTTOM_BORDER, SCANLINE_ZONE_RETRACE, SCANLINE_ZONE_VBLANK, SCANLINE_ZONE_FRAME_END }; // timers emu_timer *m_frame_timer; emu_timer *m_hsync_on_timer; emu_timer *m_hsync_off_timer; emu_timer *m_fsync_timer; // incidentals double m_tpfs; int m_divider; int m_field_sync_falling_edge_scanline; bool m_wide; bool m_video_changed; uint16_t m_top_border_scanlines; uint16_t m_body_scanlines; bool m_recording_scanline; bool m_supports_partial_body_scanlines; // video state uint16_t m_physical_scanline; uint16_t m_logical_scanline; uint16_t m_logical_scanline_zone; bool m_horizontal_sync; bool m_field_sync; uint32_t m_partial_scanline_clocks; // functions void change_horizontal_sync(bool line); void change_field_sync(bool line); void update_field_sync_timer(); void next_scanline(); int32_t get_clocks_since_hsync(); // debugging std::string scanline_zone_string(scanline_zone zone) const; }; // actual base class for MC6847 family of devices class mc6847_base_device : public mc6847_friend_device { public: template devcb_base &set_input_callback(Object &&cb) { return m_input_cb.set_callback(std::forward(cb)); } void set_get_fixed_mode(uint8_t mode) { m_fixed_mode = mode; } void set_black_and_white(bool bw) { m_black_and_white = bw; } /* updates the screen -- this will call begin_update(), followed by update_row() repeatedly and after all row updating is complete, end_update() */ uint32_t screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect); // mode changing operations DECLARE_WRITE_LINE_MEMBER( ag_w ) { change_mode(MODE_AG, state); } DECLARE_WRITE_LINE_MEMBER( gm2_w ) { change_mode(MODE_GM2, state); } DECLARE_WRITE_LINE_MEMBER( gm1_w ) { change_mode(MODE_GM1, state); } DECLARE_WRITE_LINE_MEMBER( gm0_w ) { change_mode(MODE_GM0, state); } DECLARE_WRITE_LINE_MEMBER( as_w ) { change_mode(MODE_AS, state); } DECLARE_WRITE_LINE_MEMBER( css_w ) { change_mode(MODE_CSS, state); } DECLARE_WRITE_LINE_MEMBER( intext_w ) { change_mode(MODE_INTEXT, state); } DECLARE_WRITE_LINE_MEMBER( inv_w ) { change_mode(MODE_INV, state); } protected: mc6847_base_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock, const uint8_t *fontdata, double tpfs); // device-level overrides virtual void device_start() override; virtual void device_reset() override; virtual ioport_constructor device_input_ports() const override; // other overrides virtual void field_sync_changed(bool line) override; virtual void record_body_scanline(uint16_t physical_scanline, uint16_t scanline) override; virtual void record_partial_body_scanline(uint16_t physical_scanline, uint16_t logical_scanline, int32_t start_clock, int32_t end_clock) override; void set_custom_palette(const pixel_t *custom_palette) { if (m_palette != m_bw_palette) { m_palette = custom_palette ? custom_palette : s_palette; } } private: struct video_scanline { uint8_t m_sample_count; uint8_t m_mode[32]; uint8_t m_data[32]; }; // palette static const int PALETTE_LENGTH = 16; static const uint32_t s_palette[PALETTE_LENGTH]; // callbacks /* if specified, this gets called whenever reading a byte (offs_t ~0 specifies DA* entering the tristate mode) */ devcb_read8 m_input_cb; /* if true, this is black and white */ bool m_black_and_white; // incidentals uint8_t m_fixed_mode; uint8_t m_fixed_mode_mask; const pixel_t *m_palette; pixel_t m_bw_palette[PALETTE_LENGTH]; // state uint8_t m_mode; uint16_t m_video_address; bool m_dirty; video_scanline m_data[192]; void change_mode(uint8_t mode, int state) { // sanity check, to ensure that we're not changing fixed modes assert((mode & m_fixed_mode_mask) == 0); // calculate new mode uint8_t new_mode; if (state) new_mode = m_mode | mode; else new_mode = m_mode & ~mode; // has the mode changed? if (new_mode != m_mode) { // it has! check dirty flag video_flush(); if (!m_dirty) { m_dirty = true; } // and set the new mode m_mode = new_mode; } } // setup functions void setup_fixed_mode(); // runtime functions void record_body_scanline(uint16_t physical_scanline, uint16_t scanline, int32_t start_pos, int32_t end_pos); pixel_t border_value(uint8_t mode, const pixel_t *palette, bool is_mc6847t1); // template function for doing video update collection template void record_scanline_res(int scanline, int32_t start_pos, int32_t end_pos); // miscellaneous uint8_t input(uint16_t address); int32_t scanline_position_from_clock(int32_t clocks_since_hsync); }; //************************************************************************** // VARIATIONS //************************************************************************** class mc6847_ntsc_device : public mc6847_base_device { public: mc6847_ntsc_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); }; class mc6847_pal_device : public mc6847_base_device { public: mc6847_pal_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); }; class mc6847y_ntsc_device : public mc6847_base_device { public: mc6847y_ntsc_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); }; class mc6847y_pal_device : public mc6847_base_device { public: mc6847y_pal_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); }; class mc6847t1_ntsc_device : public mc6847_base_device { public: mc6847t1_ntsc_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); }; class mc6847t1_pal_device : public mc6847_base_device { public: mc6847t1_pal_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); }; class s68047_device : public mc6847_base_device { public: s68047_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); void hack_black_becomes_blue(bool flag); private: static const uint32_t s_s68047_hack_palette[16]; }; class m5c6847p1_device : public mc6847_base_device { public: m5c6847p1_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); }; DECLARE_DEVICE_TYPE(MC6847_NTSC, mc6847_ntsc_device) DECLARE_DEVICE_TYPE(MC6847_PAL, mc6847_pal_device) DECLARE_DEVICE_TYPE(MC6847Y_NTSC, mc6847y_ntsc_device) DECLARE_DEVICE_TYPE(MC6847Y_PAL, mc6847y_pal_device) DECLARE_DEVICE_TYPE(MC6847T1_NTSC, mc6847t1_ntsc_device) DECLARE_DEVICE_TYPE(MC6847T1_PAL, mc6847t1_pal_device) DECLARE_DEVICE_TYPE(S68047, s68047_device) DECLARE_DEVICE_TYPE(M5C6847P1, m5c6847p1_device) #endif // MAME_VIDEO_MC6847_H