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 }