New source for TNT RAMDAC emulation (DACula & RaDACal).

This commit is contained in:
Maxim Poliakovski 2024-02-09 09:45:34 +01:00
parent 478bd31dc7
commit dd95468d74
2 changed files with 329 additions and 0 deletions

View File

@ -0,0 +1,213 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-24 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/** Apple RAMDAC ASICs (RaDACal & DACula) emulation. */
#include <core/bitops.h>
#include <devices/video/appleramdac.h>
#include <loguru.hpp>
#include <memaccess.h>
AppleRamdac::AppleRamdac(DacFlavour flavour) {
this->flavour = flavour;
supports_types(HWCompType::IOBUS_DEV);
set_name(this->flavour == DacFlavour::DACULA ? "DACula" : "RaDACal");
}
uint16_t AppleRamdac::iodev_read(uint32_t address) {
switch(address) {
case RamdacRegs::MULTI:
switch(this->dac_addr) {
case RamdacRegs::MISC_CTRL:
return this->dac_cr;
case RamdacRegs::PLL_CTRL:
return this->pll_cr;
case RamdacRegs::VENDOR_ID:
return DACULA_VENDOR_SIERRA;
default:
LOG_F(WARNING, "%s: read from unsupported multi-register at 0x%X",
this->name.c_str(), this->dac_addr);
}
break;
default:
LOG_F(WARNING, "%s: read from unsupported register at 0x%X",
this->name.c_str(), address);
}
return 0;
}
void AppleRamdac::iodev_write(uint32_t address, uint16_t value) {
switch(address) {
case RamdacRegs::ADDRESS:
this->dac_addr = value;
break;
case RamdacRegs::CURSOR_CLUT:
this->clut_color[this->comp_index++] = value;
if (this->comp_index >= 3) {
this->cursor_clut[this->dac_addr & 7] = (this->clut_color[0] << 16) |
(this->clut_color[1] << 8) | this->clut_color[2];
this->dac_addr++; // auto-increment CLUT address
this->comp_index = 0;
}
break;
case RamdacRegs::MULTI:
switch (this->dac_addr) {
case RamdacRegs::CURSOR_POS_HI:
this->cursor_xpos = (value << 8) | this->cursor_pos_lo;
break;
case RamdacRegs::CURSOR_POS_LO:
this->cursor_pos_lo = value;
break;
case RamdacRegs::MISC_CTRL:
if (bit_changed(this->dac_cr, value, 1)) {
if (value & 2)
this->cursor_ctrl_cb(true);
else
this->cursor_ctrl_cb(false);
}
this->dac_cr = value;
break;
case RamdacRegs::DBL_BUF_CTRL:
this->dbl_buf_cr = value;
break;
case RamdacRegs::TEST_CTRL:
this->tst_cr = value;
if (value & 1)
LOG_F(WARNING, "%s: DAC test enabled!", this->name.c_str());
break;
case RamdacRegs::PLL_CTRL:
this->pll_cr = value;
break;
case RamdacRegs::VIDCLK_M_SET_A:
this->clk_m[0] = value;
break;
case RamdacRegs::VIDCLK_PN_SET_A:
this->clk_pn[0] = value;
break;
case RamdacRegs::VIDCLK_M_SET_B:
this->clk_m[1] = value;
break;
case RamdacRegs::VIDCLK_PN_SET_B:
this->clk_pn[1] = value;
break;
default:
LOG_F(WARNING, "%s: write to unsupported multi-register at 0x%X",
this->name.c_str(), this->dac_addr);
}
break;
case RamdacRegs::CLUT_DATA:
this->clut_color[this->comp_index++] = value;
if (this->comp_index >= 3) {
this->set_clut_entry_cb(this->dac_addr, this->clut_color);
this->dac_addr++; // auto-increment CLUT address
this->comp_index = 0;
}
break;
default:
LOG_F(WARNING, "%s: write to unsupported register at 0x%X",
this->name.c_str(), address);
}
}
int AppleRamdac::get_clock_div() {
return 1 << ((dac_cr >> 6) + 1);
}
int AppleRamdac::get_pix_width() {
return 1 << (((this->dac_cr >> 2) & 3) + 3);
}
int AppleRamdac::get_dot_freq() {
uint8_t m = this->clk_m[this->pll_cr & 1];
uint8_t p = this->clk_pn[this->pll_cr & 1] >> 5;
uint8_t n = this->clk_pn[this->pll_cr & 1] & 0x1F;
double dot_freq = 14318180.0f * (double)m / ((double)n * (double)(1 << p));
return static_cast<int>(dot_freq + 0.5f);
}
// =========================== HW cursor stuff =============================
void AppleRamdac::measure_hw_cursor(uint8_t *fb_ptr) {
uint8_t *src_bw_ptr = fb_ptr + this->fb_pitch * (this->video_height - 1);
int cur_pos_y_start = -1;
int cur_pos_y_end = -1;
// forward scanning to find the first line of the cursor
for (int h = 0; h < this->video_height; h++, fb_ptr += this->fb_pitch) {
if (((uint64_t *)fb_ptr)[0] | ((uint64_t *)fb_ptr)[1]) {
cur_pos_y_start = h;
break;
}
}
if (cur_pos_y_start < 0)
return; // bail out because no cursor data start was found
// backward scanning to find the last line of the cursor
for (int h = this->video_height - 1; h >= 0; h--, src_bw_ptr -= this->fb_pitch) {
if (((uint64_t *)src_bw_ptr)[0] | ((uint64_t *)src_bw_ptr)[1]) {
cur_pos_y_end = h;
break;
}
}
if (cur_pos_y_end < 0)
return; // bail out because no cursor data end was found
this->cursor_height = cur_pos_y_end - cur_pos_y_start + 1;
this->cursor_ypos = cur_pos_y_start;
}
void AppleRamdac::draw_hw_cursor(uint8_t *src_buf, uint8_t *dst_buf, int dst_pitch) {
uint8_t *dst_row = dst_buf;
this->measure_hw_cursor(src_buf);
if (this->cursor_xpos >= this->video_width)
return;
src_buf += this->fb_pitch * this->cursor_ypos;
dst_row += this->cursor_ypos * dst_pitch + this->cursor_xpos * sizeof(uint32_t);
dst_pitch -= 32 * sizeof(uint32_t);
for (int h = 0; h < this->cursor_height; h++) {
for (int x = 0; x < 2; x++) { // two sets of 16 pixels
uint64_t pix_data = READ_QWORD_BE_A(src_buf + x * 8);
if (!pix_data) { // skip processing of 16 transparent pixels
dst_row += 16 * sizeof(uint32_t);
break;
}
for (int p = 0; p < 16; p++) {
uint8_t pix = pix_data >> 60; // each pixel is 4 bits wide
if (pix & 8) { // check control bit: 0 - transparent, 1 - opaque
WRITE_DWORD_LE_A(dst_row, this->cursor_clut[pix & 7]);
}
pix_data <<= 4;
dst_row += sizeof(uint32_t);
}
}
src_buf += this->fb_pitch;
dst_row += dst_pitch;
}
}

116
devices/video/appleramdac.h Normal file
View File

@ -0,0 +1,116 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-24 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/** Definitions for the Apple RAMDAC ASICs (RaDACal & DACula). */
#ifndef APPLE_RAMDAC_H
#define APPLE_RAMDAC_H
#include <devices/common/hwcomponent.h>
#include <devices/ioctrl/macio.h>
#include <cinttypes>
#include <functional>
enum DacFlavour {
RADACAL,
DACULA,
};
#define DACULA_VENDOR_SIERRA 0x3C
constexpr auto VIDEO_XTAL = 14318180.0f; // external crystal oscillator frequency
namespace RamdacRegs {
enum RamdacRegs : uint8_t {
ADDRESS = 0, // address register
CURSOR_CLUT = 1, // cursor palette data
MULTI = 2, // multipurpose section
CLUT_DATA = 3, // color palette data
// multipurpose section registers (both RaDACal & DACula)
CURSOR_POS_HI = 0x10, // cursor position, high-order byte
CURSOR_POS_LO = 0x11, // cursor position, low-order byte
MISC_CTRL = 0x20, // miscellaneus control bits
DBL_BUF_CTRL = 0x21, // double buffer control bits
TEST_CTRL = 0x22, // enable/disable DAC tests
// multipurpose section registers (DACula only)
PLL_CTRL = 0x23, // phase locked loop control
VIDCLK_M_SET_A = 0x24, // M parameter for the set A
VIDCLK_PN_SET_A = 0x25, // P & N parameters for the set A
VIDCLK_M_SET_B = 0x27, // M parameter for the set B
VIDCLK_PN_SET_B = 0x28, // P & N parameters for the set B
VENDOR_ID = 0x40,
};
}; // namespace RamdacRegs
typedef std::function<void(uint8_t index, uint8_t *colors)> SetClutEntryCallback;
typedef std::function<void(bool cursor_on)> CursorCtrlCallback;
class AppleRamdac : public HWComponent, public IobusDevice {
public:
AppleRamdac(DacFlavour flavour);
~AppleRamdac() = default;
uint16_t iodev_read(uint32_t address);
void iodev_write(uint32_t address, uint16_t value);
int get_clock_div();
int get_pix_width();
int get_dot_freq();
void set_fb_parameters(int width, int height, uint32_t pitch) {
this->video_width = width;
this->video_height = height;
this->fb_pitch = pitch;
};
void measure_hw_cursor(uint8_t *fb_ptr);
void draw_hw_cursor(uint8_t *src_buf, uint8_t *dst_buf, int dst_pitch);
SetClutEntryCallback set_clut_entry_cb = nullptr;
CursorCtrlCallback cursor_ctrl_cb = nullptr;
protected:
DacFlavour flavour;
uint8_t dac_addr = 0;
uint8_t dac_cr = 0;
uint8_t dbl_buf_cr = 0;
uint8_t tst_cr = 0;
uint16_t cursor_xpos = 0; // horizontal position of the cursor region
uint16_t cursor_ypos = 0;
uint16_t cursor_height = 0;
uint8_t cursor_pos_lo = 0;
uint8_t clk_m[2] = {};
uint8_t clk_pn[2] = {};
uint8_t pll_cr = 0;
uint8_t comp_index = 0;
uint8_t clut_color[3] = {};
uint32_t cursor_clut[8] = {};
int video_width = 0;
int video_height = 0;
uint32_t fb_pitch = 0;
};
#endif // APPLE_RAMDAC_H