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