1075 lines
25 KiB
C
1075 lines
25 KiB
C
/*
|
|
|
|
This file is part of Emu-Pizza
|
|
|
|
Emu-Pizza is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Emu-Pizza is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Emu-Pizza. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
#include <signal.h>
|
|
#include <string.h>
|
|
#include <strings.h>
|
|
#include <time.h>
|
|
|
|
#include "cycles.h"
|
|
#include "gameboy.h"
|
|
#include "global.h"
|
|
#include "gpu.h"
|
|
#include "interrupt.h"
|
|
#include "mmu.h"
|
|
#include "utils.h"
|
|
#include "sgb.h"
|
|
|
|
/* Gameboy OAM 4 bytes data */
|
|
typedef struct gpu_oam_s
|
|
{
|
|
uint8_t y;
|
|
uint8_t x;
|
|
uint8_t pattern;
|
|
|
|
uint8_t palette_cgb : 3;
|
|
uint8_t vram_bank : 1;
|
|
uint8_t palette : 1;
|
|
uint8_t x_flip : 1;
|
|
uint8_t y_flip : 1;
|
|
uint8_t priority : 1;
|
|
|
|
} gpu_oam_t;
|
|
|
|
/* Gameboy Color additional tile attributes */
|
|
typedef struct gpu_cgb_bg_tile_s
|
|
{
|
|
uint8_t palette : 3;
|
|
uint8_t vram_bank : 1;
|
|
uint8_t spare : 1;
|
|
uint8_t x_flip : 1;
|
|
uint8_t y_flip : 1;
|
|
uint8_t priority : 1;
|
|
|
|
} gpu_cgb_bg_tile_t;
|
|
|
|
/* ordered sprite list */
|
|
typedef struct oam_list_s
|
|
{
|
|
int idx;
|
|
struct oam_list_s *next;
|
|
} oam_list_t;
|
|
|
|
/* pointer to interrupt flags (handy) */
|
|
interrupts_flags_t *gpu_if;
|
|
|
|
/* internal functions prototypes */
|
|
void gpu_draw_sprite_line(gpu_oam_t *oam,
|
|
uint8_t sprites_size,
|
|
uint8_t line);
|
|
void gpu_draw_window_line(int tile_idx, uint8_t frame_x,
|
|
uint8_t frame_y, uint8_t line);
|
|
|
|
/* 2 bit to 8 bit color lookup */
|
|
// TODO: hook these up to the same color logic that that the other GB core uses
|
|
static const uint32_t gpu_color_lookup[] = {0xffffffff, 0xffaaaaaa, 0xff555555, 0xff000000};
|
|
|
|
/* function to call when frame is ready */
|
|
gpu_frame_ready_cb_t gpu_frame_ready_cb;
|
|
|
|
/* global state of GPU */
|
|
gpu_t gpu;
|
|
|
|
void gpu_dump_oam()
|
|
{
|
|
/* make it point to the first OAM object */
|
|
gpu_oam_t *oam = (gpu_oam_t *)mmu_addr(0xFE00);
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < 40; i++)
|
|
{
|
|
if (oam[i].x != 0 && oam[i].y != 0)
|
|
printf("OAM X %d Y %d VRAM %d PATTERN %d\n", oam[i].x, oam[i].y,
|
|
oam[i].vram_bank,
|
|
oam[i].pattern);
|
|
}
|
|
}
|
|
|
|
/* init pointers */
|
|
void gpu_init_pointers()
|
|
{
|
|
/* make gpu field points to the related memory area */
|
|
gpu.lcd_ctrl = mmu_addr(0xFF40);
|
|
gpu.lcd_status = mmu_addr(0xFF41);
|
|
gpu.scroll_y = mmu_addr(0xFF42);
|
|
gpu.scroll_x = mmu_addr(0xFF43);
|
|
gpu.window_y = mmu_addr(0xFF4A);
|
|
gpu.window_x = mmu_addr(0xFF4B);
|
|
gpu.ly = mmu_addr(0xFF44);
|
|
gpu.lyc = mmu_addr(0xFF45);
|
|
gpu_if = mmu_addr(0xFF0F);
|
|
}
|
|
|
|
/* reset */
|
|
void gpu_reset()
|
|
{
|
|
/* init counters */
|
|
gpu.next = 456 << global_cpu_double_speed;
|
|
gpu.frame_counter = 0;
|
|
}
|
|
|
|
/* init GPU states */
|
|
void gpu_init(gpu_frame_ready_cb_t cb)
|
|
{
|
|
/* reset gpu structure */
|
|
bzero(&gpu, sizeof(gpu_t));
|
|
|
|
/* init memory pointers */
|
|
gpu_init_pointers();
|
|
|
|
/* init counters */
|
|
gpu.next = 456 << global_cpu_double_speed;
|
|
gpu.frame_counter = 0;
|
|
|
|
/* step for normal CPU speed */
|
|
gpu.step = 4;
|
|
|
|
/* init palette */
|
|
memcpy(gpu.bg_palette, gpu_color_lookup, sizeof(uint32_t) * 4);
|
|
memcpy(gpu.obj_palette_0, gpu_color_lookup, sizeof(uint32_t) * 4);
|
|
memcpy(gpu.obj_palette_1, gpu_color_lookup, sizeof(uint32_t) * 4);
|
|
|
|
/* set callback */
|
|
gpu_frame_ready_cb = cb;
|
|
}
|
|
|
|
/* turn on/off lcd */
|
|
void gpu_toggle(uint8_t state)
|
|
{
|
|
/* from off to on */
|
|
if (state & 0x80)
|
|
{
|
|
/* LCD turned on */
|
|
gpu.next = cycles.cnt + (456 << global_cpu_double_speed);
|
|
*gpu.ly = 0;
|
|
(*gpu.lcd_status).mode = 0x00;
|
|
(*gpu.lcd_status).ly_coincidence = 0x00;
|
|
}
|
|
else
|
|
{
|
|
/* LCD turned off - reset stuff */
|
|
gpu.next = cycles.cnt - 1; // + (80 << global_cpu_double_speed);
|
|
*gpu.ly = 0;
|
|
(*gpu.lcd_status).mode = 0x00;
|
|
}
|
|
}
|
|
|
|
/* push frame on screen */
|
|
void gpu_draw_frame()
|
|
{
|
|
/* increase frame counter */
|
|
gpu.frame_counter++;
|
|
|
|
/* is it the case to push samples? */
|
|
/*if ((global_emulation_speed == GLOBAL_EMULATION_SPEED_DOUBLE &&
|
|
(gpu.frame_counter & 0x0001) != 0) ||
|
|
(global_emulation_speed == GLOBAL_EMULATION_SPEED_4X &&
|
|
(gpu.frame_counter & 0x0003) != 0))
|
|
return;*/
|
|
|
|
if (global_sgb)
|
|
{
|
|
sgb_take_frame(gpu.frame_buffer);
|
|
}
|
|
|
|
/* call the callback */
|
|
if (gpu_frame_ready_cb)
|
|
(*gpu_frame_ready_cb)();
|
|
|
|
/* reset priority matrix */
|
|
bzero(gpu.priority, 160 * 144);
|
|
bzero(gpu.palette_idx, 160 * 144);
|
|
|
|
return;
|
|
}
|
|
|
|
/* draw a single line */
|
|
void gpu_draw_line(uint8_t line)
|
|
{
|
|
/* avoid mess */
|
|
if (line > 144)
|
|
return;
|
|
|
|
/* is it the case to push samples? */
|
|
/*if ((global_emulation_speed == GLOBAL_EMULATION_SPEED_DOUBLE &&
|
|
(gpu.frame_counter & 0x0001) != 0) ||
|
|
(global_emulation_speed == GLOBAL_EMULATION_SPEED_4X &&
|
|
(gpu.frame_counter & 0x0003) != 0))
|
|
return;*/
|
|
|
|
int i, t, y, px_start, px_drawn;
|
|
uint8_t *tiles_map, tile_subline, palette_idx, x_flip, priority;
|
|
uint16_t tiles_addr, tile_n, tile_idx, tile_line;
|
|
uint16_t tile_y;
|
|
|
|
/* gotta show BG? Answer is always YES in case of Gameboy Color */
|
|
if ((*gpu.lcd_ctrl).bg || global_cgb)
|
|
{
|
|
gpu_cgb_bg_tile_t *tiles_map_cgb = NULL;
|
|
uint8_t *tiles = NULL;
|
|
uint32_t *palette;
|
|
|
|
if (global_cgb)
|
|
{
|
|
/* CGB tile map into VRAM0 */
|
|
tiles_map = mmu_addr_vram0() + ((*gpu.lcd_ctrl).bg_tiles_map ? 0x1C00 : 0x1800);
|
|
|
|
/* additional attribute table is into VRAM1 */
|
|
tiles_map_cgb = mmu_addr_vram1() + ((*gpu.lcd_ctrl).bg_tiles_map ? 0x1C00 : 0x1800);
|
|
}
|
|
else
|
|
{
|
|
/* never flip */
|
|
x_flip = 0;
|
|
|
|
/* get tile map offset */
|
|
tiles_map = mmu_addr((*gpu.lcd_ctrl).bg_tiles_map ? 0x9C00 : 0x9800);
|
|
|
|
if ((*gpu.lcd_ctrl).bg_tiles)
|
|
tiles_addr = 0x8000;
|
|
else
|
|
tiles_addr = 0x9000;
|
|
|
|
/* get absolute address of tiles area */
|
|
tiles = mmu_addr(tiles_addr);
|
|
|
|
/* monochrome GB uses a single BG palette */
|
|
palette = gpu.bg_palette;
|
|
|
|
/* always priority = 0 */
|
|
priority = 0;
|
|
}
|
|
|
|
/* calc tile y */
|
|
tile_y = (*(gpu.scroll_y) + line) & 0xFF;
|
|
|
|
/* calc first tile idx */
|
|
tile_idx = ((tile_y >> 3) * 32) + (*(gpu.scroll_x) / 8);
|
|
|
|
/* tile line because if we reach the end of the line, */
|
|
/* we have to rewind to the first tile of the same line */
|
|
tile_line = ((tile_y >> 3) * 32);
|
|
|
|
/* calc first pixel of frame buffer of the current line */
|
|
uint_fast16_t pos_fb = line * 160;
|
|
uint_fast16_t pos;
|
|
|
|
/* calc tile subline */
|
|
tile_subline = tile_y % 8;
|
|
|
|
/* walk through different tiles */
|
|
for (t = 0; t < 21; t++)
|
|
{
|
|
/* resolv tile data memory area */
|
|
if ((*gpu.lcd_ctrl).bg_tiles == 0)
|
|
tile_n = (int8_t)tiles_map[tile_idx];
|
|
else
|
|
tile_n = (tiles_map[tile_idx] & 0x00FF);
|
|
|
|
/* if color gameboy, resolv which palette is bound */
|
|
if (global_cgb)
|
|
{
|
|
/* extract palette index (0-31) */
|
|
palette_idx = tiles_map_cgb[tile_idx].palette;
|
|
|
|
/* get palette pointer to 4 (16bit) colors */
|
|
palette = &gpu.cgb_palette_bg_rgb888[palette_idx * 4];
|
|
|
|
/* get priority of the tile */
|
|
priority = tiles_map_cgb[tile_idx].priority;
|
|
|
|
if (tiles_map_cgb[tile_idx].vram_bank)
|
|
tiles = mmu_addr_vram1() +
|
|
((*gpu.lcd_ctrl).bg_tiles ? 0x0000 : 0x1000);
|
|
else
|
|
tiles = mmu_addr_vram0() +
|
|
((*gpu.lcd_ctrl).bg_tiles ? 0x0000 : 0x1000);
|
|
|
|
/* calc subline in case of flip_y */
|
|
if (tiles_map_cgb[tile_idx].y_flip)
|
|
tile_subline = 7 - (tile_y % 8);
|
|
else
|
|
tile_subline = tile_y % 8;
|
|
|
|
/* save x_flip */
|
|
x_flip = tiles_map_cgb[tile_idx].x_flip;
|
|
}
|
|
|
|
/* calc tile data pointer */
|
|
int16_t tile_ptr = (tile_n * 16) + (tile_subline * 2);
|
|
|
|
/* pixels are handled in a super shitty way */
|
|
/* bit 0 of the pixel is taken from even position tile bytes */
|
|
/* bit 1 of the pixel is taken from odd position tile bytes */
|
|
|
|
uint8_t pxa[8];
|
|
uint8_t shft;
|
|
uint8_t b1 = *(tiles + tile_ptr);
|
|
uint8_t b2 = *(tiles + tile_ptr + 1);
|
|
|
|
for (y = 0; y < 8; y++)
|
|
{
|
|
if (x_flip)
|
|
shft = (1 << (7 - y));
|
|
else
|
|
shft = (1 << y);
|
|
|
|
pxa[y] = ((b1 & shft) ? 1 : 0) |
|
|
((b2 & shft) ? 2 : 0);
|
|
}
|
|
|
|
/* particular cases for first and last tile */
|
|
/* (could be shown just a part) */
|
|
if (t == 0)
|
|
{
|
|
px_start = (*(gpu.scroll_x) % 8);
|
|
|
|
px_drawn = 8 - px_start;
|
|
|
|
/* set n pixels */
|
|
for (i = 0; i < px_drawn; i++)
|
|
{
|
|
pos = pos_fb + (px_drawn - i - 1);
|
|
|
|
gpu.priority[pos] = priority;
|
|
gpu.palette_idx[pos] = pxa[i];
|
|
gpu.frame_buffer[pos] = palette[pxa[i]];
|
|
}
|
|
}
|
|
else if (t == 20)
|
|
{
|
|
px_drawn = *(gpu.scroll_x) % 8;
|
|
|
|
/* set n pixels */
|
|
for (i = 0; i < px_drawn; i++)
|
|
{
|
|
pos = pos_fb + (px_drawn - i - 1);
|
|
|
|
gpu.priority[pos] = priority;
|
|
gpu.palette_idx[pos] = pxa[i];
|
|
gpu.frame_buffer[pos] = palette[pxa[i + (8 - px_drawn)]];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* set 8 pixels */
|
|
for (i = 0; i < 8; i++)
|
|
{
|
|
pos = pos_fb + (7 - i);
|
|
|
|
gpu.priority[pos] = priority;
|
|
gpu.palette_idx[pos] = pxa[i];
|
|
gpu.frame_buffer[pos] = palette[pxa[i]];
|
|
}
|
|
|
|
px_drawn = 8;
|
|
}
|
|
|
|
/* go to the next tile and rewind in case we reached the 32th */
|
|
tile_idx++;
|
|
|
|
/* don't go to the next line, just rewind */
|
|
if (tile_idx == (tile_line + 32))
|
|
tile_idx = tile_line;
|
|
|
|
/* go to the next block of 8 pixels of the frame buffer */
|
|
pos_fb += px_drawn;
|
|
}
|
|
}
|
|
|
|
/* gotta show sprites? */
|
|
if ((*gpu.lcd_ctrl).sprites)
|
|
{
|
|
/* make it point to the first OAM object */
|
|
gpu_oam_t *oam = (gpu_oam_t *)mmu_addr(0xFE00);
|
|
|
|
/* calc sprite height */
|
|
uint8_t h = ((*gpu.lcd_ctrl).sprites_size + 1) * 8;
|
|
|
|
int sort[40];
|
|
int j = 0;
|
|
|
|
/* prepare sorted list of oams */
|
|
for (i = 0; i < 40; i++)
|
|
sort[i] = -1;
|
|
|
|
for (i = 0; i < 40; i++)
|
|
{
|
|
/* the sprite intersects the current line? */
|
|
if (oam[i].x != 0 && oam[i].y != 0 &&
|
|
oam[i].x < 168 && oam[i].y < 160 &&
|
|
line < (oam[i].y + h - 16) &&
|
|
line >= (oam[i].y - 16))
|
|
{
|
|
/* color GB uses memory position as priority criteria */
|
|
if (global_cgb)
|
|
{
|
|
sort[j++] = i;
|
|
continue;
|
|
}
|
|
|
|
/* find its position on sort array */
|
|
for (j = 0; j < 40; j++)
|
|
{
|
|
if (sort[j] == -1)
|
|
{
|
|
sort[j] = i;
|
|
break;
|
|
}
|
|
|
|
if (global_cgb)
|
|
continue;
|
|
|
|
if ((oam[i].y < oam[sort[j]].y) ||
|
|
((oam[i].y == oam[sort[j]].y) &&
|
|
(oam[i].x < oam[sort[j]].x)))
|
|
{
|
|
int z;
|
|
|
|
for (z = 40; z > j; z--)
|
|
sort[z] = sort[z - 1];
|
|
|
|
sort[j] = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* draw ordered sprite list */
|
|
for (i = 0; i < 40 && sort[i] != -1; i++)
|
|
gpu_draw_sprite_line(&oam[sort[i]],
|
|
(*gpu.lcd_ctrl).sprites_size, line);
|
|
}
|
|
|
|
/* wanna show window? */
|
|
if (global_window && (*gpu.lcd_ctrl).window)
|
|
{
|
|
/* at least the current line is covering the window area? */
|
|
if (line < *(gpu.window_y))
|
|
return;
|
|
|
|
/* TODO - reset this in a better place */
|
|
if (line == *(gpu.window_y))
|
|
gpu.window_skipped_lines = 0;
|
|
|
|
int z, first_z;
|
|
uint8_t tile_pos_x, tile_pos_y;
|
|
|
|
/* gotta draw a window? check if it is inside screen coordinates */
|
|
if (*(gpu.window_y) >= 144 ||
|
|
*(gpu.window_x) >= 160)
|
|
{
|
|
gpu.window_skipped_lines++;
|
|
return;
|
|
}
|
|
|
|
/* calc the first interesting tile */
|
|
first_z = ((line - *(gpu.window_y) -
|
|
gpu.window_skipped_lines) >>
|
|
3)
|
|
<< 5;
|
|
|
|
for (z = first_z; z < first_z + 21; z++)
|
|
{
|
|
/* calc tile coordinates on frame buffer */
|
|
tile_pos_x = ((z & 0x1F) << 3) + *(gpu.window_x) - 7;
|
|
tile_pos_y = ((z >> 5) << 3) + *(gpu.window_y) +
|
|
gpu.window_skipped_lines;
|
|
|
|
/* gone over the current line? */
|
|
if (tile_pos_y > line)
|
|
break;
|
|
|
|
if (tile_pos_y < (line - 7))
|
|
continue;
|
|
|
|
/* gone over the screen visible X? */
|
|
/* being between last column and first one is valid */
|
|
if (tile_pos_x >= 160 && tile_pos_x < 248)
|
|
break;
|
|
|
|
/* gone over the screen visible section? stop it */
|
|
if (tile_pos_y >= 144) // || (tile_pos_x >= 160))
|
|
break;
|
|
|
|
/* put tile on frame buffer */
|
|
gpu_draw_window_line(z, (uint8_t)tile_pos_x,
|
|
(uint8_t)tile_pos_y, line);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* draw a tile in x,y coordinates */
|
|
void gpu_draw_window_line(int tile_idx, uint8_t frame_x,
|
|
uint8_t frame_y, uint8_t line)
|
|
{
|
|
int i, p, y, pos;
|
|
int16_t tile_n;
|
|
uint8_t *tiles_map;
|
|
gpu_cgb_bg_tile_t *tiles_map_cgb = NULL;
|
|
uint8_t *tiles, x_flip;
|
|
uint32_t *palette;
|
|
|
|
if (global_cgb)
|
|
{
|
|
/* CGB tile map into VRAM0 */
|
|
tiles_map = mmu_addr_vram0() + ((*gpu.lcd_ctrl).window_tiles_map ? 0x1C00 : 0x1800);
|
|
|
|
/* additional attribute table is into VRAM1 */
|
|
tiles_map_cgb = mmu_addr_vram1() + ((*gpu.lcd_ctrl).window_tiles_map ? 0x1C00 : 0x1800);
|
|
|
|
/* get palette index */
|
|
uint8_t palette_idx = tiles_map_cgb[tile_idx].palette;
|
|
x_flip = tiles_map_cgb[tile_idx].x_flip;
|
|
|
|
/* get palette pointer to 4 (16bit) colors */
|
|
palette = &gpu.cgb_palette_bg_rgb888[palette_idx * 4];
|
|
|
|
/* attribute table will tell us where is the tile */
|
|
if (tiles_map_cgb[tile_idx].vram_bank)
|
|
tiles = mmu_addr_vram1() +
|
|
((*gpu.lcd_ctrl).bg_tiles ? 0x0000 : 0x1000);
|
|
else
|
|
tiles = mmu_addr_vram0() +
|
|
((*gpu.lcd_ctrl).bg_tiles ? 0x0000 : 0x1000);
|
|
}
|
|
else
|
|
{
|
|
/* get tile map offset */
|
|
tiles_map = mmu_addr((*gpu.lcd_ctrl).window_tiles_map ? 0x9C00 : 0x9800);
|
|
|
|
/* get tile offset */
|
|
if ((*gpu.lcd_ctrl).bg_tiles)
|
|
tiles = mmu_addr(0x8000);
|
|
else
|
|
tiles = mmu_addr(0x9000);
|
|
|
|
/* monochrome GB uses a single BG palette */
|
|
palette = gpu.bg_palette;
|
|
|
|
/* never flip */
|
|
x_flip = 0;
|
|
}
|
|
|
|
/* obtain tile number */
|
|
if ((*gpu.lcd_ctrl).bg_tiles == 0)
|
|
tile_n = (int8_t)tiles_map[tile_idx];
|
|
else
|
|
tile_n = (tiles_map[tile_idx] & 0x00ff);
|
|
|
|
/* calc vertical offset INSIDE the tile */
|
|
p = (line - frame_y) * 2;
|
|
|
|
/* calc frame position buffer for 4 pixels */
|
|
uint32_t pos_fb = (line * 160);
|
|
|
|
/* calc tile pointer */
|
|
int16_t tile_ptr = (tile_n * 16) + p;
|
|
|
|
/* pixels are handled in a super shitty way */
|
|
/* bit 0 of the pixel is taken from even position tile bytes */
|
|
/* bit 1 of the pixel is taken from odd position tile bytes */
|
|
|
|
uint8_t pxa[8];
|
|
uint8_t shft;
|
|
|
|
for (y = 0; y < 8; y++)
|
|
{
|
|
//uint8_t shft = (1 << y);
|
|
|
|
if (x_flip)
|
|
shft = (1 << (7 - y));
|
|
else
|
|
shft = (1 << y);
|
|
|
|
pxa[y] = ((*(tiles + tile_ptr) & shft) ? 1 : 0) |
|
|
((*(tiles + tile_ptr + 1) & shft) ? 2 : 0);
|
|
}
|
|
|
|
/* set 8 pixels (full tile line) */
|
|
for (i = 0; i < 8; i++)
|
|
{
|
|
/* over the last column? */
|
|
uint8_t x = frame_x + (7 - i);
|
|
|
|
if (x > 159)
|
|
continue;
|
|
|
|
/* calc position on frame buffer */
|
|
pos = pos_fb + x;
|
|
|
|
/* can overwrite sprites? depends on pixel priority */
|
|
if (gpu.priority[pos] != 0x02)
|
|
gpu.frame_buffer[pos] = palette[pxa[i]];
|
|
}
|
|
}
|
|
|
|
/* draw a sprite tile in x,y coordinates */
|
|
void gpu_draw_sprite_line(gpu_oam_t *oam, uint8_t sprites_size, uint8_t line)
|
|
{
|
|
int_fast32_t x, y, pos, fb_x, off;
|
|
uint_fast16_t p, i, j;
|
|
uint8_t sprite_bytes;
|
|
int16_t tile_ptr;
|
|
uint32_t *palette;
|
|
uint8_t *tiles;
|
|
|
|
/* REMEMBER! position of sprites is relative to the visible screen area */
|
|
/* ... and y is shifted by 16 pixels, x by 8 */
|
|
y = oam->y - 16;
|
|
x = oam->x - 8;
|
|
|
|
if (x < -7)
|
|
return;
|
|
|
|
/* first pixel on frame buffer position */
|
|
uint32_t tile_pos_fb = (y * 160) + x;
|
|
|
|
/* choose palette */
|
|
if (global_cgb)
|
|
{
|
|
uint8_t palette_idx = oam->palette_cgb;
|
|
|
|
/* get palette pointer to 4 (16bit) colors */
|
|
palette = &gpu.cgb_palette_oam_rgb888[palette_idx * 4];
|
|
|
|
/* tiles are into vram0 */
|
|
if (oam->vram_bank)
|
|
tiles = mmu_addr_vram1();
|
|
else
|
|
tiles = mmu_addr_vram0();
|
|
}
|
|
else
|
|
{
|
|
/* tiles are int fixed 0x8000 address */
|
|
tiles = mmu_addr(0x8000);
|
|
|
|
if (oam->palette)
|
|
palette = gpu.obj_palette_1;
|
|
else
|
|
palette = gpu.obj_palette_0;
|
|
}
|
|
|
|
/* calc sprite in byte */
|
|
sprite_bytes = 16 * (sprites_size + 1);
|
|
|
|
/* walk through 8x8 pixels (2bit per pixel -> 4 pixels per byte) */
|
|
/* 1 line is 8 pixels -> 2 bytes per line */
|
|
for (p = 0; p < sprite_bytes; p += 2)
|
|
{
|
|
uint8_t tile_y = p / 2;
|
|
|
|
if (tile_y + y != line)
|
|
continue;
|
|
|
|
/* calc frame position buffer for 4 pixels */
|
|
uint32_t pos_fb = (tile_pos_fb + (tile_y * 160)) & 0xFFFF; //% 65536;
|
|
|
|
/* calc tile pointer */
|
|
if (oam->y_flip)
|
|
tile_ptr = (oam->pattern * 16) + (sprite_bytes - p - 2);
|
|
else
|
|
tile_ptr = (oam->pattern * 16) + p;
|
|
|
|
/* pixels are handled in a super shitty way */
|
|
/* bit 0 of the pixel is taken from even position tile bytes */
|
|
/* bit 1 of the pixel is taken from odd position tile bytes */
|
|
|
|
uint8_t pxa[8];
|
|
|
|
for (j = 0; j < 8; j++)
|
|
{
|
|
uint8_t shft = (1 << j);
|
|
|
|
pxa[j] = ((*(tiles + tile_ptr) & shft) ? 1 : 0) |
|
|
((*(tiles + tile_ptr + 1) & shft) ? 2 : 0);
|
|
}
|
|
|
|
/* set 8 pixels (full tile line) */
|
|
for (i = 0; i < 8; i++)
|
|
{
|
|
if (oam->x_flip)
|
|
off = i;
|
|
else
|
|
off = 7 - i;
|
|
|
|
/* is it on screen? */
|
|
fb_x = x + off;
|
|
|
|
if (fb_x < 0 || fb_x > 160)
|
|
continue;
|
|
|
|
/* set serial position on frame buffer */
|
|
pos = pos_fb + off;
|
|
|
|
/* is it inside the screen? */
|
|
if (pos >= 144 * 160 || pos < 0)
|
|
continue;
|
|
|
|
if (global_cgb)
|
|
{
|
|
/* sprite color 0 = transparent */
|
|
if (pxa[i] != 0x00)
|
|
{
|
|
/* flag clr = sprites always on top of bg and window */
|
|
if ((*gpu.lcd_ctrl).bg == 0)
|
|
{
|
|
gpu.frame_buffer[pos] = palette[pxa[i]];
|
|
gpu.priority[pos] = 0x02;
|
|
}
|
|
else
|
|
{
|
|
if (((gpu.priority[pos] == 0) &&
|
|
(oam->priority == 0 ||
|
|
(oam->priority == 1 &&
|
|
gpu.palette_idx[pos] == 0x00))) ||
|
|
(gpu.priority[pos] == 1 &&
|
|
gpu.palette_idx[pos] == 0x00))
|
|
{
|
|
gpu.frame_buffer[pos] = palette[pxa[i]];
|
|
gpu.priority[pos] = (oam->priority ? 0x00 : 0x02);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* push on screen pixels not set to zero (transparent) */
|
|
/* and if the priority is set to one, overwrite just */
|
|
/* bg pixels set to zero */
|
|
if ((pxa[i] != 0x00) &&
|
|
(oam->priority == 0 ||
|
|
(oam->priority == 1 &&
|
|
gpu.frame_buffer[pos] == gpu.bg_palette[0x00])))
|
|
{
|
|
gpu.frame_buffer[pos] = palette[pxa[i]];
|
|
gpu.priority[pos] = (oam->priority ? 0x00 : 0x02);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* update GPU internal state given CPU T-states */
|
|
void gpu_step()
|
|
{
|
|
char ly_changed = 0;
|
|
char mode_changed = 0;
|
|
|
|
/* take different action based on current state */
|
|
switch ((*gpu.lcd_status).mode)
|
|
{
|
|
/*
|
|
* during HBLANK (CPU can access VRAM)
|
|
*/
|
|
case 0:
|
|
/* handle HDMA stuff during hblank */
|
|
cycles_hdma();
|
|
|
|
/*
|
|
* if current line == 143 (and it's about to turn 144)
|
|
* enter mode 01 (VBLANK)
|
|
*/
|
|
if (*gpu.ly == 143)
|
|
{
|
|
/* notify mode has changes */
|
|
mode_changed = 1;
|
|
|
|
(*gpu.lcd_status).mode = 0x01;
|
|
|
|
/* mode one lasts 456 cycles */
|
|
gpu.next = cycles.cnt +
|
|
(456 << global_cpu_double_speed);
|
|
|
|
/* DRAW! TODO */
|
|
/* CHECK INTERRUPTS! TODO */
|
|
cycles_vblank();
|
|
|
|
/* set VBLANK interrupt flag */
|
|
gpu_if->lcd_vblank = 1;
|
|
|
|
/* apply gameshark patches */
|
|
//mmu_apply_gs();
|
|
|
|
/* and finally push it on screen! */
|
|
gpu_draw_frame();
|
|
}
|
|
else
|
|
{
|
|
/* notify mode has changed */
|
|
mode_changed = 1;
|
|
|
|
/* enter OAM mode */
|
|
(*gpu.lcd_status).mode = 0x02;
|
|
|
|
/* mode 2 needs 80 cycles */
|
|
gpu.next = cycles.cnt +
|
|
(80 << global_cpu_double_speed);
|
|
}
|
|
|
|
/* notify mode has changed */
|
|
ly_changed = 1;
|
|
|
|
/* inc current line */
|
|
(*gpu.ly)++;
|
|
|
|
// cycles_hblank(*gpu.ly);
|
|
|
|
break;
|
|
|
|
/*
|
|
* during VBLANK (CPU can access VRAM)
|
|
*/
|
|
case 1:
|
|
/* notify ly has changed */
|
|
ly_changed = 1;
|
|
|
|
/* inc current line */
|
|
(*gpu.ly)++;
|
|
|
|
/* reached the bottom? */
|
|
if ((*gpu.ly) > 153)
|
|
{
|
|
/* go back to line 0 */
|
|
(*gpu.ly) = 0;
|
|
|
|
/* switch to OAM mode */
|
|
(*gpu.lcd_status).mode = 0x02;
|
|
|
|
/* */
|
|
gpu.next =
|
|
cycles.cnt + (80 << global_cpu_double_speed);
|
|
}
|
|
else
|
|
gpu.next =
|
|
cycles.cnt + (456 << global_cpu_double_speed);
|
|
|
|
break;
|
|
|
|
/*
|
|
* during OAM (LCD access FE00-FE90, so CPU cannot)
|
|
*/
|
|
case 2:
|
|
/* reset clock counter */
|
|
gpu.next =
|
|
cycles.cnt + (172 << global_cpu_double_speed);
|
|
|
|
/* notify mode has changed */
|
|
mode_changed = 1;
|
|
|
|
/* switch to VRAM mode */
|
|
(*gpu.lcd_status).mode = 0x03;
|
|
|
|
break;
|
|
|
|
/*
|
|
* during VRAM (LCD access both OAM and VRAM, so CPU cannot)
|
|
*/
|
|
case 3:
|
|
/* reset clock counter */
|
|
gpu.next =
|
|
cycles.cnt + (204 << global_cpu_double_speed);
|
|
|
|
/* notify mode has changed */
|
|
mode_changed = 1;
|
|
|
|
/* go back to HBLANK mode */
|
|
(*gpu.lcd_status).mode = 0x00;
|
|
|
|
/* draw line */
|
|
gpu_draw_line(*gpu.ly);
|
|
|
|
/* notify cycles */
|
|
// cycles_hblank(*gpu.ly);
|
|
|
|
//printf("COLLA %d\n", *gpu.ly);
|
|
|
|
break;
|
|
}
|
|
|
|
/* ly changed? is it the case to trig an interrupt? */
|
|
if (ly_changed)
|
|
{
|
|
/* check if we gotta trigger an interrupt */
|
|
if ((*gpu.ly) == (*gpu.lyc))
|
|
{
|
|
/* set lcd status flags indicating there's a concidence */
|
|
(*gpu.lcd_status).ly_coincidence = 1;
|
|
|
|
/* an interrupt is desiderable? */
|
|
if ((*gpu.lcd_status).ir_ly_coincidence)
|
|
gpu_if->lcd_ctrl = 1;
|
|
}
|
|
else
|
|
{
|
|
/* set lcd status flags indicating there's NOT a concidence */
|
|
(*gpu.lcd_status).ly_coincidence = 0;
|
|
}
|
|
}
|
|
|
|
/* mode changed? is is the case to trig an interrupt? */
|
|
if (mode_changed)
|
|
{
|
|
if ((*gpu.lcd_status).mode == 0x00 &&
|
|
(*gpu.lcd_status).ir_mode_00)
|
|
gpu_if->lcd_ctrl = 1;
|
|
else if ((*gpu.lcd_status).mode == 0x01 &&
|
|
(*gpu.lcd_status).ir_mode_01)
|
|
gpu_if->lcd_ctrl = 1;
|
|
else if ((*gpu.lcd_status).mode == 0x02 &&
|
|
(*gpu.lcd_status).ir_mode_10)
|
|
gpu_if->lcd_ctrl = 1;
|
|
}
|
|
}
|
|
|
|
uint8_t gpu_read_reg(uint16_t a)
|
|
{
|
|
switch (a)
|
|
{
|
|
case 0xFF68:
|
|
|
|
return (gpu.cgb_palette_bg_autoinc << 7 | gpu.cgb_palette_bg_idx);
|
|
|
|
case 0xFF69:
|
|
|
|
if ((gpu.cgb_palette_bg_idx & 0x01) == 0x00)
|
|
return gpu.cgb_palette_bg[gpu.cgb_palette_bg_idx / 2] &
|
|
0x00ff;
|
|
else
|
|
return (gpu.cgb_palette_bg[gpu.cgb_palette_bg_idx / 2] &
|
|
0xff00) >>
|
|
8;
|
|
|
|
case 0xFF6A:
|
|
|
|
return (gpu.cgb_palette_oam_autoinc << 7 | gpu.cgb_palette_oam_idx);
|
|
|
|
case 0xFF6B:
|
|
|
|
if ((gpu.cgb_palette_oam_idx & 0x01) == 0x00)
|
|
return gpu.cgb_palette_oam[gpu.cgb_palette_oam_idx / 2] &
|
|
0x00ff;
|
|
else
|
|
return (gpu.cgb_palette_oam[gpu.cgb_palette_oam_idx / 2] &
|
|
0xff00) >>
|
|
8;
|
|
}
|
|
|
|
return 0x00;
|
|
}
|
|
|
|
static uint32_t makecol(uint16_t c)
|
|
{
|
|
// TODO: hook this up to the same color logic that the other cores use
|
|
return c >> 7 & 0xf8 | c >> 12 & 0x07
|
|
| c << 6 & 0xf800 | c << 1 & 0x0700
|
|
| c << 19 & 0xf80000 | c << 14 & 0x070000
|
|
| 0xff000000;
|
|
}
|
|
|
|
void gpu_write_reg(uint16_t a, uint8_t v)
|
|
{
|
|
int i;
|
|
switch (a)
|
|
{
|
|
case 0xFF47:
|
|
gpu.bg_palette[0] = gpu_color_lookup[v & 0x03];
|
|
gpu.bg_palette[1] = gpu_color_lookup[(v & 0x0c) >> 2];
|
|
gpu.bg_palette[2] = gpu_color_lookup[(v & 0x30) >> 4];
|
|
gpu.bg_palette[3] = gpu_color_lookup[(v & 0xc0) >> 6];
|
|
break;
|
|
|
|
case 0xFF48:
|
|
gpu.obj_palette_0[0] = gpu_color_lookup[v & 0x03];
|
|
gpu.obj_palette_0[1] = gpu_color_lookup[(v & 0x0c) >> 2];
|
|
gpu.obj_palette_0[2] = gpu_color_lookup[(v & 0x30) >> 4];
|
|
gpu.obj_palette_0[3] = gpu_color_lookup[(v & 0xc0) >> 6];
|
|
break;
|
|
|
|
case 0xFF49:
|
|
gpu.obj_palette_1[0] = gpu_color_lookup[v & 0x03];
|
|
gpu.obj_palette_1[1] = gpu_color_lookup[(v & 0x0c) >> 2];
|
|
gpu.obj_palette_1[2] = gpu_color_lookup[(v & 0x30) >> 4];
|
|
gpu.obj_palette_1[3] = gpu_color_lookup[(v & 0xc0) >> 6];
|
|
break;
|
|
|
|
case 0xFF68:
|
|
gpu.cgb_palette_bg_idx = (v & 0x3f);
|
|
gpu.cgb_palette_bg_autoinc = ((v & 0x80) == 0x80);
|
|
break;
|
|
|
|
case 0xFF69:
|
|
i = gpu.cgb_palette_bg_idx / 2;
|
|
|
|
if ((gpu.cgb_palette_bg_idx & 0x01) == 0x00)
|
|
{
|
|
gpu.cgb_palette_bg[i] &= 0xff00;
|
|
gpu.cgb_palette_bg[i] |= v;
|
|
}
|
|
else
|
|
{
|
|
gpu.cgb_palette_bg[i] &= 0x00ff;
|
|
gpu.cgb_palette_bg[i] |= (v << 8);
|
|
}
|
|
|
|
gpu.cgb_palette_bg_rgb888[i] = makecol(gpu.cgb_palette_bg[i]);
|
|
|
|
if (gpu.cgb_palette_bg_autoinc)
|
|
gpu.cgb_palette_bg_idx = (gpu.cgb_palette_bg_idx + 1) & 0x3f;
|
|
|
|
break;
|
|
|
|
case 0xFF6A:
|
|
gpu.cgb_palette_oam_idx = v & 0x3f;
|
|
gpu.cgb_palette_oam_autoinc = ((v & 0x80) == 0x80);
|
|
break;
|
|
|
|
case 0xFF6B:
|
|
i = gpu.cgb_palette_oam_idx / 2;
|
|
|
|
if ((gpu.cgb_palette_oam_idx & 0x01) == 0x00)
|
|
{
|
|
gpu.cgb_palette_oam[i] &= 0xff00;
|
|
gpu.cgb_palette_oam[i] |= v;
|
|
}
|
|
else
|
|
{
|
|
gpu.cgb_palette_oam[i] &= 0x00ff;
|
|
gpu.cgb_palette_oam[i] |= (v << 8);
|
|
}
|
|
|
|
gpu.cgb_palette_oam_rgb888[i] = makecol(gpu.cgb_palette_oam[i]);
|
|
|
|
if (gpu.cgb_palette_oam_autoinc)
|
|
gpu.cgb_palette_oam_idx = (gpu.cgb_palette_oam_idx + 1) & 0x3f;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
void gpu_set_speed(char speed)
|
|
{
|
|
if (speed == 1)
|
|
gpu.step = 2;
|
|
else
|
|
gpu.step = 4;
|
|
}
|