Special-case render object allocation
[lsnes.git] / src / core / render.cpp
blob2ab0a2749057a726a14c5b4deca3ebf4a9520365
1 #include "lsnes.hpp"
2 #include <snes/snes.hpp>
4 #include "core/misc.hpp"
5 #include "core/png.hpp"
6 #include "core/render.hpp"
8 #include <sstream>
9 #include <list>
10 #include <iomanip>
11 #include <cstdint>
12 #include <string>
13 #include <map>
14 #include <vector>
16 #define TAG_ZEROWIDTH 0
17 #define TAG_NARROW 1
18 #define TAG_WIDE 2
19 #define TAG_TABULATION 3
20 #define TAG_WIDTH_MASK 3
21 #define TAG_LINECHANGE 4
23 extern const char* font_hex_data;
25 namespace
27 std::vector<uint32_t> font_glyph_data;
28 std::map<uint32_t, uint32_t> font_glyph_offsets;
30 uint32_t parse_word(const char* x)
32 char buf[9] = {0};
33 char* end;
34 memcpy(buf, x, 8);
35 unsigned long v = strtoul(buf, &end, 16);
36 if(end != buf + 8)
37 v = 0xFFFFFFFFUL;
38 //std::cerr << "Parse word " << buf << std::endl;
39 return v;
42 void init_font()
44 static bool iflag = false;
45 if(iflag)
46 return;
47 //Special glyph data.
48 font_glyph_data.resize(7);
49 //Space & Unknown.
50 font_glyph_data[0] = TAG_NARROW;
51 font_glyph_data[1] = 0;
52 font_glyph_data[2] = 0;
53 font_glyph_data[3] = 0;
54 font_glyph_data[4] = 0;
55 //Tabulation.
56 font_glyph_data[5] = TAG_TABULATION;
57 //Linefeed.
58 font_glyph_data[6] = TAG_ZEROWIDTH | TAG_LINECHANGE;
60 size_t lsptr = 0;
61 uint32_t lc = 1;
62 for(size_t i = 0;; i++) {
63 //Skip spaces.
64 switch(font_hex_data[i]) {
65 case ' ':
66 case '\t':
67 //Skip spaces at start of line.
68 if(lsptr == i)
69 lsptr++;
70 case '\r':
71 case '\n':
72 case '\0': {
73 char* end;
74 uint32_t cp;
75 size_t fdatastart;
76 //Is this a comment?
77 if(lsptr == i || font_hex_data[lsptr] == '#')
78 goto skip_line;
79 cp = strtoul(font_hex_data + lsptr, &end, 16);
80 if(*end != ':') {
81 messages << "Malformed line " << lc << " in font data" << std::endl;
82 goto skip_line;
84 fdatastart = end - font_hex_data + 1;
85 if(i - fdatastart == 32) {
86 //Narrow glyph.
87 font_glyph_offsets[cp] = font_glyph_data.size();
88 font_glyph_data.push_back(TAG_NARROW);
89 for(uint32_t k = 0; k < 4; k++)
90 font_glyph_data.push_back(parse_word(end + 1 + 8 * k));
91 } else if(i - fdatastart == 64) {
92 //Wide glyph.
93 font_glyph_offsets[cp] = font_glyph_data.size();
94 font_glyph_data.push_back(TAG_WIDE);
95 for(uint32_t k = 0; k < 8; k++)
96 font_glyph_data.push_back(parse_word(end + 1 + 8 * k));
97 } else {
98 messages << "Malformed line " << lc << " in font data" << std::endl;
99 goto skip_line;
101 skip_line:
102 if(font_hex_data[i] != '\r' || font_hex_data[i + 1] != '\n')
103 lc++;
104 lsptr = i + 1;
107 if(!font_hex_data[i])
108 break;
111 //Special characters.
112 font_glyph_offsets[9] = 5;
113 font_glyph_offsets[10] = 6;
114 font_glyph_offsets[32] = 0;
116 uint32_t glyphs = 0;
117 uint32_t glyphs_narrow = 0;
118 uint32_t glyphs_wide = 0;
119 uint32_t glyphs_special = 0;
120 for(auto i : font_glyph_offsets) {
121 if(font_glyph_data[i.second] == TAG_NARROW)
122 glyphs_narrow++;
123 else if(font_glyph_data[i.second] == TAG_WIDE)
124 glyphs_wide++;
125 else
126 glyphs_special++;
127 glyphs++;
129 messages << "Loaded font data: " << glyphs << " glyphs (" << glyphs_narrow << " narrow, " <<
130 glyphs_wide << " wide, " << glyphs_special << " special)." << std::endl;
131 iflag = true;
134 inline uint32_t find_font_glyph_offset(uint32_t cp)
136 return font_glyph_offsets.count(cp) ? font_glyph_offsets[cp] : 0;
139 inline uint32_t process_tag(uint32_t tag, int32_t& x, int32_t& y, int32_t orig_x, bool hdbl, bool vdbl)
141 uint32_t dwidth;
142 switch(tag & TAG_WIDTH_MASK) {
143 case TAG_ZEROWIDTH:
144 dwidth = 0;
145 break;
146 case TAG_NARROW:
147 dwidth = 8;
148 break;
149 case TAG_WIDE:
150 dwidth = 16;
151 break;
152 case TAG_TABULATION:
153 dwidth = 0x40 - (x & 0x3F);
154 break;
156 x += dwidth * (hdbl ? 2 : 1);
157 if(tag & TAG_LINECHANGE) {
158 y += 16 * (vdbl ? 2 : 1);
159 x = orig_x;
161 return dwidth;
164 inline bool is_visible(uint32_t tag)
166 return ((tag & TAG_WIDTH_MASK) == TAG_NARROW || (tag & TAG_WIDTH_MASK) == TAG_WIDE);
171 void do_init_font()
173 init_font();
176 std::pair<uint32_t, const uint32_t*> find_glyph(uint32_t codepoint, int32_t x, int32_t y, int32_t orig_x,
177 int32_t& next_x, int32_t& next_y, bool hdbl, bool vdbl) throw()
179 init_font();
180 next_x = x;
181 next_y = y;
182 uint32_t offset = find_font_glyph_offset(codepoint);
183 uint32_t tag = font_glyph_data[offset];
184 uint32_t dwidth = process_tag(tag, next_x, next_y, orig_x, hdbl, vdbl);
185 bool visible = is_visible(tag);
186 return std::pair<uint32_t, const uint32_t*>(dwidth, visible ? &font_glyph_data[offset + 1] : NULL);
189 render_object::~render_object() throw()
193 void render_text(struct screen& scr, int32_t x, int32_t y, const std::string& text, premultiplied_color fg,
194 premultiplied_color bg, bool hdbl, bool vdbl) throw(std::bad_alloc)
196 int32_t orig_x = x;
197 uint32_t unicode_code = 0;
198 uint8_t unicode_left = 0;
199 for(size_t i = 0; i < text.length(); i++) {
200 uint8_t ch = text[i];
201 if(ch < 128)
202 unicode_code = text[i];
203 else if(ch < 192) {
204 if(!unicode_left)
205 continue;
206 unicode_code = 64 * unicode_code + ch - 128;
207 if(--unicode_left)
208 continue;
209 } else if(ch < 224) {
210 unicode_code = ch - 192;
211 unicode_left = 1;
212 continue;
213 } else if(ch < 240) {
214 unicode_code = ch - 224;
215 unicode_left = 2;
216 continue;
217 } else if(ch < 248) {
218 unicode_code = ch - 240;
219 unicode_left = 3;
220 continue;
221 } else
222 continue;
223 int32_t next_x, next_y;
224 auto p = find_glyph(unicode_code, x, y, orig_x, next_x, next_y, hdbl, vdbl);
225 uint32_t dx = 0;
226 uint32_t dw = p.first * (hdbl ? 2 : 1);
227 uint32_t dy = 0;
228 uint32_t dh = 16 * (vdbl ? 2 : 1);
229 uint32_t cx = static_cast<uint32_t>(static_cast<int32_t>(scr.originx) + x);
230 uint32_t cy = static_cast<uint32_t>(static_cast<int32_t>(scr.originy) + y);
231 while(cx > scr.width && dw > 0) {
232 dx++;
233 dw--;
234 cx++;
236 while(cy > scr.height && dh > 0) {
237 dy++;
238 dh--;
239 cy++;
241 while(cx + dw > scr.width && dw > 0)
242 dw--;
243 while(cy + dh > scr.height && dh > 0)
244 dh--;
245 if(!dw || !dh)
246 continue; //Outside screen.
248 uint32_t rshift = (p.first == 16) ? (vdbl ? 2 : 1) : (vdbl ? 3 : 2);
249 uint32_t rishift = (p.first == 16) ? 4 : 3;
250 uint32_t xshift = hdbl ? 1 : 0;
251 uint32_t yshift = vdbl ? 1 : 0;
252 uint32_t b = dx & 1;
254 if(p.second == NULL) {
255 //Blank glyph.
256 for(uint32_t j = 0; j < dh; j++) {
257 uint32_t* base = scr.rowptr(cy + j) + cx;
258 for(uint32_t i = 0; i < dw; i++)
259 bg.apply(base[i]);
261 } else {
262 for(uint32_t j = 0; j < dh; j++) {
263 uint32_t dataword = p.second[(dy + j) >> rshift];
264 uint32_t* base = scr.rowptr(cy + j) + cx;
265 uint32_t rbit = (~((dy + j) >> yshift << rishift) & 31) - (dx >> xshift);
266 if(hdbl) {
267 for(uint32_t i = 0; i < dw; i++)
268 if((dataword >> (rbit - ((i + b) >> 1))) & 1)
269 fg.apply(base[i]);
270 else
271 bg.apply(base[i]);
272 } else {
273 for(uint32_t i = 0; i < dw; i++)
274 if((dataword >> (rbit - i)) & 1)
275 fg.apply(base[i]);
276 else
277 bg.apply(base[i]);
281 x = next_x;
282 y = next_y;
286 void render_queue::add(struct render_object& obj) throw(std::bad_alloc)
288 struct node* n = reinterpret_cast<struct node*>(alloc(sizeof(node)));
289 n->obj = &obj;
290 n->next = NULL;
291 if(queue_tail)
292 queue_tail = queue_tail->next = n;
293 else
294 queue_head = queue_tail = n;
297 void render_queue::run(struct screen& scr) throw()
299 struct node* tmp = queue_head;
300 while(tmp) {
301 try {
302 (*(tmp->obj))(scr);
303 tmp = tmp->next;
304 } catch(...) {
309 void render_queue::clear() throw()
311 while(queue_head) {
312 queue_head->obj->~render_object();
313 queue_head = queue_head->next;
315 //Release all memory for reuse.
316 memory_allocated = 0;
317 pages = 0;
318 queue_tail = NULL;
321 void* render_queue::alloc(size_t block) throw(std::bad_alloc)
323 block = (block + 15) / 16 * 16;
324 if(block > RENDER_PAGE_SIZE)
325 throw std::bad_alloc();
326 if(pages == 0 || memory_allocated + block > pages * RENDER_PAGE_SIZE) {
327 memory_allocated = pages * RENDER_PAGE_SIZE;
328 memory[pages++];
330 void* mem = memory[memory_allocated / RENDER_PAGE_SIZE].content + (memory_allocated % RENDER_PAGE_SIZE);
331 memory_allocated += block;
332 return mem;
335 render_queue::render_queue() throw()
337 queue_head = NULL;
338 queue_tail = NULL;
339 memory_allocated = 0;
340 pages = 0;
343 render_queue::~render_queue() throw()
345 clear();
349 struct render_object* queue_head;
350 size_t memory_allocated;
351 struct page { char content[RENDER_PAGE_SIZE]; };
352 std::map<size_t, page> memory;
355 uint32_t screen::make_color(uint8_t r, uint8_t g, uint8_t b) throw()
357 uint32_t _r = r;
358 uint32_t _g = g;
359 uint32_t _b = b;
360 return (_r << 16) + (_g << 8) + _b;
363 lcscreen::lcscreen(const uint32_t* mem, bool hires, bool interlace, bool overscan, bool region) throw()
365 uint32_t dataoffset = 0;
366 width = hires ? 512 : 256;
367 height = 0;
368 if(region) {
369 //PAL.
370 height = 239;
371 dataoffset = overscan ? 9 : 1;
372 } else {
373 //presumably NTSC.
374 height = 224;
375 dataoffset = overscan ? 16 : 9;
377 if(interlace)
378 height <<= 1;
379 memory = mem + dataoffset * 1024;
380 pitch = interlace ? 512 : 1024;
381 user_memory = false;
384 lcscreen::lcscreen(const uint32_t* mem, uint32_t _width, uint32_t _height) throw()
386 width = _width;
387 height = _height;
388 memory = mem;
389 pitch = width;
390 user_memory = false;
393 lcscreen::lcscreen() throw()
395 width = 0;
396 height = 0;
397 memory = NULL;
398 user_memory = true;
399 pitch = 0;
400 allocated = 0;
403 lcscreen::lcscreen(const lcscreen& ls) throw(std::bad_alloc)
405 width = ls.width;
406 height = ls.height;
407 pitch = width;
408 user_memory = true;
409 allocated = static_cast<size_t>(width) * height;
410 memory = new uint32_t[allocated];
411 for(size_t l = 0; l < height; l++)
412 memcpy(const_cast<uint32_t*>(memory + l * width), ls.memory + l * ls.pitch, 4 * width);
415 lcscreen& lcscreen::operator=(const lcscreen& ls) throw(std::bad_alloc, std::runtime_error)
417 if(!user_memory)
418 throw std::runtime_error("Can't copy to non-user memory");
419 if(this == &ls)
420 return *this;
421 if(allocated < static_cast<size_t>(ls.width) * ls.height) {
422 size_t p_allocated = static_cast<size_t>(ls.width) * ls.height;
423 memory = new uint32_t[p_allocated];
424 allocated = p_allocated;
426 width = ls.width;
427 height = ls.height;
428 pitch = width;
429 for(size_t l = 0; l < height; l++)
430 memcpy(const_cast<uint32_t*>(memory + l * width), ls.memory + l * ls.pitch, 4 * width);
431 return *this;
434 lcscreen::~lcscreen()
436 if(user_memory)
437 delete[] const_cast<uint32_t*>(memory);
440 void lcscreen::load(const std::vector<char>& data) throw(std::bad_alloc, std::runtime_error)
442 if(!user_memory)
443 throw std::runtime_error("Can't load to non-user memory");
444 const uint8_t* data2 = reinterpret_cast<const uint8_t*>(&data[0]);
445 if(data.size() < 2)
446 throw std::runtime_error("Corrupt saved screenshot data");
447 uint32_t _width = static_cast<uint32_t>(data2[0]) * 256 + static_cast<uint32_t>(data2[1]);
448 if(_width > 1 && data.size() % (3 * _width) != 2)
449 throw std::runtime_error("Corrupt saved screenshot data");
450 uint32_t _height = (data.size() - 2) / (3 * _width);
451 if(allocated < static_cast<size_t>(_width) * _height) {
452 size_t p_allocated = static_cast<size_t>(_width) * _height;
453 memory = new uint32_t[p_allocated];
454 allocated = p_allocated;
456 uint32_t* mem = const_cast<uint32_t*>(memory);
457 width = _width;
458 height = _height;
459 pitch = width;
460 for(size_t i = 0; i < (data.size() - 2) / 3; i++)
461 mem[i] = static_cast<uint32_t>(data2[2 + 3 * i]) * 65536 +
462 static_cast<uint32_t>(data2[2 + 3 * i + 1]) * 256 +
463 static_cast<uint32_t>(data2[2 + 3 * i + 2]);
466 void lcscreen::save(std::vector<char>& data) throw(std::bad_alloc)
468 data.resize(2 + 3 * static_cast<size_t>(width) * height);
469 uint8_t* data2 = reinterpret_cast<uint8_t*>(&data[0]);
470 data2[0] = (width >> 8);
471 data2[1] = width;
472 for(size_t i = 0; i < (data.size() - 2) / 3; i++) {
473 data[2 + 3 * i] = memory[(i / width) * pitch + (i % width)] >> 16;
474 data[2 + 3 * i + 1] = memory[(i / width) * pitch + (i % width)] >> 8;
475 data[2 + 3 * i + 2] = memory[(i / width) * pitch + (i % width)];
479 void lcscreen::save_png(const std::string& file) throw(std::bad_alloc, std::runtime_error)
481 uint8_t* buffer = new uint8_t[3 * static_cast<size_t>(width) * height];
482 for(uint32_t j = 0; j < height; j++)
483 for(uint32_t i = 0; i < width; i++) {
484 uint32_t word = memory[pitch * j + i];
485 uint32_t l = 1 + ((word >> 15) & 0xF);
486 uint32_t r = l * ((word >> 0) & 0x1F);
487 uint32_t g = l * ((word >> 5) & 0x1F);
488 uint32_t b = l * ((word >> 10) & 0x1F);
489 buffer[3 * static_cast<size_t>(width) * j + 3 * i + 0] = r * 255 / 496;
490 buffer[3 * static_cast<size_t>(width) * j + 3 * i + 1] = g * 255 / 496;
491 buffer[3 * static_cast<size_t>(width) * j + 3 * i + 2] = b * 255 / 496;
493 try {
494 save_png_data(file, buffer, width, height);
495 delete[] buffer;
496 } catch(...) {
497 delete[] buffer;
498 throw;
502 void screen::copy_from(lcscreen& scr, uint32_t hscale, uint32_t vscale) throw()
504 if(width < originx || height < originy) {
505 //Just clear the screen.
506 for(uint32_t y = 0; y < height; y++)
507 memset(rowptr(y), 0, 4 * width);
508 return;
510 uint32_t copyable_width = 0, copyable_height = 0;
511 if(hscale)
512 copyable_width = (width - originx) / hscale;
513 if(vscale)
514 copyable_height = (height - originy) / vscale;
515 copyable_width = (copyable_width > scr.width) ? scr.width : copyable_width;
516 copyable_height = (copyable_height > scr.height) ? scr.height : copyable_height;
517 for(uint32_t y = 0; y < height; y++)
518 memset(rowptr(y), 0, 4 * width);
519 for(uint32_t y = 0; y < copyable_height; y++) {
520 uint32_t line = y * vscale + originy;
521 uint32_t* ptr = rowptr(line) + originx;
522 const uint32_t* sbase = scr.memory + y * scr.pitch;
523 for(uint32_t x = 0; x < copyable_width; x++) {
524 uint32_t c = palette[sbase[x] & 0x7FFFF];
525 for(uint32_t i = 0; i < hscale; i++)
526 *(ptr++) = c;
528 for(uint32_t j = 1; j < vscale; j++)
529 memcpy(rowptr(line + j) + originx, rowptr(line) + originx, 4 * hscale * copyable_width);
533 void screen::reallocate(uint32_t _width, uint32_t _height, bool upside_down) throw(std::bad_alloc)
535 if(_width == width && _height == height)
536 return;
537 if(!_width || !_height) {
538 width = height = originx = originy = pitch = 0;
539 if(memory && !user_memory)
540 delete[] memory;
541 memory = NULL;
542 user_memory = false;
543 flipped = upside_down;
544 return;
546 uint32_t* newmem = new uint32_t[_width * _height];
547 width = _width;
548 height = _height;
549 pitch = 4 * _width;
550 if(memory && !user_memory)
551 delete[] memory;
552 memory = newmem;
553 user_memory = false;
554 flipped = upside_down;
557 void screen::set(uint32_t* _memory, uint32_t _width, uint32_t _height, uint32_t _pitch) throw()
559 if(memory && !user_memory)
560 delete[] memory;
561 width = _width;
562 height = _height;
563 pitch = _pitch;
564 user_memory = true;
565 memory = _memory;
566 flipped = false;
569 void screen::set_origin(uint32_t _originx, uint32_t _originy) throw()
571 originx = _originx;
572 originy = _originy;
576 uint32_t* screen::rowptr(uint32_t row) throw()
578 if(flipped)
579 row = height - row - 1;
580 return reinterpret_cast<uint32_t*>(reinterpret_cast<uint8_t*>(memory) + row * pitch);
583 screen::screen() throw()
585 memory = NULL;
586 width = height = originx = originy = pitch = 0;
587 user_memory = false;
588 flipped = false;
589 palette = NULL;
590 set_palette(16, 8, 0);
593 screen::~screen() throw()
595 if(memory && !user_memory)
596 delete[] memory;
597 delete[] palette;
600 void clip_range(uint32_t origin, uint32_t size, int32_t base, int32_t& minc, int32_t& maxc) throw()
602 int64_t _origin = origin;
603 int64_t _size = size;
604 int64_t _base = base;
605 int64_t _minc = minc;
606 int64_t _maxc = maxc;
607 int64_t mincoordinate = _base + _origin + _minc;
608 int64_t maxcoordinate = _base + _origin + _maxc;
609 if(mincoordinate < 0)
610 _minc = _minc - mincoordinate;
611 if(maxcoordinate > _size)
612 _maxc = _maxc - (maxcoordinate - _size);
613 if(_minc >= maxc) {
614 minc = 0;
615 maxc = 0;
616 } else {
617 minc = _minc;
618 maxc = _maxc;
622 void screen::set_palette(uint32_t r, uint32_t g, uint32_t b)
624 if(!palette)
625 palette = new uint32_t[0x80000];
626 else if(r == palette_r && g == palette_g && b == palette_b)
627 return;
628 for(size_t i = 0; i < static_cast<size_t>(width) * height; i++) {
629 uint32_t word = memory[i];
630 uint32_t R = (word >> palette_r) & 0xFF;
631 uint32_t G = (word >> palette_g) & 0xFF;
632 uint32_t B = (word >> palette_b) & 0xFF;
633 memory[i] = (R << r) | (G << g) | (B << b);
635 for(unsigned i = 0; i < 0x80000; i++) {
636 unsigned l = 1 + ((i >> 15) & 0xF);
637 unsigned R = (i >> 0) & 0x1F;
638 unsigned G = (i >> 5) & 0x1F;
639 unsigned B = (i >> 10) & 0x1F;
640 double _l = static_cast<double>(l);
641 double m = 255.0 / 496.0;
642 R = floor(m * R * _l + 0.5);
643 G = floor(m * G * _l + 0.5);
644 B = floor(m * B * _l + 0.5);
645 palette[i] = (R << r) | (G << g) | (B << b);
647 palette_r = r;
648 palette_g = g;
649 palette_b = b;
652 void premultiplied_color::set_palette(unsigned rshift, unsigned gshift, unsigned bshift) throw()
654 uint32_t r = (orig >> 16) & 0xFF;
655 uint32_t g = (orig >> 8) & 0xFF;
656 uint32_t b = orig & 0xFF;
657 uint32_t color = (r << rshift) | (g << gshift) | (b << bshift);
658 hi = color & 0xFF00FF;
659 lo = (color & 0xFF00FF00) >> 8;
660 hi *= origa;
661 lo *= origa;