mirror of
https://github.com/holub/mame
synced 2025-05-31 10:01:51 +03:00
screen: Add svg shapes pre-computation [O. Galibert]
This commit is contained in:
parent
fe6b9390c7
commit
7d635eec4b
@ -15,7 +15,7 @@
|
||||
|
||||
#include <nanosvg/src/nanosvg.h>
|
||||
#include <nanosvg/src/nanosvgrast.h>
|
||||
|
||||
#include <set>
|
||||
|
||||
//**************************************************************************
|
||||
// DEBUGGING
|
||||
@ -50,12 +50,43 @@ public:
|
||||
static void output_notifier(const char *outname, INT32 value, void *param);
|
||||
|
||||
private:
|
||||
struct paired_entry {
|
||||
int key;
|
||||
int cache_entry;
|
||||
paired_entry(int k, int c) { key = k; cache_entry = c; }
|
||||
};
|
||||
|
||||
struct cached_bitmap {
|
||||
int x, y, sx, sy;
|
||||
std::vector<UINT32> image;
|
||||
std::vector<paired_entry> pairs;
|
||||
};
|
||||
|
||||
struct bbox {
|
||||
int x0, y0, x1, y1;
|
||||
};
|
||||
|
||||
NSVGimage *m_image;
|
||||
NSVGrasterizer *m_rasterizer;
|
||||
std::vector<bool> m_key_state;
|
||||
std::vector<std::list<NSVGshape *>> m_keyed_shapes;
|
||||
std::unordered_map<std::string, int> m_key_ids;
|
||||
int m_key_count;
|
||||
|
||||
std::unordered_map<std::string, std::list<NSVGshape *>> m_keyed_shapes;
|
||||
int m_sx, m_sy;
|
||||
double m_scale;
|
||||
std::vector<UINT32> m_background;
|
||||
|
||||
std::vector<cached_bitmap> m_cache;
|
||||
|
||||
void output_change(const char *outname, INT32 value);
|
||||
void render_state(std::vector<UINT32> &dest, const std::vector<bool> &state);
|
||||
void compute_initial_bboxes(std::vector<bbox> &bboxes);
|
||||
bool compute_mask_intersection_bbox(int key1, int key2, bbox &bb) const;
|
||||
void compute_diff_image(const std::vector<UINT32> &rend, const bbox &bb, cached_bitmap &dest) const;
|
||||
void compute_dual_diff_image(const std::vector<UINT32> &rend, const bbox &bb, const cached_bitmap &src1, const cached_bitmap &src2, cached_bitmap &dest) const;
|
||||
void rebuild_cache();
|
||||
void blit(bitmap_rgb32 &bitmap, const cached_bitmap &src) const;
|
||||
};
|
||||
|
||||
screen_device_svg_renderer::screen_device_svg_renderer(memory_region *region)
|
||||
@ -67,11 +98,28 @@ screen_device_svg_renderer::screen_device_svg_renderer(memory_region *region)
|
||||
delete[] s;
|
||||
m_rasterizer = nsvgCreateRasterizer();
|
||||
|
||||
m_key_count = 0;
|
||||
|
||||
for (NSVGshape *shape = m_image->shapes; shape != nullptr; shape = shape->next)
|
||||
if(shape->title[0]) {
|
||||
shape->flags &= ~NSVG_FLAGS_VISIBLE;
|
||||
m_keyed_shapes[shape->title].push_back(shape);
|
||||
auto it = m_key_ids.find(shape->title);
|
||||
if(it != m_key_ids.end())
|
||||
m_keyed_shapes[it->second].push_back(shape);
|
||||
else {
|
||||
int id = m_key_count;
|
||||
m_key_count++;
|
||||
m_keyed_shapes.resize(m_key_count);
|
||||
m_keyed_shapes[id].push_back(shape);
|
||||
m_key_ids[shape->title] = id;
|
||||
}
|
||||
}
|
||||
m_key_state.resize(m_key_count);
|
||||
// Don't memset a vector<bool>, they're special, and not in a good way
|
||||
for(int i=0; i != m_key_count; i++)
|
||||
m_key_state[i] = false;
|
||||
|
||||
m_sx = m_sy = 0;
|
||||
m_scale = 1.0;
|
||||
}
|
||||
|
||||
screen_device_svg_renderer::~screen_device_svg_renderer()
|
||||
@ -90,33 +138,83 @@ int screen_device_svg_renderer::height() const
|
||||
return int(m_image->height + 0.5);
|
||||
}
|
||||
|
||||
void screen_device_svg_renderer::render_state(std::vector<UINT32> &dest, const std::vector<bool> &state)
|
||||
{
|
||||
for(int key = 0; key != m_key_count; key++) {
|
||||
if (state[key])
|
||||
for(auto s : m_keyed_shapes[key])
|
||||
s->flags |= NSVG_FLAGS_VISIBLE;
|
||||
else
|
||||
for(auto s : m_keyed_shapes[key])
|
||||
s->flags &= ~NSVG_FLAGS_VISIBLE;
|
||||
}
|
||||
|
||||
nsvgRasterize(m_rasterizer, m_image, 0, 0, m_scale, (unsigned char *)&dest[0], m_sx, m_sy, m_sx*4);
|
||||
|
||||
// Nanosvg generates non-premultiplied alpha, so remultiply by
|
||||
// alpha to "blend" against a black background. Plus align the
|
||||
// channel order to what we do.
|
||||
|
||||
UINT8 *image = (UINT8 *)&dest[0];
|
||||
for(unsigned int pixel=0; pixel != m_sy*m_sx; pixel++) {
|
||||
UINT8 r = image[0];
|
||||
UINT8 g = image[1];
|
||||
UINT8 b = image[2];
|
||||
UINT8 a = image[3];
|
||||
if(a != 0xff) {
|
||||
r = r*a/255;
|
||||
g = g*a/255;
|
||||
b = b*a/255;
|
||||
}
|
||||
UINT32 color = 0xff000000 | (r << 16) | (g << 8) | (b << 0);
|
||||
*(UINT32 *)image = color;
|
||||
image += 4;
|
||||
}
|
||||
}
|
||||
|
||||
void screen_device_svg_renderer::blit(bitmap_rgb32 &bitmap, const cached_bitmap &src) const
|
||||
{
|
||||
const UINT32 *s = &src.image[0];
|
||||
for(int y=0; y<src.sy; y++) {
|
||||
UINT32 *d = &bitmap.pix(y + src.y, src.x);
|
||||
for(int x=0; x<src.sx; x++) {
|
||||
UINT32 c = *s++;
|
||||
if(c)
|
||||
*d = c;
|
||||
d++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int screen_device_svg_renderer::render(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect)
|
||||
{
|
||||
double sx = double(bitmap.width())/m_image->width;
|
||||
double sy = double(bitmap.height())/m_image->height;
|
||||
double sz = sx > sy ? sy : sx;
|
||||
nsvgRasterize(m_rasterizer, m_image, 0, 0, sz, (unsigned char *)bitmap.raw_pixptr(0, 0), bitmap.width(), bitmap.height(), bitmap.rowbytes());
|
||||
int nsx = bitmap.width();
|
||||
int nsy = bitmap.height();
|
||||
|
||||
// Annoyingly, nanosvg doesn't use the same byte order than our bitmaps
|
||||
if(nsx != m_sx || nsy != m_sy) {
|
||||
m_sx = nsx;
|
||||
m_sy = nsy;
|
||||
double sx = double(m_sx)/m_image->width;
|
||||
double sy = double(m_sy)/m_image->height;
|
||||
m_scale = sx > sy ? sy : sx;
|
||||
m_background.resize(m_sx * m_sy);
|
||||
rebuild_cache();
|
||||
}
|
||||
|
||||
// It generates non-premultiplied alpha anyway, so remultiply by
|
||||
// alpha to "blend" against a black background.
|
||||
for(unsigned int y = 0; y < m_sy; y++)
|
||||
memcpy(bitmap.raw_pixptr(y, 0), &m_background[y * m_sx], m_sx * 4);
|
||||
|
||||
for(unsigned int y=0; y<bitmap.height(); y++) {
|
||||
UINT8 *image = (UINT8 *)bitmap.raw_pixptr(y, 0);
|
||||
for(unsigned int x=0; x<bitmap.width(); x++) {
|
||||
UINT8 r = image[0];
|
||||
UINT8 g = image[1];
|
||||
UINT8 b = image[2];
|
||||
UINT8 a = image[3];
|
||||
if(a != 0xff) {
|
||||
r = r*a/255;
|
||||
g = g*a/255;
|
||||
b = b*a/255;
|
||||
}
|
||||
UINT32 color = 0xff000000 | (r << 16) | (g << 8) | (b << 0);
|
||||
*(UINT32 *)image = color;
|
||||
image += 4;
|
||||
std::list<int> to_draw;
|
||||
for(int key = 0; key != m_key_count; key++)
|
||||
if(m_key_state[key])
|
||||
to_draw.push_back(key);
|
||||
while(!to_draw.empty()) {
|
||||
int key = to_draw.front();
|
||||
to_draw.pop_front();
|
||||
blit(bitmap, m_cache[key]);
|
||||
for(auto p : m_cache[key].pairs) {
|
||||
if(m_key_state[p.key])
|
||||
to_draw.push_back(p.cache_entry);
|
||||
}
|
||||
}
|
||||
|
||||
@ -130,15 +228,297 @@ void screen_device_svg_renderer::output_notifier(const char *outname, INT32 valu
|
||||
|
||||
void screen_device_svg_renderer::output_change(const char *outname, INT32 value)
|
||||
{
|
||||
auto l = m_keyed_shapes.find(outname);
|
||||
if (l == m_keyed_shapes.end())
|
||||
auto l = m_key_ids.find(outname);
|
||||
if (l == m_key_ids.end())
|
||||
return;
|
||||
if (value)
|
||||
for(auto s : l->second)
|
||||
s->flags |= NSVG_FLAGS_VISIBLE;
|
||||
else
|
||||
for(auto s : l->second)
|
||||
s->flags &= ~NSVG_FLAGS_VISIBLE;
|
||||
m_key_state[l->second] = value;
|
||||
}
|
||||
|
||||
void screen_device_svg_renderer::compute_initial_bboxes(std::vector<bbox> &bboxes)
|
||||
{
|
||||
bboxes.resize(m_key_count);
|
||||
for(int key = 0; key != m_key_count; key++) {
|
||||
int x0, y0, x1, y1;
|
||||
x0 = y0 = x1 = y1 = -1;
|
||||
for(auto s : m_keyed_shapes[key]) {
|
||||
int xx0 = int(floor(s->bounds[0]*m_scale));
|
||||
int yy0 = int(floor(s->bounds[1]*m_scale));
|
||||
int xx1 = int(ceil (s->bounds[2]*m_scale)) + 1;
|
||||
int yy1 = int(ceil (s->bounds[3]*m_scale)) + 1;
|
||||
if(xx0 < 0)
|
||||
xx0 = 0;
|
||||
if(xx0 >= m_sx)
|
||||
xx0 = m_sx - 1;
|
||||
if(xx1 < 0)
|
||||
xx1 = 0;
|
||||
if(xx1 >= m_sx)
|
||||
xx1 = m_sx - 1;
|
||||
if(yy0 < 0)
|
||||
yy0 = 0;
|
||||
if(yy0 >= m_sy)
|
||||
yy0 = m_sy - 1;
|
||||
if(yy1 < 0)
|
||||
yy1 = 0;
|
||||
if(yy1 >= m_sy)
|
||||
yy1 = m_sy - 1;
|
||||
|
||||
if(x0 == -1) {
|
||||
x0 = xx0;
|
||||
y0 = yy0;
|
||||
x1 = xx1;
|
||||
y1 = yy1;
|
||||
} else {
|
||||
if(xx0 < x0)
|
||||
x0 = xx0;
|
||||
if(yy0 < y0)
|
||||
y0 = yy0;
|
||||
if(xx1 > x1)
|
||||
x1 = xx1;
|
||||
if(yy1 > y1)
|
||||
y1 = yy1;
|
||||
}
|
||||
}
|
||||
bboxes[key].x0 = x0;
|
||||
bboxes[key].y0 = y0;
|
||||
bboxes[key].x1 = x1;
|
||||
bboxes[key].y1 = y1;
|
||||
}
|
||||
}
|
||||
|
||||
void screen_device_svg_renderer::compute_diff_image(const std::vector<UINT32> &rend, const bbox &bb, cached_bitmap &dest) const
|
||||
{
|
||||
int x0, y0, x1, y1;
|
||||
x0 = y0 = x1 = y1 = -1;
|
||||
for(int y = bb.y0; y != bb.y1; y++) {
|
||||
const UINT32 *src1 = &m_background[bb.x0 + y * m_sx];
|
||||
const UINT32 *src2 = &rend[bb.x0 + y * m_sx];
|
||||
for(int x = bb.x0; x != bb.x1; x++) {
|
||||
if(*src1 != *src2) {
|
||||
if(x0 == -1) {
|
||||
x0 = x1 = x;
|
||||
y0 = y1 = y;
|
||||
} else {
|
||||
if(x < x0)
|
||||
x0 = x;
|
||||
if(y < y0)
|
||||
y0 = y;
|
||||
if(x > x1)
|
||||
x1 = x;
|
||||
if(y > y1)
|
||||
y1 = y;
|
||||
}
|
||||
}
|
||||
src1++;
|
||||
src2++;
|
||||
}
|
||||
}
|
||||
if(x0 == -1) {
|
||||
dest.x = dest.y = dest.sx = dest.sy = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
dest.x = x0;
|
||||
dest.y = y0;
|
||||
dest.sx = x1+1-x0;
|
||||
dest.sy = y1+1-y0;
|
||||
dest.image.resize(dest.sx * dest.sy);
|
||||
UINT32 *dst = &dest.image[0];
|
||||
for(int y = 0; y != dest.sy; y++) {
|
||||
const UINT32 *src1 = &m_background[dest.x + (y + dest.y) * m_sx];
|
||||
const UINT32 *src2 = &rend[dest.x + (y + dest.y) * m_sx];
|
||||
for(int x = 0; x != dest.sx; x++) {
|
||||
if(*src1 != *src2)
|
||||
*dst = *src2;
|
||||
else
|
||||
*dst = 0x00000000;
|
||||
src1++;
|
||||
src2++;
|
||||
dst++;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool screen_device_svg_renderer::compute_mask_intersection_bbox(int key1, int key2, bbox &bb) const
|
||||
{
|
||||
const cached_bitmap &c1 = m_cache[key1];
|
||||
const cached_bitmap &c2 = m_cache[key2];
|
||||
if(c1.x >= c2.x + c2.sx ||
|
||||
c1.x + c1.sx <= c2.x ||
|
||||
c1.y >= c2.y + c2.sy ||
|
||||
c1.y + c1.sy <= c2.y)
|
||||
return false;
|
||||
int cx0 = c1.x > c2.x ? c1.x : c2.x;
|
||||
int cy0 = c1.y > c2.y ? c1.y : c2.y;
|
||||
int cx1 = c1.x + c1.sx < c2.x + c2.sx ? c1.x + c1.sx : c2.x + c2.sx;
|
||||
int cy1 = c1.y + c1.sy < c2.y + c2.sy ? c1.y + c1.sy : c2.y + c2.sy;
|
||||
|
||||
int x0, y0, x1, y1;
|
||||
x0 = y0 = x1 = y1 = -1;
|
||||
|
||||
for(int y = cy0; y < cy1; y++) {
|
||||
const UINT32 *src1 = &c1.image[(cx0 - c1.x) + c1.sx * (y - c1.y)];
|
||||
const UINT32 *src2 = &c2.image[(cx0 - c2.x) + c2.sx * (y - c2.y)];
|
||||
for(int x = cx0; x < cx1; x++) {
|
||||
if(*src1 && *src2 && *src1 != *src2) {
|
||||
if(x0 == -1) {
|
||||
x0 = x1 = x;
|
||||
y0 = y1 = y;
|
||||
} else {
|
||||
if(x < x0)
|
||||
x0 = x;
|
||||
if(y < y0)
|
||||
y0 = y;
|
||||
if(x > x1)
|
||||
x1 = x;
|
||||
if(y > y1)
|
||||
y1 = y;
|
||||
}
|
||||
}
|
||||
src1++;
|
||||
src2++;
|
||||
}
|
||||
}
|
||||
if(x0 == -1)
|
||||
return false;
|
||||
bb.x0 = x0;
|
||||
bb.x1 = x1;
|
||||
bb.y0 = y0;
|
||||
bb.y1 = y1;
|
||||
return true;
|
||||
}
|
||||
|
||||
void screen_device_svg_renderer::compute_dual_diff_image(const std::vector<UINT32> &rend, const bbox &bb, const cached_bitmap &src1, const cached_bitmap &src2, cached_bitmap &dest) const
|
||||
{
|
||||
dest.x = bb.x0;
|
||||
dest.y = bb.y0;
|
||||
dest.sx = bb.x1 - bb.x0 + 1;
|
||||
dest.sy = bb.y1 - bb.y0 + 1;
|
||||
dest.image.resize(dest.sx*dest.sy);
|
||||
for(int y = 0; y != dest.sy; y++) {
|
||||
const UINT32 *psrc1 = &src1.image[(dest.x - src1.x) + src1.sx * (y + dest.y - src1.y)];
|
||||
const UINT32 *psrc2 = &src2.image[(dest.x - src2.x) + src2.sx * (y + dest.y - src2.y)];
|
||||
const UINT32 *psrcr = &rend [ dest.x + m_sx * (y + dest.y )];
|
||||
UINT32 *pdest = &dest.image[ dest.sx * y ];
|
||||
for(int x = 0; x != dest.sx; x++) {
|
||||
if(*psrc1 && *psrc2 && *psrc1 != *psrc2)
|
||||
*pdest = *psrcr;
|
||||
psrc1++;
|
||||
psrc2++;
|
||||
psrcr++;
|
||||
pdest++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void screen_device_svg_renderer::rebuild_cache()
|
||||
{
|
||||
m_cache.clear();
|
||||
std::vector<UINT32> rend(m_sx*m_sy);
|
||||
|
||||
// Render the background, e.g. with everything off
|
||||
std::vector<bool> state(m_key_count);
|
||||
for(int key=0; key != m_key_count; key++)
|
||||
state[key] = false;
|
||||
|
||||
render_state(m_background, state);
|
||||
|
||||
// Render each individual element independently. Try to reduce
|
||||
// the actual number of render passes with a greedy algorithm
|
||||
// using the element bounding boxes.
|
||||
std::vector<bbox> bboxes;
|
||||
compute_initial_bboxes(bboxes);
|
||||
|
||||
m_cache.resize(m_key_count);
|
||||
std::set<int> to_do;
|
||||
for(int key=0; key != m_key_count; key++)
|
||||
to_do.insert(key);
|
||||
|
||||
while(!to_do.empty()) {
|
||||
std::list<int> doing;
|
||||
for(int key : to_do) {
|
||||
for(int okey : doing) {
|
||||
// The bounding boxes include x1/y1, so the comparisons must be strict
|
||||
if(!(bboxes[key].x0 > bboxes[okey].x1 ||
|
||||
bboxes[key].x1 < bboxes[okey].x0 ||
|
||||
bboxes[key].y0 > bboxes[okey].y1 ||
|
||||
bboxes[key].y1 < bboxes[okey].y0))
|
||||
goto conflict;
|
||||
}
|
||||
doing.push_back(key);
|
||||
conflict:
|
||||
;
|
||||
}
|
||||
for(int key : doing)
|
||||
state[key] = true;
|
||||
render_state(rend, state);
|
||||
for(int key : doing) {
|
||||
state[key] = false;
|
||||
to_do.erase(key);
|
||||
}
|
||||
for(int key : doing)
|
||||
compute_diff_image(rend, bboxes[key], m_cache[key]);
|
||||
}
|
||||
|
||||
// Then it's time to pick up the interactions.
|
||||
int spos = 0;
|
||||
int epos = m_key_count;
|
||||
std::vector<std::list<int> > keys(m_key_count);
|
||||
std::vector<int> previous;
|
||||
for(int key = 0; key != m_key_count; key++)
|
||||
keys[key].push_back(key);
|
||||
int ckey = m_key_count;
|
||||
while(spos != epos) {
|
||||
for(int key = spos; key < epos-1; key++) {
|
||||
for(int key2 = keys[key].back()+1; key2 < m_key_count; key2++) {
|
||||
bbox bb;
|
||||
if(compute_mask_intersection_bbox(key, key2, bb)) {
|
||||
previous.resize(ckey+1);
|
||||
previous[ckey] = key;
|
||||
m_cache[key].pairs.push_back(paired_entry(key2, ckey));
|
||||
keys.push_back(keys[key]);
|
||||
keys.back().push_back(key2);
|
||||
bboxes.push_back(bb);
|
||||
ckey++;
|
||||
}
|
||||
}
|
||||
}
|
||||
m_cache.resize(ckey);
|
||||
std::set<int> to_do;
|
||||
for(int key = epos; key != ckey; key++)
|
||||
to_do.insert(key);
|
||||
|
||||
while(!to_do.empty()) {
|
||||
std::list<int> doing;
|
||||
for(int key : to_do) {
|
||||
for(int okey : doing) {
|
||||
// The bounding boxes include x1/y1, so the comparisons must be strict
|
||||
if(!(bboxes[key].x0 > bboxes[okey].x1 ||
|
||||
bboxes[key].x1 < bboxes[okey].x0 ||
|
||||
bboxes[key].y0 > bboxes[okey].y1 ||
|
||||
bboxes[key].y1 < bboxes[okey].y0))
|
||||
goto conflict2;
|
||||
}
|
||||
doing.push_back(key);
|
||||
conflict2:
|
||||
;
|
||||
}
|
||||
for(int key : doing)
|
||||
for(int akey : keys[key])
|
||||
state[akey] = true;
|
||||
|
||||
render_state(rend, state);
|
||||
for(int key : doing) {
|
||||
for(int akey : keys[key])
|
||||
state[akey] = false;
|
||||
to_do.erase(key);
|
||||
}
|
||||
for(int key : doing)
|
||||
compute_dual_diff_image(rend, bboxes[key], m_cache[previous[key]], m_cache[keys[key].back()], m_cache[key]);
|
||||
}
|
||||
spos = epos;
|
||||
epos = ckey;
|
||||
}
|
||||
}
|
||||
|
||||
//**************************************************************************
|
||||
|
Loading…
Reference in New Issue
Block a user