summaryrefslogtreecommitdiffstatshomepage
path: root/src/emu
diff options
context:
space:
mode:
author Olivier Galibert <galibert@pobox.com>2016-04-27 15:28:37 +0200
committer Olivier Galibert <galibert@pobox.com>2016-04-27 15:29:22 +0200
commit7d635eec4bdb6cc94fded371375f0c2e27749858 (patch)
treea459224b592ead9b7d197812c37d9dad6a8e36ad /src/emu
parentfe6b9390c7b7a8887db644e62d102976939cd922 (diff)
screen: Add svg shapes pre-computation [O. Galibert]
Diffstat (limited to 'src/emu')
-rw-r--r--src/emu/screen.cpp454
1 files changed, 417 insertions, 37 deletions
diff --git a/src/emu/screen.cpp b/src/emu/screen.cpp
index 8566e85f2b1..8000dc872cb 100644
--- a/src/emu/screen.cpp
+++ b/src/emu/screen.cpp
@@ -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;
+
+ int m_sx, m_sy;
+ double m_scale;
+ std::vector<UINT32> m_background;
- std::unordered_map<std::string, std::list<NSVGshape *>> m_keyed_shapes;
+ 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());
-
- // Annoyingly, nanosvg doesn't use the same byte order than our bitmaps
-
- // It generates non-premultiplied alpha anyway, so remultiply by
- // alpha to "blend" against a black background.
-
- 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;
+ int nsx = bitmap.width();
+ int nsy = bitmap.height();
+
+ 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();
+ }
+
+ for(unsigned int y = 0; y < m_sy; y++)
+ memcpy(bitmap.raw_pixptr(y, 0), &m_background[y * m_sx], m_sx * 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;
+ }
}
//**************************************************************************