Files
birth1mark--picoplay-ps4/source/graphics.cpp
T

1987 lines
47 KiB
C++

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <algorithm>
#include <vector>
#include <tuple>
using namespace std;
#include "graphics.h"
#include "hostVmShared.h"
#include "nibblehelpers.h"
#include "mathhelpers.h"
#include "fontdata.h"
#include "stringToDataHelpers.h"
#include "logger.h"
#include <fix32.h>
using namespace z8;
//call initialize to make sure defaults are correct
Graphics::Graphics(std::string fontdata, PicoRam* memory) {
_memory = memory;
copy_string_to_sprite_memory(fontSpriteData, fontdata);
//set default clip
clip();
pal();
color();
}
// Upper memory mapping: 0x80->0x8000, 0xA0->0xA000, 0xC0->0xC000, 0xE0->0xE000
// Mapping values are in 256-byte increments, userData starts at 0x8000
// offset = (mapping << 8) - 0x8000 = (mapping - 0x80) << 8
uint8_t* Graphics::GetP8FrameBuffer(){
uint8_t mapping = _memory->hwState.screenDataMemMapping;
if (mapping == 0) {
return _memory->spriteSheetData;
} else if (mapping == 0x60) {
return _memory->screenBuffer;
} else if (mapping >= 0x80) {
int offset = (mapping - 0x80) << 8;
return _memory->userData + offset;
}
return _memory->screenBuffer;
}
uint8_t* Graphics::GetP8SpriteSheetBuffer(){
uint8_t mapping = _memory->hwState.spriteSheetMemMapping;
if (mapping == 0) {
return _memory->spriteSheetData;
} else if (mapping == 0x60) {
return _memory->screenBuffer;
} else if (mapping >= 0x80) {
int offset = (mapping - 0x80) << 8;
return _memory->userData + offset;
}
return _memory->spriteSheetData;
}
uint8_t* Graphics::GetScreenPaletteMap(){
return _memory->drawState.screenPaletteMap;
}
//start helper methods
//based on tac08 implementation of blitter()
void Graphics::copySpriteToScreen(
uint8_t* spritebuffer,
int scr_x,
int scr_y,
int spr_x,
int spr_y,
int spr_w,
int spr_h,
bool flip_x,
bool flip_y)
{
//note: no clipping yet
int scr_w = spr_w;
int scr_h = spr_h;
auto &drawState = _memory->drawState;
auto &hwState = _memory->hwState;
uint8_t *screenBuffer = GetP8FrameBuffer();
const uint8_t writeMask = hwState.colorBitmask & 15;
const uint8_t readMask = hwState.colorBitmask >> 4;
scr_x -= drawState.camera_x;
scr_y -= drawState.camera_y;
// left clip
if (scr_x < drawState.clip_xb) {
int nclip = drawState.clip_xb - scr_x;
scr_x = drawState.clip_xb;
scr_w -= nclip;
if (!flip_x) {
spr_x += nclip;
} else {
spr_w -= nclip;
}
}
// right clip
if (scr_x + scr_w > drawState.clip_xe) {
int nclip = (scr_x + scr_w) - drawState.clip_xe;
scr_w -= nclip;
}
// top clip
if (scr_y < drawState.clip_yb) {
int nclip = drawState.clip_yb - scr_y;
scr_y = drawState.clip_yb;
scr_h -= nclip;
if (!flip_y) {
spr_y += nclip;
} else {
spr_h -= nclip;
}
}
// bottom clip
if (scr_y + scr_h > drawState.clip_ye) {
int nclip = (scr_y + scr_h) - drawState.clip_ye;
scr_h -= nclip;
}
uint8_t lastScreenBuffByte = 0;
int lastScreenBuffIdx = -1;
bool startWithHalf = false;
//starting with odd pixel
if (BITMASK(0) & spr_x) {
startWithHalf = true;
}
//since we get 2 pixels at a time, don't go through this whole loop every pixel
for (int y = 0; y < scr_h; y++) {
int x = 0;
while (x < scr_w) {
int abs_spr_x = spr_x + (flip_x ? spr_w - (x + 1) : x);
int abs_spr_y = spr_y + (flip_y ? spr_h - (y + 1) : y);
if (!IS_VALID_SPR_IDX(abs_spr_x, abs_spr_y)){
++x;
continue;
}
uint8_t bothPix = spritebuffer[COMBINED_IDX(abs_spr_x, abs_spr_y)];
//uint8_t c = (BITMASK(0) & abs_spr_x)== 0
// ? bothPix & 0x0f //just first 4 bits
// : bothPix >> 4; //just last 4 bits
uint8_t lc = bothPix & 0x0f;
uint8_t rc = (bothPix >> 4) & 0x0f;
const int finaly = scr_y + y;
int finalx = scr_x + (flip_x ? x + 1 : x);
if (x > 0 || !startWithHalf){
lc &= 0x0f; // Ensure color is only 4 bits before palette lookup
if (!(drawState.drawPaletteMap[lc] >> 4)){
lc = drawState.drawPaletteMap[lc] & 0x0f;
int screenPixelIdx = COMBINED_IDX(finalx, finaly);
if (lastScreenBuffIdx != screenPixelIdx) {
lastScreenBuffByte = screenBuffer[screenPixelIdx];
lastScreenBuffIdx = screenPixelIdx;
}
uint8_t source = (BITMASK(0) & finalx) == 0
? lastScreenBuffByte & 0x0f //just first 4 bits
: lastScreenBuffByte >> 4;
lc = (source & ~writeMask) | (lc & writeMask & readMask);
setPixelNibble(finalx, finaly, lc, screenBuffer);
}
++x;
if (flip_x){
--finalx;
}
else {
++finalx;
}
}
if (x < scr_w) {
rc &= 0x0f; // Ensure color is only 4 bits before palette lookup
if (!(drawState.drawPaletteMap[rc] >> 4)){
rc = drawState.drawPaletteMap[rc] & 0x0f;
int screenPixelIdx = COMBINED_IDX(finalx, finaly);
if (lastScreenBuffIdx != screenPixelIdx) {
lastScreenBuffByte = screenBuffer[screenPixelIdx];
lastScreenBuffIdx = screenPixelIdx;
}
uint8_t source = (BITMASK(0) & finalx) == 0
? lastScreenBuffByte & 0x0f //just first 4 bits
: lastScreenBuffByte >> 4;
rc = (source & ~writeMask) | (rc & writeMask & readMask);
setPixelNibble(finalx, finaly, rc, screenBuffer);
}
//we did two pixels so do an extra increment
++x;
}
}
}
}
//based on tac08 implementation of stretch_blitter()
//uses ints so we can shift bits and do integer division instead of floating point
void Graphics::copyStretchSpriteToScreen(
uint8_t* spritebuffer,
int spr_x,
int spr_y,
int spr_w,
int spr_h,
int scr_x,
int scr_y,
int scr_w,
int scr_h,
bool flip_x,
bool flip_y,
//skipStretchPx is currently only used for drawing stripey mode text,
//so it is only used when drawing in non-flipped mode
bool skipStretchPx)
{
if (scr_w == 0 || scr_h == 0)
return;
if (spr_h == scr_h && spr_w == scr_w && !flip_x) {
// use faster non stretch blitter if sprite is not stretched
//(or flipped horizontally - but skipping for that is just a hacky fix)
copySpriteToScreen(spritebuffer, scr_x, scr_y, spr_x, spr_y, scr_w, scr_h, flip_x, flip_y);
return;
}
auto &drawState = _memory->drawState;
auto &hwState = _memory->hwState;
uint8_t *screenBuffer = GetP8FrameBuffer();
const uint8_t writeMask = hwState.colorBitmask & 15;
const uint8_t readMask = hwState.colorBitmask >> 4;
scr_x -= drawState.camera_x;
scr_y -= drawState.camera_y;
if (scr_w < 0){
flip_x = !flip_x;
scr_w = -scr_w;
scr_x -= scr_w;
}
if (scr_h < 0) {
flip_y = !flip_y;
scr_h = -scr_h;
scr_y -= scr_h;
}
//shift bits to avoid floating point math
spr_x = spr_x << 16;
spr_y = spr_y << 16;
spr_w = spr_w << 16;
spr_h = spr_h << 16;
int dx = spr_w / scr_w;
int dy = spr_h / scr_h;
// left clip
if (scr_x < drawState.clip_xb) {
int nclip = drawState.clip_xb - scr_x;
scr_x = drawState.clip_xb;
scr_w -= nclip;
if (!flip_x) {
spr_x += nclip * dx;
} else {
spr_w -= nclip * dx;
}
}
// right clip
if (scr_x + scr_w > drawState.clip_xe) {
int nclip = (scr_x + scr_w) - drawState.clip_xe;
scr_w -= nclip;
}
// top clip
if (scr_y < drawState.clip_yb) {
int nclip = drawState.clip_yb - scr_y;
scr_y = drawState.clip_yb;
scr_h -= nclip;
if (!flip_y) {
spr_y += nclip * dy;
} else {
spr_h -= nclip * dy;
}
}
// bottom clip
if (scr_y + scr_h > drawState.clip_ye) {
int nclip = (scr_y + scr_h) - drawState.clip_ye;
scr_h -= nclip;
}
if (flip_y) {
spr_y += spr_h - 1 * dy;
dy = -dy;
}
int prevSprX = -1;
int prevSprY = -1;
//ugly duplication but see if inlining helps
if (hwState.colorBitmask == 0xff){
for (int y = 0; y < scr_h; y++) {
int sprY = ((spr_y + y * dy) >> 16);
if (sprY > 127) {
continue;
}
uint8_t* spr = spritebuffer + (sprY * 64);
if (skipStretchPx && prevSprY == sprY){
continue;
}
prevSprY = sprY;
if (!flip_x) {
for (int x = 0; x < scr_w; x++) {
int shiftedPixIndex = (spr_x + x * dx) >> 16;
if (shiftedPixIndex > 127) {
continue;
}
if (skipStretchPx && prevSprX == shiftedPixIndex){
continue;
}
prevSprX = shiftedPixIndex;
int preShiftedCombinedPixIndex = (shiftedPixIndex / 2);
uint8_t bothPix = spr[preShiftedCombinedPixIndex];
uint8_t c = shiftedPixIndex % 2 == 0
? bothPix & 0x0f //just first 4 bits
: (bothPix >> 4) & 0x0f; //just last 4 bits
c &= 0x0f; // Ensure color is only 4 bits before palette lookup
if (_memory->drawState.drawPaletteMap[c] >> 4){
continue;
}
c = drawState.drawPaletteMap[c] & 0x0f;
setPixelNibble(scr_x + x, scr_y + y, c, screenBuffer);
}
} else {
for (int x = 0; x < scr_w; x++) {
int pixIndex = (spr_x + spr_w - (x + 1) * dx);
int shiftedPixIndex = pixIndex >> 16;
if (shiftedPixIndex > 127) {
continue;
}
int combinedPixIdx = shiftedPixIndex / 2;
uint8_t bothPix = spr[combinedPixIdx];
uint8_t c = (pixIndex >> 16) % 2 == 0
? bothPix & 0x0f //just first 4 bits
: bothPix >> 4; //just last 4 bits
if (_memory->drawState.drawPaletteMap[c] >> 4){
continue;
}
c = drawState.drawPaletteMap[c] & 0x0f;
setPixelNibble(scr_x + x, scr_y + y, c, screenBuffer);
}
}
}
}
else {
for (int y = 0; y < scr_h; y++) {
int sprY = ((spr_y + y * dy) >> 16);
if (sprY > 127) {
continue;
}
uint8_t* spr = spritebuffer + (sprY) * 64;
if (!flip_x) {
for (int x = 0; x < scr_w; x++) {
int shiftedPixIndex = (spr_x + x * dx) >> 16;
if (shiftedPixIndex > 127) {
continue;
}
int combinedPixIdx = (shiftedPixIndex / 2);
uint8_t bothPix = spr[combinedPixIdx];
uint8_t c = shiftedPixIndex % 2 == 0
? bothPix & 0x0f //just first 4 bits
: (bothPix >> 4) & 0x0f; //just last 4 bits
c &= 0x0f; // Ensure color is only 4 bits before palette lookup
if (_memory->drawState.drawPaletteMap[c] >> 4){
continue;
}
c = drawState.drawPaletteMap[c] & 0x0f;
const int finalx = scr_x + x;
const int finaly = scr_y + y;
uint8_t source = (BITMASK(0) & finalx) == 0
? screenBuffer[COMBINED_IDX(finalx, finaly)] & 0x0f //just first 4 bits
: screenBuffer[COMBINED_IDX(finalx, finaly)] >> 4;
c = (source & ~writeMask) | (c & writeMask & readMask);
setPixelNibble(finalx, finaly, c, screenBuffer);
}
} else {
for (int x = 0; x < scr_w; x++) {
int pixIndex = (spr_x + spr_w - (x + 1) * dx);
int shiftedPixIndex = pixIndex >> 16;
if (shiftedPixIndex > 127) {
continue;
}
int combinedPixIdx = (shiftedPixIndex / 2);
uint8_t bothPix = spr[combinedPixIdx];
uint8_t c = (pixIndex >> 16) % 2 == 0
? bothPix & 0x0f //just first 4 bits
: bothPix >> 4; //just last 4 bits
if (_memory->drawState.drawPaletteMap[c] >> 4){
continue;
}
c = drawState.drawPaletteMap[c] & 0x0f;
const int finalx = scr_x + x;
const int finaly = scr_y + y;
uint8_t source = (BITMASK(0) & finalx) == 0
? screenBuffer[COMBINED_IDX(finalx, finaly)] & 0x0f //just first 4 bits
: screenBuffer[COMBINED_IDX(finalx, finaly)] >> 4;
c = (source & ~writeMask) | (c & writeMask & readMask);
setPixelNibble(finalx, finaly, c, screenBuffer);
}
}
}
}
}
void Graphics::swap(int *x, int *y) {
int temp;
temp = *x;
*x = *y;
*y = temp;
}
void Graphics::applyCameraToPoint(int *x, int *y) {
*x -= _memory->drawState.camera_x;
*y -= _memory->drawState.camera_y;
}
void Graphics::sortPointsLtoR(int *x1, int *y1, int *x2, int *y2){
if (*x1 > *x2) {
swap(x1, x2);
swap(y1, y2);
}
}
void Graphics::sortCoordsForRect(int *x1, int *y1, int *x2, int *y2){
if (*x1 > *x2) {
swap(x1, x2);
}
if (*y1 > *y2) {
swap(y1, y2);
}
}
bool Graphics::isOnScreen(int x, int y) {
return
x >= 0 &&
x <= 127 &&
y >= 0 &&
y <= 127;
}
bool Graphics::isColorTransparent(uint8_t color) {
color = color & 0x0f;
return (_memory->drawState.drawPaletteMap[color] >> 4) > 0; //upper bits indicate transparency
}
uint8_t Graphics::getDrawPalMappedColor(uint8_t color) {
color = color & 0x0f;
return _memory->drawState.drawPaletteMap[color] & 0x0f; //bits 0-3 are color
}
uint8_t Graphics::getScreenPalMappedColor(uint8_t color) {
color = color & 0x0f;
return _memory->drawState.screenPaletteMap[color] & 0x8f;
}
bool Graphics::isWithinClip(int x, int y) {
return
x >= _memory->drawState.clip_xb &&
x < _memory->drawState.clip_xe &&
y >= _memory->drawState.clip_yb &&
y < _memory->drawState.clip_ye;
}
bool Graphics::isXWithinClip(int x) {
return
x >= _memory->drawState.clip_xb &&
x < _memory->drawState.clip_xe;
}
bool Graphics::isYWithinClip(int y) {
return
y >= _memory->drawState.clip_yb &&
y < _memory->drawState.clip_ye;
}
int clampCoordToScreenDims(int val) {
return clamp(val, 0, 128);
}
int Graphics::clampXCoordToClip(int x) {
return clamp(
x,
(int)_memory->drawState.clip_xb,
(int)_memory->drawState.clip_xe - 1);
}
int Graphics::clampYCoordToClip(int y) {
return clamp(
y,
(int)_memory->drawState.clip_yb,
(int)_memory->drawState.clip_ye - 1);
}
void Graphics::_safeSetPixelFromPen(int x, int y) {
if (isWithinClip(x, y)){
_setPixelFromPen(x, y);
}
}
void Graphics::_setPixelFromSprite(int x, int y, uint8_t col) {
x = x & 127;
y = y & 127;
auto &drawState = _memory->drawState;
auto &hwState = _memory->hwState;
uint8_t *screenBuffer = GetP8FrameBuffer();
//col = getDrawPalMappedColor(col);
col = drawState.drawPaletteMap[col & 0x0f] & 0x0f;
if (hwState.colorBitmask != 0xff) {
//from pico 8 wiki:
//dst_color = (dst_color & ~write_mask) | (src_color & write_mask & read_mask)
uint8_t writeMask = hwState.colorBitmask & 15;
uint8_t readMask = hwState.colorBitmask >> 4;
//uint8_t source = pget(x, y);
uint8_t source = (BITMASK(0) & x) == 0
? screenBuffer[COMBINED_IDX(x, y)] & 0x0f //just first 4 bits
: screenBuffer[COMBINED_IDX(x, y)] >> 4;
col = (source & ~writeMask) | (col & writeMask & readMask);
}
setPixelNibble(x, y, col, screenBuffer);
}
void Graphics::_setPixelFromPen(int x, int y) {
x = x & 127;
y = y & 127;
auto &drawState = _memory->drawState;
auto &hwState = _memory->hwState;
uint8_t *screenBuffer = GetP8FrameBuffer();
uint8_t col = drawState.color;
uint8_t col0 = col & 0x0f;
uint8_t col1 = (col & 0xf0) >> 4;
uint8_t finalC = col0;
//uint8_t gridX = x % 4;
//uint8_t gridY = y % 4;
//uint8_t bitPlace = gridY * 4 + gridX;
uint8_t bitPlace = 15 - ((x & 3) + 4 * (y & 3));
uint16_t fillp = ((uint16_t)drawState.fillPattern[1] << 8) + drawState.fillPattern[0];
bool altColor = (fillp >> bitPlace) & 0x1;
if (altColor) {
if (drawState.fillPatternTransparencyBit & 1){
return;
}
finalC = col1;
}
//finalC = getDrawPalMappedColor(finalC);
finalC = drawState.drawPaletteMap[finalC & 0x0f] & 0x0f;
//from pico 8 wiki:
//dst_color = (dst_color & ~write_mask) | (src_color & write_mask & read_mask)
if (hwState.colorBitmask != 0xff){
uint8_t writeMask = hwState.colorBitmask & 15;
uint8_t readMask = hwState.colorBitmask >> 4;
//camera should already be applied, and x and y should be safe coords by now, so don't use pget
//uint8_t source = pget(x, y);
uint8_t source = (BITMASK(0) & x) == 0
? screenBuffer[COMBINED_IDX(x, y)] & 0x0f //just first 4 bits
: screenBuffer[COMBINED_IDX(x, y)] >> 4;
finalC = (source & ~writeMask) | (finalC & writeMask & readMask);
}
setPixelNibble(x, y, finalC, screenBuffer);
}
void Graphics::cls() {
this->cls(0);
}
void Graphics::cls(uint8_t color) {
color = color & 15;
uint8_t val = color << 4 | color;
memset(GetP8FrameBuffer(), val, sizeof(_memory->screenBuffer));
_memory->drawState.text_x = 0;
_memory->drawState.text_y = 0;
_memory->drawState.clip_xb = 0;
_memory->drawState.clip_yb = 0;
_memory->drawState.clip_xe = 128;
_memory->drawState.clip_ye = 128;
}
void Graphics::pset(int x, int y){
this->pset(x, y, _memory->drawState.color);
}
void Graphics::pset(int x, int y, uint8_t col){
color(col);
applyCameraToPoint(&x, &y);
if (isWithinClip(x, y)){
_setPixelFromPen(x, y);
}
}
uint8_t Graphics::pget(int x, int y){
applyCameraToPoint(&x, &y);
if (isOnScreen(x, y)){
return getPixelNibble(x, y, GetP8FrameBuffer());
}
return 0;
}
uint8_t Graphics::color(){
return color(6);
}
uint8_t Graphics::color(uint8_t col){
uint8_t prev = _memory->drawState.color;
_memory->drawState.color = col;
return prev;
}
void Graphics::line () {
//just invalidate line state
_memory->drawState.line_x = 0;
_memory->drawState.line_y = 0;
_memory->drawState.lineInvalid = 1;
}
void Graphics::line (uint8_t col){
color(col);
this->line();
}
void Graphics::line (int x1, int y1){
if (this->_memory->drawState.lineInvalid == false){
this->line(_memory->drawState.line_x, _memory->drawState.line_y, x1, y1, _memory->drawState.color);
}
}
void Graphics::line (int x1, int y1, uint8_t col){
if (_memory->drawState.lineInvalid == false){
this->line(_memory->drawState.line_x, _memory->drawState.line_y, x1, y1, col);
}
}
void Graphics::line (int x1, int y1, int x2, int y2){
this->line(x1, y1, x2, y2, _memory->drawState.color);
}
void Graphics::_private_h_line(int x1, int x2, int y){
auto &drawState = _memory->drawState;
auto &hwState = _memory->hwState;
uint8_t * screenBuffer = GetP8FrameBuffer();
if (!(y >= drawState.clip_yb && y < drawState.clip_ye)) {
return;
}
if ((x1 < drawState.clip_xb && x2 < drawState.clip_xb) ||
(x1 > drawState.clip_xe && x2 > drawState.clip_xe)) {
return;
}
int maxx = clamp(
std::max(x1, x2),
(int)drawState.clip_xb,
(int)drawState.clip_xe - 1);
int minx = clamp(
std::min(x1, x2),
(int)drawState.clip_xb,
(int)drawState.clip_xe - 1);
bool canmemset = hwState.colorBitmask == 0xff &&
drawState.fillPattern[0] == 0 &&
drawState.fillPattern[1] == 0 &&
maxx - minx > 1;
if (canmemset) {
//zepto 8 adapted otimized line draw with memset
uint8_t *p = screenBuffer + (y*64);
uint8_t color = getDrawPalMappedColor(drawState.color);
if (minx & 1)
{
p[minx / 2] = (p[minx / 2] & 0x0f) | (color << 4);
++minx;
}
if ((maxx & 1) == 0)
{
p[maxx / 2] = (p[maxx / 2] & 0xf0) | color;
--maxx;
}
memset(p + minx / 2, color * 0x11, (maxx - minx + 1) / 2);
}
else {
for (int x = minx; x <= maxx; x++){
_setPixelFromPen(x, y);
}
}
}
void Graphics::_private_v_line (int y1, int y2, int x){
auto &drawState = _memory->drawState;
auto &hwState = _memory->hwState;
uint8_t * screenBuffer = GetP8FrameBuffer();
if (!(x >= drawState.clip_xb && x < drawState.clip_xe)) {
return;
}
if ((y1 < drawState.clip_yb && y2 < drawState.clip_yb) ||
(y1 > drawState.clip_ye && y2 > drawState.clip_ye)) {
return;
}
int maxy = clamp(
std::max(y1, y2),
(int)drawState.clip_yb,
(int)drawState.clip_ye - 1);
int miny = clamp(
std::min(y1, y2),
(int)drawState.clip_yb,
(int)drawState.clip_ye - 1);
bool skipPen = hwState.colorBitmask == 0xff &&
drawState.fillPattern[0] == 0 &&
drawState.fillPattern[1] == 0;
if (skipPen) {
uint8_t color = getDrawPalMappedColor(drawState.color);
uint8_t mask = (x & 1) ? 0x0f : 0xf0;
uint8_t nibble = (x & 1) ? color << 4 : color;
for (int16_t y = miny; y <= maxy; ++y)
{
int pixIdx = COMBINED_IDX(x, y);
auto &data = screenBuffer[pixIdx];
data = (data & mask) | nibble;
}
}
else {
for (int y = miny; y <= maxy; y++){
_setPixelFromPen(x, y);
}
}
}
void Graphics::line(int x0, int y0, int x1, int y1, uint8_t col) {
_memory->drawState.line_x = x1;
_memory->drawState.line_y = y1;
_memory->drawState.lineInvalid = 0;
applyCameraToPoint(&x0, &y0);
applyCameraToPoint(&x1, &y1);
color(col);
//vertical line
if (x0 == x1) {
_private_v_line(y0, y1, x0);
}
else if (y0 == y1) {
_private_h_line(x0, x1, y0);
}
else {
//tac08 line impl for diagonals (this should work for horizontal and vertical as well,
//but it has more branching)
int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
int dy = -abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
int err = dx + dy, e2; /* error value e_xy */
for (;;) { /* loop */
_safeSetPixelFromPen(x0, y0);
if (x0 == x1 && y0 == y1)
break;
e2 = 2 * err;
if (e2 >= dy) {
err += dy;
x0 += sx;
} /* e_xy+e_x > 0 */
if (e2 <= dx) {
err += dx;
y0 += sy;
} /* e_xy+e_y < 0 */
}
}
}
void Graphics::tline(int x0, int y0, int x1, int y1, fix32 mx, fix32 my){
tline(
x0,
y0,
x1,
y1,
mx,
my,
fix32::frombits(0x2000), // 1/8
fix32(0)
);
}
fix32 getTlineMask(uint8_t dim){
if (dim == 0) {
return fix32::frombits(0xffffff);
}
return fix32(dim) - fix32::frombits(1);
}
//ported from zepto 8 impl
void Graphics::tline(int x0, int y0, int x1, int y1, fix32 mx, fix32 my, fix32 mdx, fix32 mdy){
applyCameraToPoint(&x0, &y0);
applyCameraToPoint(&x1, &y1);
//determine whether x or y coordinates need to get incremented
bool xDifGreater = std::abs(x1 - x0) >= std::abs(y1 - y0);
int dx = xDifGreater ? x0 <= x1 ? 1 : -1 : 0;
int dy = xDifGreater ? 0 : y0 <= y1 ? 1 : -1;
bool vertical = x0 == x1;
int x = clampXCoordToClip(x0);
int xend = clampXCoordToClip(x1);
int y = clampYCoordToClip(y0);
int yend = clampYCoordToClip(y1);
auto &ds = _memory->drawState;
// Retrieve masks for wrap-around and subtract 0x0.0001
fix32 xmask = getTlineMask(ds.tlineMapWidth);
fix32 ymask = getTlineMask(ds.tlineMapHeight);
// Advance texture coordinates; do it in steps to avoid overflows
int delta = abs(xDifGreater ? x - x0 : y - y0);
while (delta) {
int step = std::min(8192, delta);
mx = (mx & ~xmask) | ((mx + mdx * fix32(step)) & xmask);
my = (my & ~ymask) | ((my + mdy * fix32(step)) & ymask);
delta -= step;
}
for (;;) {
// Find sprite in map memory
int sx = (ds.tlineMapXOffset + int(mx & xmask));
int sy = (ds.tlineMapYOffset + int(my & ymask));
uint8_t sprite = mget(sx, sy);
//uint8_t bits = fget(sprite);
int spr_x = (sprite % 16) * 8;
int spr_y = (sprite / 16) * 8;
// If found, draw pixel //todo layer param
//if (cell && ((layer == 0) || (fget(cell) & layer))) {
if (sprite) {
//uint8_t col = _memory->spriteSheetData.gfx.get(spr_x + (int(mx << 3) & 0x7),
// spr_y + (int(my << 3) & 0x7));
uint8_t col = getPixelNibble(
spr_x + (int(mx << 3) & 0x7),
spr_y + (int(my << 3) & 0x7),
_memory->spriteSheetData);
if (!isColorTransparent(col) && isWithinClip(x, y)) {
_setPixelFromSprite(x, y, col);
}
}
// Advance source coordinates
mx = (mx & ~xmask) | ((mx + mdx) & xmask);
my = (my & ~ymask) | ((my + mdy) & ymask);
// Advance destination coordinates
if (xDifGreater) {
if (x == xend)
break;
x += dx;
y = y0 + ((float)(x - x0) / (x1 - x0)) * (y1 - y0);
}
else {
if (y == yend)
break;
y += dy;
if (! vertical) {
x = x0 + ((float)(y - y0) / (y1 - y0)) * (x1 - x0);
}
}
}
}
void Graphics::circ(int ox, int oy){
this->circ(ox, oy, 4);
}
void Graphics::circ(int ox, int oy, int r){
this->circ(ox, oy, r, _memory->drawState.color);
}
void Graphics::circ(int ox, int oy, int r, uint8_t col){
color(col);
applyCameraToPoint(&ox, &oy);
int x = r;
int y = 0;
int decisionOver2 = 1-x;
while (y <= x) {
_safeSetPixelFromPen(ox + x, oy + y);
_safeSetPixelFromPen(ox + y, oy + x);
_safeSetPixelFromPen(ox - x, oy + y);
_safeSetPixelFromPen(ox - y, oy + x);
_safeSetPixelFromPen(ox - x, oy - y);
_safeSetPixelFromPen(ox - y, oy - x);
_safeSetPixelFromPen(ox + x, oy - y);
_safeSetPixelFromPen(ox + y, oy - x);
y += 1;
if (decisionOver2 < 0) {
decisionOver2=decisionOver2+2*y+1;
}
else {
x = x-1;
decisionOver2 = decisionOver2 + 2 * (y - x) + 1;
}
}
}
void Graphics::circfill(int ox, int oy){
this->circfill(ox, oy, 4);
}
void Graphics::circfill(int ox, int oy, int r){
this->circfill(ox, oy, r, _memory->drawState.color);
}
void Graphics::_invertedCircfill(int ox, int oy, int r, uint8_t col){
color(col);
applyCameraToPoint(&ox, &oy);
int clipLeft = _memory->drawState.clip_xb;
int clipRight = _memory->drawState.clip_xe;
int clipTop = _memory->drawState.clip_yb;
int clipBottom = _memory->drawState.clip_ye;
int circleTop = oy - r;
int circleBottom = oy + r;
// Fill rows above the circle
for (int row = clipTop; row < circleTop && row < clipBottom; row++) {
_private_h_line(clipLeft, clipRight - 1, row);
}
// Fill rows that intersect the circle (left and right of circle)
if (r >= 0) {
int x = -r, y = 0, err = 2 - 2 * r;
int lastY = -1;
do {
// For each y offset, draw lines outside the circle
if (oy + y != lastY) {
// Right side of screen (to the right of circle)
if (ox + x + 1 < clipRight) {
_private_h_line(ox - x + 1, clipRight - 1, oy + y);
}
// Left side of screen (to the left of circle)
if (ox + x - 1 >= clipLeft) {
_private_h_line(clipLeft, ox + x - 1, oy + y);
}
}
if (y > 0 && oy - y != lastY) {
// Right side of screen (to the right of circle)
if (ox + x + 1 < clipRight) {
_private_h_line(ox - x + 1, clipRight - 1, oy - y);
}
// Left side of screen (to the left of circle)
if (ox + x - 1 >= clipLeft) {
_private_h_line(clipLeft, ox + x - 1, oy - y);
}
}
lastY = oy + y;
int savedR = r;
r = err;
if (r > x)
err += ++x * 2 + 1;
if (r <= y)
err += ++y * 2 + 1;
r = savedR;
} while (x < 0);
}
// Fill rows below the circle
for (int row = circleBottom + 1; row < clipBottom; row++) {
_private_h_line(clipLeft, clipRight - 1, row);
}
}
void Graphics::circfill(int ox, int oy, int r, uint8_t col){
// Check if inverted fill mode is enabled (bit 1 of colorSettingFlag at 0x5f34)
bool invertedFill = (_memory->drawState.colorSettingFlag & 0x02) != 0;
if (invertedFill) {
return _invertedCircfill(ox, oy, r, col);
}
color(col);
applyCameraToPoint(&ox, &oy);
if (r == 0) {
_safeSetPixelFromPen(ox, oy);
}
else if (r == 1) {
_safeSetPixelFromPen(ox, oy - 1);
_private_h_line(ox-1, ox+1, oy);
_safeSetPixelFromPen(ox, oy + 1);
}
else if (r > 0) {
int x = -r, y = 0, err = 2 - 2 * r;
do {
_private_h_line(ox - x, ox + x, oy + y);
_private_h_line(ox - x, ox + x, oy - y);
r = err;
if (r > x)
err += ++x * 2 + 1;
if (r <= y)
err += ++y * 2 + 1;
} while (x < 0);
}
}
void Graphics::oval(int x0, int y0, int x1, int y1) {
this->oval(x0, y0, x1, y1, _memory->drawState.color);
}
//https://stackoverflow.com/a/8448181
void Graphics::oval(int x0, int y0, int x1, int y1, uint8_t col) {
color(col);
applyCameraToPoint(&x0, &y0);
applyCameraToPoint(&x1, &y1);
sortCoordsForRect(&x0, &y0, &x1, &y1);
//x radius and y radius
int xr = (x1 - x0) / 2;
int yr = (y1 - y0) / 2;
//center location
int xc = x0 + xr;
int yc = y0 + yr;
int wx, wy;
int thresh;
int asq = xr * xr;
int bsq = yr * yr;
int xa, ya;
_safeSetPixelFromPen(xc, yc+yr);
_safeSetPixelFromPen(xc, yc-yr);
wx = 0;
wy = yr;
xa = 0;
ya = asq * 2 * yr;
thresh = asq / 4 - asq * yr;
for (;;) {
thresh += xa + bsq;
if (thresh >= 0) {
ya -= asq * 2;
thresh -= ya;
wy--;
}
xa += bsq * 2;
wx++;
if (xa >= ya) {
break;
}
_safeSetPixelFromPen(xc+wx, yc-wy);
_safeSetPixelFromPen(xc-wx, yc-wy);
_safeSetPixelFromPen(xc+wx, yc+wy);
_safeSetPixelFromPen(xc-wx, yc+wy);
}
_safeSetPixelFromPen(xc+xr, yc);
_safeSetPixelFromPen(xc-xr, yc);
wx = xr;
wy = 0;
xa = bsq * 2 * xr;
ya = 0;
thresh = bsq / 4 - bsq * xr;
for (;;) {
thresh += ya + asq;
if (thresh >= 0) {
xa -= bsq * 2;
thresh = thresh - xa;
wx--;
}
ya += asq * 2;
wy++;
if (ya > xa) {
break;
}
if (ya == 0 && xa == 0) {
break;
}
_safeSetPixelFromPen(xc+wx, yc-wy);
_safeSetPixelFromPen(xc-wx, yc-wy);
_safeSetPixelFromPen(xc+wx, yc+wy);
_safeSetPixelFromPen(xc-wx, yc+wy);
}
}
void Graphics::ovalfill(int x0, int y0, int x1, int y1) {
this->ovalfill(x0, y0, x1, y1, _memory->drawState.color);
}
//https://stackoverflow.com/a/8448181
void Graphics::ovalfill(int x0, int y0, int x1, int y1, uint8_t col){
color(col);
applyCameraToPoint(&x0, &y0);
applyCameraToPoint(&x1, &y1);
sortCoordsForRect(&x0, &y0, &x1, &y1);
//x radius and y radius
int xr = (x1 - x0) / 2;
int yr = (y1 - y0) / 2;
//center location
int xc = x0 + xr;
int yc = y0 + yr;
int wx, wy;
int thresh;
int asq = xr * xr;
int bsq = yr * yr;
int xa, ya;
_private_v_line(yc+yr, yc-yr, xc);
wx = 0;
wy = yr;
xa = 0;
ya = asq * 2 * yr;
thresh = asq / 4 - asq * yr;
for (;;) {
thresh += xa + bsq;
if (thresh >= 0) {
ya -= asq * 2;
thresh -= ya;
wy--;
}
xa += bsq * 2;
wx++;
if (xa >= ya) {
break;
}
_private_h_line(xc+wx, xc-wx, yc-wy);
_private_h_line(xc+wx, xc-wx, yc+wy);
}
_private_h_line(xc+xr, xc-xr, yc);
wx = xr;
wy = 0;
xa = bsq * 2 * xr;
ya = 0;
thresh = bsq / 4 - bsq * xr;
for (;;) {
thresh += ya + asq;
if (thresh >= 0) {
xa -= bsq * 2;
thresh = thresh - xa;
wx--;
}
ya += asq * 2;
wy++;
if (ya > xa) {
break;
}
if (ya == 0 && xa == 0) {
break;
}
_private_h_line(xc+wx, xc-wx, yc-wy);
_private_h_line(xc+wx, xc-wx, yc+wy);
}
/*
//this algo doesn't like up correctly
//center line
_private_h_line(xc - xr, xc + xr, yc, col);
for (int y = 1; y <= yr; y++) {
int x = currWidth - (dx - 1); // try slopes of dx - 1 or more
for ( ; x > 0; x--) {
if (x*x*hh + y*y*ww <= hhww) {
break;
}
}
dx = currWidth - x; // current approximation of the slope
currWidth = x;
_private_h_line(xc - currWidth, xc + currWidth, yc + y, col);
_private_h_line(xc - currWidth, xc + currWidth, yc - y, col);
}
*/
}
void Graphics::rect(int x1, int y1, int x2, int y2) {
this->rect(x1, y1, x2, y2, _memory->drawState.color);
}
void Graphics::rect(int x1, int y1, int x2, int y2, uint8_t col) {
_memory->drawState.color = col;
//applyCameraToPoint(&x1, &y1);
x1 -= _memory->drawState.camera_x;
y1 -= _memory->drawState.camera_y;
//applyCameraToPoint(&x2, &y2);
x2 -= _memory->drawState.camera_x;
y2 -= _memory->drawState.camera_y;
int temp;
if (x1 > x2) {
temp = x1;
x1 = x2;
x2 = temp;
}
if (y1 > y2) {
temp = y1;
y1 = y2;
y2 = temp;
}
_private_h_line(x1, x2, y1);
_private_h_line(x1, x2, y2);
_private_v_line(y1, y2, x1);
_private_v_line(y1, y2, x2);
}
void Graphics::rectfill(int x1, int y1, int x2, int y2) {
this->rectfill(x1, y1, x2, y2, _memory->drawState.color);
}
void Graphics::rectfill(int x1, int y1, int x2, int y2, uint8_t col) {
auto &drawState = _memory->drawState;
drawState.color = col;
//applyCameraToPoint(&x1, &y1);
x1 -= drawState.camera_x;
y1 -= drawState.camera_y;
//applyCameraToPoint(&x2, &y2);
x2 -= drawState.camera_x;
y2 -= drawState.camera_y;
int temp;
if (x1 > x2) {
temp = x1;
x1 = x2;
x2 = temp;
}
if (y1 > y2) {
temp = y1;
y1 = y2;
y2 = temp;
}
for (int y = y1; y <= y2; y++) {
_private_h_line(x1, x2, y);
}
}
#include <cmath>
int Graphics::_getRRectCutAmount(int radius, int row) {
//reverse engineered look up tables for pixel perfect matches (and speed)
if (radius == 6) {
static int cuts[] = {5,3,2,1,1,0};
return (row < 6) ? cuts[row] : 0;
} else if (radius == 7) {
static int cuts[] = {6,4,3,2,1,1,0};
return (row < 7) ? cuts[row] : 0;
} else if (radius == 8) {
static int cuts[] = {7,5,3,2,2,1,1,0};
return (row < 8) ? cuts[row] : 0;
} else if (radius == 9) {
static int cuts[] = {7,5,4,3,2,1,1,0};
return (row < 8) ? cuts[row] : 0;
} else if (radius == 10) {
static int cuts[] = {7,5,4,3,2,1,1,0};
return (row < 8) ? cuts[row] : 0;
} else if (radius == 11 || radius == 12) {
static int cuts[] = {9,7,5,4,3,2,2,1,1,0,0};
return (row < 11) ? cuts[row] : 0;
} else if (radius == 13 || radius == 14) {
static int cuts[] = {11,8,7,5,4,3,3,2,1,1,1,0,0};
return (row < 13) ? cuts[row] : 0;
} else if (radius == 15 || radius == 16) {
static int cuts[] = {13,10,8,7,5,4,4,3,2,2,1,1,1,0,0};
return (row < 15) ? cuts[row] : 0;
} else {
//not pixel perfect, but it is rounded
if (row >= radius) return 0;
// Calculate using circle equation
double y = static_cast<double>(radius - row - 1);
double discriminant = static_cast<double>(radius * radius) - (y * y);
if (discriminant <= 0) return 0;
double x = static_cast<double>(radius) - std::sqrt(discriminant);
int cut = static_cast<int>(x + 0.5) - 1;
// Handle the tail end where cuts often stay at 1 for multiple rows
// This prevents premature jumps to 0
if (cut == 0 && row < radius - 1) {
double tail_y = static_cast<double>(radius - row);
if (tail_y <= 2.5) {
cut = 1;
}
}
return (cut < 0) ? 0 : cut;
}
}
void Graphics::rrect(int x, int y, int w, int h, int r) {
this->rrect(x, y, w, h, r, _memory->drawState.color);
}
void Graphics::rrect(int x, int y, int w, int h, int r, uint8_t col) {
color(col);
// Pico 8 docs say: "The radius used is clamped to fall the range 0 .. min(width,height)/2."
int maxRadius = (w < h ? w : h) / 2;
if (r > maxRadius) {
r = maxRadius;
}
// no radius: fall back to rect
if (r <= 0) {
this->rect(x, y, x + w - 1, y + h - 1, col);
return;
}
applyCameraToPoint(&x, &y);
for (int row = 0; row < h; row++) {
int cutAmount = 0;
if (row < r) {
//top half
if (r <= 5) {
cutAmount = r - 1 - row;
} else {
int yFromCorner = row;
int rSquared = r * r;
int maxX = 0;
for (int testX = 0; testX < r; testX++) {
if (testX * testX + yFromCorner * yFromCorner <= rSquared) {
maxX = testX;
}
}
cutAmount = r - 1 - maxX;
}
} else if (row >= h - r) {
// bottom half
int bottomRow = h - 1 - row;
if (r <= 5) {
cutAmount = r - 1 - bottomRow;
} else {
cutAmount = _getRRectCutAmount(r, bottomRow);
}
}
if (cutAmount < 0) cutAmount = 0;
if (cutAmount * 2 >= w) cutAmount = (w - 1) / 2;
int lineStart = x + cutAmount;
int lineEnd = x + w - 1 - cutAmount;
if (lineStart <= lineEnd) {
if (row == 0 || row == h - 1) {
_private_h_line(lineStart, lineEnd, y + row);
} else {
_safeSetPixelFromPen(lineStart, y + row);
if (lineStart != lineEnd) {
_safeSetPixelFromPen(lineEnd, y + row);
}
}
}
}
}
void Graphics::rrectfill(int x, int y, int w, int h, int r) {
this->rrectfill(x, y, w, h, r, _memory->drawState.color);
}
void Graphics::rrectfill(int x, int y, int w, int h, int r, uint8_t col) {
color(col);
// Pico 8 docs say: "The radius used is clamped to fall the range 0 .. min(width,height)/2."
int maxRadius = (w < h ? w : h) / 2;
if (r > maxRadius) {
r = maxRadius;
}
// no radius: fall back to rectfill
if (r <= 0) {
this->rectfill(x, y, x + w - 1, y + h - 1, col);
return;
}
applyCameraToPoint(&x, &y);
for (int row = 0; row < h; row++) {
int cutAmount = 0;
if (row < r) {
//top half
if (r <= 5) {
// Small radius appear to be a simple stepping pattern
cutAmount = r - 1 - row;
} else {
// Large radius: use pixel perfect look ups with fallback
cutAmount = _getRRectCutAmount(r, row);
}
} else if (row >= h - r) {
//bottom half
int bottomRow = h - 1 - row;
if (r <= 5) {
cutAmount = r - 1 - bottomRow;
} else {
cutAmount = _getRRectCutAmount(r, bottomRow);
}
}
if (cutAmount < 0) cutAmount = 0;
if (cutAmount * 2 >= w) cutAmount = (w - 1) / 2;
int lineStart = x + cutAmount;
int lineEnd = x + w - 1 - cutAmount;
if (lineStart <= lineEnd) {
_private_h_line(lineStart, lineEnd, y + row);
}
}
}
fix32 Graphics::fillp(fix32 pat) {
int32_t prev = (_memory->drawState.fillPattern[0] << 16)
| (_memory->drawState.fillPattern[1] << 24)
| (_memory->drawState.fillPatternTransparencyBit << 8);
uint8_t patByte0 = pat.bits() >> 16;
uint8_t patByte1 = pat.bits() >> 24;
uint8_t patTranspByte = pat.bits() >> 15;
_memory->drawState.fillPattern[0] = patByte0;
_memory->drawState.fillPattern[1] = patByte1;
_memory->drawState.fillPatternTransparencyBit = patTranspByte & 1;
return z8::fix32::frombits(prev);
}
int Graphics::drawCharacter(
uint8_t ch,
int x,
int y,
uint8_t fgColor,
uint8_t bgColor,
uint8_t printMode,
int forceCharWidth,
int forceCharHeight) {
int extraCharWidth = 0;
bool useCustomFont = (printMode & PRINT_MODE_CUSTOM_FONT) == PRINT_MODE_CUSTOM_FONT;
int defaultCharWidth = useCustomFont ? _memory->data[0x5600] : 4;
int defaultWideCharWidth = useCustomFont ? _memory->data[0x5601] : 8;
int defaultCharHeight = useCustomFont ? _memory->data[0x5602] : 5;
if (ch > 0x0f) {
// Determine render width:
// - If forceCharWidth is set (0-3 via \^x), use it to limit rendering
// - For custom fonts without forced width, render all 8 columns (charWidth only affects cursor)
// - For standard font, use defaultCharWidth (glyph data matches the width)
bool widthIsForced = forceCharWidth > -1 && forceCharWidth < 4;
uint8_t renderWidth;
if (widthIsForced) {
renderWidth = ch < 0x80 ? forceCharWidth : (forceCharWidth + 4);
} else if (useCustomFont) {
renderWidth = 8; // Custom fonts: render all 8 columns
} else {
renderWidth = ch < 0x80 ? defaultCharWidth : defaultWideCharWidth; // Standard font
}
if (ch >= 0x80) {
extraCharWidth = defaultWideCharWidth - defaultCharWidth;
}
// Height: use forced height if set, otherwise use defaultCharHeight from font metadata
uint8_t renderHeight = forceCharHeight > -1 && forceCharHeight < 5 ? forceCharHeight : defaultCharHeight;
auto result = drawCharacterFromBytes(
useCustomFont ? &(_memory->data[0x5600 + ch*8]) : &(defaultFontBinaryData[ch*8]),
x,
y,
fgColor,
bgColor,
printMode,
renderWidth,
renderHeight
);
extraCharWidth += get<0>(result);
}
if ((printMode & PRINT_MODE_ON) == PRINT_MODE_ON){
return 0;
}
return extraCharWidth;
}
std::tuple<int, int> Graphics::drawCharacterFromBytes(
uint8_t chBytes[],
int x,
int y,
uint8_t fgColor,
uint8_t bgColor,
uint8_t printMode,
uint8_t charWidth,
uint8_t charHeight) {
applyCameraToPoint(&x, &y);
uint8_t *screenBuffer = GetP8FrameBuffer();
int extraCharWidth = 0;
int extraCharHeight = 0;
int wFactor = 1;
int hFactor = 1;
bool evenPxOnly = false;
bool invertColors = false;
bool solidBg = false;
if ((printMode & PRINT_MODE_ON) == PRINT_MODE_ON){
if ((printMode & PRINT_MODE_WIDE) == PRINT_MODE_WIDE) {
wFactor = 2;
extraCharWidth = charWidth;
}
if((printMode & PRINT_MODE_TALL) == PRINT_MODE_TALL) {
hFactor = 2;
extraCharHeight = charHeight;
}
if((printMode & PRINT_MODE_STRIPEY) == PRINT_MODE_STRIPEY) {
//draw every other pixel-- also kinda broken on pico 8 0.2.4
evenPxOnly = true;
}
if((printMode & PRINT_MODE_INVERTED) == PRINT_MODE_INVERTED) {
invertColors = true;
}
if((printMode & PRINT_MODE_SOLID_BG) == PRINT_MODE_SOLID_BG) {
solidBg = true;
}
}
//possible todo: check perf if this is better than doing it in the other loop (m)
// if (bgColor != 0) {
// uint8_t prevPenColor = _memory->drawState.color;
// rectfill(x-1, y-1, x + charWidth*wFactor - 1, y + charHeight*hFactor - 1, bgColor);
// _memory->drawState.color = prevPenColor;
// }
fgColor &= 0x0f;
bgColor &= 0x0f;
// Font data is always 8 bytes per character (8 rows of 8 pixels), even if charHeight or charWidth > 8
// Rows/Cols beyond 8 should be blank (charHeight > 8 affects line spacing, not glyph size)
const int maxFontDataRows = 8;
for (int relDestY = 0; relDestY < charHeight * hFactor; relDestY++) {
for(int relDestX = 0; relDestX < charWidth * wFactor; relDestX++) {
// Only read from font data if within the 8x8 limit
int fontRow = relDestY / hFactor;
int fontCol = relDestX / wFactor;
if ((fontRow >= maxFontDataRows || fontCol >= maxFontDataRows)) {
continue;
}
bool on = BITMASK(fontCol) & chBytes[fontRow];
on &= hFactor == 1 || !evenPxOnly || (relDestY % 2 == 0);
on &= wFactor == 1 || !evenPxOnly || (relDestX % 2 == 0);
int absDestX = x + relDestX;
int absDestY = y + relDestY;
if (isWithinClip(absDestX, absDestY)) {
if (invertColors) {
on = !on;
}
if (on) {
setPixelNibble(absDestX, absDestY, fgColor, screenBuffer);
} else if (solidBg) {
setPixelNibble(absDestX, absDestY, bgColor, screenBuffer);
}
}
}
}
std::tuple<int, int> retVal (extraCharWidth, extraCharHeight);
return retVal;
}
void Graphics::spr(
int n,
int x,
int y,
fix32 w = 1.0,
fix32 h = 1.0,
bool flip_x = false,
bool flip_y = false)
{
int spr_x = (n % 16) * 8;
int spr_y = (n / 16) * 8;
int16_t spr_w = (int16_t)(w * (fix32)8);
int16_t spr_h = (int16_t)(h * (fix32)8);
copySpriteToScreen(GetP8SpriteSheetBuffer(), x, y, spr_x, spr_y, spr_w, spr_h, flip_x, flip_y);
}
void Graphics::sspr(
int sx,
int sy,
int sw,
int sh,
int dx,
int dy,
int dw,
int dh,
bool flip_x = false,
bool flip_y = false)
{
copyStretchSpriteToScreen(GetP8SpriteSheetBuffer(), sx, sy, sw, sh, dx, dy, dw, dh, flip_x, flip_y, false);
}
bool Graphics::fget(uint8_t n, uint8_t f){
return (this->fget(n) >> f) & 1;
}
uint8_t Graphics::fget(uint8_t n){
return _memory->spriteFlags[n];
}
void Graphics::fset(uint8_t n, uint8_t f, bool v){
if (v) {
fset(n, fget(n) | (1 << f));
}
else {
fset(n, fget(n) & ~(1 << f));
}
}
void Graphics::fset(uint8_t n, uint8_t v){
_memory->spriteFlags[n] = v;
}
uint8_t Graphics::sget(uint8_t x, uint8_t y){
if (IS_VALID_SPR_IDX(x, y)) {
return getPixelNibble(x, y, GetP8SpriteSheetBuffer());
}
return 0;
}
void Graphics::sset(uint8_t x, uint8_t y, uint8_t c){
if (IS_VALID_SPR_IDX(x, y)) {
setPixelNibble(x, y, c, GetP8SpriteSheetBuffer());
}
return;
}
std::tuple<int16_t, int16_t> Graphics::camera() {
return this->camera(0, 0);
}
std::tuple<int16_t, int16_t> Graphics::camera(int16_t x, int16_t y) {
std::tuple<int16_t, int16_t> prev (_memory->drawState.camera_x, _memory->drawState.camera_y);
_memory->drawState.camera_x = x;
_memory->drawState.camera_y = y;
return prev;
}
std::tuple<uint8_t, uint8_t, uint8_t, uint8_t> Graphics::clip() {
return this->clip(0, 0, 128, 128);
}
std::tuple<uint8_t, uint8_t, uint8_t, uint8_t> Graphics::clip(int x, int y, int w, int h) {
auto prev = std::make_tuple(
_memory->drawState.clip_xb,
_memory->drawState.clip_xe,
_memory->drawState.clip_yb,
_memory->drawState.clip_ye
);
int xe = x + w;
int ye = y + h;
_memory->drawState.clip_xb = clampCoordToScreenDims(x);
_memory->drawState.clip_yb = clampCoordToScreenDims(y);
_memory->drawState.clip_xe = clampCoordToScreenDims(xe);
_memory->drawState.clip_ye = clampCoordToScreenDims(ye);
return prev;
}
uint8_t Graphics::mget(int celx, int cely){
const bool bigMap = _memory->hwState.mapMemMapping >= 0x80;
const int bigMapLocation = _memory->hwState.mapMemMapping << 8;
const int mapSize = bigMap
? 0x10000 - bigMapLocation
: 8192;
const int mapW = _memory->hwState.widthOfTheMap == 0 ? 256 : _memory->hwState.widthOfTheMap;
const int mapH = mapSize / mapW;
const int maxXIdx = mapW - 1;
const int maxYIdx = mapH - 1;
const int idx = cely * mapW + celx;
if (celx < 0 || celx > maxXIdx || cely < 0 || cely > maxYIdx) {
return 0;
}
if (bigMap){
const int offset = 0x8000 - mapSize;
return _memory->userData[offset + idx];
}
else {
if (idx < 4096) {
return _memory->mapData[idx];
}
else if (idx < 8192){
return _memory->spriteSheetData[idx];
}
}
return 0;
}
void Graphics::mset(int celx, int cely, uint8_t snum){
const bool bigMap = _memory->hwState.mapMemMapping >= 0x80;
const int bigMapLocation = _memory->hwState.mapMemMapping << 8;
const int mapSize = bigMap
? 0x10000 - bigMapLocation
: 8192;
const int mapW = _memory->hwState.widthOfTheMap == 0 ? 256 : _memory->hwState.widthOfTheMap;
const int mapH = mapSize / mapW;
const int maxXIdx = mapW - 1;
const int maxYIdx = mapH - 1;
if (celx < 0 || celx > maxXIdx || cely < 0 || cely > maxYIdx) {
return;
}
const int idx = cely * mapW + celx;
if (bigMap){
const int offset = 0x8000 - mapSize;
_memory->userData[offset + idx] = snum;
}
else {
if (idx < 4096) {
_memory->mapData[idx] = snum;
}
else if (idx < 8192) {
_memory->spriteSheetData[idx] = snum;
}
}
}
void Graphics::map(int celx, int cely, int sx, int sy, int celw, int celh) {
map(celx, cely, sx, sy, celw, celh, 0);
}
void Graphics::map(int celx, int cely, int sx, int sy, int celw, int celh, uint8_t layer) {
for (int y = 0; y < celh; y++) {
for (int x = 0; x < celw; x++) {
uint8_t cell = mget(celx + x, cely + y);
if (cell && ((layer == 0) || (fget(cell) & layer))) {
spr(cell, sx + x * 8, sy + y * 8);
}
}
}
}
void Graphics::pal() {
for (uint8_t c = 0; c < 16; c++) {
_memory->drawState.drawPaletteMap[c] = c;
_memory->drawState.screenPaletteMap[c] = c;
}
this->palt();
}
void Graphics::pal(uint8_t p) {
for (uint8_t c = 0; c < 16; c++) {
if (p == 0) {
// Preserve transparency bit when resetting palette mapping
// But ensure color 0 is transparent by default if it wasn't set
uint8_t transparencyBit = _memory->drawState.drawPaletteMap[c] & 0x10;
if (c == 0 && transparencyBit == 0) {
// Color 0 should be transparent by default
transparencyBit = 0x10;
}
_memory->drawState.drawPaletteMap[c] = c | transparencyBit;
} else if (p == 1) {
_memory->drawState.screenPaletteMap[c] = c;
}
}
}
uint8_t Graphics::pal(uint8_t c0, uint8_t c1, uint8_t p){
//0-15 alowed
c0 &= 0x0f;
uint8_t prev = 0;
if (p == 0) {
//for draw palette we have to preserve the transparency bit
prev = _memory->drawState.drawPaletteMap[c0] & 0xf;
_memory->drawState.drawPaletteMap[c0] = (_memory->drawState.drawPaletteMap[c0] & 0x10) | (c1 & 0xf);
} else if (p == 1) {
//0-15, or 127-143 allowed
prev = _memory->drawState.screenPaletteMap[c0] & 0xf;
c1 &= 0x8f;
_memory->drawState.screenPaletteMap[c0] = c1;
}
return prev;
}
void Graphics::palt() {
_memory->drawState.drawPaletteMap[0] |= 1UL << 4;
for (uint8_t c = 1; c < 16; c++) {
_memory->drawState.drawPaletteMap[c] &= ~(1UL << 4);
}
}
bool Graphics::palt(uint8_t c, bool t){
c = c & 15;
bool prev = _memory->drawState.drawPaletteMap[c] & 0xf0;
if (t) {
_memory->drawState.drawPaletteMap[c] |= 1UL << 4;
}
else {
_memory->drawState.drawPaletteMap[c] &= ~(1UL << 4);
}
return prev;
}
std::tuple<uint8_t, uint8_t> Graphics::cursor() {
return this->cursor(0, 0);
}
std::tuple<uint8_t, uint8_t> Graphics::cursor(int x, int y) {
std::tuple<uint8_t, uint8_t> prev (_memory->drawState.text_x, _memory->drawState.text_y);
_memory->drawState.text_x = x;
_memory->drawState.text_y = y;
return prev;
}
std::tuple<uint8_t, uint8_t> Graphics::cursor(int x, int y, uint8_t col) {
color(col);
return this->cursor(x, y);
}