1 // Written in the D programming language.
2 
3 /**
4 This module contains opengl based drawing buffer implementation.
5 
6 To enable OpenGL support, build with version(USE_OPENGL);
7 
8 Synopsis:
9 
10 ----
11 import dlangui.graphics.gldrawbuf;
12 
13 ----
14 
15 Copyright: Vadim Lopatin, 2014
16 License:   Boost License 1.0
17 Authors:   Vadim Lopatin, coolreader.org@gmail.com
18 */
19 module dlangui.graphics.gldrawbuf;
20 
21 public import dlangui.core.config;
22 static if (BACKEND_GUI):
23 static if (ENABLE_OPENGL):
24 
25 import dlangui.graphics.drawbuf;
26 import dlangui.graphics.colors;
27 import dlangui.core.logger;
28 private import dlangui.graphics.glsupport;
29 private import std.algorithm;
30 
31 /// Reference counted GLTexture object
32 alias TextureRef = Ref!GLTexture;
33 
34 interface GLConfigCallback {
35     void saveConfiguration();
36     void restoreConfiguration();
37 }
38 
39 /// drawing buffer - image container which allows to perform some drawing operations
40 class GLDrawBuf : DrawBuf, GLConfigCallback {
41     // width
42     protected int _dx;
43     // height
44     protected int _dy;
45     protected bool _framebuffer; // not yet supported
46     protected uint _framebufferId; // not yet supported
47     protected Scene _scene;
48 
49     /// get current scene (exists only between beforeDrawing() and afterDrawing() calls)
50     @property Scene scene() { return _scene; }
51 
52     this(int dx, int dy, bool framebuffer = false) {
53         resize(dx, dy);
54         _framebuffer = framebuffer;
55     }
56 
57     /// returns current width
58     @property override int width() { return _dx; }
59     /// returns current height
60     @property override int height() { return _dy; }
61 
62     override void saveConfiguration() {
63     }
64     override void restoreConfiguration() {
65         glSupport.setOrthoProjection(Rect(0, 0, _dx, _dy), Rect(0, 0, _dx, _dy));
66     }
67 
68     /// reserved for hardware-accelerated drawing - begins drawing queue
69     override void beforeDrawing() {
70         _alpha = 0;
71         if (_scene !is null) {
72             _scene.reset();
73         }
74         _scene = new Scene(this);
75     }
76 
77     /// reserved for hardware-accelerated drawing - ends drawing queue
78     override void afterDrawing() {
79         glSupport.setOrthoProjection(Rect(0, 0, _dx, _dy), Rect(0, 0, _dx, _dy));
80         glSupport.beforeRenderGUI();
81         _scene.draw();
82         glSupport.queue.flush();
83         glSupport.flushGL();
84         destroy(_scene);
85         _scene = null;
86     }
87 
88     /// resize buffer
89     override void resize(int width, int height) {
90         _dx = width;
91         _dy = height;
92         resetClipping();
93     }
94 
95     /// draw custom OpenGL scene
96     override void drawCustomOpenGLScene(Rect rc, OpenGLDrawableDelegate handler) {
97         _scene.add(new CustomDrawnSceneItem(Rect(0, 0, width, height), rc, handler));
98     }
99 
100     /// fill the whole buffer with solid color (no clipping applied)
101     override void fill(uint color) {
102         if (hasClipping) {
103             fillRect(_clipRect, color);
104             return;
105         }
106         assert(_scene !is null);
107         _scene.add(new SolidRectSceneItem(Rect(0, 0, _dx, _dy), applyAlpha(color)));
108     }
109     /// fill rectangle with solid color (clipping is applied)
110     override void fillRect(Rect rc, uint color) {
111         assert(_scene !is null);
112         color = applyAlpha(color);
113         if (!isFullyTransparentColor(color) && applyClipping(rc))
114             _scene.add(new SolidRectSceneItem(rc, color));
115     }
116 
117     /// fill rectangle with solid color and pattern (clipping is applied) 0=solid fill, 1 = dotted
118     override void fillRectPattern(Rect rc, uint color, int pattern) {
119         if (pattern == PatternType.solid)
120             fillRect(rc, color);
121         else {
122             assert(_scene !is null);
123             color = applyAlpha(color);
124             if (!isFullyTransparentColor(color) && applyClipping(rc))
125                 _scene.add(new PatternRectSceneItem(rc, color, pattern));
126         }
127     }
128 
129     /// draw pixel at (x, y) with specified color
130     override void drawPixel(int x, int y, uint color) {
131         assert(_scene !is null);
132         if (!_clipRect.isPointInside(x, y))
133             return;
134         color = applyAlpha(color);
135         if (isFullyTransparentColor(color))
136             return;
137         _scene.add(new SolidRectSceneItem(Rect(x, y, x + 1, y + 1), color));
138     }
139     /// draw 8bit alpha image - usually font glyph using specified color (clipping is applied)
140     override void drawGlyph(int x, int y, Glyph * glyph, uint color) {
141         assert(_scene !is null);
142         Rect dstrect = Rect(x,y, x + glyph.correctedBlackBoxX, y + glyph.blackBoxY);
143         Rect srcrect = Rect(0, 0, glyph.correctedBlackBoxX, glyph.blackBoxY);
144         color = applyAlpha(color);
145         if (!isFullyTransparentColor(color) && applyClipping(dstrect, srcrect)) {
146             if (!glGlyphCache.isInCache(glyph.id))
147                 glGlyphCache.put(glyph);
148             _scene.add(new GlyphSceneItem(glyph.id, dstrect, srcrect, color, null));
149         }
150     }
151     /// draw source buffer rectangle contents to destination buffer
152     override void drawFragment(int x, int y, DrawBuf src, Rect srcrect) {
153         assert(_scene !is null);
154         Rect dstrect = Rect(x, y, x + srcrect.width, y + srcrect.height);
155         //Log.v("GLDrawBuf.frawFragment dst=", dstrect, " src=", srcrect);
156         if (applyClipping(dstrect, srcrect)) {
157             if (!glImageCache.isInCache(src.id))
158                 glImageCache.put(src);
159             _scene.add(new TextureSceneItem(src.id, dstrect, srcrect, applyAlpha(0xFFFFFF), 0, null));
160         }
161     }
162     /// draw source buffer rectangle contents to destination buffer rectangle applying rescaling
163     override void drawRescaled(Rect dstrect, DrawBuf src, Rect srcrect) {
164         assert(_scene !is null);
165         //Log.v("GLDrawBuf.frawRescaled dst=", dstrect, " src=", srcrect);
166         if (applyClipping(dstrect, srcrect)) {
167             if (!glImageCache.isInCache(src.id))
168                 glImageCache.put(src);
169             _scene.add(new TextureSceneItem(src.id, dstrect, srcrect, applyAlpha(0xFFFFFF), 0, null));
170         }
171     }
172 
173     /// draw line from point p1 to p2 with specified color
174     override void drawLine(Point p1, Point p2, uint colour) {
175         assert(_scene !is null);
176         if (!clipLine(_clipRect, p1, p2))
177             return;
178         _scene.add(new LineSceneItem(p1, p2, colour));
179     }
180 
181     /// draw filled triangle in float coordinates; clipping is already applied
182     override protected void fillTriangleFClipped(PointF p1, PointF p2, PointF p3, uint colour) {
183         assert(_scene !is null);
184         _scene.add(new TriangleSceneItem(p1, p2, p3, colour));
185     }
186 
187     /// cleanup resources
188     override void clear() {
189         if (_framebuffer) {
190             // TODO: delete framebuffer
191         }
192     }
193     ~this() { clear(); }
194 }
195 
196 /// base class for all drawing scene items.
197 class SceneItem {
198     abstract void draw();
199     /// when true, save configuration before drawing, and restore after drawing
200     @property bool needSaveConfiguration() { return false; }
201     /// when true, don't destroy item after drawing, since it's owned by some other component
202     @property bool persistent() { return false; }
203     void beforeDraw() { }
204     void afterDraw() { }
205 }
206 
207 class CustomSceneItem : SceneItem {
208     private SceneItem[] _items;
209     void add(SceneItem item) {
210         _items ~= item;
211     }
212     override void draw() {
213         foreach(SceneItem item; _items) {
214             item.beforeDraw();
215             item.draw();
216             item.afterDraw();
217         }
218     }
219     override @property bool needSaveConfiguration() { return true; }
220 }
221 
222 /// Drawing scene (operations sheduled for drawing)
223 class Scene {
224     private SceneItem[] _items;
225     private GLConfigCallback _configCallback;
226     this(GLConfigCallback configCallback) {
227         _configCallback = configCallback;
228         activeSceneCount++;
229     }
230     ~this() {
231         activeSceneCount--;
232     }
233     /// add new scene item to scene
234     void add(SceneItem item) {
235         _items ~= item;
236     }
237     /// draws all scene items and removes them from list
238     void draw() {
239         foreach(SceneItem item; _items) {
240             if (item.needSaveConfiguration) {
241                 _configCallback.saveConfiguration();
242             }
243             item.beforeDraw();
244             item.draw();
245             item.afterDraw();
246             if (item.needSaveConfiguration) {
247                 _configCallback.restoreConfiguration();
248             }
249         }
250         reset();
251     }
252     /// resets scene for new drawing - deletes all items
253     void reset() {
254         foreach(ref SceneItem item; _items) {
255             if (!item.persistent) // only destroy items not owner by other components
256                 destroy(item);
257             item = null;
258         }
259         _items.length = 0;
260     }
261 }
262 
263 private __gshared int activeSceneCount = 0;
264 bool hasActiveScene() {
265     return activeSceneCount > 0;
266 }
267 
268 enum MIN_TEX_SIZE = 64;
269 enum MAX_TEX_SIZE  = 4096;
270 private int nearestPOT(int n) {
271     for (int i = MIN_TEX_SIZE; i <= MAX_TEX_SIZE; i *= 2) {
272         if (n <= i)
273             return i;
274     }
275     return MIN_TEX_SIZE;
276 }
277 
278 private int correctTextureSize(int n) {
279     if (n < 16)
280         return 16;
281     version(POT_TEXTURE_SIZES) {
282         return nearestPOT(n);
283     } else {
284         return n;
285     }
286 }
287 
288 /// object deletion listener callback function type
289 void onObjectDestroyedCallback(uint pobject) {
290     glImageCache.onCachedObjectDeleted(pobject);
291 }
292 
293 /// object deletion listener callback function type
294 void onGlyphDestroyedCallback(uint pobject) {
295     glGlyphCache.onCachedObjectDeleted(pobject);
296 }
297 
298 private __gshared GLImageCache glImageCache;
299 private __gshared GLGlyphCache glGlyphCache;
300 
301 void initGLCaches() {
302     if (!glImageCache)
303         glImageCache = new GLImageCache;
304     if (!glGlyphCache)
305         glGlyphCache = new GLGlyphCache;
306 }
307 
308 void destroyGLCaches() {
309     if (glImageCache)
310         destroy(glImageCache);
311     if (glGlyphCache)
312         destroy(glGlyphCache);
313 }
314 
315 private abstract class GLCache
316 {
317     static class GLCacheItem
318     {
319         @property GLCachePage page() { return _page; }
320 
321         uint _objectId;
322         // image size
323         Rect _rc;
324         bool _deleted;
325 
326         this(GLCachePage page, uint objectId) { _page = page; _objectId = objectId; }
327 
328         private GLCachePage _page;
329     }
330 
331     static abstract class GLCachePage {
332     private:
333         GLCache _cache;
334         int _tdx;
335         int _tdy;
336         ColorDrawBuf _drawbuf;
337         int _currentLine;
338         int _nextLine;
339         int _x;
340         bool _closed;
341         bool _needUpdateTexture;
342         Tex2D _texture;
343         int _itemCount;
344 
345     public:
346         this(GLCache cache, int dx, int dy) {
347             _cache = cache;
348             _tdx = correctTextureSize(dx);
349             _tdy = correctTextureSize(dy);
350             _itemCount = 0;
351         }
352 
353         ~this() {
354             if (_drawbuf) {
355                 destroy(_drawbuf);
356                 _drawbuf = null;
357             }
358             if (_texture && _texture.ID != 0) {
359                 destroy(_texture);
360                 _texture = null;
361             }
362         }
363 
364         final void updateTexture() {
365             if (_drawbuf is null)
366                 return; // no draw buffer!!!
367             if (_texture is null || _texture.ID == 0) {
368                 _texture = new Tex2D();
369                 Log.d("updateTexture - new texture id=", _texture.ID);
370                 if (!_texture.ID)
371                     return;
372             }
373             Log.d("updateTexture for cache page - setting image ", _drawbuf.width, "x", _drawbuf.height, " tx=", _texture ? _texture.ID : 0);
374             uint * pixels = _drawbuf.scanLine(0);
375             if (!glSupport.setTextureImage(_texture, _drawbuf.width, _drawbuf.height, cast(ubyte*)pixels)) {
376                 destroy(_texture);
377                 _texture = null;
378                 return;
379             }
380             _needUpdateTexture = false;
381             if (_closed) {
382                 destroy(_drawbuf);
383                 _drawbuf = null;
384             }
385         }
386 
387         final GLCacheItem reserveSpace(uint objectId, int width, int height) {
388             auto cacheItem = new GLCacheItem(this, objectId);
389             if (_closed)
390                 return null;
391 
392             int spacer = (width == _tdx || height == _tdy) ? 0 : 1;
393 
394             // next line if necessary
395             if (_x + width + spacer * 2 > _tdx) {
396                 // move to next line
397                 _currentLine = _nextLine;
398                 _x = 0;
399             }
400             // check if no room left for glyph height
401             if (_currentLine + height + spacer * 2 > _tdy) {
402                 _closed = true;
403                 return null;
404             }
405             cacheItem._rc = Rect(_x + spacer, _currentLine + spacer, _x + width + spacer, _currentLine + height + spacer);
406             if (height && width) {
407                 if (_nextLine < _currentLine + height + 2 * spacer)
408                     _nextLine = _currentLine + height + 2 * spacer;
409                 if (!_drawbuf) {
410                     _drawbuf = new ColorDrawBuf(_tdx, _tdy);
411                     //_drawbuf.SetBackgroundColor(0x000000);
412                     //_drawbuf.SetTextColor(0xFFFFFF);
413                     _drawbuf.fill(0xFF000000);
414                 }
415                 _x += width + spacer;
416                 _needUpdateTexture = true;
417             }
418             _itemCount++;
419             return cacheItem;
420         }
421 
422         final int deleteItem(GLCacheItem item) {
423             _itemCount--;
424             return _itemCount;
425         }
426 
427         final void close() {
428             _closed = true;
429             if (_needUpdateTexture)
430                 updateTexture();
431         }
432     }
433 
434     GLCacheItem[uint] _map;
435     GLCachePage[] _pages;
436     GLCachePage _activePage;
437     int tdx;
438     int tdy;
439 
440     final void removePage(GLCachePage page) {
441         if (_activePage == page)
442             _activePage = null;
443         foreach(i; 0 .. _pages.length)
444             if (_pages[i] == page) {
445                 _pages = _pages.remove(i);
446                 break;
447             }
448         destroy(page);
449     }
450 
451     final void updateTextureSize() {
452         if (!tdx) {
453             // TODO
454             tdx = tdy = 1024; //getMaxTextureSize();
455             if (tdx > 1024)
456                 tdx = tdy = 1024;
457         }
458     }
459 
460     this() {
461     }
462     ~this() {
463         clear();
464     }
465     /// check if item is in cache
466     final bool isInCache(uint obj) {
467         if (obj in _map)
468             return true;
469         return false;
470     }
471     /// clears cache
472     final void clear() {
473         foreach(i; 0 .. _pages.length) {
474             destroy(_pages[i]);
475             _pages[i] = null;
476         }
477         destroy(_pages);
478         destroy(_map);
479     }
480     /// handle cached object deletion, mark as deleted
481     final void onCachedObjectDeleted(uint objectId) {
482         if (objectId in _map) {
483             GLCacheItem item = _map[objectId];
484             if (hasActiveScene()) {
485                 item._deleted = true;
486             } else {
487                 int itemsLeft = item.page.deleteItem(item);
488                 if (itemsLeft <= 0) {
489                     removePage(item.page);
490                 }
491                 _map.remove(objectId);
492                 destroy(item);
493             }
494         }
495     }
496     /// remove deleted items - remove page if contains only deleted items
497     final void removeDeletedItems() {
498         uint[] list;
499         foreach(GLCacheItem item; _map) {
500             if (item._deleted)
501                 list ~= item._objectId;
502         }
503         foreach(i; 0 .. list.length) {
504             onCachedObjectDeleted(list[i]);
505         }
506     }
507 }
508 
509 /// OpenGL texture cache for ColorDrawBuf objects
510 private class GLImageCache : GLCache
511 {
512     static class GLImageCachePage : GLCachePage {
513 
514         this(GLImageCache cache, int dx, int dy) {
515             super(cache, dx, dy);
516             Log.v("created image cache page ", dx, "x", dy);
517         }
518 
519         void convertPixelFormat(GLCacheItem item) {
520             Rect rc = item._rc;
521             if (rc.top > 0)
522                 rc.top--;
523             if (rc.left > 0)
524                 rc.left--;
525             if (rc.right < _tdx)
526                 rc.right++;
527             if (rc.bottom < _tdy)
528                 rc.bottom++;
529             for (int y = rc.top; y < rc.bottom; y++) {
530                 uint * row = _drawbuf.scanLine(y);
531                 for (int x = rc.left; x < rc.right; x++) {
532                     uint cl = row[x];
533                     // invert A
534                     cl ^= 0xFF000000;
535                     // swap R and B
536                     uint r = (cl & 0x00FF0000) >> 16;
537                     uint b = (cl & 0x000000FF) << 16;
538                     row[x] = (cl & 0xFF00FF00) | r | b;
539                 }
540             }
541         }
542 
543         GLCacheItem addItem(DrawBuf buf) {
544             GLCacheItem cacheItem = reserveSpace(buf.id, buf.width, buf.height);
545             if (cacheItem is null)
546                 return null;
547             buf.onDestroyCallback = &onObjectDestroyedCallback;
548             _drawbuf.drawImage(cacheItem._rc.left, cacheItem._rc.top, buf);
549             convertPixelFormat(cacheItem);
550             _needUpdateTexture = true;
551             return cacheItem;
552         }
553         void drawItem(GLCacheItem item, Rect dstrc, Rect srcrc, uint color, uint options, Rect * clip) {
554             if (_needUpdateTexture)
555                 updateTexture();
556             if (_texture && _texture.ID != 0) {
557                 int rx = dstrc.middlex;
558                 int ry = dstrc.middley;
559                 // convert coordinates to cached texture
560                 srcrc.offset(item._rc.left, item._rc.top);
561                 if (clip) {
562                     int srcw = srcrc.width();
563                     int srch = srcrc.height();
564                     int dstw = dstrc.width();
565                     int dsth = dstrc.height();
566                     if (dstw) {
567                         srcrc.left += clip.left * srcw / dstw;
568                         srcrc.right -= clip.right * srcw / dstw;
569                     }
570                     if (dsth) {
571                         srcrc.top += clip.top * srch / dsth;
572                         srcrc.bottom -= clip.bottom * srch / dsth;
573                     }
574                     dstrc.left += clip.left;
575                     dstrc.right -= clip.right;
576                     dstrc.top += clip.top;
577                     dstrc.bottom -= clip.bottom;
578                 }
579                 if (!dstrc.empty)
580                     glSupport.queue.addTexturedRect(_texture, _tdx, _tdy, color, color, color, color, srcrc, dstrc, true);
581             }
582         }
583     }
584 
585     /// put new object to cache
586     void put(DrawBuf img) {
587         updateTextureSize();
588         GLCacheItem res = null;
589         if (img.width <= tdx / 3 && img.height < tdy / 3) {
590             // trying to reuse common page for small images
591             if (_activePage is null) {
592                 _activePage = new GLImageCachePage(this, tdx, tdy);
593                 _pages ~= _activePage;
594             }
595             res = (cast(GLImageCachePage)_activePage).addItem(img);
596             if (!res) {
597                 auto page = new GLImageCachePage(this, tdx, tdy);
598                 _pages ~= page;
599                 res = page.addItem(img);
600                 _activePage = page;
601             }
602         } else {
603             // use separate page for big image
604             auto page = new GLImageCachePage(this, img.width, img.height);
605             _pages ~= page;
606             res = page.addItem(img);
607             page.close();
608         }
609         _map[img.id] = res;
610     }
611     /// draw cached item
612     void drawItem(uint objectId, Rect dstrc, Rect srcrc, uint color, int options, Rect * clip) {
613         GLCacheItem* item = objectId in _map;
614         if (item) {
615             auto page = (cast(GLImageCachePage)item.page);
616             page.drawItem(*item, dstrc, srcrc, color, options, clip);
617         }
618     }
619 }
620 
621 private class GLGlyphCache : GLCache
622 {
623     static class GLGlyphCachePage : GLCachePage {
624 
625         this(GLGlyphCache cache, int dx, int dy) {
626             super(cache, dx, dy);
627             Log.v("created glyph cache page ", dx, "x", dy);
628         }
629 
630         GLCacheItem addItem(Glyph* glyph) {
631             GLCacheItem cacheItem = reserveSpace(glyph.id, glyph.correctedBlackBoxX, glyph.blackBoxY);
632             if (cacheItem is null)
633                 return null;
634             //_drawbuf.drawGlyph(cacheItem._rc.left, cacheItem._rc.top, glyph, 0xFFFFFF);
635             _drawbuf.drawGlyphToTexture(cacheItem._rc.left, cacheItem._rc.top, glyph);
636             _needUpdateTexture = true;
637             return cacheItem;
638         }
639 
640         void drawItem(GLCacheItem item, Rect dstrc, Rect srcrc, uint color, Rect * clip) {
641             if (_needUpdateTexture)
642                 updateTexture();
643             if (_texture && _texture.ID != 0) {
644                 // convert coordinates to cached texture
645                 srcrc.offset(item._rc.left, item._rc.top);
646                 if (clip) {
647                     int srcw = srcrc.width();
648                     int srch = srcrc.height();
649                     int dstw = dstrc.width();
650                     int dsth = dstrc.height();
651                     if (dstw) {
652                         srcrc.left += clip.left * srcw / dstw;
653                         srcrc.right -= clip.right * srcw / dstw;
654                     }
655                     if (dsth) {
656                         srcrc.top += clip.top * srch / dsth;
657                         srcrc.bottom -= clip.bottom * srch / dsth;
658                     }
659                     dstrc.left += clip.left;
660                     dstrc.right -= clip.right;
661                     dstrc.top += clip.top;
662                     dstrc.bottom -= clip.bottom;
663                 }
664                 if (!dstrc.empty) {
665                     //Log.d("drawing glyph with color ", color);
666                     glSupport.queue.addTexturedRect(_texture, _tdx, _tdy, color, color, color, color, srcrc, dstrc, false);
667                 }
668             }
669         }
670     }
671 
672     /// put new item to cache
673     void put(Glyph* glyph) {
674         updateTextureSize();
675         GLCacheItem res = null;
676         if (_activePage is null) {
677             _activePage = new GLGlyphCachePage(this, tdx, tdy);
678             _pages ~= _activePage;
679         }
680         res = (cast(GLGlyphCachePage)_activePage).addItem(glyph);
681         if (!res) {
682             auto page = new GLGlyphCachePage(this, tdx, tdy);
683             _pages ~= page;
684             res = page.addItem(glyph);
685              _activePage = page;
686         }
687         _map[glyph.id] = res;
688     }
689     /// draw cached item
690     void drawItem(uint objectId, Rect dstrc, Rect srcrc, uint color, Rect * clip) {
691         GLCacheItem* item = objectId in _map;
692         if (item)
693             (cast(GLGlyphCachePage)item.page).drawItem(*item, dstrc, srcrc, color, clip);
694     }
695 }
696 
697 
698 
699 
700 
701 private class LineSceneItem : SceneItem {
702 private:
703     Point _p1;
704     Point _p2;
705     uint _color;
706 
707 public:
708     this(Point p1, Point p2, uint color) {
709         _p1 = p1;
710         _p2 = p2;
711         _color = color;
712     }
713     override void draw() {
714         glSupport.queue.addLine(Rect(_p1, _p2), _color, _color);
715     }
716 }
717 
718 private class TriangleSceneItem : SceneItem {
719 private:
720     PointF _p1;
721     PointF _p2;
722     PointF _p3;
723     uint _color;
724 
725 public:
726     this(PointF p1, PointF p2, PointF p3, uint color) {
727         _p1 = p1;
728         _p2 = p2;
729         _p3 = p3;
730         _color = color;
731     }
732     override void draw() {
733         glSupport.queue.addTriangle(_p1, _p2, _p3, _color, _color, _color);
734     }
735 }
736 
737 private class SolidRectSceneItem : SceneItem {
738 private:
739     Rect _rc;
740     uint _color;
741 
742 public:
743     this(Rect rc, uint color) {
744         _rc = rc;
745         _color = color;
746     }
747     override void draw() {
748         glSupport.queue.addSolidRect(_rc, _color);
749     }
750 }
751 
752 private class PatternRectSceneItem : SceneItem {
753 private:
754     Rect _rc;
755     uint _color;
756     int _pattern;
757 
758 public:
759     this(Rect rc, uint color, int pattern) {
760         _rc = rc;
761         _color = color;
762         _pattern = pattern;
763     }
764     override void draw() {
765         // TODO: support patterns
766         // TODO: optimize
767         for (int y = _rc.top; y < _rc.bottom; y++) {
768             for (int x = _rc.left; x < _rc.right; x++)
769                 if ((x ^ y) & 1) {
770                     glSupport.queue.addSolidRect(Rect(x, y, x + 1, y + 1), _color);
771                 }
772         }
773     }
774 }
775 
776 private class TextureSceneItem : SceneItem {
777 private:
778     uint objectId;
779     //CacheableObject * img;
780     Rect dstrc;
781     Rect srcrc;
782     uint color;
783     uint options;
784     Rect * clip;
785 
786 public:
787     override void draw() {
788         if (glImageCache)
789             glImageCache.drawItem(objectId, dstrc, srcrc, color, options, clip);
790     }
791 
792     this(uint _objectId, Rect _dstrc, Rect _srcrc, uint _color, uint _options, Rect * _clip)
793     {
794         objectId = _objectId;
795         dstrc = _dstrc;
796         srcrc = _srcrc;
797         color = _color;
798         options = _options;
799         clip = _clip;
800     }
801 }
802 
803 /// character glyph
804 private class GlyphSceneItem : SceneItem {
805 private:
806     uint objectId;
807     Rect dstrc;
808     Rect srcrc;
809     uint color;
810     Rect * clip;
811 
812 public:
813     override void draw() {
814         if (glGlyphCache)
815             glGlyphCache.drawItem(objectId, dstrc, srcrc, color, clip);
816     }
817     this(uint _objectId, Rect _dstrc, Rect _srcrc, uint _color, Rect * _clip)
818     {
819         objectId = _objectId;
820         dstrc = _dstrc;
821         srcrc = _srcrc;
822         color = _color;
823         clip = _clip;
824     }
825 }
826 
827 private class CustomDrawnSceneItem : SceneItem {
828 private:
829     Rect _windowRect;
830     Rect _rc;
831     OpenGLDrawableDelegate _handler;
832 
833 public:
834     this(Rect windowRect, Rect rc, OpenGLDrawableDelegate handler) {
835         _windowRect = windowRect;
836         _rc = rc;
837         _handler = handler;
838     }
839     override void draw() {
840         if (_handler) {
841             glSupport.queue.flush();
842             glSupport.setOrthoProjection(_windowRect, _rc);
843             glSupport.clearDepthBuffer();
844             _handler(_windowRect, _rc);
845             glSupport.setOrthoProjection(_windowRect, _windowRect);
846         }
847     }
848 }
849 
850 /// GL Texture object from image
851 static class GLTexture : RefCountedObject {
852     protected string _resourceId;
853     protected int _dx;
854     protected int _dy;
855     protected int _tdx;
856     protected int _tdy;
857 
858     @property Point imageSize() {
859         return Point(_dx, _dy);
860     }
861 
862     protected Tex2D _texture;
863     /// returns texture object
864     @property Tex2D texture() { return _texture; }
865     /// returns texture id
866     @property uint textureId() { return _texture ? _texture.ID : 0; }
867 
868     bool isValid() {
869         return _texture && _texture.ID;
870     }
871     /// image coords to UV
872     float[2] uv(int x, int y) {
873         float[2] res;
874         res[0] = cast(float)x / _tdx;
875         res[1] = cast(float)y / _tdy;
876         return res;
877     }
878     float[2] uv(Point pt) {
879         float[2] res;
880         res[0] = cast(float)pt.x / _tdx;
881         res[1] = cast(float)pt.y / _tdy;
882         return res;
883     }
884     /// return UV coords for bottom right corner
885     float[2] uv() {
886         return uv(_dx, _dy);
887     }
888 
889     this(string resourceId, int mipmapLevels = 0) {
890         import dlangui.graphics.resources;
891         _resourceId = resourceId;
892         string path = drawableCache.findResource(resourceId);
893         this(cast(ColorDrawBuf)imageCache.get(path), mipmapLevels);
894     }
895 
896     this(ColorDrawBuf buf, int mipmapLevels = 0) {
897         if (buf) {
898             _dx = buf.width;
899             _dy = buf.height;
900             _tdx = correctTextureSize(_dx);
901             _tdy = correctTextureSize(_dy);
902             _texture = new Tex2D();
903             if (!_texture.ID) {
904                 _texture = null;
905                 return;
906             }
907             uint * pixels = buf.scanLine(0);
908             buf.invertAlphaAndByteOrder();
909             if (!glSupport.setTextureImage(_texture, buf.width, buf.height, cast(ubyte*)pixels, mipmapLevels)) {
910                 destroy(_texture);
911                 _texture = null;
912                 buf.invertAlphaAndByteOrder();
913                 return;
914             }
915             buf.invertAlphaAndByteOrder();
916         }
917     }
918 
919     ~this() {
920         import std.string : empty;
921         if (!_resourceId.empty)
922             GLTextureCache.instance.onItemRemoved(_resourceId);
923         if (_texture && _texture.ID != 0) {
924             destroy(_texture);
925             _texture = null;
926         }
927     }
928 }
929 
930 /// Cache for GLTexture
931 class GLTextureCache {
932     protected GLTexture[string] _map;
933 
934     static __gshared GLTextureCache _instance;
935 
936     static @property GLTextureCache instance() {
937         if (!_instance)
938             _instance = new GLTextureCache();
939         return _instance;
940     }
941 
942     private void onItemRemoved(string resourceId) {
943         if (resourceId in _map) {
944             _map.remove(resourceId);
945         }
946     }
947 
948     GLTexture get(string resourceId) {
949         if (auto p = resourceId in _map) {
950             return *p;
951         }
952         GLTexture tx = new GLTexture(resourceId, 6);
953         _map[resourceId] = tx;
954         return tx;
955     }
956 }