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 }