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 }