Commit 767cbb9a authored by Deucе's avatar Deucе 👌🏾
Browse files

Many X11 scaling improvements...

1) Initialize the r2y array for xBR so it actually works.
2) Add a vertical (only) interpolation scaler for aspect ratio enforcement
3) Add a simple muliplier scaler, so that can be removed from x_event.c
4) Use a new graphics buffer free list, which allows tracking last
   drawn screen instead of last bitmap rectangle, removing various hacks
5) Share the Y'CbCr <-> R'dG'dB'd tables between xBR and scale.c
parent 26def071
......@@ -11,6 +11,7 @@
#include "genwrap.h"
#include "dirwrap.h"
#include "xpbeep.h"
#include "scale.h"
#if (defined CIOLIB_IMPORTS)
#undef CIOLIB_IMPORTS
......@@ -1664,6 +1665,7 @@ int bitmap_drv_init(void (*drawrect_cb) (struct rectlist *data)
pthread_mutex_init(&screenb.screenlock, NULL);
pthread_mutex_init(&free_rect_lock, NULL);
pthread_mutex_lock(&vstatlock);
init_r2y();
vstat.vmem=NULL;
vstat.flags = VIDMODES_FLAG_PALETTE_VMEM;
pthread_mutex_lock(&screena.screenlock);
......
#include "scale.h"
#include "xbr.h"
uint32_t r2y[1<<24];
uint32_t y2r[1<<24];
static int r2y_inited;
static void pointy_scale3(uint32_t* src, uint32_t* dest, int width, int height);
static void pointy_scale5(uint32_t* src, uint32_t* dest, int width, int height);
static void interpolate_height(uint32_t* src, uint32_t* dst, int width, int height, int newheight);
static void multiply_scale(uint32_t* src, uint32_t* dst, int width, int height, int xmult, int ymult);
static struct graphics_buffer *free_list;
#define CLAMP(x) do { \
if (x < 0) \
x = 0; \
else if (x > 255) \
x = 255; \
} while(0)
void
init_r2y(void)
{
int r, g, b;
int y, u, v;
const double luma = 255.0 / 219;
const double col = 255.0 / 224;
if (r2y_inited)
return;
for (r = 0; r < 256; r++) {
for (g = 0; g < 256; g++) {
for (b = 0; b < 256; b++) {
y = 16 + ( 65.738 * r + 129.057 * g + 25.064 * b + 128) / 256;
CLAMP(y);
u = 128 + (-37.945 * r - 74.494 * g + 112.439 * b + 128) / 256;
CLAMP(u);
v = 128 + (112.439 * r - 94.154 * g - 18.285 * b + 128) / 256;
CLAMP(v);
r2y[(r<<16) | (g<<8) | b] = (y<<16)|(u<<8)|v;
}
}
}
for (y = 0; y < 256; y++) {
for (u = 0; u < 256; u++) {
for (v = 0; v < 256; v++) {
const int c = y - 16;
const int d = u - 128;
const int e = v - 128;
r = luma * c + col * 1.402 * e;
CLAMP(r);
g = luma * c - col * 1.772 * (0.114 / 0.587) * d - col * 1.402 * (0.299 / 0.587) * e;
CLAMP(g);
b = luma * c + col * 1.772 * d;
CLAMP(b);
uint32_t*
do_scale(struct rectlist* rect, uint32_t **target, size_t *targetsz, int* xscale, int* yscale, int* w, int* h)
y2r[(y<<16) | (u<<8) | v] = (r<<16)|(g<<8)|b;
}
}
}
r2y_inited = 1;
}
struct graphics_buffer *
get_buffer(void)
{
struct graphics_buffer* ret = NULL;
if (free_list) {
ret = free_list;
free_list = free_list->next;
ret->next = NULL;
return ret;
}
ret = calloc(1, sizeof(struct graphics_buffer));
return ret;
}
void
release_buffer(struct graphics_buffer *buf)
{
buf->next = free_list;
free_list = buf;
}
struct graphics_buffer *
do_scale(struct rectlist* rect, int* xscale, int* yscale, double ratio)
{
struct graphics_buffer* ret1 = get_buffer();
struct graphics_buffer* ret2 = get_buffer();
int pointy5 = 0;
int pointy3 = 0;
int xbr2 = 0;
int xbr4 = 0;
int ymult = 1;
int xmult = 1;
int newscale = 1;
int total_scaling = 1;
static uint32_t *tmptarget = NULL;
static size_t tmptargetsz = 0;
uint32_t *ctarget;
uint32_t *csrc = rect->data;
int total_xscaling = 1;
int total_yscaling = 1;
struct graphics_buffer *ctarget;
struct graphics_buffer *csrc;
uint32_t* nt;
int swidth = rect->rect.width;
int sheight = rect->rect.height;
*w = rect->rect.width;
*h = rect->rect.height;
// As a first pass, only do equal integer scaling
if (*xscale != *yscale)
return rect->data;
int fheight;
switch (*xscale) {
case 1:
return rect->data;
break;
case 2:
xbr2 = 1;
break;
......@@ -45,105 +124,155 @@ do_scale(struct rectlist* rect, uint32_t **target, size_t *targetsz, int* xscale
pointy3 = 1;
xbr2 = 1;
break;
case 7: // TODO: Do we want a pointy7 and pointy11?
xmult = 7;
ymult = 7;
break;
default:
total_scaling = *xscale;
total_xscaling = *xscale;
*xscale = 1;
while (total_scaling > 1 && ((total_scaling % 5) == 0)) {
total_yscaling = *yscale;
*yscale = 1;
while (total_xscaling > 1 && ((total_xscaling % 5) == 0) && ((total_yscaling % 5) == 0)) {
pointy5++;
total_scaling /= 5;
total_xscaling /= 5;
*xscale *= 5;
total_yscaling /= 5;
*yscale *= 5;
}
while (total_scaling > 1 && ((total_scaling % 3) == 0)) {
while (total_xscaling > 1 && ((total_xscaling % 3) == 0) && ((total_yscaling % 3) == 0)) {
pointy3++;
total_scaling /= 3;
total_xscaling /= 3;
*xscale *= 3;
total_yscaling /= 3;
*yscale *= 3;
}
while (total_scaling > 1 && ((total_scaling % 4) == 0)) {
while (total_xscaling > 1 && ((total_xscaling % 4) == 0) && ((total_yscaling % 4) == 0)) {
xbr4++;
total_scaling /= 4;
total_xscaling /= 4;
*xscale *= 4;
total_yscaling /= 4;
*yscale *= 4;
}
while (total_scaling > 1 && ((total_scaling % 2) == 0)) {
while (total_xscaling > 1 && ((total_xscaling % 2) == 0) && ((total_yscaling % 2) == 0)) {
xbr2++;
total_scaling /= 2;
total_xscaling /= 2;
*xscale *= 2;
total_yscaling /= 2;
*yscale *= 2;
}
if (*xscale == 1)
return rect->data;
break;
}
if (*xscale != *yscale) {
if (*yscale == *xscale * 2)
ymult *= 2;
else
return NULL;
}
// Calculate the scaled height from ratio...
fheight = lround((double)(rect->rect.height * (*yscale)) / ratio);
// Now make sure target is big enough...
size_t needsz = rect->rect.width * rect->rect.height * (*xscale) * (*xscale) * sizeof(uint32_t);
if (needsz > *targetsz) {
nt = realloc(*target, needsz);
size_t needsz = rect->rect.width * (*xscale) * fheight * sizeof(uint32_t);
if (needsz > ret1->sz) {
nt = realloc(ret1->data, needsz);
if (nt == NULL)
return rect->data;
*target = nt;
*targetsz = needsz;
return NULL;
ret1->data = nt;
ret1->sz = needsz;
}
ctarget = *target;
// And if we need an extra target, do the same there...
if (pointy3 + pointy5 + xbr4 + xbr2 > 1) {
if (needsz > tmptargetsz) {
nt = realloc(tmptarget, needsz);
if (nt == NULL)
return rect->data;
tmptarget = nt;
tmptargetsz = needsz;
}
if (needsz > ret2->sz) {
nt = realloc(ret2->data, needsz);
if (nt == NULL)
return NULL;
ret2->data = nt;
ret2->sz = needsz;
}
// And finally, scale...
// Copy rect into first buffer
// TODO: Unify bitmap rects and scaling buffers so this can just whomp on over.
csrc = ret1;
ctarget = ret2;
memcpy(csrc->data, rect->data, rect->rect.width * rect->rect.height * sizeof(rect->data[0]));
csrc->w = rect->rect.width;
csrc->h = rect->rect.height;
// And scale...
if (ymult != 1 || xmult != 1) {
multiply_scale(csrc->data, ctarget->data, csrc->w, csrc->h, xmult, ymult);
ctarget->w = csrc->w * xmult;
ctarget->h = csrc->h * ymult;
ymult = 1;
xmult = 1;
csrc = ctarget;
if (ctarget == ret1)
ctarget = ret2;
else
ctarget = ret1;
}
while (xbr4 > 0) {
xbr_filter(csrc, ctarget, swidth, sheight, 4);
xbr_filter(csrc->data, ctarget->data, csrc->w, csrc->h, 4);
xbr4--;
swidth *= 4;
sheight *= 4;
ctarget->w = csrc->w * 4;
ctarget->h = csrc->h * 4;
csrc = ctarget;
if (ctarget == tmptarget)
ctarget = *target;
if (ctarget == ret1)
ctarget = ret2;
else
ctarget = tmptarget;
ctarget = ret1;
}
while (xbr2 > 0) {
xbr_filter(csrc, ctarget, swidth, sheight, 2);
xbr_filter(csrc->data, ctarget->data, csrc->w, csrc->h, 2);
xbr2--;
swidth *= 2;
sheight *= 2;
ctarget->w = csrc->w * 2;
ctarget->h = csrc->h * 2;
csrc = ctarget;
if (ctarget == tmptarget)
ctarget = *target;
if (ctarget == ret1)
ctarget = ret2;
else
ctarget = tmptarget;
ctarget = ret1;
}
while (pointy5 > 0) {
pointy_scale5(csrc, ctarget, swidth, sheight);
pointy_scale5(csrc->data, ctarget->data, csrc->w, csrc->h);
pointy5--;
swidth *= 5;
sheight *= 5;
ctarget->w = csrc->w * 5;
ctarget->h = csrc->h * 5;
csrc = ctarget;
if (ctarget == tmptarget)
ctarget = *target;
if (ctarget == ret1)
ctarget = ret2;
else
ctarget = tmptarget;
ctarget = ret1;
}
while (pointy3 > 0) {
pointy_scale3(csrc, ctarget, swidth, sheight);
pointy_scale3(csrc->data, ctarget->data, csrc->w, csrc->h);
pointy3--;
swidth *= 3;
sheight *= 3;
ctarget->w = csrc->w * 3;
ctarget->h = csrc->h * 3;
csrc = ctarget;
if (ctarget == tmptarget)
ctarget = *target;
if (ctarget == ret1)
ctarget = ret2;
else
ctarget = tmptarget;
ctarget = ret1;
}
*w *= *xscale;
*h *= *xscale;
// And finally, interpolate if needed
if (ratio != 1) {
interpolate_height(csrc->data, ctarget->data, csrc->w, csrc->h, fheight);
ctarget->h = fheight;
ctarget->w = csrc->w;
csrc = ctarget;
if (ctarget == ret1)
ctarget = ret2;
else
ctarget = ret1;
}
*xscale = newscale;
*yscale = newscale;
release_buffer(ctarget);
return csrc;
}
......@@ -339,3 +468,93 @@ pointy_scale3(uint32_t* src, uint32_t* dest, int width, int height)
prevline = -width;
}
}
static __attribute__((always_inline))
uint32_t blend(const uint32_t c1, const uint32_t c2, const double weight)
{
uint8_t yuv1[4];
uint8_t yuv2[4];
int y, u, v;
const double iw = 1.0 - weight;
*(uint32_t *)yuv1 = r2y[c1];
*(uint32_t *)yuv2 = r2y[c2];
#ifdef __BIG_ENDIAN__
y = yuv1[1] * iw + yuv2[1] * weight;
u = yuv1[2] * iw + yuv2[2] * weight;
v = yuv1[3] * iw + yuv2[3] * weight;
#else
y = yuv1[2] * iw + yuv2[2] * weight;
u = yuv1[1] * iw + yuv2[1] * weight;
v = yuv1[0] * iw + yuv2[0] * weight;
#endif
CLAMP(y);
CLAMP(u);
CLAMP(v);
return y2r[(y<<16)|(u<<8)|v];
}
/*
* This does non-integer *height* scaling. It does not scale in the other
* direction. This does the interpolation using Y'UV to prevent dimming of
* pixels.
*/
static void
interpolate_height(uint32_t* src, uint32_t* dst, int width, int height, int newheight)
{
int x, y;
const double mult = (double)height / newheight;
for (y = 0; y < newheight; y++) {
for (x = 0; x < width; x++) {
// First, calculate which two pixels this is between.
const double ypos = mult * y;
const int yposi = ypos;
if (y == ypos) {
// Exact match!
*dst = src[width * yposi + x];
}
else {
const double weight = ypos - yposi;
// Now pick the two pixels
const uint32_t pix1 = src[yposi * width + x] & 0xffffff;
uint32_t pix2;
if (yposi < height - 1)
pix2 = src[(yposi + 1) * width + x] & 0xffffff;
else
pix2 = src[yposi * width + x] & 0xffffff;
if (pix1 == pix2)
*dst = pix1;
else {
*dst = blend(pix1, pix2, weight);
}
}
dst++;
}
}
}
static void
multiply_scale(uint32_t* src, uint32_t* dst, int width, int height, int xmult, int ymult)
{
int nheight = height * ymult;
int nwidth = width * xmult;
int x, y;
int mx, my;
uint32_t* slstart;
for (y = 0; y < height; y++) {
slstart = src;
for (my = 0; my < ymult; my++) {
src = slstart;
for (x = 0; x < width; x++) {
for (mx = 0; mx < xmult; mx++) {
*dst = *src;
dst++;
}
src++;
}
}
}
}
#include "bitmap_con.h"
uint32_t* do_scale(struct rectlist* rect, uint32_t **target, size_t *targetsz, int* xscale, int* yscale, int* w, int* h);
struct graphics_buffer {
uint32_t* data;
size_t sz;
size_t w;
size_t h;
struct graphics_buffer *next;
};
extern uint32_t r2y[1<<24];
extern uint32_t y2r[1<<24];
struct graphics_buffer * get_buffer(void);
void release_buffer(struct graphics_buffer *);
void init_r2y(void);
struct graphics_buffer * do_scale(struct rectlist* rect, int* xscale, int* yscale, double ratio);
......@@ -85,7 +85,7 @@ static unsigned long base_pixel;
static int r_shift;
static int g_shift;
static int b_shift;
static struct rectlist *last = NULL;
static struct graphics_buffer *last = NULL;
/* Array of Graphics Contexts */
static GC gc;
......@@ -208,9 +208,13 @@ static struct {
static void resize_xim(void)
{
int width = bitmap_width * x_cvstat.scaling;
double ratio = (double)x_cvstat.scale_numerator / x_cvstat.scale_denominator;
int height = lround((double)(bitmap_height * x_cvstat.scaling * x_cvstat.vmultiplier) / ratio);
if (xim) {
if (bitmap_width*x_cvstat.scaling == xim->width
&& bitmap_height*x_cvstat.scaling*x_cvstat.vmultiplier == xim->height) {
if (width == xim->width
&& height == xim->height) {
return;
}
#ifdef XDestroyImage
......@@ -220,11 +224,11 @@ static void resize_xim(void)
#endif
}
if (last) {
bitmap_drv_free_rect(last);
release_buffer(last);
last = NULL;
}
xim=x11.XCreateImage(dpy,&visual,depth,ZPixmap,0,NULL,bitmap_width*x_cvstat.scaling,bitmap_height*x_cvstat.scaling*x_cvstat.vmultiplier,32,0);
xim->data=(char *)malloc(xim->bytes_per_line*xim->height);
xim = x11.XCreateImage(dpy, &visual, depth, ZPixmap, 0, NULL, width, height, 32, 0);
xim->data=(char *)calloc(1, xim->bytes_per_line*xim->height);
}
/* Swiped from FreeBSD libc */
......@@ -346,6 +350,7 @@ static int init_window()
static void map_window()
{
XSizeHints *sh;
int scaled_height;
sh = x11.XAllocSizeHints();
if (sh == NULL) {
......@@ -357,7 +362,9 @@ static void map_window()
sh->base_height = bitmap_height*x_cvstat.scaling*x_cvstat.vmultiplier;
sh->min_width = sh->width_inc = sh->min_aspect.x = sh->max_aspect.x = bitmap_width;
sh->min_height = sh->height_inc = sh->min_aspect.y = sh->max_aspect.y = bitmap_height;
scaled_height = ceil((double)bitmap_height*x_cvstat.vmultiplier / ((double)x_cvstat.scale_numerator / x_cvstat.scale_denominator));
sh->min_height = sh->height_inc = sh->min_aspect.y = sh->max_aspect.y = scaled_height;
sh->flags = USSize | PMinSize | PSize | PResizeInc | PAspect;
x11.XSetWMNormalHints(dpy, win, sh);
......@@ -371,10 +378,11 @@ static void map_window()
/* Resize the window. This function is called after a mode change. */
static void resize_window()
{
x11.XResizeWindow(dpy, win, bitmap_width*x_cvstat.scaling, bitmap_height*x_cvstat.scaling*x_cvstat.vmultiplier);
resize_xim();
int scaled_height = ceil((double)bitmap_height*x_cvstat.scaling*x_cvstat.vmultiplier / ((double)x_cvstat.scale_numerator / x_cvstat.scale_denominator));
x11.XResizeWindow(dpy, win, bitmap_width*x_cvstat.scaling, scaled_height);
resize_xim();
return;
return;
}
static void init_mode_internal(int mode)
......@@ -386,7 +394,7 @@ static void init_mode_internal(int mode)
pthread_mutex_lock(&blinker_lock);
pthread_mutex_lock(&vstatlock);
if (last) {
bitmap_drv_free_rect(last);
release_buffer(last);
last = NULL;
}
bitmap_drv_init_mode(mode, &bitmap_width, &bitmap_height);
......@@ -451,7 +459,6 @@ static void local_draw_rect(struct rectlist *rect)
{
int x,y,xscale,yscale,xoff=0,yoff=0;
int xscaling, yscaling;
int rxscaling, ryscaling;
unsigned int r, g, b;
unsigned long pixel;
int cleft;
......@@ -459,14 +466,9 @@ static void local_draw_rect(struct rectlist *rect)
int ctop;
int cbottom = -1;
int idx;
int ridx;
int ridx_part;
uint32_t last_pixel = 0x55555555;
static uint32_t* target = NULL;
size_t targetsz = 0;
uint32_t* source;
int width, height;
int lheight, lines;
struct graphics_buffer *source;
int lines;
if (bitmap_width != rect->rect.width || bitmap_height != rect->rect.height)
return;
......@@ -481,23 +483,32 @@ static void local_draw_rect(struct rectlist *rect)
// Scale...
xscaling = x_cvstat.scaling;
yscaling = x_cvstat.scaling*x_cvstat.vmultiplier;
source = do_scale(rect, &target, &targetsz, &xscaling, &yscaling, &width, &height);
rxscaling = width / rect->rect.width;
ryscaling = height / rect->rect.height;
cleft = width;
ctop = height;
source = do_scale(rect, &xscaling, &yscaling, (double)x_cvstat.scale_numerator / x_cvstat.scale_denominator);
bitmap_drv_free_rect(rect);
if (source == NULL)
return;
cleft = source->w;
ctop = source->h;
lines = 0;
lheight = x_cvstat.charheight * ryscaling;
xoff = (x11_window_width - source->w) / 2;
if (xoff < 0)
xoff = 0;
yoff = (x11_window_height - source->h) / 2;
if (yoff < 0)
yoff = 0;
if (last && (last->w != source->w || last->h != source->h)) {
release_buffer(last);
last = NULL;
}
/* TODO: Translate into local colour depth */