mirror of
https://github.com/mlaux/gb6.git
synced 2026-10-24 16:02:16 +00:00
541 lines
14 KiB
C
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);
|
|
} |