1 // Written in the D programming language.
2 
3 /**
4 DLANGUI library.
5 
6 This module contains opengl based drawing buffer implementation.
7 
8 To enable OpenGL support, build with version(USE_OPENGL);
9 
10 Synopsis:
11 
12 ----
13 import dlangui.graphics.gldrawbuf;
14 
15 ----
16 
17 Copyright: Vadim Lopatin, 2014
18 License:   $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
19 Authors:   $(WEB coolreader.org, Vadim Lopatin)
20 */
21 module dlangui.graphics.gldrawbuf;
22 
23 version (USE_OPENGL) {
24 
25 import dlangui.graphics.drawbuf;
26 import dlangui.core.logger;
27 private import dlangui.graphics.glsupport;
28 private import std.algorithm;
29 
30 /// drawing buffer - image container which allows to perform some drawing operations
31 class GLDrawBuf : DrawBuf {
32     // width
33     protected int _dx;
34     // height
35     protected int _dy;
36     protected bool _framebuffer; // not yet supported
37     protected uint _framebufferId; // not yet supported
38     protected Scene _scene;
39 
40     /// get current scene (exists only between beforeDrawing() and afterDrawing() calls
41     @property Scene scene() { return _scene; }
42 
43     this(int dx, int dy, bool framebuffer = false) {
44         _dx = dx;
45         _dy = dy;
46         _framebuffer = framebuffer;
47     }
48 
49     /// returns current width
50     @property override int width() { return _dx; }
51     /// returns current height
52     @property override int height() { return _dy; }
53 
54     /// reserved for hardware-accelerated drawing - begins drawing batch
55     override void beforeDrawing() {
56         if (_scene !is null) {
57             _scene.reset();
58         }
59         _scene = new Scene();
60     }
61 
62     /// reserved for hardware-accelerated drawing - ends drawing batch
63     override void afterDrawing() { 
64         setOrthoProjection(_dx, _dy);
65         _scene.draw();
66         flushGL();
67         destroy(_scene);
68         _scene = null;
69     }
70 
71     /// resize buffer
72     override void resize(int width, int height) {
73         _dx = width;
74         _dy = height;
75     }
76 
77     /// fill the whole buffer with solid color (no clipping applied)
78     override void fill(uint color) {
79         assert(_scene !is null);
80         _scene.add(new SolidRectSceneItem(Rect(0, 0, _dx, _dy), color));
81     }
82     /// fill rectangle with solid color (clipping is applied)
83     override void fillRect(Rect rc, uint color) {
84         assert(_scene !is null);
85         _scene.add(new SolidRectSceneItem(rc, color));
86     }
87     /// draw 8bit alpha image - usually font glyph using specified color (clipping is applied)
88 	override void drawGlyph(int x, int y, Glyph * glyph, uint color) {
89         assert(_scene !is null);
90 		Rect dstrect = Rect(x,y, x + glyph.blackBoxX, y + glyph.blackBoxY);
91 		Rect srcrect = Rect(0, 0, glyph.blackBoxX, glyph.blackBoxY);
92 		//Log.v("GLDrawBuf.drawGlyph dst=", dstrect, " src=", srcrect, " color=", color);
93         if (applyClipping(dstrect, srcrect)) {
94             if (!glGlyphCache.get(glyph.id))
95                 glGlyphCache.put(glyph);
96             _scene.add(new GlyphSceneItem(glyph.id, dstrect, srcrect, color, null));
97         }
98     }
99     /// draw source buffer rectangle contents to destination buffer
100     override void drawFragment(int x, int y, DrawBuf src, Rect srcrect) {
101         assert(_scene !is null);
102         Rect dstrect = Rect(x, y, x + srcrect.width, y + srcrect.height);
103         //Log.v("GLDrawBuf.frawFragment dst=", dstrect, " src=", srcrect);
104         if (applyClipping(dstrect, srcrect)) {
105             if (!glImageCache.get(src.id))
106                 glImageCache.put(src);
107             _scene.add(new TextureSceneItem(src.id, dstrect, srcrect, 0xFFFFFF, 0, null, 0));
108         }
109     }
110     /// draw source buffer rectangle contents to destination buffer rectangle applying rescaling
111     override void drawRescaled(Rect dstrect, DrawBuf src, Rect srcrect) {
112         assert(_scene !is null);
113         //Log.v("GLDrawBuf.frawRescaled dst=", dstrect, " src=", srcrect);
114         if (applyClipping(dstrect, srcrect)) {
115             if (!glImageCache.get(src.id))
116                 glImageCache.put(src);
117             _scene.add(new TextureSceneItem(src.id, dstrect, srcrect, 0xFFFFFF, 0, null, 0));
118         }
119     }
120 
121     /// cleanup resources
122     override void clear() {
123         if (_framebuffer) {
124             // TODO: delete framebuffer
125         }
126     }
127     ~this() { clear(); }
128 }
129 
130 /// base class for all drawing scene items.
131 class SceneItem {
132     abstract void draw();
133 }
134 
135 /// Drawing scene (operations sheduled for drawing)
136 class Scene {
137     private SceneItem[] _items;
138     this() {
139         activeSceneCount++;
140     }
141     ~this() {
142         activeSceneCount--;
143     }
144     /// add new scene item to scene
145     void add(SceneItem item) {
146         _items ~= item;
147     }
148     /// draws all scene items and removes them from list
149     void draw() {
150         foreach(SceneItem item; _items)
151             item.draw();
152         reset();
153     }
154     /// resets scene for new drawing - deletes all items
155     void reset() {
156         foreach(ref SceneItem item; _items) {
157             destroy(item);
158             item = null;
159         }
160         _items.length = 0;
161     }
162 }
163 
164 private __gshared int activeSceneCount = 0;
165 bool hasActiveScene() {
166     return activeSceneCount > 0;
167 }
168 
169 immutable int MIN_TEX_SIZE = 64;
170 immutable int MAX_TEX_SIZE  = 4096;
171 private int nearestPOT(int n) {
172     for (int i = MIN_TEX_SIZE; i <= MAX_TEX_SIZE; i *= 2) {
173 		if (n <= i)
174 			return i;
175 	}
176 	return MIN_TEX_SIZE;
177 }
178 
179 /// object deletion listener callback function type
180 void onObjectDestroyedCallback(uint pobject) {
181 	glImageCache.onCachedObjectDeleted(pobject);
182 }
183 
184 /// object deletion listener callback function type
185 void onGlyphDestroyedCallback(uint pobject) {
186 	glGlyphCache.onCachedObjectDeleted(pobject);
187 }
188 
189 private __gshared GLImageCache glImageCache;
190 
191 private __gshared GLGlyphCache glGlyphCache;
192 
193 shared static this() {
194     glImageCache = new GLImageCache();
195     glGlyphCache = new GLGlyphCache();
196 }
197 
198 void LVGLClearImageCache() {
199 	glImageCache.clear();
200 	glGlyphCache.clear();
201 }
202 
203 /// OpenGL texture cache for ColorDrawBuf objects
204 private class GLImageCache {
205 
206     static class GLImageCacheItem {
207         private GLImageCachePage _page;
208 
209         @property GLImageCachePage page() { return _page; }
210             
211         uint _objectId;
212         Rect _rc;
213         bool _deleted;
214 
215         this(GLImageCachePage page, uint objectId) { _page = page; _objectId = objectId; }
216     };
217 
218     static class GLImageCachePage {
219         private GLImageCache _cache;
220         private int _tdx;
221         private int _tdy;
222         private ColorDrawBuf _drawbuf;
223         private int _currentLine;
224         private int _nextLine;
225         private int _x;
226         private bool _closed;
227         private bool _needUpdateTexture;
228         private uint _textureId;
229         private int _itemCount;
230 
231         this(GLImageCache cache, int dx, int dy) {
232             _cache = cache;
233             Log.v("created image cache page ", dx, "x", dy);
234             _tdx = nearestPOT(dx);
235             _tdy = nearestPOT(dy);
236             _itemCount = 0;
237         }
238 
239         ~this() {
240             if (_drawbuf) {
241                 destroy(_drawbuf);
242                 _drawbuf = null;
243             }
244             if (_textureId != 0) {
245                 deleteTexture(_textureId);
246                 _textureId = 0;
247             }
248         }
249 
250         void updateTexture() {
251             if (_drawbuf is null)
252                 return; // no draw buffer!!!
253             if (_textureId == 0) {
254                 //CRLog::debug("updateTexture - new texture");
255                 _textureId = genTexture();
256                 if (!_textureId)
257                     return;
258             }
259             //CRLog::debug("updateTexture - setting image %dx%d", _drawbuf.width, _drawbuf.height);
260             uint * pixels = _drawbuf.scanLine(0);
261             if (!setTextureImage(_textureId, _drawbuf.width, _drawbuf.height, cast(ubyte*)pixels)) {
262                 deleteTexture(_textureId);
263                 _textureId = 0;
264                 return;
265             }
266             _needUpdateTexture = false;
267             if (_closed) {
268                 destroy(_drawbuf);
269                 _drawbuf = null;
270             }
271         }
272 
273         void convertPixelFormat(GLImageCacheItem item) {
274             Rect rc = item._rc;
275             for (int y = rc.top - 1; y <= rc.bottom; y++) {
276                 uint * row = _drawbuf.scanLine(y);
277                 for (int x = rc.left - 1; x <= rc.right; x++) {
278                     uint cl = row[x];
279                     // invert A
280                     cl ^= 0xFF000000;
281                     // swap R and B
282                     uint r = (cl & 0x00FF0000) >> 16;
283                     uint b = (cl & 0x000000FF) << 16;
284                     row[x] = (cl & 0xFF00FF00) | r | b;
285                 }
286             }
287         }
288 
289         GLImageCacheItem reserveSpace(uint objectId, int width, int height) {
290             GLImageCacheItem cacheItem = new GLImageCacheItem(this, objectId);
291             if (_closed)
292                 return null;
293 
294             // next line if necessary
295             if (_x + width + 2 > _tdx) {
296                 // move to next line
297                 _currentLine = _nextLine;
298                 _x = 0;
299             }
300             // check if no room left for glyph height
301             if (_currentLine + height + 2 > _tdy) {
302                 _closed = true;
303                 return null;
304             }
305             cacheItem._rc = Rect(_x + 1, _currentLine + 1, _x + width + 1, _currentLine + height + 1);
306             if (height && width) {
307                 if (_nextLine < _currentLine + height + 2)
308                     _nextLine = _currentLine + height + 2;
309                 if (!_drawbuf) {
310                     _drawbuf = new ColorDrawBuf(_tdx, _tdy);
311                     //_drawbuf.SetBackgroundColor(0x000000);
312                     //_drawbuf.SetTextColor(0xFFFFFF);
313                     _drawbuf.fill(0xFF000000);
314                 }
315                 _x += width + 1;
316                 _needUpdateTexture = true;
317             }
318             _itemCount++;
319             return cacheItem;
320         }
321         int deleteItem(GLImageCacheItem item) {
322             _itemCount--;
323             return _itemCount;
324         }
325         GLImageCacheItem addItem(DrawBuf buf) {
326             GLImageCacheItem cacheItem = reserveSpace(buf.id, buf.width, buf.height);
327             if (cacheItem is null)
328                 return null;
329             buf.onDestroyCallback = &onObjectDestroyedCallback;
330             _drawbuf.drawImage(cacheItem._rc.left, cacheItem._rc.top, buf);
331             convertPixelFormat(cacheItem);
332             _needUpdateTexture = true;
333             return cacheItem;
334         }
335         void drawItem(GLImageCacheItem item, Rect dstrc, Rect srcrc, uint color, uint options, Rect * clip, int rotationAngle) {
336             //CRLog::trace("drawing item at %d,%d %dx%d <= %d,%d %dx%d ", x, y, dx, dy, srcx, srcy, srcdx, srcdy);
337             if (_needUpdateTexture)
338                 updateTexture();
339             if (_textureId != 0) {
340                 if (!isTexture(_textureId)) {
341                     Log.e("Invalid texture ", _textureId);
342                     return;
343                 }
344                 //rotationAngle = 0;
345                 int rx = dstrc.middlex;
346                 int ry = dstrc.middley;
347                 if (rotationAngle) {
348                     //rotationAngle = 0;
349                     //setRotation(rx, ry, rotationAngle);
350                 }
351                 // convert coordinates to cached texture
352                 srcrc.offset(item._rc.left, item._rc.top);
353                 if (clip) {
354                     int srcw = srcrc.width();
355                     int srch = srcrc.height();
356                     int dstw = dstrc.width();
357                     int dsth = dstrc.height();
358                     if (dstw) {
359                         srcrc.left += clip.left * srcw / dstw;
360                         srcrc.right -= clip.right * srcw / dstw;
361                     }
362                     if (dsth) {
363                         srcrc.top += clip.top * srch / dsth;
364                         srcrc.bottom -= clip.bottom * srch / dsth;
365                     }
366                     dstrc.left += clip.left;
367                     dstrc.right -= clip.right;
368                     dstrc.top += clip.top;
369                     dstrc.bottom -= clip.bottom;
370                 }
371                 if (!dstrc.empty)
372                     drawColorAndTextureRect(_textureId, _tdx, _tdy, srcrc, dstrc, color, srcrc.width() != dstrc.width() || srcrc.height() != dstrc.height());
373                 //drawColorAndTextureRect(vertices, texcoords, color, _textureId);
374 
375                 if (rotationAngle) {
376                     // unset rotation
377                     setRotation(rx, ry, 0);
378                     //                glMatrixMode(GL_PROJECTION);
379                     //                glPopMatrix();
380                     //                checkError("pop matrix");
381                 }
382 
383             }
384         }
385         void close() {
386             _closed = true;
387             if (_needUpdateTexture)
388                 updateTexture();
389         }
390     }
391 
392     private GLImageCacheItem[uint] _map;
393     private GLImageCachePage[] _pages;
394     private GLImageCachePage _activePage;
395     private int tdx;
396     private int tdy;
397 
398     private void removePage(GLImageCachePage page) {
399         if (_activePage == page)
400             _activePage = null;
401         for (int i = 0; i < _pages.length; i++)
402             if (_pages[i] == page) {
403                 _pages.remove(i);
404                 break;
405             }
406         destroy(page);
407     }
408 
409     private void updateTextureSize() {
410         if (!tdx) {
411             // TODO
412             tdx = tdy = 1024; //getMaxTextureSize(); 
413             if (tdx > 1024)
414                 tdx = tdy = 1024;
415         }
416     }
417 
418     this() {
419     }
420     ~this() {
421         clear();
422     }
423     /// returns true if object exists in cache
424     bool get(uint obj) {
425         if (obj in _map)
426             return true;
427         return false;
428     }
429     /// put new object to cache
430     void put(DrawBuf img) {
431         updateTextureSize();
432         GLImageCacheItem res = null;
433         if (img.width <= tdx / 3 && img.height < tdy / 3) {
434             // trying to reuse common page for small images
435             if (_activePage is null) {
436                 _activePage = new GLImageCachePage(this, tdx, tdy);
437                 _pages ~= _activePage;
438             }
439             res = _activePage.addItem(img);
440             if (!res) {
441                 _activePage = new GLImageCachePage(this, tdx, tdy);
442                 _pages ~= _activePage;
443                 res = _activePage.addItem(img);
444             }
445         } else {
446             // use separate page for big image
447             GLImageCachePage page = new GLImageCachePage(this, img.width, img.height);
448             _pages ~= page;
449             res = page.addItem(img);
450             page.close();
451         }
452         _map[img.id] = res;
453     }
454     /// clears cache
455     void clear() {
456         for (int i = 0; i < _pages.length; i++) {
457             destroy(_pages[i]);
458             _pages[i] = null;
459         }
460         _pages.clear();
461         _map.clear();
462     }
463     /// draw cached item
464     void drawItem(uint objectId, Rect dstrc, Rect srcrc, uint color, int options, Rect * clip, int rotationAngle) {
465         if (objectId in _map) {
466             GLImageCacheItem item = _map[objectId];
467             item.page.drawItem(item, dstrc, srcrc, color, options, clip, rotationAngle);
468         }
469     }
470     /// handle cached object deletion, mark as deleted
471     void onCachedObjectDeleted(uint objectId) {
472         if (objectId in _map) {
473             GLImageCacheItem item = _map[objectId];
474             if (hasActiveScene()) {
475                 item._deleted = true;
476             } else {
477                 int itemsLeft = item.page.deleteItem(item);
478                 //CRLog::trace("itemsLeft = %d", itemsLeft);
479                 if (itemsLeft <= 0) {
480                     //CRLog::trace("removing page");
481                     removePage(item.page);
482                 }
483                 _map.remove(objectId);
484                 delete item;
485             }
486         }
487     }
488     /// remove deleted items - remove page if contains only deleted items
489     void removeDeletedItems() {
490         uint[] list;
491         foreach (GLImageCacheItem item; _map) {
492             if (item._deleted)
493                 list ~= item._objectId;
494         }
495         for (int i = 0 ; i < list.length; i++) {
496             onCachedObjectDeleted(list[i]);
497         }
498     }
499 };
500 
501 
502 
503 private class TextureSceneItem : SceneItem {
504 	private uint objectId;
505     //CacheableObject * img;
506     private Rect dstrc;
507     private Rect srcrc;
508 	private uint color;
509 	private uint options;
510 	private Rect * clip;
511     private int rotationAngle;
512 
513 	override void draw() {
514 		if (glImageCache)
515             glImageCache.drawItem(objectId, dstrc, srcrc, color, options, clip, rotationAngle);
516 	}
517 
518     this(uint _objectId, Rect _dstrc, Rect _srcrc, uint _color, uint _options, Rect * _clip, int _rotationAngle)
519 	{
520         objectId = _objectId;
521         dstrc = _dstrc;
522         srcrc = _srcrc;
523         color = _color;
524         options = _options;
525         clip = _clip;
526         rotationAngle = _rotationAngle;
527 	}
528 
529 	~this() {
530 	}
531 };
532 
533 
534 /// by some reason ALPHA texture does not work as expected
535 private immutable USE_RGBA_TEXTURE_FOR_GLYPHS = true;
536 
537 private class GLGlyphCache {
538 
539     static class GLGlyphCacheItem {
540         GLGlyphCachePage _page;
541     public:
542         @property GLGlyphCachePage page() { return _page; }
543         uint _objectId;
544         // image size
545         Rect _rc;
546         bool _deleted;
547         this(GLGlyphCachePage page, uint objectId) { _page = page; _objectId = objectId; }
548     };
549 
550     static class GLGlyphCachePage {
551         private GLGlyphCache _cache;
552         private int _tdx;
553         private int _tdy;
554         private GrayDrawBuf _drawbuf;
555         private int _currentLine;
556         private int _nextLine;
557         private int _x;
558         private bool _closed;
559         private bool _needUpdateTexture;
560         private uint _textureId;
561         private int _itemCount;
562 
563         this(GLGlyphCache cache, int dx, int dy) {
564             _cache = cache;
565             Log.v("created image cache page ", dx, "x", dy);
566             _tdx = nearestPOT(dx);
567             _tdy = nearestPOT(dy);
568             _itemCount = 0;
569         }
570 
571         ~this() {
572             if (_drawbuf) {
573                 destroy(_drawbuf);
574                 _drawbuf = null;
575             }
576             if (_textureId != 0) {
577                 deleteTexture(_textureId);
578                 _textureId = 0;
579             }
580         }
581 
582         static if (USE_RGBA_TEXTURE_FOR_GLYPHS) {
583             uint[] _rgbaBuffer;
584         }
585         void updateTexture() {
586             if (_drawbuf is null)
587                 return; // no draw buffer!!!
588             if (_textureId == 0) {
589                 //CRLog::debug("updateTexture - new texture");
590                 _textureId = genTexture();
591                 if (!_textureId)
592                     return;
593             }
594             //CRLog::debug("updateTexture - setting image %dx%d", _drawbuf.width, _drawbuf.height);
595             ubyte * pixels = _drawbuf.scanLine(0);
596             static if (USE_RGBA_TEXTURE_FOR_GLYPHS) {
597                 int len = _drawbuf.width * _drawbuf.height;
598                 _rgbaBuffer.length = len;
599                 for (int i = 0; i < len; i++)
600                     _rgbaBuffer[i] = ((cast(uint)pixels[i]) << 24) | 0x00FFFFFF;
601                 if (!setTextureImage(_textureId, _drawbuf.width, _drawbuf.height, cast(ubyte*)_rgbaBuffer.ptr)) {
602                     deleteTexture(_textureId);
603                     _textureId = 0;
604                     return;
605                 }
606             } else {
607                 if (!setTextureImageAlpha(_textureId, _drawbuf.width, _drawbuf.height, pixels)) {
608                     deleteTexture(_textureId);
609                     _textureId = 0;
610                     return;
611                 }
612             }
613             _needUpdateTexture = false;
614             if (_closed) {
615                 destroy(_drawbuf);
616                 _drawbuf = null;
617             }
618         }
619         GLGlyphCacheItem reserveSpace(uint objectId, int width, int height) {
620             GLGlyphCacheItem cacheItem = new GLGlyphCacheItem(this, objectId);
621             if (_closed)
622                 return null;
623 
624             // next line if necessary
625             if (_x + width + 2 > _tdx) {
626                 // move to next line
627                 _currentLine = _nextLine;
628                 _x = 0;
629             }
630             // check if no room left for glyph height
631             if (_currentLine + height + 2 > _tdy) {
632                 _closed = true;
633                 return null;
634             }
635             cacheItem._rc = Rect(_x + 1, _currentLine + 1, _x + width + 1, _currentLine + height + 1);
636             if (height && width) {
637                 if (_nextLine < _currentLine + height + 2)
638                     _nextLine = _currentLine + height + 2;
639                 if (!_drawbuf) {
640                     _drawbuf = new GrayDrawBuf(_tdx, _tdy);
641                     //_drawbuf.SetBackgroundColor(0x000000);
642                     //_drawbuf.SetTextColor(0xFFFFFF);
643                     _drawbuf.fill(0x00000000);
644                 }
645                 _x += width + 1;
646                 _needUpdateTexture = true;
647             }
648             _itemCount++;
649             return cacheItem;
650         }
651         int deleteItem(GLGlyphCacheItem item) {
652             _itemCount--;
653             return _itemCount;
654         }
655         GLGlyphCacheItem addItem(Glyph * glyph) {
656             GLGlyphCacheItem cacheItem = reserveSpace(glyph.id, glyph.blackBoxX, glyph.blackBoxY);
657             if (cacheItem is null)
658                 return null;
659             _drawbuf.drawGlyph(cacheItem._rc.left, cacheItem._rc.top, glyph, 0xFFFFFF);
660             _needUpdateTexture = true;
661             return cacheItem;
662         }
663         void drawItem(GLGlyphCacheItem item, Rect dstrc, Rect srcrc, uint color, Rect * clip) {
664             //CRLog::trace("drawing item at %d,%d %dx%d <= %d,%d %dx%d ", x, y, dx, dy, srcx, srcy, srcdx, srcdy);
665             if (_needUpdateTexture)
666                 updateTexture();
667             if (_textureId != 0) {
668                 if (!isTexture(_textureId)) {
669                     Log.e("Invalid texture ", _textureId);
670                     return;
671                 }
672                 // convert coordinates to cached texture
673                 srcrc.offset(item._rc.left, item._rc.top);
674                 if (clip) {
675                     int srcw = srcrc.width();
676                     int srch = srcrc.height();
677                     int dstw = dstrc.width();
678                     int dsth = dstrc.height();
679                     if (dstw) {
680                         srcrc.left += clip.left * srcw / dstw;
681                         srcrc.right -= clip.right * srcw / dstw;
682                     }
683                     if (dsth) {
684                         srcrc.top += clip.top * srch / dsth;
685                         srcrc.bottom -= clip.bottom * srch / dsth;
686                     }
687                     dstrc.left += clip.left;
688                     dstrc.right -= clip.right;
689                     dstrc.top += clip.top;
690                     dstrc.bottom -= clip.bottom;
691                 }
692                 if (!dstrc.empty) {
693                     //Log.d("drawing glyph with color ", color);
694                     drawColorAndTextureRect(_textureId, _tdx, _tdy, srcrc, dstrc, color, false);
695                 }
696 
697             }
698         }
699         void close() {
700             _closed = true;
701             if (_needUpdateTexture)
702                 updateTexture();
703             static if (USE_RGBA_TEXTURE_FOR_GLYPHS) {
704                 _rgbaBuffer = null;
705             }
706         }
707     }
708 
709     GLGlyphCacheItem[uint] _map;
710     GLGlyphCachePage[] _pages;
711     GLGlyphCachePage _activePage;
712     int tdx;
713     int tdy;
714     void removePage(GLGlyphCachePage page) {
715         if (_activePage == page)
716             _activePage = null;
717         for (int i = 0; i < _pages.length; i++)
718             if (_pages[i] == page) {
719                 _pages.remove(i);
720                 break;
721             }
722         destroy(page);
723     }
724     private void updateTextureSize() {
725         if (!tdx) {
726             // TODO
727             tdx = tdy = 1024; //getMaxTextureSize(); 
728             if (tdx > 1024)
729                 tdx = tdy = 1024;
730         }
731     }
732 
733     this() {
734     }
735     ~this() {
736         clear();
737     }
738     /// check if item is in cache
739     bool get(uint obj) {
740         if (obj in _map)
741             return true;
742         return false;
743     }
744     /// put new item to cache
745     void put(Glyph * glyph) {
746         updateTextureSize();
747         GLGlyphCacheItem res = null;
748 		if (_activePage is null) {
749 			_activePage = new GLGlyphCachePage(this, tdx, tdy);
750 			_pages ~= _activePage;
751 		}
752 		res = _activePage.addItem(glyph);
753 		if (!res) {
754 			_activePage = new GLGlyphCachePage(this, tdx, tdy);
755 			_pages ~= _activePage;
756 			res = _activePage.addItem(glyph);
757 		}
758         _map[glyph.id] = res;
759     }
760     void clear() {
761         for (int i = 0; i < _pages.length; i++) {
762             destroy(_pages[i]);
763             _pages[i] = null;
764         }
765         _pages.clear();
766         _map.clear();
767     }
768     /// draw cached item
769     void drawItem(uint objectId, Rect dstrc, Rect srcrc, uint color, Rect * clip) {
770         GLGlyphCacheItem * item = objectId in _map;
771         if (item)
772             item.page.drawItem(*item, dstrc, srcrc, color, clip);
773     }
774     /// handle cached object deletion, mark as deleted
775     void onCachedObjectDeleted(uint objectId) {
776         if (objectId in _map) {
777             GLGlyphCacheItem item = _map[objectId];
778             if (hasActiveScene()) {
779                 item._deleted = true;
780             } else {
781                 int itemsLeft = item.page.deleteItem(item);
782                 //CRLog::trace("itemsLeft = %d", itemsLeft);
783                 if (itemsLeft <= 0) {
784                     //CRLog::trace("removing page");
785                     removePage(item.page);
786                 }
787                 _map.remove(objectId);
788                 delete item;
789             }
790         }
791     }
792     /// remove deleted items - remove page if contains only deleted items
793     void removeDeletedItems() {
794         uint[] list;
795         foreach (GLGlyphCacheItem item; _map) {
796             if (item._deleted)
797                 list ~= item._objectId;
798         }
799         for (int i = 0 ; i < list.length; i++) {
800             onCachedObjectDeleted(list[i]);
801         }
802     }
803 };
804 
805 
806 
807 
808 
809 
810 
811 class SolidRectSceneItem : SceneItem {
812     Rect _rc;
813     uint _color;
814     this(Rect rc, uint color) {
815         _rc = rc;
816         _color = color;
817     }
818     override void draw() {
819         drawSolidFillRect(_rc, _color, _color, _color, _color);
820     }
821 }
822 
823 private class GlyphSceneItem : SceneItem {
824 	uint objectId;
825     Rect dstrc;
826     Rect srcrc;
827 	uint color;
828 	Rect * clip;
829 public:
830 	override void draw() {
831 		if (glGlyphCache)
832             glGlyphCache.drawItem(objectId, dstrc, srcrc, color, clip);
833 	}
834     this(uint _objectId, Rect _dstrc, Rect _srcrc, uint _color, Rect * _clip)
835 	{
836         objectId = _objectId;
837         dstrc = _dstrc;
838         srcrc = _srcrc;
839         color = _color;
840         clip = _clip;
841 	}
842 	~this() {
843 	}
844 }
845 
846 
847 }