Files
gb6/system6/lcd_mac.c
T

541 lines
14 KiB
C

/* Game Boy emulator for 68k Macs
lcd_mac.c - LCD rendering with 1x/2x scaling */
#include <Quickdraw.h>
#include <Windows.h>
#include <Palettes.h>
#include "../src/lcd.h"
#include "../src/dmg.h"
#include "../src/cgb.h"
#include "../src/prof.h"
#include "emulator.h"
#include "lcd_mac.h"
#include "settings.h"
#include "gb_palettes.h"
int current_palette = 0;
// set for window updates, palette/scale/mode etc
static int force_all = 1;
static int half_res_mode(void)
{
return screen_scale == 1 && video_mode == VIDEO_BW;
}
void lcd_mac_invalidate(void)
{
force_all = 1;
if (dmg.lcd) {
dmg.lcd->row_stride = half_res_mode() ? 2 : 1;
}
}
// packed byte bits: [7-6] [5-4] [3-2] [1-0]
// GB pixel: p0 p1 p2 p3
// lookup tables for 2x dithered rendering
// index = 4 packed GB pixels (2 bits each), output = 8 screen pixels (1bpp)
static unsigned char dither_row0[256];
static unsigned char dither_row1[256];
// 4 GB pixels -> 4 screen bits
// one table per screen row of the doubled line, hi/lo pack two source
// bytes into one output byte like the 2x tables do
static unsigned char half_top_hi[256];
static unsigned char half_top_lo[256];
static unsigned char half_bot_hi[256];
static unsigned char half_bot_lo[256];
// LUTs for indexed color modes - map packed byte directly to screen pixels
// 1x: 4 pixels per packed byte
static unsigned long color_lut_1x[256];
// 2x: 8 pixels per packed byte (each GB pixel doubled), split into two 32-bit values
static unsigned long color_lut_2x_lo[256]; // pixels 0,0,1,1
static unsigned long color_lut_2x_hi[256]; // pixels 2,2,3,3
// only needed for 2x in 2bbp mode since 1x can use pixels[] directly
static unsigned short gray2_2x[256]; // 8 pixels @ 2bpp = 2 bytes
static unsigned short depth4_1x[256]; // 4 pixels @ 4bpp = 2 bytes
static unsigned long depth4_2x[256]; // 8 pixels @ 4bpp = 4 bytes
// 0/4, 1/4, checkerboard, solid
static int half_dither_bit(int color, int row, int col)
{
if (color == 3) {
return 1;
}
if (color == 2) {
return row == col;
}
if (color == 1) {
return row && col;
}
return 0;
}
void init_dither_lut(void)
{
// 2x2 dither patterns for each GB color (0-3)
// stored in high 2 bits: 00=white, 01=right black, 10=left black, 11=both black
// color 0 (white): top=00 bot=00
// color 1 (light gray): top=00 bot=01 (one black pixel, bottom-right)
// color 2 (dark gray): top=10 bot=01 (checkerboard)
// color 3 (black): top=11 bot=11
static const unsigned char pat_top[4] = { 0x00, 0x00, 0x80, 0xc0 };
static const unsigned char pat_bot[4] = { 0x00, 0x40, 0x40, 0xc0 };
int idx;
int top, bot;
for (idx = 0; idx < 256; idx++) {
int p0 = (idx >> 6) & 3;
int p1 = (idx >> 4) & 3;
int p2 = (idx >> 2) & 3;
int p3 = idx & 3;
dither_row0[idx] = pat_top[p0] | (pat_top[p1] >> 2) |
(pat_top[p2] >> 4) | (pat_top[p3] >> 6);
dither_row1[idx] = pat_bot[p0] | (pat_bot[p1] >> 2) |
(pat_bot[p2] >> 4) | (pat_bot[p3] >> 6);
top = (half_dither_bit(p0, 0, 0) << 3) | (half_dither_bit(p1, 0, 1) << 2) |
(half_dither_bit(p2, 0, 0) << 1) | half_dither_bit(p3, 0, 1);
bot = (half_dither_bit(p0, 1, 0) << 3) | (half_dither_bit(p1, 1, 1) << 2) |
(half_dither_bit(p2, 1, 0) << 1) | half_dither_bit(p3, 1, 1);
half_top_hi[idx] = top << 4;
half_top_lo[idx] = top;
half_bot_hi[idx] = bot << 4;
half_bot_lo[idx] = bot;
}
}
static void build_indexed8_luts(const unsigned char *gray_index)
{
int k;
for (k = 0; k < 256; k++) {
unsigned char c0 = gray_index[(k >> 6) & 3];
unsigned char c1 = gray_index[(k >> 4) & 3];
unsigned char c2 = gray_index[(k >> 2) & 3];
unsigned char c3 = gray_index[k & 3];
// 1x: 4 consecutive pixels
color_lut_1x[k] = ((unsigned long)c0 << 24) | ((unsigned long)c1 << 16) |
((unsigned long)c2 << 8) | c3;
// 2x: each pixel doubled horizontally
color_lut_2x_lo[k] = ((unsigned long)c0 << 24) | ((unsigned long)c0 << 16) |
((unsigned long)c1 << 8) | c1;
color_lut_2x_hi[k] = ((unsigned long)c2 << 24) | ((unsigned long)c2 << 16) |
((unsigned long)c3 << 8) | c3;
}
}
static void build_gray2_2x_lut(void)
{
int k;
for (k = 0; k < 256; k++) {
unsigned c0 = (k >> 6) & 3;
unsigned c1 = (k >> 4) & 3;
unsigned c2 = (k >> 2) & 3;
unsigned c3 = k & 3;
gray2_2x[k] = (unsigned short) ((c0 << 14) | (c0 << 12)
| (c1 << 10) | (c1 << 8)
| (c2 << 6) | (c2 << 4)
| (c3 << 2) | c3);
}
}
// left pixel in the high nibble, same pixel order as the GB byte
static void build_depth4_luts(const unsigned char *gray_index)
{
int k;
for (k = 0; k < 256; k++) {
unsigned long c0 = gray_index[(k >> 6) & 3];
unsigned long c1 = gray_index[(k >> 4) & 3];
unsigned long c2 = gray_index[(k >> 2) & 3];
unsigned long c3 = gray_index[k & 3];
depth4_1x[k] = (unsigned short) ((c0 << 12) | (c1 << 8) | (c2 << 4) | c3);
depth4_2x[k] = (c0 << 28) | (c0 << 24) | (c1 << 20) | (c1 << 16)
| (c2 << 12) | (c2 << 8) | (c3 << 4) | c3;
}
}
void init_video_luts(WindowPtr wp)
{
static const unsigned char gray4_index[4] = { 0, 5, 10, 15 };
int k;
PaletteHandle pal;
unsigned char gray_index[4];
if (video_mode == VIDEO_GRAY2) {
build_gray2_2x_lut();
} else if (video_mode == VIDEO_GRAY4) {
build_depth4_luts(gray4_index);
} else {
pal = NewPalette(4, nil, pmTolerant, 0);
for (k = 0; k < 4; k++) {
SetEntryColor(pal, k, &gb_palettes[current_palette].colors[k]);
}
SetPalette(wp, pal, true);
ActivatePalette(wp);
for (k = 0; k < 4; k++) {
gray_index[k] = Entry2Index(k);
}
if (video_mode == VIDEO_INDEXED4) {
build_depth4_luts(gray_index);
} else {
build_indexed8_luts(gray_index);
}
}
}
// which pixmap holds the frame for screenshot
PixMap *lcd_active_pixmap(void)
{
if (dmg.cgb && dmg.cgb->mode) {
return &offscreen_pixmap;
}
return VIDEO_IS_LOW_DEPTH(video_mode) ? &lowdepth_pixmap : &offscreen_pixmap;
}
// one CopyBits per contiguous run of rows sharing a scroll offset and dirty status
static void lcd_blit_bands(
const BitMap *src,
const BitMap *dst,
struct lcd *lcd_ptr,
int scale,
int all)
{
Rect src_rect, dst_rect;
int voff = lcd_ptr->row_voff;
int y0 = 0;
while (y0 < 144) {
int off = lcd_ptr->row_scx[y0 + voff];
int dirty = all || lcd_ptr->row_dirty[y0 + voff] != 0;
int y1 = y0 + 1;
while (y1 < 144 && lcd_ptr->row_scx[y1 + voff] == off
&& (all || (lcd_ptr->row_dirty[y1 + voff] != 0) == dirty)) {
y1++;
}
if (dirty) {
src_rect.top = (y0 + voff) * scale;
src_rect.bottom = (y1 + voff) * scale;
src_rect.left = off * scale;
src_rect.right = (off + 160) * scale;
dst_rect.top = y0 * scale;
dst_rect.bottom = y1 * scale;
dst_rect.left = 0;
dst_rect.right = 160 * scale;
CopyBits(src, dst, &src_rect, &dst_rect, srcCopy, NULL);
}
y0 = y1;
}
}
void lcd_blit_color_bands(PixMap *src, struct lcd *lcd_ptr, int scale, int all)
{
CGrafPtr port;
SetPort(g_wp);
port = (CGrafPtr) g_wp;
lcd_blit_bands((BitMap *) src, (BitMap *) *port->portPixMap,
lcd_ptr, scale, all);
}
static void lcd_blit_bw_bands(struct lcd *lcd_ptr, int scale, int all)
{
SetPort(g_wp);
lcd_blit_bands(&offscreen_bmp, &g_wp->portBits, lcd_ptr, scale, all);
}
// 1x rendering: the renderer only fills even lines (row_stride 2), and
// each one becomes the two rows of a 2x2 ordered dither cell
static void lcd_draw_1x_bw(struct lcd *lcd_ptr)
{
int gy;
unsigned char *src = lcd_ptr->pixels;
unsigned char *dst = (unsigned char *) offscreen_buf;
if (!force_all && !lcd_ptr->frame_dirty) {
return;
}
for (gy = 0; gy < LCD_BUF_ROWS; gy += 2) {
// not ROW_DIRTY_OFFSET bc the actual pixels are the same for that
if (force_all || (lcd_ptr->row_dirty[gy] & ROW_DIRTY_CONTENT)) {
unsigned char *bot = dst + 21;
int gx;
for (gx = 0; gx < 21; gx++) {
// 2 packed bytes = 8 pixels -> 1 output byte per screen row
unsigned char a = src[gx * 2];
unsigned char b = src[gx * 2 + 1];
dst[gx] = half_top_hi[a] | half_top_lo[b];
bot[gx] = half_bot_hi[a] | half_bot_lo[b];
}
}
src += 84;
dst += 42;
}
lcd_blit_bw_bands(lcd_ptr, 1, force_all);
}
static void lcd_draw_1x_indexed(struct lcd *lcd_ptr)
{
int gy;
unsigned char *src = lcd_ptr->pixels;
unsigned long *dst = (unsigned long *) offscreen_color_buf;
if (screen_depth == 1) {
return;
}
if (!force_all && !lcd_ptr->frame_dirty) {
return;
}
for (gy = 0; gy < LCD_BUF_ROWS; gy++) {
// not ROW_DIRTY_OFFSET bc the actual pixels are the same for that
if (force_all || (lcd_ptr->row_dirty[gy] & ROW_DIRTY_CONTENT)) {
int gx;
for (gx = 0; gx < 42; gx++) {
// LUT maps packed byte directly to 4 color pixels
dst[gx] = color_lut_1x[src[gx]];
}
}
src += 42;
dst += 42;
}
lcd_blit_color_bands(&offscreen_pixmap, lcd_ptr, 1, force_all);
}
static void lcd_draw_2x_bw(struct lcd *lcd_ptr)
{
int gy;
unsigned char *src = lcd_ptr->pixels;
unsigned char *dst = (unsigned char *) offscreen_buf;
if (!force_all && !lcd_ptr->frame_dirty) {
return;
}
for (gy = 0; gy < LCD_BUF_ROWS; gy++) {
unsigned char *row0 = dst;
unsigned char *row1 = dst + 42;
int gx;
if (!force_all && !(lcd_ptr->row_dirty[gy] & ROW_DIRTY_CONTENT)) {
src += 42;
dst += 84;
continue;
}
for (gx = 0; gx < 42; gx++) {
// packed byte is already the LUT index
unsigned char idx = *src++;
*row0++ = dither_row0[idx];
*row1++ = dither_row1[idx];
}
// advance 2 screen rows at a time
dst += 84;
}
lcd_blit_bw_bands(lcd_ptr, 2, force_all);
}
static void lcd_draw_2x_indexed(struct lcd *lcd_ptr)
{
int gy;
unsigned char *src = lcd_ptr->pixels;
unsigned long *dst = (unsigned long *) offscreen_color_buf;
if (screen_depth == 1) {
return;
}
if (!force_all && !lcd_ptr->frame_dirty) {
return;
}
for (gy = 0; gy < LCD_BUF_ROWS; gy++) {
unsigned long *row0 = dst;
unsigned long *row1 = dst + 84;
int gx;
if (!force_all && !(lcd_ptr->row_dirty[gy] & ROW_DIRTY_CONTENT)) {
src += 42;
dst += 168;
continue;
}
for (gx = 0; gx < 42; gx++) {
// LUT maps packed byte to 8 doubled pixels (two 32-bit values)
unsigned char idx = *src++;
unsigned long lo = color_lut_2x_lo[idx];
unsigned long hi = color_lut_2x_hi[idx];
// row0: p0 p0 p1 p1 p2 p2 p3 p3
// row1: p0 p0 p1 p1 p2 p2 p3 p3
row0[0] = lo; row0[1] = hi;
row1[0] = lo; row1[1] = hi;
row0 += 2;
row1 += 2;
}
dst += 168; // 2 rows * 84 longs per row
}
lcd_blit_color_bands(&offscreen_pixmap, lcd_ptr, 2, force_all);
}
// the one mode with nothing to convert, small speed boost when scrolling
// for slow machines
static void lcd_draw_1x_gray2(struct lcd *lcd_ptr)
{
if (!force_all && !lcd_ptr->frame_dirty) {
return;
}
lcd_blit_color_bands(&lowdepth_pixmap, lcd_ptr, 1, force_all);
}
static void lcd_draw_2x_gray2(struct lcd *lcd_ptr)
{
int gy;
unsigned char *src = lcd_ptr->pixels;
unsigned short *dst = (unsigned short *) offscreen_color_buf;
if (!force_all && !lcd_ptr->frame_dirty) {
return;
}
for (gy = 0; gy < LCD_BUF_ROWS; gy++) {
unsigned short *row0 = dst;
unsigned short *row1 = dst + 42;
int gx;
if (!force_all && !(lcd_ptr->row_dirty[gy] & ROW_DIRTY_CONTENT)) {
src += 42;
dst += 84;
continue;
}
for (gx = 0; gx < 42; gx++) {
unsigned short v = gray2_2x[*src++];
*row0++ = v;
*row1++ = v;
}
dst += 84;
}
lcd_blit_color_bands(&lowdepth_pixmap, lcd_ptr, 2, force_all);
}
static void lcd_draw_1x_depth4(struct lcd *lcd_ptr)
{
int gy;
unsigned char *src = lcd_ptr->pixels;
unsigned short *dst = (unsigned short *) offscreen_color_buf;
if (!force_all && !lcd_ptr->frame_dirty) {
return;
}
for (gy = 0; gy < LCD_BUF_ROWS; gy++) {
if (force_all || (lcd_ptr->row_dirty[gy] & ROW_DIRTY_CONTENT)) {
int gx;
for (gx = 0; gx < 42; gx++) {
dst[gx] = depth4_1x[src[gx]];
}
}
src += 42;
dst += 42;
}
lcd_blit_color_bands(&lowdepth_pixmap, lcd_ptr, 1, force_all);
}
static void lcd_draw_2x_depth4(struct lcd *lcd_ptr)
{
int gy;
unsigned char *src = lcd_ptr->pixels;
unsigned long *dst = (unsigned long *) offscreen_color_buf;
if (!force_all && !lcd_ptr->frame_dirty) {
return;
}
for (gy = 0; gy < LCD_BUF_ROWS; gy++) {
unsigned long *row0 = dst;
unsigned long *row1 = dst + 42;
int gx;
if (!force_all && !(lcd_ptr->row_dirty[gy] & ROW_DIRTY_CONTENT)) {
src += 42;
dst += 84;
continue;
}
for (gx = 0; gx < 42; gx++) {
unsigned long v = depth4_2x[*src++];
*row0++ = v;
*row1++ = v;
}
dst += 84;
}
lcd_blit_color_bands(&lowdepth_pixmap, lcd_ptr, 2, force_all);
}
void (*draw_funcs[2][VIDEO_MODE_COUNT])(struct lcd *) = {
{ lcd_draw_1x_bw, lcd_draw_1x_indexed, lcd_draw_1x_gray2,
lcd_draw_1x_depth4, lcd_draw_1x_depth4 },
{ lcd_draw_2x_bw, lcd_draw_2x_indexed, lcd_draw_2x_gray2,
lcd_draw_2x_depth4, lcd_draw_2x_depth4 },
};
// lcd_mac_cgb.c
void lcd_draw_cgb(struct lcd *lcd_ptr, int all);
// called by dmg_step at vblank
void lcd_draw(struct lcd *lcd_ptr)
{
PROF_SET(PROF_DRAW);
if (dmg.cgb && dmg.cgb->mode && screen_depth > 1) {
lcd_draw_cgb(lcd_ptr, force_all);
force_all = 0;
PROF_SET(PROF_RENDER);
return;
}
// screen scale is 1 based, video mode is 0 based
draw_funcs[screen_scale - 1][video_mode](lcd_ptr);
force_all = 0;
PROF_SET(PROF_RENDER);
}