From 7d635eec4bdb6cc94fded371375f0c2e27749858 Mon Sep 17 00:00:00 2001 From: Olivier Galibert Date: Wed, 27 Apr 2016 15:28:37 +0200 Subject: [PATCH] screen: Add svg shapes pre-computation [O. Galibert] --- src/emu/screen.cpp | 448 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 414 insertions(+), 34 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 #include - +#include //************************************************************************** // 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 image; + std::vector pairs; + }; + + struct bbox { + int x0, y0, x1, y1; + }; + NSVGimage *m_image; NSVGrasterizer *m_rasterizer; + std::vector m_key_state; + std::vector> m_keyed_shapes; + std::unordered_map m_key_ids; + int m_key_count; - std::unordered_map> m_keyed_shapes; + int m_sx, m_sy; + double m_scale; + std::vector m_background; + + std::vector m_cache; void output_change(const char *outname, INT32 value); + void render_state(std::vector &dest, const std::vector &state); + void compute_initial_bboxes(std::vector &bboxes); + bool compute_mask_intersection_bbox(int key1, int key2, bbox &bb) const; + void compute_diff_image(const std::vector &rend, const bbox &bb, cached_bitmap &dest) const; + void compute_dual_diff_image(const std::vector &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, 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 &dest, const std::vector &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; ywidth; - 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 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 &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 &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 &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 rend(m_sx*m_sy); + + // Render the background, e.g. with everything off + std::vector 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 bboxes; + compute_initial_bboxes(bboxes); + + m_cache.resize(m_key_count); + std::set to_do; + for(int key=0; key != m_key_count; key++) + to_do.insert(key); + + while(!to_do.empty()) { + std::list 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 > keys(m_key_count); + std::vector 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 to_do; + for(int key = epos; key != ckey; key++) + to_do.insert(key); + + while(!to_do.empty()) { + std::list 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; + } } //**************************************************************************