2016-03-30 20:07:55 +00:00
|
|
|
|
#include <stdbool.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <assert.h>
|
2016-03-30 20:36:24 +00:00
|
|
|
|
#include <string.h>
|
2020-12-25 12:14:17 +00:00
|
|
|
|
#include <math.h>
|
2016-03-30 20:07:55 +00:00
|
|
|
|
#include "gb.h"
|
|
|
|
|
|
2018-03-03 13:47:36 +00:00
|
|
|
|
/* FIFO functions */
|
|
|
|
|
|
2018-03-04 21:27:31 +00:00
|
|
|
|
static inline unsigned fifo_size(GB_fifo_t *fifo)
|
2018-03-03 13:47:36 +00:00
|
|
|
|
{
|
2018-04-06 15:26:04 +00:00
|
|
|
|
return (fifo->write_end - fifo->read_end) & (GB_FIFO_LENGTH - 1);
|
2018-03-03 13:47:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void fifo_clear(GB_fifo_t *fifo)
|
|
|
|
|
{
|
|
|
|
|
fifo->read_end = fifo->write_end = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static GB_fifo_item_t *fifo_pop(GB_fifo_t *fifo)
|
|
|
|
|
{
|
|
|
|
|
GB_fifo_item_t *ret = &fifo->fifo[fifo->read_end];
|
|
|
|
|
fifo->read_end++;
|
2018-04-06 15:26:04 +00:00
|
|
|
|
fifo->read_end &= (GB_FIFO_LENGTH - 1);
|
2018-03-03 13:47:36 +00:00
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-09 16:52:36 +00:00
|
|
|
|
static void fifo_push_bg_row(GB_fifo_t *fifo, uint8_t lower, uint8_t upper, uint8_t palette, bool bg_priority, bool flip_x)
|
2018-03-03 13:47:36 +00:00
|
|
|
|
{
|
2018-03-09 16:52:36 +00:00
|
|
|
|
if (!flip_x) {
|
2021-02-22 12:45:30 +00:00
|
|
|
|
unrolled for (unsigned i = 8; i--;) {
|
2018-03-09 16:52:36 +00:00
|
|
|
|
fifo->fifo[fifo->write_end] = (GB_fifo_item_t) {
|
|
|
|
|
(lower >> 7) | ((upper >> 7) << 1),
|
|
|
|
|
palette,
|
|
|
|
|
0,
|
|
|
|
|
bg_priority,
|
|
|
|
|
};
|
|
|
|
|
lower <<= 1;
|
|
|
|
|
upper <<= 1;
|
|
|
|
|
|
|
|
|
|
fifo->write_end++;
|
2018-04-06 15:26:04 +00:00
|
|
|
|
fifo->write_end &= (GB_FIFO_LENGTH - 1);
|
2018-03-09 16:52:36 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
2021-02-22 12:45:30 +00:00
|
|
|
|
unrolled for (unsigned i = 8; i--;) {
|
2018-03-09 16:52:36 +00:00
|
|
|
|
fifo->fifo[fifo->write_end] = (GB_fifo_item_t) {
|
|
|
|
|
(lower & 1) | ((upper & 1) << 1),
|
|
|
|
|
palette,
|
|
|
|
|
0,
|
|
|
|
|
bg_priority,
|
|
|
|
|
};
|
|
|
|
|
lower >>= 1;
|
|
|
|
|
upper >>= 1;
|
|
|
|
|
|
|
|
|
|
fifo->write_end++;
|
2018-04-06 15:26:04 +00:00
|
|
|
|
fifo->write_end &= (GB_FIFO_LENGTH - 1);
|
2018-03-09 16:52:36 +00:00
|
|
|
|
}
|
2018-03-03 13:47:36 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-04 21:27:31 +00:00
|
|
|
|
static void fifo_overlay_object_row(GB_fifo_t *fifo, uint8_t lower, uint8_t upper, uint8_t palette, bool bg_priority, uint8_t priority, bool flip_x)
|
|
|
|
|
{
|
|
|
|
|
while (fifo_size(fifo) < 8) {
|
|
|
|
|
fifo->fifo[fifo->write_end] = (GB_fifo_item_t) {0,};
|
|
|
|
|
fifo->write_end++;
|
2018-04-06 15:26:04 +00:00
|
|
|
|
fifo->write_end &= (GB_FIFO_LENGTH - 1);
|
2018-03-04 21:27:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint8_t flip_xor = flip_x? 0: 0x7;
|
|
|
|
|
|
2021-02-22 12:45:30 +00:00
|
|
|
|
unrolled for (unsigned i = 8; i--;) {
|
2018-03-04 21:27:31 +00:00
|
|
|
|
uint8_t pixel = (lower >> 7) | ((upper >> 7) << 1);
|
2018-04-06 15:26:04 +00:00
|
|
|
|
GB_fifo_item_t *target = &fifo->fifo[(fifo->read_end + (i ^ flip_xor)) & (GB_FIFO_LENGTH - 1)];
|
2018-03-04 21:27:31 +00:00
|
|
|
|
if (pixel != 0 && (target->pixel == 0 || target->priority > priority)) {
|
|
|
|
|
target->pixel = pixel;
|
|
|
|
|
target->palette = palette;
|
|
|
|
|
target->bg_priority = bg_priority;
|
|
|
|
|
target->priority = priority;
|
|
|
|
|
}
|
|
|
|
|
lower <<= 1;
|
|
|
|
|
upper <<= 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2017-02-19 00:22:50 +00:00
|
|
|
|
/*
|
2021-12-17 19:16:23 +00:00
|
|
|
|
Each line is 456 cycles. Without scrolling, objects or a window:
|
2017-02-19 00:22:50 +00:00
|
|
|
|
Mode 2 - 80 cycles / OAM Transfer
|
|
|
|
|
Mode 3 - 172 cycles / Rendering
|
|
|
|
|
Mode 0 - 204 cycles / HBlank
|
|
|
|
|
|
|
|
|
|
Mode 1 is VBlank
|
|
|
|
|
*/
|
|
|
|
|
|
2018-02-24 22:48:45 +00:00
|
|
|
|
#define MODE2_LENGTH (80)
|
2019-02-16 02:19:16 +00:00
|
|
|
|
#define LINE_LENGTH (456)
|
2017-02-19 00:22:50 +00:00
|
|
|
|
#define LINES (144)
|
|
|
|
|
#define WIDTH (160)
|
2020-02-08 11:28:46 +00:00
|
|
|
|
#define BORDERED_WIDTH 256
|
|
|
|
|
#define BORDERED_HEIGHT 224
|
2018-02-24 22:48:45 +00:00
|
|
|
|
#define FRAME_LENGTH (LCDC_PERIOD)
|
2018-02-20 19:17:12 +00:00
|
|
|
|
#define VIRTUAL_LINES (FRAME_LENGTH / LINE_LENGTH) // = 154
|
2017-02-19 00:22:50 +00:00
|
|
|
|
|
2016-10-26 21:14:02 +00:00
|
|
|
|
typedef struct __attribute__((packed)) {
|
2016-06-18 17:29:11 +00:00
|
|
|
|
uint8_t y;
|
|
|
|
|
uint8_t x;
|
|
|
|
|
uint8_t tile;
|
|
|
|
|
uint8_t flags;
|
2021-11-06 23:10:58 +00:00
|
|
|
|
} object_t;
|
2016-03-30 20:07:55 +00:00
|
|
|
|
|
2021-12-04 13:04:46 +00:00
|
|
|
|
void GB_display_vblank(GB_gameboy_t *gb)
|
2018-02-24 22:48:45 +00:00
|
|
|
|
{
|
2018-02-10 12:42:14 +00:00
|
|
|
|
gb->vblank_just_occured = true;
|
2021-12-04 13:04:46 +00:00
|
|
|
|
gb->cycles_since_vblank_callback = 0;
|
|
|
|
|
gb->lcd_disabled_outside_of_vblank = false;
|
2018-11-16 14:04:40 +00:00
|
|
|
|
|
2019-07-15 17:47:16 +00:00
|
|
|
|
/* TODO: Slow in turbo mode! */
|
|
|
|
|
if (GB_is_hle_sgb(gb)) {
|
2018-11-16 14:04:40 +00:00
|
|
|
|
GB_sgb_render(gb);
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-21 13:00:53 +00:00
|
|
|
|
if (gb->turbo) {
|
|
|
|
|
if (GB_timing_sync_turbo(gb)) {
|
2016-03-30 20:07:55 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-04-19 18:55:58 +00:00
|
|
|
|
|
2020-02-15 17:21:43 +00:00
|
|
|
|
bool is_ppu_stopped = !GB_is_cgb(gb) && gb->stopped && gb->io_registers[GB_IO_LCDC] & 0x80;
|
|
|
|
|
|
2021-12-04 13:04:46 +00:00
|
|
|
|
if (!gb->disable_rendering && ((!(gb->io_registers[GB_IO_LCDC] & 0x80) || is_ppu_stopped) || gb->cgb_repeated_a_frame || gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON)) {
|
2017-08-12 18:42:47 +00:00
|
|
|
|
/* LCD is off, set screen to white or black (if LCD is on in stop mode) */
|
2020-02-15 17:21:43 +00:00
|
|
|
|
if (!GB_is_sgb(gb)) {
|
2020-01-29 12:19:11 +00:00
|
|
|
|
uint32_t color = 0;
|
|
|
|
|
if (GB_is_cgb(gb)) {
|
2020-03-25 18:33:13 +00:00
|
|
|
|
color = GB_convert_rgb15(gb, 0x7FFF, false);
|
2020-01-29 12:19:11 +00:00
|
|
|
|
}
|
|
|
|
|
else {
|
2020-02-15 17:21:43 +00:00
|
|
|
|
color = is_ppu_stopped ?
|
|
|
|
|
gb->background_palettes_rgb[0] :
|
2020-01-29 12:19:11 +00:00
|
|
|
|
gb->background_palettes_rgb[4];
|
|
|
|
|
}
|
2020-02-08 11:28:46 +00:00
|
|
|
|
if (gb->border_mode == GB_BORDER_ALWAYS) {
|
|
|
|
|
for (unsigned y = 0; y < LINES; y++) {
|
|
|
|
|
for (unsigned x = 0; x < WIDTH; x++) {
|
|
|
|
|
gb ->screen[x + y * BORDERED_WIDTH + (BORDERED_WIDTH - WIDTH) / 2 + (BORDERED_HEIGHT - LINES) / 2 * BORDERED_WIDTH] = color;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
for (unsigned i = 0; i < WIDTH * LINES; i++) {
|
|
|
|
|
gb ->screen[i] = color;
|
|
|
|
|
}
|
2018-11-14 22:21:21 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2020-02-08 11:28:46 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-04-02 22:29:43 +00:00
|
|
|
|
if (!gb->disable_rendering && gb->border_mode == GB_BORDER_ALWAYS && !GB_is_sgb(gb)) {
|
2020-02-08 11:28:46 +00:00
|
|
|
|
GB_borrow_sgb_border(gb);
|
|
|
|
|
uint32_t border_colors[16 * 4];
|
|
|
|
|
|
|
|
|
|
if (!gb->has_sgb_border && GB_is_cgb(gb) && gb->model != GB_MODEL_AGB) {
|
2021-03-28 23:47:57 +00:00
|
|
|
|
uint16_t colors[] = {
|
2020-02-08 11:28:46 +00:00
|
|
|
|
0x2095, 0x5129, 0x1EAF, 0x1EBA, 0x4648,
|
|
|
|
|
0x30DA, 0x69AD, 0x2B57, 0x2B5D, 0x632C,
|
|
|
|
|
0x1050, 0x3C84, 0x0E07, 0x0E18, 0x2964,
|
|
|
|
|
};
|
2020-04-11 16:21:00 +00:00
|
|
|
|
unsigned index = gb->rom? gb->rom[0x14e] % 5 : 0;
|
2021-11-03 22:32:15 +00:00
|
|
|
|
if (gb->model == GB_MODEL_CGB_0) {
|
|
|
|
|
index = 1; // CGB 0 was only available in Indigo!
|
|
|
|
|
}
|
2021-03-28 23:47:57 +00:00
|
|
|
|
gb->borrowed_border.palette[0] = LE16(colors[index]);
|
|
|
|
|
gb->borrowed_border.palette[10] = LE16(colors[5 + index]);
|
|
|
|
|
gb->borrowed_border.palette[14] = LE16(colors[10 + index]);
|
2020-02-08 11:28:46 +00:00
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (unsigned i = 0; i < 16 * 4; i++) {
|
2021-03-28 23:47:57 +00:00
|
|
|
|
border_colors[i] = GB_convert_rgb15(gb, LE16(gb->borrowed_border.palette[i]), true);
|
2020-02-08 11:28:46 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (unsigned tile_y = 0; tile_y < 28; tile_y++) {
|
|
|
|
|
for (unsigned tile_x = 0; tile_x < 32; tile_x++) {
|
|
|
|
|
if (tile_x >= 6 && tile_x < 26 && tile_y >= 5 && tile_y < 23) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2021-03-28 23:47:57 +00:00
|
|
|
|
uint16_t tile = LE16(gb->borrowed_border.map[tile_x + tile_y * 32]);
|
2021-03-30 21:54:55 +00:00
|
|
|
|
uint8_t flip_x = (tile & 0x4000)? 0:7;
|
|
|
|
|
uint8_t flip_y = (tile & 0x8000)? 7:0;
|
2020-02-08 11:28:46 +00:00
|
|
|
|
uint8_t palette = (tile >> 10) & 3;
|
|
|
|
|
for (unsigned y = 0; y < 8; y++) {
|
2021-03-30 21:54:55 +00:00
|
|
|
|
unsigned base = (tile & 0xFF) * 32 + (y ^ flip_y) * 2;
|
2020-02-08 11:28:46 +00:00
|
|
|
|
for (unsigned x = 0; x < 8; x++) {
|
2021-03-30 21:54:55 +00:00
|
|
|
|
uint8_t bit = 1 << (x ^ flip_x);
|
|
|
|
|
uint8_t color = ((gb->borrowed_border.tiles[base] & bit) ? 1 : 0) |
|
|
|
|
|
((gb->borrowed_border.tiles[base + 1] & bit) ? 2 : 0) |
|
|
|
|
|
((gb->borrowed_border.tiles[base + 16] & bit) ? 4 : 0) |
|
|
|
|
|
((gb->borrowed_border.tiles[base + 17] & bit) ? 8 : 0);
|
2020-02-08 11:28:46 +00:00
|
|
|
|
uint32_t *output = gb->screen + tile_x * 8 + x + (tile_y * 8 + y) * 256;
|
|
|
|
|
if (color == 0) {
|
|
|
|
|
*output = border_colors[0];
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
*output = border_colors[color + palette * 16];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-11-14 22:21:21 +00:00
|
|
|
|
}
|
2017-05-27 14:30:12 +00:00
|
|
|
|
}
|
2016-03-30 20:36:24 +00:00
|
|
|
|
}
|
2020-04-29 13:06:11 +00:00
|
|
|
|
GB_handle_rumble(gb);
|
2016-03-30 20:36:24 +00:00
|
|
|
|
|
2020-02-08 11:28:46 +00:00
|
|
|
|
if (gb->vblank_callback) {
|
|
|
|
|
gb->vblank_callback(gb);
|
|
|
|
|
}
|
2017-04-21 13:00:53 +00:00
|
|
|
|
GB_timing_sync(gb);
|
2016-03-30 20:07:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-12-25 12:14:17 +00:00
|
|
|
|
static inline void temperature_tint(double temperature, double *r, double *g, double *b)
|
|
|
|
|
{
|
|
|
|
|
if (temperature >= 0) {
|
|
|
|
|
*r = 1;
|
|
|
|
|
*g = pow(1 - temperature, 0.375);
|
|
|
|
|
if (temperature >= 0.75) {
|
|
|
|
|
*b = 0;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
*b = sqrt(0.75 - temperature);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
*b = 1;
|
|
|
|
|
double squared = pow(temperature, 2);
|
|
|
|
|
*g = 0.125 * squared + 0.3 * temperature + 1.0;
|
|
|
|
|
*r = 0.21875 * squared + 0.5 * temperature + 1.0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-06-18 17:29:11 +00:00
|
|
|
|
static inline uint8_t scale_channel(uint8_t x)
|
2016-03-30 20:07:55 +00:00
|
|
|
|
{
|
|
|
|
|
return (x << 3) | (x >> 2);
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-12 14:22:22 +00:00
|
|
|
|
static inline uint8_t scale_channel_with_curve(uint8_t x)
|
|
|
|
|
{
|
2020-12-24 21:17:20 +00:00
|
|
|
|
return (uint8_t[]){0,6,12,20,28,36,45,56,66,76,88,100,113,125,137,149,161,172,182,192,202,210,218,225,232,238,243,247,250,252,254,255}[x];
|
2017-10-12 14:22:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-09-13 14:13:21 +00:00
|
|
|
|
static inline uint8_t scale_channel_with_curve_agb(uint8_t x)
|
|
|
|
|
{
|
2020-12-24 21:17:20 +00:00
|
|
|
|
return (uint8_t[]){0,3,8,14,20,26,33,40,47,54,62,70,78,86,94,103,112,120,129,138,147,157,166,176,185,195,205,215,225,235,245,255}[x];
|
2019-09-13 14:13:21 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline uint8_t scale_channel_with_curve_sgb(uint8_t x)
|
|
|
|
|
{
|
|
|
|
|
return (uint8_t[]){0,2,5,9,15,20,27,34,42,50,58,67,76,85,94,104,114,123,133,143,153,163,173,182,192,202,211,220,229,238,247,255}[x];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2020-02-08 11:28:46 +00:00
|
|
|
|
uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color, bool for_border)
|
2017-10-12 14:22:22 +00:00
|
|
|
|
{
|
|
|
|
|
uint8_t r = (color) & 0x1F;
|
|
|
|
|
uint8_t g = (color >> 5) & 0x1F;
|
|
|
|
|
uint8_t b = (color >> 10) & 0x1F;
|
|
|
|
|
|
2020-02-08 11:28:46 +00:00
|
|
|
|
if (gb->color_correction_mode == GB_COLOR_CORRECTION_DISABLED || (for_border && !gb->has_sgb_border)) {
|
2017-10-12 14:22:22 +00:00
|
|
|
|
r = scale_channel(r);
|
|
|
|
|
g = scale_channel(g);
|
|
|
|
|
b = scale_channel(b);
|
|
|
|
|
}
|
2020-12-25 12:14:17 +00:00
|
|
|
|
else if (GB_is_sgb(gb) || for_border) {
|
|
|
|
|
r = scale_channel_with_curve_sgb(r);
|
|
|
|
|
g = scale_channel_with_curve_sgb(g);
|
|
|
|
|
b = scale_channel_with_curve_sgb(b);
|
|
|
|
|
}
|
2017-10-12 14:22:22 +00:00
|
|
|
|
else {
|
2019-09-13 14:13:21 +00:00
|
|
|
|
bool agb = gb->model == GB_MODEL_AGB;
|
|
|
|
|
r = agb? scale_channel_with_curve_agb(r) : scale_channel_with_curve(r);
|
|
|
|
|
g = agb? scale_channel_with_curve_agb(g) : scale_channel_with_curve(g);
|
|
|
|
|
b = agb? scale_channel_with_curve_agb(b) : scale_channel_with_curve(b);
|
2017-10-12 14:22:22 +00:00
|
|
|
|
|
|
|
|
|
if (gb->color_correction_mode != GB_COLOR_CORRECTION_CORRECT_CURVES) {
|
2019-09-13 14:13:21 +00:00
|
|
|
|
uint8_t new_r, new_g, new_b;
|
|
|
|
|
if (agb) {
|
2020-02-17 21:05:11 +00:00
|
|
|
|
new_g = (g * 6 + b * 1) / 7;
|
2019-09-13 14:13:21 +00:00
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
new_g = (g * 3 + b) / 4;
|
|
|
|
|
}
|
2020-02-17 21:05:11 +00:00
|
|
|
|
new_r = r;
|
|
|
|
|
new_b = b;
|
2020-03-25 18:33:13 +00:00
|
|
|
|
if (gb->color_correction_mode == GB_COLOR_CORRECTION_REDUCE_CONTRAST) {
|
|
|
|
|
r = new_r;
|
|
|
|
|
g = new_r;
|
|
|
|
|
b = new_r;
|
|
|
|
|
|
|
|
|
|
new_r = new_r * 7 / 8 + ( g + b) / 16;
|
|
|
|
|
new_g = new_g * 7 / 8 + (r + b) / 16;
|
|
|
|
|
new_b = new_b * 7 / 8 + (r + g ) / 16;
|
|
|
|
|
|
|
|
|
|
new_r = new_r * (224 - 32) / 255 + 32;
|
|
|
|
|
new_g = new_g * (220 - 36) / 255 + 36;
|
|
|
|
|
new_b = new_b * (216 - 40) / 255 + 40;
|
|
|
|
|
}
|
2021-06-25 16:57:56 +00:00
|
|
|
|
else if (gb->color_correction_mode == GB_COLOR_CORRECTION_LOW_CONTRAST) {
|
|
|
|
|
r = new_r;
|
|
|
|
|
g = new_r;
|
|
|
|
|
b = new_r;
|
|
|
|
|
|
|
|
|
|
new_r = new_r * 7 / 8 + ( g + b) / 16;
|
|
|
|
|
new_g = new_g * 7 / 8 + (r + b) / 16;
|
|
|
|
|
new_b = new_b * 7 / 8 + (r + g ) / 16;
|
|
|
|
|
|
|
|
|
|
new_r = new_r * (162 - 67) / 255 + 67;
|
|
|
|
|
new_g = new_g * (167 - 62) / 255 + 62;
|
|
|
|
|
new_b = new_b * (157 - 58) / 255 + 58;
|
|
|
|
|
}
|
2020-03-25 18:33:13 +00:00
|
|
|
|
else if (gb->color_correction_mode == GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS) {
|
2017-10-12 14:22:22 +00:00
|
|
|
|
uint8_t old_max = MAX(r, MAX(g, b));
|
|
|
|
|
uint8_t new_max = MAX(new_r, MAX(new_g, new_b));
|
|
|
|
|
|
|
|
|
|
if (new_max != 0) {
|
|
|
|
|
new_r = new_r * old_max / new_max;
|
|
|
|
|
new_g = new_g * old_max / new_max;
|
|
|
|
|
new_b = new_b * old_max / new_max;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint8_t old_min = MIN(r, MIN(g, b));
|
|
|
|
|
uint8_t new_min = MIN(new_r, MIN(new_g, new_b));
|
|
|
|
|
|
|
|
|
|
if (new_min != 0xff) {
|
|
|
|
|
new_r = 0xff - (0xff - new_r) * (0xff - old_min) / (0xff - new_min);
|
|
|
|
|
new_g = 0xff - (0xff - new_g) * (0xff - old_min) / (0xff - new_min);
|
2019-09-13 14:13:21 +00:00
|
|
|
|
new_b = 0xff - (0xff - new_b) * (0xff - old_min) / (0xff - new_min);
|
2017-10-12 14:22:22 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
r = new_r;
|
|
|
|
|
g = new_g;
|
|
|
|
|
b = new_b;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-25 12:14:17 +00:00
|
|
|
|
if (gb->light_temperature) {
|
|
|
|
|
double light_r, light_g, light_b;
|
|
|
|
|
temperature_tint(gb->light_temperature, &light_r, &light_g, &light_b);
|
|
|
|
|
r = round(light_r * r);
|
|
|
|
|
g = round(light_g * g);
|
|
|
|
|
b = round(light_b * b);
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-12 14:22:22 +00:00
|
|
|
|
return gb->rgb_encode_callback(gb, r, g, b);
|
|
|
|
|
}
|
|
|
|
|
|
2016-06-18 17:29:11 +00:00
|
|
|
|
void GB_palette_changed(GB_gameboy_t *gb, bool background_palette, uint8_t index)
|
2016-03-30 20:07:55 +00:00
|
|
|
|
{
|
2018-06-16 10:59:33 +00:00
|
|
|
|
if (!gb->rgb_encode_callback || !GB_is_cgb(gb)) return;
|
2021-12-17 19:16:23 +00:00
|
|
|
|
uint8_t *palette_data = background_palette? gb->background_palettes_data : gb->object_palettes_data;
|
2016-06-18 17:29:11 +00:00
|
|
|
|
uint16_t color = palette_data[index & ~1] | (palette_data[index | 1] << 8);
|
2016-03-30 20:07:55 +00:00
|
|
|
|
|
2021-12-17 19:16:23 +00:00
|
|
|
|
(background_palette? gb->background_palettes_rgb : gb->object_palettes_rgb)[index / 2] = GB_convert_rgb15(gb, color, false);
|
2017-10-12 14:22:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GB_set_color_correction_mode(GB_gameboy_t *gb, GB_color_correction_mode_t mode)
|
|
|
|
|
{
|
|
|
|
|
gb->color_correction_mode = mode;
|
2018-06-16 10:59:33 +00:00
|
|
|
|
if (GB_is_cgb(gb)) {
|
2017-10-16 17:48:39 +00:00
|
|
|
|
for (unsigned i = 0; i < 32; i++) {
|
|
|
|
|
GB_palette_changed(gb, false, i * 2);
|
|
|
|
|
GB_palette_changed(gb, true, i * 2);
|
|
|
|
|
}
|
2017-10-12 14:22:22 +00:00
|
|
|
|
}
|
2016-03-30 20:07:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-12-25 12:14:17 +00:00
|
|
|
|
void GB_set_light_temperature(GB_gameboy_t *gb, double temperature)
|
|
|
|
|
{
|
|
|
|
|
gb->light_temperature = temperature;
|
|
|
|
|
if (GB_is_cgb(gb)) {
|
|
|
|
|
for (unsigned i = 0; i < 32; i++) {
|
|
|
|
|
GB_palette_changed(gb, false, i * 2);
|
|
|
|
|
GB_palette_changed(gb, true, i * 2);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-06-12 16:39:05 +00:00
|
|
|
|
/*
|
2017-02-19 00:22:50 +00:00
|
|
|
|
STAT interrupt is implemented based on this finding:
|
|
|
|
|
http://board.byuu.org/phpbb3/viewtopic.php?p=25527#p25531
|
|
|
|
|
|
|
|
|
|
General timing is based on GiiBiiAdvance's documents:
|
|
|
|
|
https://github.com/AntonioND/giibiiadvance
|
2016-06-12 16:39:05 +00:00
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
2018-02-24 22:48:45 +00:00
|
|
|
|
void GB_STAT_update(GB_gameboy_t *gb)
|
2016-03-30 20:07:55 +00:00
|
|
|
|
{
|
2018-02-28 22:12:04 +00:00
|
|
|
|
if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) return;
|
|
|
|
|
|
2018-06-03 23:07:38 +00:00
|
|
|
|
bool previous_interrupt_line = gb->stat_interrupt_line;
|
2018-02-24 22:48:45 +00:00
|
|
|
|
/* Set LY=LYC bit */
|
2018-07-03 18:56:32 +00:00
|
|
|
|
/* TODO: This behavior might not be correct for CGB revisions other than C and E */
|
|
|
|
|
if (gb->ly_for_comparison != (uint16_t)-1 || gb->model <= GB_MODEL_CGB_C) {
|
2018-05-11 09:38:55 +00:00
|
|
|
|
if (gb->ly_for_comparison == gb->io_registers[GB_IO_LYC]) {
|
|
|
|
|
gb->lyc_interrupt_line = true;
|
|
|
|
|
gb->io_registers[GB_IO_STAT] |= 4;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
if (gb->ly_for_comparison != (uint16_t)-1) {
|
|
|
|
|
gb->lyc_interrupt_line = false;
|
|
|
|
|
}
|
|
|
|
|
gb->io_registers[GB_IO_STAT] &= ~4;
|
2018-04-27 10:40:39 +00:00
|
|
|
|
}
|
2017-02-19 00:22:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-06-03 23:07:38 +00:00
|
|
|
|
switch (gb->mode_for_interrupt) {
|
|
|
|
|
case 0: gb->stat_interrupt_line = gb->io_registers[GB_IO_STAT] & 8; break;
|
2018-02-24 22:48:45 +00:00
|
|
|
|
case 1: gb->stat_interrupt_line = gb->io_registers[GB_IO_STAT] & 0x10; break;
|
2018-06-03 23:07:38 +00:00
|
|
|
|
case 2: gb->stat_interrupt_line = gb->io_registers[GB_IO_STAT] & 0x20; break;
|
|
|
|
|
default: gb->stat_interrupt_line = false;
|
2017-09-08 20:46:38 +00:00
|
|
|
|
}
|
2018-02-24 22:48:45 +00:00
|
|
|
|
|
|
|
|
|
/* User requested a LY=LYC interrupt and the LY=LYC bit is on */
|
2018-04-27 10:40:39 +00:00
|
|
|
|
if ((gb->io_registers[GB_IO_STAT] & 0x40) && gb->lyc_interrupt_line) {
|
2018-02-24 22:48:45 +00:00
|
|
|
|
gb->stat_interrupt_line = true;
|
2017-09-08 20:46:38 +00:00
|
|
|
|
}
|
2017-09-08 14:25:01 +00:00
|
|
|
|
|
2018-04-01 18:45:56 +00:00
|
|
|
|
if (gb->stat_interrupt_line && !previous_interrupt_line) {
|
2018-02-24 22:48:45 +00:00
|
|
|
|
gb->io_registers[GB_IO_IF] |= 2;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GB_lcd_off(GB_gameboy_t *gb)
|
|
|
|
|
{
|
|
|
|
|
gb->display_state = 0;
|
|
|
|
|
gb->display_cycles = 0;
|
|
|
|
|
/* When the LCD is disabled, state is constant */
|
2017-02-19 00:22:50 +00:00
|
|
|
|
|
2018-03-18 18:32:19 +00:00
|
|
|
|
/* When the LCD is off, LY is 0 and STAT mode is 0. */
|
2018-02-24 22:48:45 +00:00
|
|
|
|
gb->io_registers[GB_IO_LY] = 0;
|
|
|
|
|
gb->io_registers[GB_IO_STAT] &= ~3;
|
|
|
|
|
if (gb->hdma_on_hblank) {
|
|
|
|
|
gb->hdma_on_hblank = false;
|
|
|
|
|
gb->hdma_on = false;
|
2017-09-08 14:25:01 +00:00
|
|
|
|
|
2018-02-24 22:48:45 +00:00
|
|
|
|
/* Todo: is this correct? */
|
|
|
|
|
gb->hdma_steps_left = 0xff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gb->oam_read_blocked = false;
|
|
|
|
|
gb->vram_read_blocked = false;
|
|
|
|
|
gb->oam_write_blocked = false;
|
|
|
|
|
gb->vram_write_blocked = false;
|
2019-01-19 17:32:26 +00:00
|
|
|
|
gb->cgb_palettes_blocked = false;
|
2018-02-24 22:48:45 +00:00
|
|
|
|
|
|
|
|
|
gb->current_line = 0;
|
|
|
|
|
gb->ly_for_comparison = 0;
|
2018-03-23 23:58:37 +00:00
|
|
|
|
|
2018-03-27 12:46:00 +00:00
|
|
|
|
gb->accessed_oam_row = -1;
|
2020-02-26 20:24:08 +00:00
|
|
|
|
gb->wy_triggered = false;
|
2021-12-10 17:49:52 +00:00
|
|
|
|
|
|
|
|
|
if (unlikely(gb->lcd_line_callback)) {
|
|
|
|
|
gb->lcd_line_callback(gb, 0);
|
|
|
|
|
}
|
2018-02-24 22:48:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-03-23 16:07:14 +00:00
|
|
|
|
static void add_object_from_index(GB_gameboy_t *gb, unsigned index)
|
2018-03-04 20:21:56 +00:00
|
|
|
|
{
|
2018-03-23 16:07:14 +00:00
|
|
|
|
if (gb->n_visible_objs == 10) return;
|
2018-04-07 10:59:36 +00:00
|
|
|
|
|
|
|
|
|
/* TODO: It appears that DMA blocks PPU access to OAM, but it needs verification. */
|
|
|
|
|
if (gb->dma_steps_left && (gb->dma_cycles >= 0 || gb->is_dma_restarting)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-02-15 17:21:43 +00:00
|
|
|
|
|
|
|
|
|
if (gb->oam_ppu_blocked) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2018-03-23 16:07:14 +00:00
|
|
|
|
|
2018-03-04 20:21:56 +00:00
|
|
|
|
/* This reverse sorts the visible objects by location and priority */
|
2021-11-06 23:10:58 +00:00
|
|
|
|
object_t *objects = (object_t *) &gb->oam;
|
2018-03-04 20:21:56 +00:00
|
|
|
|
bool height_16 = (gb->io_registers[GB_IO_LCDC] & 4) != 0;
|
2018-03-23 16:07:14 +00:00
|
|
|
|
signed y = objects[index].y - 16;
|
|
|
|
|
if (y <= gb->current_line && y + (height_16? 16 : 8) > gb->current_line) {
|
|
|
|
|
unsigned j = 0;
|
|
|
|
|
for (; j < gb->n_visible_objs; j++) {
|
2019-03-16 18:56:22 +00:00
|
|
|
|
if (gb->obj_comparators[j] <= objects[index].x) break;
|
2018-03-04 20:21:56 +00:00
|
|
|
|
}
|
2018-03-23 16:07:14 +00:00
|
|
|
|
memmove(gb->visible_objs + j + 1, gb->visible_objs + j, gb->n_visible_objs - j);
|
2019-03-16 18:56:22 +00:00
|
|
|
|
memmove(gb->obj_comparators + j + 1, gb->obj_comparators + j, gb->n_visible_objs - j);
|
2018-03-23 16:07:14 +00:00
|
|
|
|
gb->visible_objs[j] = index;
|
2019-03-16 18:56:22 +00:00
|
|
|
|
gb->obj_comparators[j] = objects[index].x;
|
2018-03-23 16:07:14 +00:00
|
|
|
|
gb->n_visible_objs++;
|
2018-03-04 20:21:56 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-26 22:40:28 +00:00
|
|
|
|
static uint8_t data_for_tile_sel_glitch(GB_gameboy_t *gb, bool *should_use, bool *cgb_d_glitch)
|
2020-11-20 14:24:16 +00:00
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
Based on Matt Currie's research here:
|
|
|
|
|
https://github.com/mattcurrie/mealybug-tearoom-tests/blob/master/the-comprehensive-game-boy-ppu-documentation.md#tile_sel-bit-4
|
|
|
|
|
*/
|
|
|
|
|
*should_use = true;
|
2021-10-26 22:40:28 +00:00
|
|
|
|
*cgb_d_glitch = false;
|
|
|
|
|
|
2020-11-20 14:24:16 +00:00
|
|
|
|
if (gb->io_registers[GB_IO_LCDC] & 0x10) {
|
2021-10-26 22:40:28 +00:00
|
|
|
|
if (gb->model != GB_MODEL_CGB_D) {
|
|
|
|
|
*should_use = !(gb->current_tile & 0x80);
|
|
|
|
|
return gb->current_tile;
|
|
|
|
|
}
|
|
|
|
|
*cgb_d_glitch = true;
|
|
|
|
|
*should_use = false;
|
|
|
|
|
gb->io_registers[GB_IO_LCDC] &= ~0x10;
|
|
|
|
|
if (gb->fetcher_state == 3) {
|
|
|
|
|
*should_use = false;
|
|
|
|
|
*cgb_d_glitch = true;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
2020-11-20 14:24:16 +00:00
|
|
|
|
}
|
|
|
|
|
return gb->data_for_sel_glitch;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2018-03-03 13:47:36 +00:00
|
|
|
|
static void render_pixel_if_possible(GB_gameboy_t *gb)
|
|
|
|
|
{
|
2021-12-17 19:12:26 +00:00
|
|
|
|
const GB_fifo_item_t *fifo_item = NULL;
|
|
|
|
|
const GB_fifo_item_t *oam_fifo_item = NULL;
|
2018-03-04 21:27:31 +00:00
|
|
|
|
bool draw_oam = false;
|
|
|
|
|
bool bg_enabled = true, bg_priority = false;
|
2018-04-06 15:26:04 +00:00
|
|
|
|
|
2020-02-28 20:36:51 +00:00
|
|
|
|
if (fifo_size(&gb->bg_fifo)) {
|
2018-03-03 15:22:23 +00:00
|
|
|
|
fifo_item = fifo_pop(&gb->bg_fifo);
|
2018-03-09 16:52:36 +00:00
|
|
|
|
bg_priority = fifo_item->bg_priority;
|
2020-02-28 20:36:51 +00:00
|
|
|
|
|
|
|
|
|
if (fifo_size(&gb->oam_fifo)) {
|
|
|
|
|
oam_fifo_item = fifo_pop(&gb->oam_fifo);
|
2021-12-17 19:12:26 +00:00
|
|
|
|
if (oam_fifo_item->pixel && (gb->io_registers[GB_IO_LCDC] & 2) && unlikely(!gb->objects_disabled)) {
|
2020-02-28 20:36:51 +00:00
|
|
|
|
draw_oam = true;
|
|
|
|
|
bg_priority |= oam_fifo_item->bg_priority;
|
|
|
|
|
}
|
2018-03-04 21:27:31 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-28 20:36:51 +00:00
|
|
|
|
|
|
|
|
|
if (!fifo_item) return;
|
2020-02-24 19:23:06 +00:00
|
|
|
|
|
2018-03-03 13:47:36 +00:00
|
|
|
|
/* Drop pixels for scrollings */
|
2020-02-08 11:28:46 +00:00
|
|
|
|
if (gb->position_in_line >= 160 || (gb->disable_rendering && !gb->sgb)) {
|
2018-03-03 13:47:36 +00:00
|
|
|
|
gb->position_in_line++;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-03 17:36:21 +00:00
|
|
|
|
/* Mixing */
|
|
|
|
|
|
|
|
|
|
if ((gb->io_registers[GB_IO_LCDC] & 0x1) == 0) {
|
|
|
|
|
if (gb->cgb_mode) {
|
2018-03-20 22:02:35 +00:00
|
|
|
|
bg_priority = false;
|
2018-03-03 17:36:21 +00:00
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
bg_enabled = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-12-17 19:12:26 +00:00
|
|
|
|
|
|
|
|
|
if (unlikely(gb->background_disabled)) {
|
|
|
|
|
bg_enabled = false;
|
|
|
|
|
static const GB_fifo_item_t empty_item = {0,};
|
|
|
|
|
fifo_item = &empty_item;
|
|
|
|
|
}
|
2019-07-17 21:53:11 +00:00
|
|
|
|
|
|
|
|
|
uint8_t icd_pixel = 0;
|
2020-02-08 11:28:46 +00:00
|
|
|
|
uint32_t *dest = NULL;
|
|
|
|
|
if (!gb->sgb) {
|
|
|
|
|
if (gb->border_mode != GB_BORDER_ALWAYS) {
|
2020-03-06 12:41:13 +00:00
|
|
|
|
dest = gb->screen + gb->lcd_x + gb->current_line * WIDTH;
|
2020-02-08 11:28:46 +00:00
|
|
|
|
}
|
|
|
|
|
else {
|
2020-03-06 12:41:13 +00:00
|
|
|
|
dest = gb->screen + gb->lcd_x + gb->current_line * BORDERED_WIDTH + (BORDERED_WIDTH - WIDTH) / 2 + (BORDERED_HEIGHT - LINES) / 2 * BORDERED_WIDTH;
|
2020-02-08 11:28:46 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-05 09:27:01 +00:00
|
|
|
|
{
|
|
|
|
|
uint8_t pixel = bg_enabled? fifo_item->pixel : 0;
|
2018-03-04 21:27:31 +00:00
|
|
|
|
if (pixel && bg_priority) {
|
|
|
|
|
draw_oam = false;
|
|
|
|
|
}
|
2018-03-03 17:36:21 +00:00
|
|
|
|
if (!gb->cgb_mode) {
|
|
|
|
|
pixel = ((gb->io_registers[GB_IO_BGP] >> (pixel << 1)) & 3);
|
|
|
|
|
}
|
2018-11-15 23:53:01 +00:00
|
|
|
|
if (gb->sgb) {
|
2019-02-15 15:04:48 +00:00
|
|
|
|
if (gb->current_lcd_line < LINES) {
|
2020-03-06 12:41:13 +00:00
|
|
|
|
gb->sgb->screen_buffer[gb->lcd_x + gb->current_lcd_line * WIDTH] = gb->stopped? 0 : pixel;
|
2019-02-15 15:04:48 +00:00
|
|
|
|
}
|
2018-11-14 22:21:21 +00:00
|
|
|
|
}
|
2019-07-15 20:02:58 +00:00
|
|
|
|
else if (gb->model & GB_MODEL_NO_SFC_BIT) {
|
2019-07-16 17:44:27 +00:00
|
|
|
|
if (gb->icd_pixel_callback) {
|
2019-07-17 21:53:11 +00:00
|
|
|
|
icd_pixel = pixel;
|
2019-07-16 17:44:27 +00:00
|
|
|
|
}
|
2019-07-15 20:02:58 +00:00
|
|
|
|
}
|
2020-02-15 17:21:43 +00:00
|
|
|
|
else if (gb->cgb_palettes_ppu_blocked) {
|
|
|
|
|
*dest = gb->rgb_encode_callback(gb, 0, 0, 0);
|
|
|
|
|
}
|
2018-11-14 22:21:21 +00:00
|
|
|
|
else {
|
2020-02-08 11:28:46 +00:00
|
|
|
|
*dest = gb->background_palettes_rgb[fifo_item->palette * 4 + pixel];
|
2018-11-14 22:21:21 +00:00
|
|
|
|
}
|
2018-03-03 17:36:21 +00:00
|
|
|
|
}
|
2018-03-04 21:27:31 +00:00
|
|
|
|
|
|
|
|
|
if (draw_oam) {
|
|
|
|
|
uint8_t pixel = oam_fifo_item->pixel;
|
|
|
|
|
if (!gb->cgb_mode) {
|
2018-03-30 14:06:27 +00:00
|
|
|
|
/* Todo: Verify access timings */
|
2018-03-04 21:27:31 +00:00
|
|
|
|
pixel = ((gb->io_registers[oam_fifo_item->palette? GB_IO_OBP1 : GB_IO_OBP0] >> (pixel << 1)) & 3);
|
|
|
|
|
}
|
2018-11-15 23:53:01 +00:00
|
|
|
|
if (gb->sgb) {
|
2019-02-15 15:04:48 +00:00
|
|
|
|
if (gb->current_lcd_line < LINES) {
|
2020-03-06 12:41:13 +00:00
|
|
|
|
gb->sgb->screen_buffer[gb->lcd_x + gb->current_lcd_line * WIDTH] = gb->stopped? 0 : pixel;
|
2019-02-15 15:04:48 +00:00
|
|
|
|
}
|
2018-11-14 22:21:21 +00:00
|
|
|
|
}
|
2019-07-15 20:02:58 +00:00
|
|
|
|
else if (gb->model & GB_MODEL_NO_SFC_BIT) {
|
2019-07-16 17:44:27 +00:00
|
|
|
|
if (gb->icd_pixel_callback) {
|
2019-07-17 21:53:11 +00:00
|
|
|
|
icd_pixel = pixel;
|
2019-07-16 17:44:27 +00:00
|
|
|
|
}
|
2019-07-15 20:02:58 +00:00
|
|
|
|
}
|
2020-02-15 17:21:43 +00:00
|
|
|
|
else if (gb->cgb_palettes_ppu_blocked) {
|
|
|
|
|
*dest = gb->rgb_encode_callback(gb, 0, 0, 0);
|
|
|
|
|
}
|
2018-11-14 22:21:21 +00:00
|
|
|
|
else {
|
2021-12-17 19:16:23 +00:00
|
|
|
|
*dest = gb->object_palettes_rgb[oam_fifo_item->palette * 4 + pixel];
|
2018-11-14 22:21:21 +00:00
|
|
|
|
}
|
2018-03-04 21:27:31 +00:00
|
|
|
|
}
|
2020-04-24 17:37:57 +00:00
|
|
|
|
|
2019-07-17 21:53:11 +00:00
|
|
|
|
if (gb->model & GB_MODEL_NO_SFC_BIT) {
|
|
|
|
|
if (gb->icd_pixel_callback) {
|
|
|
|
|
gb->icd_pixel_callback(gb, icd_pixel);
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-03-04 21:27:31 +00:00
|
|
|
|
|
2018-03-03 13:47:36 +00:00
|
|
|
|
gb->position_in_line++;
|
2020-03-06 12:41:13 +00:00
|
|
|
|
gb->lcd_x++;
|
2020-02-29 15:06:08 +00:00
|
|
|
|
gb->window_is_being_fetched = false;
|
2018-03-03 13:47:36 +00:00
|
|
|
|
}
|
2018-03-04 20:21:56 +00:00
|
|
|
|
|
2018-03-10 22:17:57 +00:00
|
|
|
|
/* All verified CGB timings are based on CGB CPU E. CGB CPUs >= D are known to have
|
|
|
|
|
slightly different timings than CPUs <= C.
|
|
|
|
|
|
|
|
|
|
Todo: Add support to CPU C and older */
|
|
|
|
|
|
2018-04-05 12:33:21 +00:00
|
|
|
|
static inline uint8_t fetcher_y(GB_gameboy_t *gb)
|
|
|
|
|
{
|
2020-02-23 21:16:45 +00:00
|
|
|
|
return gb->wx_triggered? gb->window_y : gb->current_line + gb->io_registers[GB_IO_SCY];
|
2018-04-05 12:33:21 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-04-07 00:26:10 +00:00
|
|
|
|
static void advance_fetcher_state_machine(GB_gameboy_t *gb)
|
2018-04-06 16:29:49 +00:00
|
|
|
|
{
|
|
|
|
|
typedef enum {
|
|
|
|
|
GB_FETCHER_GET_TILE,
|
|
|
|
|
GB_FETCHER_GET_TILE_DATA_LOWER,
|
|
|
|
|
GB_FETCHER_GET_TILE_DATA_HIGH,
|
|
|
|
|
GB_FETCHER_PUSH,
|
|
|
|
|
GB_FETCHER_SLEEP,
|
|
|
|
|
} fetcher_step_t;
|
|
|
|
|
|
2021-10-08 16:29:43 +00:00
|
|
|
|
static const fetcher_step_t fetcher_state_machine [8] = {
|
2018-04-06 16:29:49 +00:00
|
|
|
|
GB_FETCHER_SLEEP,
|
|
|
|
|
GB_FETCHER_GET_TILE,
|
|
|
|
|
GB_FETCHER_SLEEP,
|
|
|
|
|
GB_FETCHER_GET_TILE_DATA_LOWER,
|
|
|
|
|
GB_FETCHER_SLEEP,
|
|
|
|
|
GB_FETCHER_GET_TILE_DATA_HIGH,
|
|
|
|
|
GB_FETCHER_PUSH,
|
|
|
|
|
GB_FETCHER_PUSH,
|
|
|
|
|
};
|
2020-03-04 21:43:05 +00:00
|
|
|
|
switch (fetcher_state_machine[gb->fetcher_state & 7]) {
|
2018-04-06 16:29:49 +00:00
|
|
|
|
case GB_FETCHER_GET_TILE: {
|
|
|
|
|
uint16_t map = 0x1800;
|
|
|
|
|
|
2020-02-23 21:16:45 +00:00
|
|
|
|
if (!(gb->io_registers[GB_IO_LCDC] & 0x20)) {
|
|
|
|
|
gb->wx_triggered = false;
|
2020-03-01 21:58:28 +00:00
|
|
|
|
gb->wx166_glitch = false;
|
2020-02-23 21:16:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-04-06 16:29:49 +00:00
|
|
|
|
/* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */
|
2020-02-23 21:16:45 +00:00
|
|
|
|
if (gb->io_registers[GB_IO_LCDC] & 0x08 && !gb->wx_triggered) {
|
2018-04-06 16:29:49 +00:00
|
|
|
|
map = 0x1C00;
|
|
|
|
|
}
|
2020-02-23 21:16:45 +00:00
|
|
|
|
else if (gb->io_registers[GB_IO_LCDC] & 0x40 && gb->wx_triggered) {
|
2018-04-06 16:29:49 +00:00
|
|
|
|
map = 0x1C00;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */
|
|
|
|
|
uint8_t y = fetcher_y(gb);
|
2020-02-23 21:16:45 +00:00
|
|
|
|
uint8_t x = 0;
|
|
|
|
|
if (gb->wx_triggered) {
|
|
|
|
|
x = gb->window_tile_x;
|
|
|
|
|
}
|
|
|
|
|
else {
|
2021-10-08 16:29:43 +00:00
|
|
|
|
/* TODO: There is some CGB timing error around here.
|
|
|
|
|
Adjusting SCX by 7 or less shouldn't have an effect on a CGB,
|
|
|
|
|
but SameBoy is affected by a change of both 7 and 6 (but not less). */
|
|
|
|
|
x = ((gb->io_registers[GB_IO_SCX] + gb->position_in_line + 8) / 8) & 0x1F;
|
2020-02-23 21:16:45 +00:00
|
|
|
|
}
|
2018-07-03 19:14:53 +00:00
|
|
|
|
if (gb->model > GB_MODEL_CGB_C) {
|
2018-07-03 19:25:09 +00:00
|
|
|
|
/* This value is cached on the CGB-D and newer, so it cannot be used to mix tiles together */
|
2018-04-06 16:29:49 +00:00
|
|
|
|
gb->fetcher_y = y;
|
|
|
|
|
}
|
2020-05-29 20:10:23 +00:00
|
|
|
|
gb->last_tile_index_address = map + x + y / 8 * 32;
|
|
|
|
|
gb->current_tile = gb->vram[gb->last_tile_index_address];
|
2020-02-15 17:21:43 +00:00
|
|
|
|
if (gb->vram_ppu_blocked) {
|
|
|
|
|
gb->current_tile = 0xFF;
|
|
|
|
|
}
|
2018-06-16 10:59:33 +00:00
|
|
|
|
if (GB_is_cgb(gb)) {
|
2018-04-06 16:29:49 +00:00
|
|
|
|
/* The CGB actually accesses both the tile index AND the attributes in the same T-cycle.
|
2020-05-29 20:10:23 +00:00
|
|
|
|
This probably means the CGB has a 16-bit data bus for the VRAM. */
|
|
|
|
|
gb->current_tile_attributes = gb->vram[gb->last_tile_index_address + 0x2000];
|
2020-02-15 17:21:43 +00:00
|
|
|
|
if (gb->vram_ppu_blocked) {
|
|
|
|
|
gb->current_tile_attributes = 0xFF;
|
|
|
|
|
}
|
2018-04-06 16:29:49 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2018-04-07 00:00:26 +00:00
|
|
|
|
gb->fetcher_state++;
|
2018-04-06 16:29:49 +00:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case GB_FETCHER_GET_TILE_DATA_LOWER: {
|
2020-11-20 14:24:16 +00:00
|
|
|
|
bool use_glitched = false;
|
2021-10-26 22:40:28 +00:00
|
|
|
|
bool cgb_d_glitch = false;
|
2020-11-20 14:24:16 +00:00
|
|
|
|
if (gb->tile_sel_glitch) {
|
2021-10-26 22:40:28 +00:00
|
|
|
|
gb->current_tile_data[0] = data_for_tile_sel_glitch(gb, &use_glitched, &cgb_d_glitch);
|
2020-11-20 14:24:16 +00:00
|
|
|
|
}
|
2018-04-06 16:29:49 +00:00
|
|
|
|
uint8_t y_flip = 0;
|
|
|
|
|
uint16_t tile_address = 0;
|
2018-07-03 19:14:53 +00:00
|
|
|
|
uint8_t y = gb->model > GB_MODEL_CGB_C ? gb->fetcher_y : fetcher_y(gb);
|
2018-04-06 16:29:49 +00:00
|
|
|
|
|
|
|
|
|
/* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */
|
|
|
|
|
if (gb->io_registers[GB_IO_LCDC] & 0x10) {
|
|
|
|
|
tile_address = gb->current_tile * 0x10;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
tile_address = (int8_t)gb->current_tile * 0x10 + 0x1000;
|
|
|
|
|
}
|
|
|
|
|
if (gb->current_tile_attributes & 8) {
|
|
|
|
|
tile_address += 0x2000;
|
|
|
|
|
}
|
|
|
|
|
if (gb->current_tile_attributes & 0x40) {
|
|
|
|
|
y_flip = 0x7;
|
|
|
|
|
}
|
2020-11-20 14:24:16 +00:00
|
|
|
|
if (!use_glitched) {
|
|
|
|
|
gb->current_tile_data[0] =
|
|
|
|
|
gb->vram[tile_address + ((y & 7) ^ y_flip) * 2];
|
|
|
|
|
if (gb->vram_ppu_blocked) {
|
|
|
|
|
gb->current_tile_data[0] = 0xFF;
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-10-26 22:40:28 +00:00
|
|
|
|
if ((gb->io_registers[GB_IO_LCDC] & 0x10) && gb->tile_sel_glitch) {
|
2020-11-20 14:24:16 +00:00
|
|
|
|
gb->data_for_sel_glitch =
|
2020-02-15 17:21:43 +00:00
|
|
|
|
gb->vram[tile_address + ((y & 7) ^ y_flip) * 2];
|
2020-11-20 14:24:16 +00:00
|
|
|
|
if (gb->vram_ppu_blocked) {
|
|
|
|
|
gb->data_for_sel_glitch = 0xFF;
|
|
|
|
|
}
|
2020-02-15 17:21:43 +00:00
|
|
|
|
}
|
2021-10-26 22:40:28 +00:00
|
|
|
|
else if (cgb_d_glitch) {
|
|
|
|
|
gb->data_for_sel_glitch = gb->vram[gb->current_tile * 0x10 + ((y & 7) ^ y_flip) * 2];
|
|
|
|
|
if (gb->vram_ppu_blocked) {
|
|
|
|
|
gb->data_for_sel_glitch = 0xFF;
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-04-06 16:29:49 +00:00
|
|
|
|
}
|
2018-04-07 00:00:26 +00:00
|
|
|
|
gb->fetcher_state++;
|
2018-04-06 16:29:49 +00:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case GB_FETCHER_GET_TILE_DATA_HIGH: {
|
2020-11-20 14:24:16 +00:00
|
|
|
|
/* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */
|
|
|
|
|
|
|
|
|
|
bool use_glitched = false;
|
2021-10-26 22:40:28 +00:00
|
|
|
|
bool cgb_d_glitch = false;
|
2020-11-20 14:24:16 +00:00
|
|
|
|
if (gb->tile_sel_glitch) {
|
2021-10-26 22:40:28 +00:00
|
|
|
|
gb->current_tile_data[1] = data_for_tile_sel_glitch(gb, &use_glitched, &cgb_d_glitch);
|
2020-11-20 14:24:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-04-06 16:29:49 +00:00
|
|
|
|
uint16_t tile_address = 0;
|
2018-07-03 19:14:53 +00:00
|
|
|
|
uint8_t y = gb->model > GB_MODEL_CGB_C ? gb->fetcher_y : fetcher_y(gb);
|
2018-04-06 16:29:49 +00:00
|
|
|
|
|
|
|
|
|
if (gb->io_registers[GB_IO_LCDC] & 0x10) {
|
|
|
|
|
tile_address = gb->current_tile * 0x10;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
tile_address = (int8_t)gb->current_tile * 0x10 + 0x1000;
|
|
|
|
|
}
|
|
|
|
|
if (gb->current_tile_attributes & 8) {
|
|
|
|
|
tile_address += 0x2000;
|
|
|
|
|
}
|
|
|
|
|
uint8_t y_flip = 0;
|
|
|
|
|
if (gb->current_tile_attributes & 0x40) {
|
|
|
|
|
y_flip = 0x7;
|
|
|
|
|
}
|
2021-10-26 22:40:28 +00:00
|
|
|
|
gb->last_tile_data_address = tile_address + ((y & 7) ^ y_flip) * 2 + 1 - cgb_d_glitch;
|
2020-11-20 14:24:16 +00:00
|
|
|
|
if (!use_glitched) {
|
|
|
|
|
gb->current_tile_data[1] =
|
|
|
|
|
gb->vram[gb->last_tile_data_address];
|
|
|
|
|
if (gb->vram_ppu_blocked) {
|
|
|
|
|
gb->current_tile_data[1] = 0xFF;
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-10-26 22:40:28 +00:00
|
|
|
|
if ((gb->io_registers[GB_IO_LCDC] & 0x10) && gb->tile_sel_glitch) {
|
|
|
|
|
gb->data_for_sel_glitch = gb->vram[gb->last_tile_data_address];
|
|
|
|
|
if (gb->vram_ppu_blocked) {
|
|
|
|
|
gb->data_for_sel_glitch = 0xFF;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (cgb_d_glitch) {
|
|
|
|
|
gb->data_for_sel_glitch = gb->vram[gb->current_tile * 0x10 + ((y & 7) ^ y_flip) * 2 + 1];
|
|
|
|
|
if (gb->vram_ppu_blocked) {
|
|
|
|
|
gb->data_for_sel_glitch = 0xFF;
|
2020-11-20 14:24:16 +00:00
|
|
|
|
}
|
2020-02-15 17:21:43 +00:00
|
|
|
|
}
|
2018-04-06 16:29:49 +00:00
|
|
|
|
}
|
2020-03-03 00:21:19 +00:00
|
|
|
|
if (gb->wx_triggered) {
|
|
|
|
|
gb->window_tile_x++;
|
|
|
|
|
gb->window_tile_x &= 0x1f;
|
2018-04-06 16:29:49 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-03-03 00:21:19 +00:00
|
|
|
|
// fallthrough
|
2018-04-06 16:29:49 +00:00
|
|
|
|
case GB_FETCHER_PUSH: {
|
2020-03-04 21:43:05 +00:00
|
|
|
|
if (gb->fetcher_state < 7) {
|
2020-03-03 00:21:19 +00:00
|
|
|
|
gb->fetcher_state++;
|
|
|
|
|
}
|
2018-04-07 10:02:53 +00:00
|
|
|
|
if (fifo_size(&gb->bg_fifo) > 0) break;
|
2020-02-23 21:16:45 +00:00
|
|
|
|
|
2018-04-06 16:29:49 +00:00
|
|
|
|
fifo_push_bg_row(&gb->bg_fifo, gb->current_tile_data[0], gb->current_tile_data[1],
|
|
|
|
|
gb->current_tile_attributes & 7, gb->current_tile_attributes & 0x80, gb->current_tile_attributes & 0x20);
|
2020-02-23 22:20:58 +00:00
|
|
|
|
gb->fetcher_state = 0;
|
2018-04-06 16:29:49 +00:00
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case GB_FETCHER_SLEEP:
|
2018-04-07 00:00:26 +00:00
|
|
|
|
{
|
|
|
|
|
gb->fetcher_state++;
|
|
|
|
|
}
|
2018-04-06 16:29:49 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-06 23:10:58 +00:00
|
|
|
|
static uint16_t get_object_line_address(GB_gameboy_t *gb, const object_t *object)
|
2020-02-21 14:43:51 +00:00
|
|
|
|
{
|
|
|
|
|
/* TODO: what does the PPU read if DMA is active? */
|
|
|
|
|
if (gb->oam_ppu_blocked) {
|
2021-11-06 23:10:58 +00:00
|
|
|
|
static const object_t blocked = {0xFF, 0xFF, 0xFF, 0xFF};
|
2020-02-21 14:43:51 +00:00
|
|
|
|
object = &blocked;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool height_16 = (gb->io_registers[GB_IO_LCDC] & 4) != 0; /* Todo: Which T-cycle actually reads this? */
|
|
|
|
|
uint8_t tile_y = (gb->current_line - object->y) & (height_16? 0xF : 7);
|
2018-04-06 16:29:49 +00:00
|
|
|
|
|
2020-02-21 14:43:51 +00:00
|
|
|
|
if (object->flags & 0x40) { /* Flip Y */
|
|
|
|
|
tile_y ^= height_16? 0xF : 7;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Todo: I'm not 100% sure an access to OAM can't trigger the OAM bug while we're accessing this */
|
|
|
|
|
uint16_t line_address = (height_16? object->tile & 0xFE : object->tile) * 0x10 + tile_y * 2;
|
|
|
|
|
|
|
|
|
|
if (gb->cgb_mode && (object->flags & 0x8)) { /* Use VRAM bank 2 */
|
|
|
|
|
line_address += 0x2000;
|
|
|
|
|
}
|
|
|
|
|
return line_address;
|
2018-04-06 16:29:49 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-12-25 23:47:59 +00:00
|
|
|
|
static inline uint8_t flip(uint8_t x)
|
|
|
|
|
{
|
|
|
|
|
x = (x & 0xF0) >> 4 | (x & 0x0F) << 4;
|
|
|
|
|
x = (x & 0xCC) >> 2 | (x & 0x33) << 2;
|
|
|
|
|
x = (x & 0xAA) >> 1 | (x & 0x55) << 1;
|
|
|
|
|
return x;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline void get_tile_data(const GB_gameboy_t *gb, uint8_t tile_x, uint8_t y, uint16_t map, uint8_t *attributes, uint8_t *data0, uint8_t *data1)
|
|
|
|
|
{
|
|
|
|
|
uint8_t current_tile = gb->vram[map + (tile_x & 0x1F) + y / 8 * 32];
|
|
|
|
|
*attributes = GB_is_cgb(gb)? gb->vram[0x2000 + map + (tile_x & 0x1F) + y / 8 * 32] : 0;
|
|
|
|
|
|
|
|
|
|
uint16_t tile_address = 0;
|
|
|
|
|
|
|
|
|
|
/* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */
|
|
|
|
|
if (gb->io_registers[GB_IO_LCDC] & 0x10) {
|
|
|
|
|
tile_address = current_tile * 0x10;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
tile_address = (int8_t)current_tile * 0x10 + 0x1000;
|
|
|
|
|
}
|
|
|
|
|
if (*attributes & 8) {
|
|
|
|
|
tile_address += 0x2000;
|
|
|
|
|
}
|
|
|
|
|
uint8_t y_flip = 0;
|
|
|
|
|
if (*attributes & 0x40) {
|
|
|
|
|
y_flip = 0x7;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*data0 = gb->vram[tile_address + ((y & 7) ^ y_flip) * 2];
|
|
|
|
|
*data1 = gb->vram[tile_address + ((y & 7) ^ y_flip) * 2 + 1];
|
|
|
|
|
|
|
|
|
|
if (*attributes & 0x20) {
|
|
|
|
|
*data0 = flip(*data0);
|
|
|
|
|
*data1 = flip(*data1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void render_line(GB_gameboy_t *gb)
|
|
|
|
|
{
|
|
|
|
|
if (gb->disable_rendering) return;
|
|
|
|
|
if (!gb->screen) return;
|
|
|
|
|
if (gb->current_line > 144) return; // Corrupt save state
|
|
|
|
|
|
|
|
|
|
struct {
|
|
|
|
|
unsigned pixel:2; // Color, 0-3
|
|
|
|
|
unsigned priority:6; // Object priority – 0 in DMG, OAM index in CGB
|
|
|
|
|
unsigned palette:3; // Palette, 0 - 7 (CGB); 0-1 in DMG (or just 0 for BG)
|
|
|
|
|
bool bg_priority:1; // BG priority bit
|
2021-12-26 00:38:54 +00:00
|
|
|
|
} _object_buffer[160 + 16]; // allocate extra to avoid per pixel checks
|
|
|
|
|
static const uint8_t empty_object_buffer[sizeof(_object_buffer)];
|
|
|
|
|
const typeof(_object_buffer[0]) *object_buffer;
|
|
|
|
|
|
2021-12-25 23:47:59 +00:00
|
|
|
|
if (gb->n_visible_objs && !gb->objects_disabled && (gb->io_registers[GB_IO_LCDC] & 2)) {
|
2021-12-26 00:38:54 +00:00
|
|
|
|
object_buffer = &_object_buffer[0];
|
2021-12-25 23:47:59 +00:00
|
|
|
|
object_t *objects = (object_t *) &gb->oam;
|
2021-12-26 00:38:54 +00:00
|
|
|
|
memset(_object_buffer, 0, sizeof(_object_buffer));
|
|
|
|
|
|
2021-12-25 23:47:59 +00:00
|
|
|
|
while (gb->n_visible_objs) {
|
|
|
|
|
unsigned object_index = gb->visible_objs[gb->n_visible_objs - 1];
|
|
|
|
|
unsigned priority = gb->object_priority == GB_OBJECT_PRIORITY_X? 0 : object_index;
|
|
|
|
|
const object_t *object = &objects[object_index];
|
|
|
|
|
gb->n_visible_objs--;
|
|
|
|
|
|
|
|
|
|
uint16_t line_address = get_object_line_address(gb, object);
|
|
|
|
|
uint8_t data0 = gb->vram[line_address];
|
|
|
|
|
uint8_t data1 = gb->vram[line_address + 1];
|
2021-12-26 21:24:08 +00:00
|
|
|
|
if (gb->n_visible_objs == 0) {
|
|
|
|
|
gb->data_for_sel_glitch = data1;
|
|
|
|
|
}
|
2021-12-25 23:47:59 +00:00
|
|
|
|
if (object->flags & 0x20) {
|
|
|
|
|
data0 = flip(data0);
|
|
|
|
|
data1 = flip(data1);
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-26 00:38:54 +00:00
|
|
|
|
typeof(_object_buffer[0]) *p = _object_buffer + object->x;
|
2021-12-25 23:47:59 +00:00
|
|
|
|
if (object->x >= 168) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
unrolled for (unsigned x = 0; x < 8; x++) {
|
|
|
|
|
unsigned pixel = (data0 >> 7) | ((data1 >> 7) << 1);
|
|
|
|
|
data0 <<= 1;
|
|
|
|
|
data1 <<= 1;
|
|
|
|
|
if (pixel && (!p->pixel || priority < p->priority)) {
|
|
|
|
|
p->pixel = pixel;
|
|
|
|
|
p->priority = priority;
|
|
|
|
|
|
|
|
|
|
if (gb->cgb_mode) {
|
|
|
|
|
p->palette = object->flags & 0x7;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
p->palette = (object->flags & 0x10) >> 4;
|
|
|
|
|
}
|
|
|
|
|
p->bg_priority = object->flags & 0x80;
|
|
|
|
|
}
|
|
|
|
|
p++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-12-26 00:38:54 +00:00
|
|
|
|
else {
|
|
|
|
|
object_buffer = (const void *)empty_object_buffer;
|
|
|
|
|
}
|
2021-12-25 23:47:59 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t *restrict p = gb->screen;
|
|
|
|
|
typeof(object_buffer[0]) *object_buffer_pointer = object_buffer + 8;
|
|
|
|
|
if (gb->border_mode == GB_BORDER_ALWAYS) {
|
|
|
|
|
p += (BORDERED_WIDTH - (WIDTH)) / 2 + BORDERED_WIDTH * (BORDERED_HEIGHT - LINES) / 2;
|
|
|
|
|
p += BORDERED_WIDTH * gb->current_line;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
p += WIDTH * gb->current_line;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (unlikely(gb->background_disabled) || (!gb->cgb_mode && !(gb->io_registers[GB_IO_LCDC] & 1))) {
|
|
|
|
|
uint32_t bg = gb->background_palettes_rgb[gb->cgb_mode? 0 : (gb->io_registers[GB_IO_BGP] & 3)];
|
|
|
|
|
for (unsigned i = 160; i--;) {
|
|
|
|
|
if (unlikely(object_buffer_pointer->pixel)) {
|
|
|
|
|
uint8_t pixel = object_buffer_pointer->pixel;
|
|
|
|
|
if (!gb->cgb_mode) {
|
|
|
|
|
pixel = ((gb->io_registers[GB_IO_OBP0 + object_buffer_pointer->palette] >> (pixel << 1)) & 3);
|
|
|
|
|
}
|
|
|
|
|
*(p++) = gb->object_palettes_rgb[pixel + (object_buffer_pointer->palette & 7) * 4];
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
*(p++) = bg;
|
|
|
|
|
}
|
|
|
|
|
object_buffer_pointer++;
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unsigned pixels = 0;
|
|
|
|
|
uint8_t tile_x = gb->io_registers[GB_IO_SCX] / 8;
|
|
|
|
|
unsigned fractional_scroll = gb->io_registers[GB_IO_SCX] & 7;
|
|
|
|
|
uint16_t map = 0x1800;
|
|
|
|
|
if (gb->io_registers[GB_IO_LCDC] & 0x08) {
|
|
|
|
|
map = 0x1C00;
|
|
|
|
|
}
|
|
|
|
|
uint8_t y = gb->current_line + gb->io_registers[GB_IO_SCY];
|
|
|
|
|
uint8_t attributes;
|
|
|
|
|
uint8_t data0, data1;
|
|
|
|
|
get_tile_data(gb, tile_x, y, map, &attributes, &data0, &data1);
|
|
|
|
|
|
|
|
|
|
#define DO_PIXEL() \
|
|
|
|
|
uint8_t pixel = (data0 >> 7) | ((data1 >> 7) << 1);\
|
|
|
|
|
data0 <<= 1;\
|
|
|
|
|
data1 <<= 1;\
|
|
|
|
|
\
|
|
|
|
|
if (unlikely(object_buffer_pointer->pixel) && (pixel == 0 || !(object_buffer_pointer->bg_priority || (attributes & 0x80)) || !(gb->io_registers[GB_IO_LCDC] & 1))) {\
|
|
|
|
|
pixel = object_buffer_pointer->pixel;\
|
|
|
|
|
if (!gb->cgb_mode) {\
|
|
|
|
|
pixel = ((gb->io_registers[GB_IO_OBP0 + object_buffer_pointer->palette] >> (pixel << 1)) & 3);\
|
|
|
|
|
}\
|
|
|
|
|
*(p++) = gb->object_palettes_rgb[pixel + (object_buffer_pointer->palette & 7) * 4];\
|
|
|
|
|
}\
|
|
|
|
|
else {\
|
|
|
|
|
if (!gb->cgb_mode) {\
|
|
|
|
|
pixel = ((gb->io_registers[GB_IO_BGP] >> (pixel << 1)) & 3);\
|
|
|
|
|
}\
|
|
|
|
|
*(p++) = gb->background_palettes_rgb[pixel + (attributes & 7) * 4];\
|
|
|
|
|
}\
|
|
|
|
|
pixels++;\
|
|
|
|
|
object_buffer_pointer++\
|
|
|
|
|
|
|
|
|
|
// First 1-8 pixels
|
|
|
|
|
data0 <<= fractional_scroll;
|
|
|
|
|
data1 <<= fractional_scroll;
|
|
|
|
|
bool check_window = gb->wy_triggered && (gb->io_registers[GB_IO_LCDC] & 0x20);
|
|
|
|
|
for (unsigned i = fractional_scroll; i < 8; i++) {
|
|
|
|
|
if (check_window && gb->io_registers[GB_IO_WX] == pixels + 7) {
|
|
|
|
|
activate_window:
|
|
|
|
|
check_window = false;
|
|
|
|
|
map = gb->io_registers[GB_IO_LCDC] & 0x40? 0x1C00 : 0x1800;
|
|
|
|
|
tile_x = -1;
|
|
|
|
|
y = ++gb->window_y;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
DO_PIXEL();
|
|
|
|
|
}
|
|
|
|
|
tile_x++;
|
|
|
|
|
|
|
|
|
|
while (pixels < 160 - 8) {
|
|
|
|
|
get_tile_data(gb, tile_x, y, map, &attributes, &data0, &data1);
|
|
|
|
|
for (unsigned i = 0; i < 8; i++) {
|
|
|
|
|
if (check_window && gb->io_registers[GB_IO_WX] == pixels + 7) {
|
|
|
|
|
goto activate_window;
|
|
|
|
|
}
|
|
|
|
|
DO_PIXEL();
|
|
|
|
|
}
|
|
|
|
|
tile_x++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gb->fetcher_state = (160 - pixels) & 7;
|
|
|
|
|
get_tile_data(gb, tile_x, y, map, &attributes, &data0, &data1);
|
|
|
|
|
while (pixels < 160) {
|
|
|
|
|
if (check_window && gb->io_registers[GB_IO_WX] == pixels + 7) {
|
|
|
|
|
goto activate_window;
|
|
|
|
|
}
|
|
|
|
|
DO_PIXEL();
|
|
|
|
|
}
|
|
|
|
|
tile_x++;
|
|
|
|
|
|
|
|
|
|
get_tile_data(gb, tile_x, y, map, &attributes, gb->current_tile_data, gb->current_tile_data + 1);
|
|
|
|
|
#undef DO_PIXEL
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void render_line_sgb(GB_gameboy_t *gb)
|
|
|
|
|
{
|
|
|
|
|
if (gb->current_line > 144) return; // Corrupt save state
|
|
|
|
|
|
|
|
|
|
struct {
|
|
|
|
|
unsigned pixel:2; // Color, 0-3
|
|
|
|
|
unsigned palette:1; // Palette, 0 - 7 (CGB); 0-1 in DMG (or just 0 for BG)
|
|
|
|
|
bool bg_priority:1; // BG priority bit
|
2021-12-26 00:38:54 +00:00
|
|
|
|
} _object_buffer[160 + 16]; // allocate extra to avoid per pixel checks
|
|
|
|
|
static const uint8_t empty_object_buffer[sizeof(_object_buffer)];
|
|
|
|
|
const typeof(_object_buffer[0]) *object_buffer;
|
2021-12-25 23:47:59 +00:00
|
|
|
|
|
|
|
|
|
if (gb->n_visible_objs && !gb->objects_disabled && (gb->io_registers[GB_IO_LCDC] & 2)) {
|
2021-12-26 00:38:54 +00:00
|
|
|
|
object_buffer = &_object_buffer[0];
|
2021-12-25 23:47:59 +00:00
|
|
|
|
object_t *objects = (object_t *) &gb->oam;
|
2021-12-26 00:38:54 +00:00
|
|
|
|
memset(_object_buffer, 0, sizeof(_object_buffer));
|
2021-12-25 23:47:59 +00:00
|
|
|
|
|
|
|
|
|
while (gb->n_visible_objs) {
|
|
|
|
|
const object_t *object = &objects[gb->visible_objs[gb->n_visible_objs - 1]];
|
|
|
|
|
gb->n_visible_objs--;
|
|
|
|
|
|
|
|
|
|
uint16_t line_address = get_object_line_address(gb, object);
|
|
|
|
|
uint8_t data0 = gb->vram[line_address];
|
|
|
|
|
uint8_t data1 = gb->vram[line_address + 1];
|
|
|
|
|
if (object->flags & 0x20) {
|
|
|
|
|
data0 = flip(data0);
|
|
|
|
|
data1 = flip(data1);
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-26 00:38:54 +00:00
|
|
|
|
typeof(_object_buffer[0]) *p = _object_buffer + object->x;
|
2021-12-25 23:47:59 +00:00
|
|
|
|
if (object->x >= 168) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
unrolled for (unsigned x = 0; x < 8; x++) {
|
|
|
|
|
unsigned pixel = (data0 >> 7) | ((data1 >> 7) << 1);
|
|
|
|
|
data0 <<= 1;
|
|
|
|
|
data1 <<= 1;
|
|
|
|
|
if (!p->pixel) {
|
|
|
|
|
p->pixel = pixel;
|
|
|
|
|
p->palette = (object->flags & 0x10) >> 4;
|
|
|
|
|
p->bg_priority = object->flags & 0x80;
|
|
|
|
|
}
|
|
|
|
|
p++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-12-26 00:38:54 +00:00
|
|
|
|
else {
|
|
|
|
|
object_buffer = (const void *)empty_object_buffer;
|
|
|
|
|
}
|
2021-12-25 23:47:59 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uint8_t *restrict p = gb->sgb->screen_buffer;
|
|
|
|
|
typeof(object_buffer[0]) *object_buffer_pointer = object_buffer + 8;
|
|
|
|
|
p += WIDTH * gb->current_line;
|
|
|
|
|
|
|
|
|
|
if (unlikely(gb->background_disabled) || (!gb->cgb_mode && !(gb->io_registers[GB_IO_LCDC] & 1))) {
|
|
|
|
|
for (unsigned i = 160; i--;) {
|
|
|
|
|
if (unlikely(object_buffer_pointer->pixel)) {
|
|
|
|
|
uint8_t pixel = object_buffer_pointer->pixel;
|
|
|
|
|
pixel = ((gb->io_registers[GB_IO_OBP0 + object_buffer_pointer->palette] >> (pixel << 1)) & 3);
|
|
|
|
|
*(p++) = pixel;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
*(p++) = gb->io_registers[GB_IO_BGP] & 3;
|
|
|
|
|
}
|
|
|
|
|
object_buffer_pointer++;
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unsigned pixels = 0;
|
|
|
|
|
uint8_t tile_x = gb->io_registers[GB_IO_SCX] / 8;
|
|
|
|
|
unsigned fractional_scroll = gb->io_registers[GB_IO_SCX] & 7;
|
|
|
|
|
uint16_t map = 0x1800;
|
|
|
|
|
if (gb->io_registers[GB_IO_LCDC] & 0x08) {
|
|
|
|
|
map = 0x1C00;
|
|
|
|
|
}
|
|
|
|
|
uint8_t y = gb->current_line + gb->io_registers[GB_IO_SCY];
|
|
|
|
|
uint8_t attributes;
|
|
|
|
|
uint8_t data0, data1;
|
|
|
|
|
get_tile_data(gb, tile_x, y, map, &attributes, &data0, &data1);
|
|
|
|
|
|
|
|
|
|
#define DO_PIXEL() \
|
|
|
|
|
uint8_t pixel = (data0 >> 7) | ((data1 >> 7) << 1);\
|
|
|
|
|
data0 <<= 1;\
|
|
|
|
|
data1 <<= 1;\
|
|
|
|
|
\
|
|
|
|
|
if (unlikely(object_buffer_pointer->pixel) && (pixel == 0 || !object_buffer_pointer->bg_priority || !(gb->io_registers[GB_IO_LCDC] & 1))) {\
|
|
|
|
|
pixel = object_buffer_pointer->pixel;\
|
|
|
|
|
pixel = ((gb->io_registers[GB_IO_OBP0 + object_buffer_pointer->palette] >> (pixel << 1)) & 3);\
|
|
|
|
|
*(p++) = pixel;\
|
|
|
|
|
}\
|
|
|
|
|
else {\
|
|
|
|
|
pixel = ((gb->io_registers[GB_IO_BGP] >> (pixel << 1)) & 3);\
|
|
|
|
|
*(p++) = pixel;\
|
|
|
|
|
}\
|
|
|
|
|
pixels++;\
|
|
|
|
|
object_buffer_pointer++\
|
|
|
|
|
|
|
|
|
|
// First 1-8 pixels
|
|
|
|
|
data0 <<= fractional_scroll;
|
|
|
|
|
data1 <<= fractional_scroll;
|
|
|
|
|
bool check_window = gb->wy_triggered && (gb->io_registers[GB_IO_LCDC] & 0x20);
|
|
|
|
|
for (unsigned i = fractional_scroll; i < 8; i++) {
|
|
|
|
|
if (check_window && gb->io_registers[GB_IO_WX] == pixels + 7) {
|
|
|
|
|
activate_window:
|
|
|
|
|
check_window = false;
|
|
|
|
|
map = gb->io_registers[GB_IO_LCDC] & 0x40? 0x1C00 : 0x1800;
|
|
|
|
|
tile_x = -1;
|
|
|
|
|
y = ++gb->window_y;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
DO_PIXEL();
|
|
|
|
|
}
|
|
|
|
|
tile_x++;
|
|
|
|
|
|
|
|
|
|
while (pixels < 160 - 8) {
|
|
|
|
|
get_tile_data(gb, tile_x, y, map, &attributes, &data0, &data1);
|
|
|
|
|
for (unsigned i = 0; i < 8; i++) {
|
|
|
|
|
if (check_window && gb->io_registers[GB_IO_WX] == pixels + 7) {
|
|
|
|
|
goto activate_window;
|
|
|
|
|
}
|
|
|
|
|
DO_PIXEL();
|
|
|
|
|
}
|
|
|
|
|
tile_x++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get_tile_data(gb, tile_x, y, map, &attributes, &data0, &data1);
|
|
|
|
|
while (pixels < 160) {
|
|
|
|
|
if (check_window && gb->io_registers[GB_IO_WX] == pixels + 7) {
|
|
|
|
|
goto activate_window;
|
|
|
|
|
}
|
|
|
|
|
DO_PIXEL();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline uint16_t mode3_batching_length(GB_gameboy_t *gb)
|
|
|
|
|
{
|
|
|
|
|
if (gb->model & GB_MODEL_NO_SFC_BIT) return 0;
|
|
|
|
|
if (gb->hdma_on) return 0;
|
|
|
|
|
if (gb->dma_steps_left) return 0;
|
|
|
|
|
if (gb->wy_triggered && (gb->io_registers[GB_IO_LCDC] & 0x20) && (gb->io_registers[GB_IO_WX] < 8 || gb->io_registers[GB_IO_WX] == 166)) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// No objects or window, timing is trivial
|
|
|
|
|
if (gb->n_visible_objs == 0 && !(gb->wy_triggered && (gb->io_registers[GB_IO_LCDC] & 0x20))) return 167 + (gb->io_registers[GB_IO_SCX] & 7);
|
|
|
|
|
|
|
|
|
|
if (gb->hdma_on_hblank) return 0;
|
|
|
|
|
|
|
|
|
|
// 300 is a bit more than the maximum Mode 3 length
|
|
|
|
|
|
|
|
|
|
// No HBlank interrupt
|
|
|
|
|
if (!(gb->io_registers[GB_IO_STAT] & 0x8)) return 300;
|
|
|
|
|
// No STAT interrupt requested
|
|
|
|
|
if (!(gb->interrupt_enable & 2)) return 300;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-02 01:00:10 +00:00
|
|
|
|
/*
|
|
|
|
|
TODO: It seems that the STAT register's mode bits are always "late" by 4 T-cycles.
|
|
|
|
|
The PPU logic can be greatly simplified if that delay is simply emulated.
|
|
|
|
|
*/
|
2021-12-25 23:47:59 +00:00
|
|
|
|
void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force)
|
2018-02-24 22:48:45 +00:00
|
|
|
|
{
|
2021-12-04 13:04:46 +00:00
|
|
|
|
gb->cycles_since_vblank_callback += cycles / 2;
|
|
|
|
|
|
2019-06-07 10:53:50 +00:00
|
|
|
|
/* The PPU does not advance while in STOP mode on the DMG */
|
|
|
|
|
if (gb->stopped && !GB_is_cgb(gb)) {
|
2021-12-04 13:04:46 +00:00
|
|
|
|
if (gb->cycles_since_vblank_callback >= LCDC_PERIOD) {
|
|
|
|
|
GB_display_vblank(gb);
|
2019-06-07 10:53:50 +00:00
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
2021-11-06 23:10:58 +00:00
|
|
|
|
object_t *objects = (object_t *) &gb->oam;
|
2018-02-24 22:48:45 +00:00
|
|
|
|
|
2021-12-25 23:47:59 +00:00
|
|
|
|
GB_BATCHABLE_STATE_MACHINE(gb, display, cycles, 2, !force) {
|
2018-02-24 22:48:45 +00:00
|
|
|
|
GB_STATE(gb, display, 1);
|
|
|
|
|
GB_STATE(gb, display, 2);
|
2021-12-25 23:47:59 +00:00
|
|
|
|
GB_STATE(gb, display, 3);
|
|
|
|
|
GB_STATE(gb, display, 4);
|
|
|
|
|
GB_STATE(gb, display, 5);
|
2018-02-24 22:48:45 +00:00
|
|
|
|
GB_STATE(gb, display, 6);
|
|
|
|
|
GB_STATE(gb, display, 7);
|
|
|
|
|
GB_STATE(gb, display, 8);
|
2018-03-23 16:07:14 +00:00
|
|
|
|
// GB_STATE(gb, display, 9);
|
2018-02-24 22:48:45 +00:00
|
|
|
|
GB_STATE(gb, display, 10);
|
|
|
|
|
GB_STATE(gb, display, 11);
|
|
|
|
|
GB_STATE(gb, display, 12);
|
|
|
|
|
GB_STATE(gb, display, 13);
|
|
|
|
|
GB_STATE(gb, display, 14);
|
|
|
|
|
GB_STATE(gb, display, 15);
|
|
|
|
|
GB_STATE(gb, display, 16);
|
|
|
|
|
GB_STATE(gb, display, 17);
|
2021-12-14 18:27:38 +00:00
|
|
|
|
GB_STATE(gb, display, 19);
|
2018-03-03 15:22:23 +00:00
|
|
|
|
GB_STATE(gb, display, 20);
|
2018-03-04 20:21:56 +00:00
|
|
|
|
GB_STATE(gb, display, 21);
|
|
|
|
|
GB_STATE(gb, display, 22);
|
2018-03-05 19:17:37 +00:00
|
|
|
|
GB_STATE(gb, display, 23);
|
2021-06-18 23:14:16 +00:00
|
|
|
|
GB_STATE(gb, display, 24);
|
2018-03-19 21:49:53 +00:00
|
|
|
|
GB_STATE(gb, display, 25);
|
2018-04-02 22:43:24 +00:00
|
|
|
|
GB_STATE(gb, display, 26);
|
2018-04-07 00:00:26 +00:00
|
|
|
|
GB_STATE(gb, display, 27);
|
|
|
|
|
GB_STATE(gb, display, 28);
|
2018-05-11 09:38:55 +00:00
|
|
|
|
GB_STATE(gb, display, 29);
|
2018-07-03 19:25:09 +00:00
|
|
|
|
GB_STATE(gb, display, 30);
|
2020-12-23 21:49:57 +00:00
|
|
|
|
GB_STATE(gb, display, 31);
|
2019-01-19 17:32:26 +00:00
|
|
|
|
GB_STATE(gb, display, 32);
|
|
|
|
|
GB_STATE(gb, display, 33);
|
|
|
|
|
GB_STATE(gb, display, 34);
|
|
|
|
|
GB_STATE(gb, display, 35);
|
|
|
|
|
GB_STATE(gb, display, 36);
|
2019-02-16 02:19:16 +00:00
|
|
|
|
GB_STATE(gb, display, 37);
|
|
|
|
|
GB_STATE(gb, display, 38);
|
2020-02-21 14:16:02 +00:00
|
|
|
|
GB_STATE(gb, display, 39);
|
2020-02-21 14:43:51 +00:00
|
|
|
|
GB_STATE(gb, display, 40);
|
2020-02-21 19:44:44 +00:00
|
|
|
|
GB_STATE(gb, display, 41);
|
2020-02-27 20:49:34 +00:00
|
|
|
|
GB_STATE(gb, display, 42);
|
2018-02-24 22:48:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) {
|
|
|
|
|
while (true) {
|
2021-12-04 13:04:46 +00:00
|
|
|
|
if (gb->cycles_since_vblank_callback < LCDC_PERIOD) {
|
|
|
|
|
GB_SLEEP(gb, display, 1, LCDC_PERIOD - gb->cycles_since_vblank_callback);
|
|
|
|
|
}
|
|
|
|
|
GB_display_vblank(gb);
|
2020-05-29 22:25:21 +00:00
|
|
|
|
gb->cgb_repeated_a_frame = true;
|
2017-06-17 19:17:58 +00:00
|
|
|
|
}
|
2018-02-24 22:48:45 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
2018-03-05 19:17:37 +00:00
|
|
|
|
|
2020-03-26 18:54:18 +00:00
|
|
|
|
gb->is_odd_frame = false;
|
|
|
|
|
|
2018-06-16 10:59:33 +00:00
|
|
|
|
if (!GB_is_cgb(gb)) {
|
2018-03-23 09:58:51 +00:00
|
|
|
|
GB_SLEEP(gb, display, 23, 1);
|
|
|
|
|
}
|
2018-02-24 22:48:45 +00:00
|
|
|
|
|
2019-02-16 02:19:16 +00:00
|
|
|
|
/* Handle mode 2 on the very first line 0 */
|
2018-02-24 22:48:45 +00:00
|
|
|
|
gb->current_line = 0;
|
2020-02-23 21:16:45 +00:00
|
|
|
|
gb->window_y = -1;
|
2020-12-23 21:49:57 +00:00
|
|
|
|
gb->wy_triggered = false;
|
2020-02-23 21:16:45 +00:00
|
|
|
|
|
2018-02-24 22:48:45 +00:00
|
|
|
|
gb->ly_for_comparison = 0;
|
|
|
|
|
gb->io_registers[GB_IO_STAT] &= ~3;
|
2018-06-03 23:07:38 +00:00
|
|
|
|
gb->mode_for_interrupt = -1;
|
2018-02-24 22:48:45 +00:00
|
|
|
|
gb->oam_read_blocked = false;
|
|
|
|
|
gb->vram_read_blocked = false;
|
|
|
|
|
gb->oam_write_blocked = false;
|
|
|
|
|
gb->vram_write_blocked = false;
|
2019-01-19 17:32:26 +00:00
|
|
|
|
gb->cgb_palettes_blocked = false;
|
|
|
|
|
gb->cycles_for_line = MODE2_LENGTH - 4;
|
|
|
|
|
GB_STAT_update(gb);
|
|
|
|
|
GB_SLEEP(gb, display, 2, MODE2_LENGTH - 4);
|
|
|
|
|
|
|
|
|
|
gb->oam_write_blocked = true;
|
|
|
|
|
gb->cycles_for_line += 2;
|
2018-02-24 22:48:45 +00:00
|
|
|
|
GB_STAT_update(gb);
|
2019-01-19 17:32:26 +00:00
|
|
|
|
GB_SLEEP(gb, display, 34, 2);
|
2018-02-24 22:48:45 +00:00
|
|
|
|
|
2019-02-16 02:19:16 +00:00
|
|
|
|
gb->n_visible_objs = 0;
|
|
|
|
|
gb->cycles_for_line += 8; // Mode 0 is shorter on the first line 0, so we augment cycles_for_line by 8 extra cycles.
|
|
|
|
|
|
2018-02-24 22:48:45 +00:00
|
|
|
|
gb->io_registers[GB_IO_STAT] &= ~3;
|
|
|
|
|
gb->io_registers[GB_IO_STAT] |= 3;
|
2018-06-03 23:07:38 +00:00
|
|
|
|
gb->mode_for_interrupt = 3;
|
2019-02-16 02:19:16 +00:00
|
|
|
|
|
|
|
|
|
gb->oam_write_blocked = true;
|
2018-02-24 22:48:45 +00:00
|
|
|
|
gb->oam_read_blocked = true;
|
2019-02-16 02:19:16 +00:00
|
|
|
|
gb->vram_read_blocked = gb->cgb_double_speed;
|
|
|
|
|
gb->vram_write_blocked = gb->cgb_double_speed;
|
|
|
|
|
if (!GB_is_cgb(gb)) {
|
|
|
|
|
gb->vram_read_blocked = true;
|
|
|
|
|
gb->vram_write_blocked = true;
|
2018-03-09 21:31:49 +00:00
|
|
|
|
}
|
2019-02-16 02:19:16 +00:00
|
|
|
|
gb->cycles_for_line += 2;
|
|
|
|
|
GB_SLEEP(gb, display, 37, 2);
|
2018-03-09 21:31:49 +00:00
|
|
|
|
|
2019-02-16 02:19:16 +00:00
|
|
|
|
gb->cgb_palettes_blocked = true;
|
2020-05-29 13:30:40 +00:00
|
|
|
|
gb->cycles_for_line += (GB_is_cgb(gb) && gb->model <= GB_MODEL_CGB_C)? 2 : 3;
|
|
|
|
|
GB_SLEEP(gb, display, 38, (GB_is_cgb(gb) && gb->model <= GB_MODEL_CGB_C)? 2 : 3);
|
2019-01-19 17:32:26 +00:00
|
|
|
|
|
2019-02-16 02:19:16 +00:00
|
|
|
|
gb->vram_read_blocked = true;
|
|
|
|
|
gb->vram_write_blocked = true;
|
2020-02-23 21:48:08 +00:00
|
|
|
|
gb->wx_triggered = false;
|
2020-03-01 21:58:28 +00:00
|
|
|
|
gb->wx166_glitch = false;
|
2019-02-16 02:19:16 +00:00
|
|
|
|
goto mode_3_start;
|
2018-02-24 22:48:45 +00:00
|
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
/* Lines 0 - 143 */
|
2020-02-23 21:16:45 +00:00
|
|
|
|
gb->window_y = -1;
|
2018-02-24 22:48:45 +00:00
|
|
|
|
for (; gb->current_line < LINES; gb->current_line++) {
|
2021-12-10 17:49:52 +00:00
|
|
|
|
if (unlikely(gb->lcd_line_callback)) {
|
|
|
|
|
gb->lcd_line_callback(gb, gb->current_line);
|
|
|
|
|
}
|
2020-02-23 21:16:45 +00:00
|
|
|
|
|
2019-01-19 17:32:26 +00:00
|
|
|
|
gb->oam_write_blocked = GB_is_cgb(gb) && !gb->cgb_double_speed;
|
2018-03-27 12:46:00 +00:00
|
|
|
|
gb->accessed_oam_row = 0;
|
2019-01-19 17:32:26 +00:00
|
|
|
|
|
|
|
|
|
GB_SLEEP(gb, display, 35, 2);
|
|
|
|
|
gb->oam_write_blocked = GB_is_cgb(gb);
|
|
|
|
|
|
|
|
|
|
GB_SLEEP(gb, display, 6, 1);
|
2018-02-24 22:48:45 +00:00
|
|
|
|
gb->io_registers[GB_IO_LY] = gb->current_line;
|
2017-06-18 18:27:07 +00:00
|
|
|
|
gb->oam_read_blocked = true;
|
2018-05-11 09:38:55 +00:00
|
|
|
|
gb->ly_for_comparison = gb->current_line? -1 : 0;
|
2018-02-28 22:12:04 +00:00
|
|
|
|
|
2018-03-08 20:11:10 +00:00
|
|
|
|
/* The OAM STAT interrupt occurs 1 T-cycle before STAT actually changes, except on line 0.
|
2018-06-08 14:16:15 +00:00
|
|
|
|
PPU glitch? */
|
2018-06-02 21:36:05 +00:00
|
|
|
|
if (gb->current_line != 0) {
|
2018-06-03 23:07:38 +00:00
|
|
|
|
gb->mode_for_interrupt = 2;
|
|
|
|
|
gb->io_registers[GB_IO_STAT] &= ~3;
|
2018-02-28 22:12:04 +00:00
|
|
|
|
}
|
2018-06-16 10:59:33 +00:00
|
|
|
|
else if (!GB_is_cgb(gb)) {
|
2018-03-17 18:34:55 +00:00
|
|
|
|
gb->io_registers[GB_IO_STAT] &= ~3;
|
|
|
|
|
}
|
2018-06-03 23:07:38 +00:00
|
|
|
|
GB_STAT_update(gb);
|
2018-03-08 20:11:10 +00:00
|
|
|
|
|
2018-03-03 13:47:36 +00:00
|
|
|
|
GB_SLEEP(gb, display, 7, 1);
|
2018-02-24 22:48:45 +00:00
|
|
|
|
|
|
|
|
|
gb->io_registers[GB_IO_STAT] &= ~3;
|
|
|
|
|
gb->io_registers[GB_IO_STAT] |= 2;
|
2018-06-03 23:07:38 +00:00
|
|
|
|
gb->mode_for_interrupt = 2;
|
2017-06-18 18:27:07 +00:00
|
|
|
|
gb->oam_write_blocked = true;
|
2018-02-24 22:48:45 +00:00
|
|
|
|
gb->ly_for_comparison = gb->current_line;
|
2018-06-03 23:07:38 +00:00
|
|
|
|
GB_STAT_update(gb);
|
|
|
|
|
gb->mode_for_interrupt = -1;
|
2018-02-24 22:48:45 +00:00
|
|
|
|
GB_STAT_update(gb);
|
2018-03-23 16:07:14 +00:00
|
|
|
|
gb->n_visible_objs = 0;
|
2018-02-24 22:48:45 +00:00
|
|
|
|
|
2021-12-25 23:47:59 +00:00
|
|
|
|
if (!gb->dma_steps_left && !gb->oam_ppu_blocked) {
|
|
|
|
|
GB_BATCHPOINT(gb, display, 5, 80);
|
|
|
|
|
}
|
2018-03-23 16:07:14 +00:00
|
|
|
|
for (gb->oam_search_index = 0; gb->oam_search_index < 40; gb->oam_search_index++) {
|
2018-06-16 10:59:33 +00:00
|
|
|
|
if (GB_is_cgb(gb)) {
|
2018-03-27 12:46:00 +00:00
|
|
|
|
add_object_from_index(gb, gb->oam_search_index);
|
2019-06-07 10:53:50 +00:00
|
|
|
|
/* The CGB does not care about the accessed OAM row as there's no OAM bug */
|
2018-03-27 12:46:00 +00:00
|
|
|
|
}
|
2018-03-23 16:07:14 +00:00
|
|
|
|
GB_SLEEP(gb, display, 8, 2);
|
2018-06-16 10:59:33 +00:00
|
|
|
|
if (!GB_is_cgb(gb)) {
|
2018-03-27 12:46:00 +00:00
|
|
|
|
add_object_from_index(gb, gb->oam_search_index);
|
|
|
|
|
gb->accessed_oam_row = (gb->oam_search_index & ~1) * 4 + 8;
|
|
|
|
|
}
|
2018-03-23 16:07:14 +00:00
|
|
|
|
if (gb->oam_search_index == 37) {
|
2018-06-16 10:59:33 +00:00
|
|
|
|
gb->vram_read_blocked = !GB_is_cgb(gb);
|
2018-03-23 16:07:14 +00:00
|
|
|
|
gb->vram_write_blocked = false;
|
2019-01-19 17:32:26 +00:00
|
|
|
|
gb->cgb_palettes_blocked = false;
|
2018-06-16 10:59:33 +00:00
|
|
|
|
gb->oam_write_blocked = GB_is_cgb(gb);
|
2018-03-23 16:07:14 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2019-02-16 02:19:16 +00:00
|
|
|
|
gb->cycles_for_line = MODE2_LENGTH + 4;
|
|
|
|
|
|
2018-03-27 12:46:00 +00:00
|
|
|
|
gb->accessed_oam_row = -1;
|
2017-02-19 00:22:50 +00:00
|
|
|
|
gb->io_registers[GB_IO_STAT] &= ~3;
|
2018-02-24 22:48:45 +00:00
|
|
|
|
gb->io_registers[GB_IO_STAT] |= 3;
|
2018-06-03 23:07:38 +00:00
|
|
|
|
gb->mode_for_interrupt = 3;
|
2018-03-10 22:17:57 +00:00
|
|
|
|
gb->vram_read_blocked = true;
|
2018-02-24 22:48:45 +00:00
|
|
|
|
gb->vram_write_blocked = true;
|
2019-01-19 17:32:26 +00:00
|
|
|
|
gb->cgb_palettes_blocked = false;
|
2018-02-24 22:48:45 +00:00
|
|
|
|
gb->oam_write_blocked = true;
|
2019-02-16 02:19:16 +00:00
|
|
|
|
gb->oam_read_blocked = true;
|
|
|
|
|
|
2018-02-24 22:48:45 +00:00
|
|
|
|
GB_STAT_update(gb);
|
2018-04-01 18:45:56 +00:00
|
|
|
|
|
2019-02-16 02:19:16 +00:00
|
|
|
|
|
|
|
|
|
uint8_t idle_cycles = 3;
|
|
|
|
|
if (GB_is_cgb(gb) && gb->model <= GB_MODEL_CGB_C) {
|
|
|
|
|
idle_cycles = 2;
|
|
|
|
|
}
|
|
|
|
|
gb->cycles_for_line += idle_cycles;
|
|
|
|
|
GB_SLEEP(gb, display, 10, idle_cycles);
|
|
|
|
|
|
|
|
|
|
gb->cgb_palettes_blocked = true;
|
|
|
|
|
gb->cycles_for_line += 2;
|
|
|
|
|
GB_SLEEP(gb, display, 32, 2);
|
|
|
|
|
mode_3_start:
|
2020-12-24 18:50:47 +00:00
|
|
|
|
/* TODO: Timing seems incorrect, might need an access conflict handling. */
|
2020-12-23 21:49:57 +00:00
|
|
|
|
if ((gb->io_registers[GB_IO_LCDC] & 0x20) &&
|
2020-12-24 18:50:47 +00:00
|
|
|
|
gb->io_registers[GB_IO_WY] == gb->current_line) {
|
2020-12-23 21:49:57 +00:00
|
|
|
|
gb->wy_triggered = true;
|
|
|
|
|
}
|
2019-02-16 02:19:16 +00:00
|
|
|
|
|
2018-03-03 13:47:36 +00:00
|
|
|
|
fifo_clear(&gb->bg_fifo);
|
2018-03-04 21:27:31 +00:00
|
|
|
|
fifo_clear(&gb->oam_fifo);
|
2018-04-06 15:26:04 +00:00
|
|
|
|
/* Fill the FIFO with 8 pixels of "junk", it's going to be dropped anyway. */
|
|
|
|
|
fifo_push_bg_row(&gb->bg_fifo, 0, 0, 0, false, false);
|
2018-03-30 14:06:27 +00:00
|
|
|
|
/* Todo: find out actual access time of SCX */
|
2018-03-03 13:47:36 +00:00
|
|
|
|
gb->position_in_line = - (gb->io_registers[GB_IO_SCX] & 7) - 8;
|
2020-03-06 12:41:13 +00:00
|
|
|
|
gb->lcd_x = 0;
|
2019-07-19 17:19:09 +00:00
|
|
|
|
|
2021-12-17 19:16:23 +00:00
|
|
|
|
gb->extra_penalty_for_object_at_0 = MIN((gb->io_registers[GB_IO_SCX] & 7), 5);
|
2019-01-19 17:32:26 +00:00
|
|
|
|
|
2018-03-03 13:47:36 +00:00
|
|
|
|
/* The actual rendering cycle */
|
2018-04-06 16:29:49 +00:00
|
|
|
|
gb->fetcher_state = 0;
|
2021-12-25 23:47:59 +00:00
|
|
|
|
if ((gb->mode3_batching_length = mode3_batching_length(gb))) {
|
|
|
|
|
GB_BATCHPOINT(gb, display, 3, gb->mode3_batching_length);
|
|
|
|
|
if (GB_BATCHED_CYCLES(gb, display) >= gb->mode3_batching_length) {
|
|
|
|
|
// Successfully batched!
|
|
|
|
|
gb->lcd_x = gb->position_in_line = 160;
|
|
|
|
|
gb->cycles_for_line += gb->mode3_batching_length;
|
|
|
|
|
if (gb->sgb) {
|
|
|
|
|
render_line_sgb(gb);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
render_line(gb);
|
|
|
|
|
}
|
|
|
|
|
GB_SLEEP(gb, display, 4, gb->mode3_batching_length);
|
|
|
|
|
goto skip_slow_mode_3;
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-03-03 13:47:36 +00:00
|
|
|
|
while (true) {
|
2020-02-27 20:49:34 +00:00
|
|
|
|
/* Handle window */
|
2020-03-03 00:21:19 +00:00
|
|
|
|
/* TODO: It appears that WX checks if the window begins *next* pixel, not *this* pixel. For this reason,
|
2020-03-01 21:58:28 +00:00
|
|
|
|
WX=167 has no effect at all (It checks if the PPU X position is 161, which never happens) and WX=166
|
|
|
|
|
has weird artifacts (It appears to activate the window during HBlank, as PPU X is temporarily 160 at
|
|
|
|
|
that point. The code should be updated to represent this, and this will fix the time travel hack in
|
|
|
|
|
WX's access conflict code. */
|
|
|
|
|
|
2020-02-27 20:49:34 +00:00
|
|
|
|
if (!gb->wx_triggered && gb->wy_triggered && (gb->io_registers[GB_IO_LCDC] & 0x20)) {
|
2020-03-01 21:58:28 +00:00
|
|
|
|
bool should_activate_window = false;
|
|
|
|
|
if (gb->io_registers[GB_IO_WX] == 0) {
|
|
|
|
|
static const uint8_t scx_to_wx0_comparisons[] = {-7, -9, -10, -11, -12, -13, -14, -14};
|
2020-12-23 21:49:57 +00:00
|
|
|
|
if (gb->position_in_line == scx_to_wx0_comparisons[gb->io_registers[GB_IO_SCX] & 7]) {
|
2020-03-01 21:58:28 +00:00
|
|
|
|
should_activate_window = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (gb->wx166_glitch) {
|
|
|
|
|
static const uint8_t scx_to_wx166_comparisons[] = {-8, -9, -10, -11, -12, -13, -14, -15};
|
|
|
|
|
if (gb->position_in_line == scx_to_wx166_comparisons[gb->io_registers[GB_IO_SCX] & 7]) {
|
|
|
|
|
should_activate_window = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (gb->io_registers[GB_IO_WX] < 166 + GB_is_cgb(gb)) {
|
2020-03-06 12:41:13 +00:00
|
|
|
|
if (gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 7)) {
|
2020-03-04 21:34:36 +00:00
|
|
|
|
should_activate_window = true;
|
|
|
|
|
}
|
2020-03-06 12:41:13 +00:00
|
|
|
|
else if (gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 6) && !gb->wx_just_changed) {
|
|
|
|
|
should_activate_window = true;
|
|
|
|
|
/* LCD-PPU horizontal desync! It only appears to happen on DMGs, but not all of them.
|
|
|
|
|
This doesn't seem to be CPU revision dependent, but most revisions */
|
|
|
|
|
if ((gb->model & GB_MODEL_FAMILY_MASK) == GB_MODEL_DMG_FAMILY && !GB_is_sgb(gb)) {
|
|
|
|
|
if (gb->lcd_x > 0) {
|
|
|
|
|
gb->lcd_x--;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-02-27 20:49:34 +00:00
|
|
|
|
}
|
2020-03-01 21:58:28 +00:00
|
|
|
|
|
|
|
|
|
if (should_activate_window) {
|
2020-02-27 20:49:34 +00:00
|
|
|
|
gb->window_y++;
|
2020-03-01 21:58:28 +00:00
|
|
|
|
/* TODO: Verify fetcher access timings in this case */
|
|
|
|
|
if (gb->io_registers[GB_IO_WX] == 0 && (gb->io_registers[GB_IO_SCX] & 7)) {
|
|
|
|
|
gb->cycles_for_line++;
|
|
|
|
|
GB_SLEEP(gb, display, 42, 1);
|
2020-02-27 20:49:34 +00:00
|
|
|
|
}
|
2020-03-01 21:58:28 +00:00
|
|
|
|
gb->wx_triggered = true;
|
|
|
|
|
gb->window_tile_x = 0;
|
|
|
|
|
fifo_clear(&gb->bg_fifo);
|
|
|
|
|
gb->fetcher_state = 0;
|
|
|
|
|
gb->window_is_being_fetched = true;
|
|
|
|
|
}
|
|
|
|
|
else if (!GB_is_cgb(gb) && gb->io_registers[GB_IO_WX] == 166 && gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 7)) {
|
|
|
|
|
gb->window_y++;
|
2020-02-27 20:49:34 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2020-02-29 15:06:08 +00:00
|
|
|
|
|
2020-02-29 22:23:50 +00:00
|
|
|
|
/* TODO: What happens when WX=0? */
|
2020-02-29 15:06:08 +00:00
|
|
|
|
if (!GB_is_cgb(gb) && gb->wx_triggered && !gb->window_is_being_fetched &&
|
|
|
|
|
gb->fetcher_state == 0 && gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 7) ) {
|
|
|
|
|
// Insert a pixel right at the FIFO's end
|
|
|
|
|
gb->bg_fifo.read_end--;
|
|
|
|
|
gb->bg_fifo.read_end &= GB_FIFO_LENGTH - 1;
|
|
|
|
|
gb->bg_fifo.fifo[gb->bg_fifo.read_end] = (GB_fifo_item_t){0,};
|
|
|
|
|
gb->window_is_being_fetched = false;
|
|
|
|
|
}
|
2020-02-27 20:49:34 +00:00
|
|
|
|
|
2018-03-04 20:21:56 +00:00
|
|
|
|
/* Handle objects */
|
2021-12-17 19:16:23 +00:00
|
|
|
|
/* When the object enabled bit is off, this proccess is skipped entirely on the DMG, but not on the CGB.
|
2018-03-23 17:01:27 +00:00
|
|
|
|
On the CGB, this bit is checked only when the pixel is actually popped from the FIFO. */
|
2018-04-04 21:51:37 +00:00
|
|
|
|
|
|
|
|
|
while (gb->n_visible_objs != 0 &&
|
2018-04-06 08:37:49 +00:00
|
|
|
|
(gb->position_in_line < 160 || gb->position_in_line >= (uint8_t)(-8)) &&
|
2019-03-16 18:56:22 +00:00
|
|
|
|
gb->obj_comparators[gb->n_visible_objs - 1] < (uint8_t)(gb->position_in_line + 8)) {
|
2018-04-04 21:51:37 +00:00
|
|
|
|
gb->n_visible_objs--;
|
|
|
|
|
}
|
2020-02-21 14:16:02 +00:00
|
|
|
|
|
|
|
|
|
gb->during_object_fetch = true;
|
2018-03-04 20:21:56 +00:00
|
|
|
|
while (gb->n_visible_objs != 0 &&
|
2018-06-16 10:59:33 +00:00
|
|
|
|
(gb->io_registers[GB_IO_LCDC] & 2 || GB_is_cgb(gb)) &&
|
2019-03-16 18:56:22 +00:00
|
|
|
|
gb->obj_comparators[gb->n_visible_objs - 1] == (uint8_t)(gb->position_in_line + 8)) {
|
2018-03-04 20:21:56 +00:00
|
|
|
|
|
2020-02-29 22:17:45 +00:00
|
|
|
|
while (gb->fetcher_state < 5 || fifo_size(&gb->bg_fifo) == 0) {
|
2018-04-07 00:00:26 +00:00
|
|
|
|
advance_fetcher_state_machine(gb);
|
|
|
|
|
gb->cycles_for_line++;
|
|
|
|
|
GB_SLEEP(gb, display, 27, 1);
|
2020-02-21 14:16:02 +00:00
|
|
|
|
if (gb->object_fetch_aborted) {
|
2020-02-21 13:14:33 +00:00
|
|
|
|
goto abort_fetching_object;
|
|
|
|
|
}
|
2018-04-07 00:00:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-04-07 00:26:10 +00:00
|
|
|
|
/* Todo: Measure if penalty occurs before or after waiting for the fetcher. */
|
2021-12-17 19:16:23 +00:00
|
|
|
|
if (gb->extra_penalty_for_object_at_0 != 0) {
|
2019-03-16 18:56:22 +00:00
|
|
|
|
if (gb->obj_comparators[gb->n_visible_objs - 1] == 0) {
|
2021-12-17 19:16:23 +00:00
|
|
|
|
gb->cycles_for_line += gb->extra_penalty_for_object_at_0;
|
|
|
|
|
GB_SLEEP(gb, display, 28, gb->extra_penalty_for_object_at_0);
|
|
|
|
|
gb->extra_penalty_for_object_at_0 = 0;
|
2020-02-21 13:14:33 +00:00
|
|
|
|
if (gb->object_fetch_aborted) {
|
|
|
|
|
goto abort_fetching_object;
|
|
|
|
|
}
|
2018-03-30 14:06:27 +00:00
|
|
|
|
}
|
2018-03-04 20:21:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-02-23 22:20:58 +00:00
|
|
|
|
/* TODO: Can this be deleted? { */
|
2020-02-21 19:44:44 +00:00
|
|
|
|
advance_fetcher_state_machine(gb);
|
|
|
|
|
gb->cycles_for_line++;
|
|
|
|
|
GB_SLEEP(gb, display, 41, 1);
|
|
|
|
|
if (gb->object_fetch_aborted) {
|
|
|
|
|
goto abort_fetching_object;
|
|
|
|
|
}
|
2020-02-23 22:20:58 +00:00
|
|
|
|
/* } */
|
2018-04-07 10:59:36 +00:00
|
|
|
|
|
2020-02-21 19:44:44 +00:00
|
|
|
|
advance_fetcher_state_machine(gb);
|
2018-03-04 21:27:31 +00:00
|
|
|
|
|
2020-02-21 19:44:44 +00:00
|
|
|
|
gb->cycles_for_line += 3;
|
|
|
|
|
GB_SLEEP(gb, display, 20, 3);
|
2020-02-21 13:14:33 +00:00
|
|
|
|
if (gb->object_fetch_aborted) {
|
|
|
|
|
goto abort_fetching_object;
|
2018-03-04 21:27:31 +00:00
|
|
|
|
}
|
2018-07-03 18:56:32 +00:00
|
|
|
|
|
2020-02-21 14:43:51 +00:00
|
|
|
|
gb->object_low_line_address = get_object_line_address(gb, &objects[gb->visible_objs[gb->n_visible_objs - 1]]);
|
2018-03-04 21:27:31 +00:00
|
|
|
|
|
2020-02-21 14:43:51 +00:00
|
|
|
|
gb->cycles_for_line++;
|
|
|
|
|
GB_SLEEP(gb, display, 39, 1);
|
|
|
|
|
if (gb->object_fetch_aborted) {
|
|
|
|
|
goto abort_fetching_object;
|
2018-03-04 21:27:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-02-21 14:43:51 +00:00
|
|
|
|
gb->during_object_fetch = false;
|
|
|
|
|
gb->cycles_for_line++;
|
|
|
|
|
GB_SLEEP(gb, display, 40, 1);
|
|
|
|
|
|
2021-11-06 23:10:58 +00:00
|
|
|
|
const object_t *object = &objects[gb->visible_objs[gb->n_visible_objs - 1]];
|
2018-03-04 21:27:31 +00:00
|
|
|
|
|
2020-02-21 14:43:51 +00:00
|
|
|
|
uint16_t line_address = get_object_line_address(gb, object);
|
2018-03-04 21:27:31 +00:00
|
|
|
|
|
|
|
|
|
uint8_t palette = (object->flags & 0x10) ? 1 : 0;
|
|
|
|
|
if (gb->cgb_mode) {
|
|
|
|
|
palette = object->flags & 0x7;
|
|
|
|
|
}
|
|
|
|
|
fifo_overlay_object_row(&gb->oam_fifo,
|
2020-02-21 14:43:51 +00:00
|
|
|
|
gb->vram_ppu_blocked? 0xFF : gb->vram[gb->object_low_line_address],
|
2020-02-15 17:21:43 +00:00
|
|
|
|
gb->vram_ppu_blocked? 0xFF : gb->vram[line_address + 1],
|
2018-03-04 21:27:31 +00:00
|
|
|
|
palette,
|
|
|
|
|
object->flags & 0x80,
|
2020-02-15 13:32:06 +00:00
|
|
|
|
gb->object_priority == GB_OBJECT_PRIORITY_INDEX? gb->visible_objs[gb->n_visible_objs - 1] : 0,
|
2018-03-04 21:27:31 +00:00
|
|
|
|
object->flags & 0x20);
|
2020-11-20 14:24:16 +00:00
|
|
|
|
|
|
|
|
|
gb->data_for_sel_glitch = gb->vram_ppu_blocked? 0xFF : gb->vram[line_address + 1];
|
2018-03-04 20:21:56 +00:00
|
|
|
|
gb->n_visible_objs--;
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-21 13:14:33 +00:00
|
|
|
|
abort_fetching_object:
|
2020-02-21 14:16:02 +00:00
|
|
|
|
gb->object_fetch_aborted = false;
|
|
|
|
|
gb->during_object_fetch = false;
|
2018-04-06 16:29:49 +00:00
|
|
|
|
|
2018-03-03 15:22:23 +00:00
|
|
|
|
render_pixel_if_possible(gb);
|
2018-04-07 10:02:53 +00:00
|
|
|
|
advance_fetcher_state_machine(gb);
|
|
|
|
|
|
2018-03-03 15:22:23 +00:00
|
|
|
|
if (gb->position_in_line == 160) break;
|
|
|
|
|
gb->cycles_for_line++;
|
2018-03-04 20:21:56 +00:00
|
|
|
|
GB_SLEEP(gb, display, 21, 1);
|
|
|
|
|
}
|
2021-12-25 23:47:59 +00:00
|
|
|
|
skip_slow_mode_3:
|
2018-07-03 19:25:09 +00:00
|
|
|
|
|
2021-12-26 17:57:18 +00:00
|
|
|
|
/* TODO: This seems incorrect (glitches Tesserae), verify further */
|
|
|
|
|
/*
|
2020-11-20 14:24:16 +00:00
|
|
|
|
if (gb->fetcher_state == 4 || gb->fetcher_state == 5) {
|
|
|
|
|
gb->data_for_sel_glitch = gb->current_tile_data[0];
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
gb->data_for_sel_glitch = gb->current_tile_data[1];
|
|
|
|
|
}
|
2021-12-26 17:57:18 +00:00
|
|
|
|
*/
|
2020-03-06 12:41:13 +00:00
|
|
|
|
while (gb->lcd_x != 160 && !gb->disable_rendering && gb->screen && !gb->sgb) {
|
|
|
|
|
/* Oh no! The PPU and LCD desynced! Fill the rest of the line whith white. */
|
|
|
|
|
uint32_t *dest = NULL;
|
|
|
|
|
if (gb->border_mode != GB_BORDER_ALWAYS) {
|
|
|
|
|
dest = gb->screen + gb->lcd_x + gb->current_line * WIDTH;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
dest = gb->screen + gb->lcd_x + gb->current_line * BORDERED_WIDTH + (BORDERED_WIDTH - WIDTH) / 2 + (BORDERED_HEIGHT - LINES) / 2 * BORDERED_WIDTH;
|
|
|
|
|
}
|
|
|
|
|
*dest = gb->background_palettes_rgb[0];
|
|
|
|
|
gb->lcd_x++;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-01 21:58:28 +00:00
|
|
|
|
/* TODO: Verify timing */
|
|
|
|
|
if (!GB_is_cgb(gb) && gb->wy_triggered && (gb->io_registers[GB_IO_LCDC] & 0x20) && gb->io_registers[GB_IO_WX] == 166) {
|
|
|
|
|
gb->wx166_glitch = true;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
gb->wx166_glitch = false;
|
|
|
|
|
}
|
|
|
|
|
gb->wx_triggered = false;
|
2018-07-03 19:25:09 +00:00
|
|
|
|
|
|
|
|
|
if (GB_is_cgb(gb) && gb->model <= GB_MODEL_CGB_C) {
|
|
|
|
|
gb->cycles_for_line++;
|
|
|
|
|
GB_SLEEP(gb, display, 30, 1);
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-23 09:58:51 +00:00
|
|
|
|
if (!gb->cgb_double_speed) {
|
|
|
|
|
gb->io_registers[GB_IO_STAT] &= ~3;
|
2018-06-03 23:07:38 +00:00
|
|
|
|
gb->mode_for_interrupt = 0;
|
2018-03-23 09:58:51 +00:00
|
|
|
|
gb->oam_read_blocked = false;
|
|
|
|
|
gb->vram_read_blocked = false;
|
|
|
|
|
gb->oam_write_blocked = false;
|
|
|
|
|
gb->vram_write_blocked = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gb->cycles_for_line++;
|
|
|
|
|
GB_SLEEP(gb, display, 22, 1);
|
|
|
|
|
|
2018-03-04 20:21:56 +00:00
|
|
|
|
gb->io_registers[GB_IO_STAT] &= ~3;
|
2018-06-03 23:07:38 +00:00
|
|
|
|
gb->mode_for_interrupt = 0;
|
2018-03-04 20:21:56 +00:00
|
|
|
|
gb->oam_read_blocked = false;
|
|
|
|
|
gb->vram_read_blocked = false;
|
|
|
|
|
gb->oam_write_blocked = false;
|
|
|
|
|
gb->vram_write_blocked = false;
|
2018-02-24 22:48:45 +00:00
|
|
|
|
GB_STAT_update(gb);
|
2018-03-19 21:49:53 +00:00
|
|
|
|
|
|
|
|
|
/* Todo: Measure this value */
|
2019-01-19 17:32:26 +00:00
|
|
|
|
gb->cycles_for_line += 2;
|
|
|
|
|
GB_SLEEP(gb, display, 33, 2);
|
|
|
|
|
gb->cgb_palettes_blocked = !gb->cgb_double_speed;
|
|
|
|
|
|
|
|
|
|
gb->cycles_for_line += 2;
|
|
|
|
|
GB_SLEEP(gb, display, 36, 2);
|
|
|
|
|
gb->cgb_palettes_blocked = false;
|
2018-03-19 21:49:53 +00:00
|
|
|
|
|
2019-01-19 17:32:26 +00:00
|
|
|
|
gb->cycles_for_line += 8;
|
|
|
|
|
GB_SLEEP(gb, display, 25, 8);
|
2018-03-19 21:49:53 +00:00
|
|
|
|
|
|
|
|
|
if (gb->hdma_on_hblank) {
|
|
|
|
|
gb->hdma_starting = true;
|
|
|
|
|
}
|
2020-12-23 21:49:57 +00:00
|
|
|
|
GB_SLEEP(gb, display, 11, LINE_LENGTH - gb->cycles_for_line - 2);
|
|
|
|
|
/*
|
|
|
|
|
TODO: Verify double speed timing
|
2020-12-24 18:50:47 +00:00
|
|
|
|
TODO: Timing differs on a DMG
|
2020-12-23 21:49:57 +00:00
|
|
|
|
*/
|
|
|
|
|
if ((gb->io_registers[GB_IO_LCDC] & 0x20) &&
|
2020-12-24 18:50:47 +00:00
|
|
|
|
(gb->io_registers[GB_IO_WY] == gb->current_line)) {
|
2020-12-23 21:49:57 +00:00
|
|
|
|
gb->wy_triggered = true;
|
|
|
|
|
}
|
|
|
|
|
GB_SLEEP(gb, display, 31, 2);
|
2021-06-18 23:14:16 +00:00
|
|
|
|
if (gb->current_line != LINES - 1) {
|
|
|
|
|
gb->mode_for_interrupt = 2;
|
|
|
|
|
}
|
2019-07-19 17:19:09 +00:00
|
|
|
|
|
2019-07-19 17:37:58 +00:00
|
|
|
|
// Todo: unverified timing
|
|
|
|
|
gb->current_lcd_line++;
|
|
|
|
|
if (gb->current_lcd_line == LINES && GB_is_sgb(gb)) {
|
2021-12-04 13:04:46 +00:00
|
|
|
|
GB_display_vblank(gb);
|
2019-07-19 17:37:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-07-19 17:19:09 +00:00
|
|
|
|
if (gb->icd_hreset_callback) {
|
|
|
|
|
gb->icd_hreset_callback(gb);
|
|
|
|
|
}
|
2018-02-24 22:48:45 +00:00
|
|
|
|
}
|
2020-03-01 21:58:28 +00:00
|
|
|
|
gb->wx166_glitch = false;
|
2018-02-24 22:48:45 +00:00
|
|
|
|
/* Lines 144 - 152 */
|
|
|
|
|
for (; gb->current_line < VIRTUAL_LINES - 1; gb->current_line++) {
|
2018-05-11 09:38:55 +00:00
|
|
|
|
gb->ly_for_comparison = -1;
|
2021-12-10 17:49:52 +00:00
|
|
|
|
if (unlikely(gb->lcd_line_callback)) {
|
|
|
|
|
gb->lcd_line_callback(gb, gb->current_line);
|
|
|
|
|
}
|
2021-12-14 18:27:38 +00:00
|
|
|
|
GB_STAT_update(gb);
|
2018-04-02 22:43:24 +00:00
|
|
|
|
GB_SLEEP(gb, display, 26, 2);
|
2021-12-14 18:27:38 +00:00
|
|
|
|
gb->io_registers[GB_IO_LY] = gb->current_line;
|
2021-06-17 22:20:05 +00:00
|
|
|
|
if (gb->current_line == LINES && !gb->stat_interrupt_line && (gb->io_registers[GB_IO_STAT] & 0x20)) {
|
|
|
|
|
gb->io_registers[GB_IO_IF] |= 2;
|
2018-03-10 13:52:22 +00:00
|
|
|
|
}
|
2018-04-02 22:43:24 +00:00
|
|
|
|
GB_SLEEP(gb, display, 12, 2);
|
2018-02-24 22:48:45 +00:00
|
|
|
|
gb->ly_for_comparison = gb->current_line;
|
2021-06-18 23:14:16 +00:00
|
|
|
|
GB_STAT_update(gb);
|
|
|
|
|
GB_SLEEP(gb, display, 24, 1);
|
|
|
|
|
|
2018-02-24 22:48:45 +00:00
|
|
|
|
if (gb->current_line == LINES) {
|
2018-04-02 22:43:24 +00:00
|
|
|
|
/* Entering VBlank state triggers the OAM interrupt */
|
2017-02-19 00:22:50 +00:00
|
|
|
|
gb->io_registers[GB_IO_STAT] &= ~3;
|
2018-02-24 22:48:45 +00:00
|
|
|
|
gb->io_registers[GB_IO_STAT] |= 1;
|
|
|
|
|
gb->io_registers[GB_IO_IF] |= 1;
|
2021-06-17 22:20:05 +00:00
|
|
|
|
if (!gb->stat_interrupt_line && (gb->io_registers[GB_IO_STAT] & 0x20)) {
|
|
|
|
|
gb->io_registers[GB_IO_IF] |= 2;
|
|
|
|
|
}
|
2018-06-03 23:07:38 +00:00
|
|
|
|
gb->mode_for_interrupt = 1;
|
2018-04-02 22:43:24 +00:00
|
|
|
|
GB_STAT_update(gb);
|
2018-02-24 22:48:45 +00:00
|
|
|
|
|
|
|
|
|
if (gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON) {
|
2020-01-30 23:29:59 +00:00
|
|
|
|
if (GB_is_cgb(gb)) {
|
2021-12-04 13:04:46 +00:00
|
|
|
|
GB_display_vblank(gb);
|
2020-01-30 23:29:59 +00:00
|
|
|
|
gb->frame_skip_state = GB_FRAMESKIP_FIRST_FRAME_SKIPPED;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
if (!GB_is_sgb(gb) || gb->current_lcd_line < LINES) {
|
2020-03-26 18:54:18 +00:00
|
|
|
|
gb->is_odd_frame ^= true;
|
2021-12-04 13:04:46 +00:00
|
|
|
|
GB_display_vblank(gb);
|
2020-01-30 23:29:59 +00:00
|
|
|
|
}
|
|
|
|
|
gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED;
|
2019-06-18 20:16:28 +00:00
|
|
|
|
}
|
2017-02-19 00:22:50 +00:00
|
|
|
|
}
|
2018-02-24 22:48:45 +00:00
|
|
|
|
else {
|
2019-06-18 20:16:28 +00:00
|
|
|
|
if (!GB_is_sgb(gb) || gb->current_lcd_line < LINES) {
|
2020-03-26 18:54:18 +00:00
|
|
|
|
gb->is_odd_frame ^= true;
|
2021-12-04 13:04:46 +00:00
|
|
|
|
GB_display_vblank(gb);
|
2019-06-21 13:58:56 +00:00
|
|
|
|
}
|
2020-05-29 22:25:21 +00:00
|
|
|
|
if (gb->frame_skip_state == GB_FRAMESKIP_FIRST_FRAME_SKIPPED) {
|
|
|
|
|
gb->cgb_repeated_a_frame = true;
|
|
|
|
|
gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
gb->cgb_repeated_a_frame = false;
|
|
|
|
|
}
|
2017-02-19 00:22:50 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-18 23:14:16 +00:00
|
|
|
|
GB_SLEEP(gb, display, 13, LINE_LENGTH - 5);
|
2017-02-19 00:22:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-04-24 21:08:06 +00:00
|
|
|
|
/* TODO: Verified on SGB2 and CGB-E. Actual interrupt timings not tested. */
|
2018-02-24 22:48:45 +00:00
|
|
|
|
/* Lines 153 */
|
2018-05-11 09:38:55 +00:00
|
|
|
|
gb->ly_for_comparison = -1;
|
2018-02-24 22:48:45 +00:00
|
|
|
|
GB_STAT_update(gb);
|
2021-12-14 18:27:38 +00:00
|
|
|
|
GB_SLEEP(gb, display, 19, 2);
|
|
|
|
|
gb->io_registers[GB_IO_LY] = 153;
|
|
|
|
|
GB_SLEEP(gb, display, 14, (gb->model > GB_MODEL_CGB_C)? 2: 4);
|
2017-02-19 00:22:50 +00:00
|
|
|
|
|
2021-12-14 18:27:38 +00:00
|
|
|
|
if (gb->model <= GB_MODEL_CGB_C && !gb->cgb_double_speed) {
|
2018-05-11 09:38:55 +00:00
|
|
|
|
gb->io_registers[GB_IO_LY] = 0;
|
|
|
|
|
}
|
|
|
|
|
gb->ly_for_comparison = 153;
|
2018-02-24 22:48:45 +00:00
|
|
|
|
GB_STAT_update(gb);
|
2018-07-03 18:56:32 +00:00
|
|
|
|
GB_SLEEP(gb, display, 15, (gb->model > GB_MODEL_CGB_C)? 4: 2);
|
2017-02-19 00:22:50 +00:00
|
|
|
|
|
2018-05-11 09:38:55 +00:00
|
|
|
|
gb->io_registers[GB_IO_LY] = 0;
|
2018-07-03 18:56:32 +00:00
|
|
|
|
gb->ly_for_comparison = (gb->model > GB_MODEL_CGB_C)? 153 : -1;
|
2018-02-24 22:48:45 +00:00
|
|
|
|
GB_STAT_update(gb);
|
2018-03-03 13:47:36 +00:00
|
|
|
|
GB_SLEEP(gb, display, 16, 4);
|
2017-09-08 14:25:01 +00:00
|
|
|
|
|
2018-02-24 22:48:45 +00:00
|
|
|
|
gb->ly_for_comparison = 0;
|
|
|
|
|
GB_STAT_update(gb);
|
2018-05-11 09:38:55 +00:00
|
|
|
|
GB_SLEEP(gb, display, 29, 12); /* Writing to LYC during this period on a CGB has side effects */
|
|
|
|
|
GB_SLEEP(gb, display, 17, LINE_LENGTH - 24);
|
|
|
|
|
|
2017-09-08 14:25:01 +00:00
|
|
|
|
|
2018-02-24 22:48:45 +00:00
|
|
|
|
gb->current_line = 0;
|
2020-12-23 21:49:57 +00:00
|
|
|
|
gb->wy_triggered = false;
|
2020-02-23 21:16:45 +00:00
|
|
|
|
|
2019-07-15 20:02:58 +00:00
|
|
|
|
// TODO: not the correct timing
|
2019-07-19 17:19:09 +00:00
|
|
|
|
gb->current_lcd_line = 0;
|
2019-07-15 20:02:58 +00:00
|
|
|
|
if (gb->icd_vreset_callback) {
|
|
|
|
|
gb->icd_vreset_callback(gb);
|
|
|
|
|
}
|
2016-03-30 20:07:55 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2016-10-26 21:14:02 +00:00
|
|
|
|
|
|
|
|
|
void GB_draw_tileset(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette_type, uint8_t palette_index)
|
|
|
|
|
{
|
|
|
|
|
uint32_t none_palette[4];
|
|
|
|
|
uint32_t *palette = NULL;
|
|
|
|
|
|
2018-06-16 10:59:33 +00:00
|
|
|
|
switch (GB_is_cgb(gb)? palette_type : GB_PALETTE_NONE) {
|
2016-10-26 21:14:02 +00:00
|
|
|
|
default:
|
|
|
|
|
case GB_PALETTE_NONE:
|
|
|
|
|
none_palette[0] = gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF);
|
|
|
|
|
none_palette[1] = gb->rgb_encode_callback(gb, 0xAA, 0xAA, 0xAA);
|
|
|
|
|
none_palette[2] = gb->rgb_encode_callback(gb, 0x55, 0x55, 0x55);
|
|
|
|
|
none_palette[3] = gb->rgb_encode_callback(gb, 0, 0, 0 );
|
|
|
|
|
palette = none_palette;
|
|
|
|
|
break;
|
|
|
|
|
case GB_PALETTE_BACKGROUND:
|
2017-04-19 20:26:39 +00:00
|
|
|
|
palette = gb->background_palettes_rgb + (4 * (palette_index & 7));
|
2016-10-26 21:14:02 +00:00
|
|
|
|
break;
|
|
|
|
|
case GB_PALETTE_OAM:
|
2021-12-17 19:16:23 +00:00
|
|
|
|
palette = gb->object_palettes_rgb + (4 * (palette_index & 7));
|
2016-10-26 21:14:02 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (unsigned y = 0; y < 192; y++) {
|
|
|
|
|
for (unsigned x = 0; x < 256; x++) {
|
2018-06-16 10:59:33 +00:00
|
|
|
|
if (x >= 128 && !GB_is_cgb(gb)) {
|
2017-04-19 20:26:39 +00:00
|
|
|
|
*(dest++) = gb->background_palettes_rgb[0];
|
2016-10-26 21:14:02 +00:00
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
uint16_t tile = (x % 128) / 8 + y / 8 * 16;
|
|
|
|
|
uint16_t tile_address = tile * 0x10 + (x >= 128? 0x2000 : 0);
|
|
|
|
|
uint8_t pixel = (((gb->vram[tile_address + (y & 7) * 2 ] >> ((~x)&7)) & 1 ) |
|
|
|
|
|
((gb->vram[tile_address + (y & 7) * 2 + 1] >> ((~x)&7)) & 1) << 1);
|
|
|
|
|
|
|
|
|
|
if (!gb->cgb_mode) {
|
|
|
|
|
if (palette_type == GB_PALETTE_BACKGROUND) {
|
|
|
|
|
pixel = ((gb->io_registers[GB_IO_BGP] >> (pixel << 1)) & 3);
|
|
|
|
|
}
|
|
|
|
|
else if (!gb->cgb_mode) {
|
|
|
|
|
if (palette_type == GB_PALETTE_OAM) {
|
|
|
|
|
pixel = ((gb->io_registers[palette_index == 0? GB_IO_OBP0 : GB_IO_OBP1] >> (pixel << 1)) & 3);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*(dest++) = palette[pixel];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GB_draw_tilemap(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette_type, uint8_t palette_index, GB_map_type_t map_type, GB_tileset_type_t tileset_type)
|
|
|
|
|
{
|
|
|
|
|
uint32_t none_palette[4];
|
|
|
|
|
uint32_t *palette = NULL;
|
|
|
|
|
uint16_t map = 0x1800;
|
|
|
|
|
|
2018-06-16 10:59:33 +00:00
|
|
|
|
switch (GB_is_cgb(gb)? palette_type : GB_PALETTE_NONE) {
|
2016-10-26 21:14:02 +00:00
|
|
|
|
case GB_PALETTE_NONE:
|
|
|
|
|
none_palette[0] = gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF);
|
|
|
|
|
none_palette[1] = gb->rgb_encode_callback(gb, 0xAA, 0xAA, 0xAA);
|
|
|
|
|
none_palette[2] = gb->rgb_encode_callback(gb, 0x55, 0x55, 0x55);
|
|
|
|
|
none_palette[3] = gb->rgb_encode_callback(gb, 0, 0, 0 );
|
|
|
|
|
palette = none_palette;
|
|
|
|
|
break;
|
|
|
|
|
case GB_PALETTE_BACKGROUND:
|
2017-04-19 20:26:39 +00:00
|
|
|
|
palette = gb->background_palettes_rgb + (4 * (palette_index & 7));
|
2016-10-26 21:14:02 +00:00
|
|
|
|
break;
|
|
|
|
|
case GB_PALETTE_OAM:
|
2021-12-17 19:16:23 +00:00
|
|
|
|
palette = gb->object_palettes_rgb + (4 * (palette_index & 7));
|
2016-10-26 21:14:02 +00:00
|
|
|
|
break;
|
|
|
|
|
case GB_PALETTE_AUTO:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (map_type == GB_MAP_9C00 || (map_type == GB_MAP_AUTO && gb->io_registers[GB_IO_LCDC] & 0x08)) {
|
|
|
|
|
map = 0x1c00;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (tileset_type == GB_TILESET_AUTO) {
|
|
|
|
|
tileset_type = (gb->io_registers[GB_IO_LCDC] & 0x10)? GB_TILESET_8800 : GB_TILESET_8000;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (unsigned y = 0; y < 256; y++) {
|
|
|
|
|
for (unsigned x = 0; x < 256; x++) {
|
|
|
|
|
uint8_t tile = gb->vram[map + x/8 + y/8 * 32];
|
|
|
|
|
uint16_t tile_address;
|
|
|
|
|
uint8_t attributes = 0;
|
|
|
|
|
|
|
|
|
|
if (tileset_type == GB_TILESET_8800) {
|
|
|
|
|
tile_address = tile * 0x10;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
tile_address = (int8_t) tile * 0x10 + 0x1000;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (gb->cgb_mode) {
|
|
|
|
|
attributes = gb->vram[map + x/8 + y/8 * 32 + 0x2000];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (attributes & 0x8) {
|
|
|
|
|
tile_address += 0x2000;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint8_t pixel = (((gb->vram[tile_address + (((attributes & 0x40)? ~y : y) & 7) * 2 ] >> (((attributes & 0x20)? x : ~x)&7)) & 1 ) |
|
|
|
|
|
((gb->vram[tile_address + (((attributes & 0x40)? ~y : y) & 7) * 2 + 1] >> (((attributes & 0x20)? x : ~x)&7)) & 1) << 1);
|
|
|
|
|
|
|
|
|
|
if (!gb->cgb_mode && (palette_type == GB_PALETTE_BACKGROUND || palette_type == GB_PALETTE_AUTO)) {
|
|
|
|
|
pixel = ((gb->io_registers[GB_IO_BGP] >> (pixel << 1)) & 3);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (palette) {
|
|
|
|
|
*(dest++) = palette[pixel];
|
|
|
|
|
}
|
|
|
|
|
else {
|
2017-04-19 20:26:39 +00:00
|
|
|
|
*(dest++) = gb->background_palettes_rgb[(attributes & 7) * 4 + pixel];
|
2016-10-26 21:14:02 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-17 19:16:23 +00:00
|
|
|
|
uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *object_height)
|
2016-10-26 21:14:02 +00:00
|
|
|
|
{
|
|
|
|
|
uint8_t count = 0;
|
2021-12-17 19:16:23 +00:00
|
|
|
|
*object_height = (gb->io_registers[GB_IO_LCDC] & 4) ? 16:8;
|
2016-10-26 21:14:02 +00:00
|
|
|
|
uint8_t oam_to_dest_index[40] = {0,};
|
2021-10-24 16:15:28 +00:00
|
|
|
|
for (signed y = 0; y < LINES; y++) {
|
2021-12-17 19:16:23 +00:00
|
|
|
|
object_t *object = (object_t *) &gb->oam;
|
|
|
|
|
uint8_t objects_in_line = 0;
|
|
|
|
|
for (uint8_t i = 0; i < 40; i++, object++) {
|
|
|
|
|
signed object_y = object->y - 16;
|
2016-10-26 21:14:02 +00:00
|
|
|
|
bool obscured = false;
|
2021-12-17 19:16:23 +00:00
|
|
|
|
// Is object not in this line?
|
|
|
|
|
if (object_y > y || object_y + *object_height <= y) continue;
|
|
|
|
|
if (++objects_in_line == 11) obscured = true;
|
2016-10-26 21:14:02 +00:00
|
|
|
|
|
|
|
|
|
GB_oam_info_t *info = NULL;
|
|
|
|
|
if (!oam_to_dest_index[i]) {
|
|
|
|
|
info = dest + count;
|
|
|
|
|
oam_to_dest_index[i] = ++count;
|
2021-12-17 19:16:23 +00:00
|
|
|
|
info->x = object->x;
|
|
|
|
|
info->y = object->y;
|
|
|
|
|
info->tile = *object_height == 16? object->tile & 0xFE : object->tile;
|
|
|
|
|
info->flags = object->flags;
|
2016-10-26 21:14:02 +00:00
|
|
|
|
info->obscured_by_line_limit = false;
|
2021-12-17 19:16:23 +00:00
|
|
|
|
info->oam_addr = 0xFE00 + i * sizeof(*object);
|
2016-10-26 21:14:02 +00:00
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
info = dest + oam_to_dest_index[i] - 1;
|
|
|
|
|
}
|
|
|
|
|
info->obscured_by_line_limit |= obscured;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (unsigned i = 0; i < count; i++) {
|
|
|
|
|
uint16_t vram_address = dest[i].tile * 0x10;
|
|
|
|
|
uint8_t flags = dest[i].flags;
|
|
|
|
|
uint8_t palette = gb->cgb_mode? (flags & 7) : ((flags & 0x10)? 1 : 0);
|
2018-06-16 10:59:33 +00:00
|
|
|
|
if (GB_is_cgb(gb) && (flags & 0x8)) {
|
2017-04-19 20:26:39 +00:00
|
|
|
|
vram_address += 0x2000;
|
|
|
|
|
}
|
2016-10-26 21:14:02 +00:00
|
|
|
|
|
2021-12-17 19:16:23 +00:00
|
|
|
|
for (unsigned y = 0; y < *object_height; y++) {
|
2021-02-22 12:45:30 +00:00
|
|
|
|
unrolled for (unsigned x = 0; x < 8; x++) {
|
2016-10-26 21:14:02 +00:00
|
|
|
|
uint8_t color = (((gb->vram[vram_address ] >> ((~x)&7)) & 1 ) |
|
|
|
|
|
((gb->vram[vram_address + 1] >> ((~x)&7)) & 1) << 1 );
|
|
|
|
|
|
|
|
|
|
if (!gb->cgb_mode) {
|
|
|
|
|
color = (gb->io_registers[palette? GB_IO_OBP1:GB_IO_OBP0] >> (color << 1)) & 3;
|
|
|
|
|
}
|
2021-12-17 19:16:23 +00:00
|
|
|
|
dest[i].image[((flags & 0x20)?7-x:x) + ((flags & 0x40)?*object_height - 1 -y:y) * 8] = gb->object_palettes_rgb[palette * 4 + color];
|
2016-10-26 21:14:02 +00:00
|
|
|
|
}
|
|
|
|
|
vram_address += 2;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return count;
|
2017-02-19 00:22:50 +00:00
|
|
|
|
}
|
2017-08-19 22:34:12 +00:00
|
|
|
|
|
2020-03-26 18:54:18 +00:00
|
|
|
|
|
|
|
|
|
bool GB_is_odd_frame(GB_gameboy_t *gb)
|
2017-08-19 22:34:12 +00:00
|
|
|
|
{
|
2020-03-26 18:54:18 +00:00
|
|
|
|
return gb->is_odd_frame;
|
2017-08-19 22:34:12 +00:00
|
|
|
|
}
|
2021-12-17 19:12:26 +00:00
|
|
|
|
|
|
|
|
|
void GB_set_object_rendering_disabled(GB_gameboy_t *gb, bool disabled)
|
|
|
|
|
{
|
|
|
|
|
gb->objects_disabled = disabled;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GB_set_background_rendering_disabled(GB_gameboy_t *gb, bool disabled)
|
|
|
|
|
{
|
|
|
|
|
gb->background_disabled = disabled;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool GB_is_object_rendering_disabled(GB_gameboy_t *gb)
|
|
|
|
|
{
|
|
|
|
|
return gb->objects_disabled;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool GB_is_background_rendering_disabled(GB_gameboy_t *gb)
|
|
|
|
|
{
|
|
|
|
|
return gb->background_disabled;
|
|
|
|
|
}
|
|
|
|
|
|