BatchDrawState.hx 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. package h2d.impl;
  2. import h3d.Indexes;
  3. import h3d.Buffer;
  4. /**
  5. Automates buffer segmentation when rendering 2D geometry with multiple unique textures.
  6. Primary use-case is to allow usage of multiple textures without the need to manually manage them.
  7. Causes extra draw call each time a texture is swapped.
  8. Due to that, for production it is recommended to combine assets in atlases for optimal performance.
  9. Depending on geometry type, vertex count should be in groups of 4 vertices per quad or 3 indices per triangle.
  10. **/
  11. class BatchDrawState {
  12. /**
  13. Current active texture of the BatchDrawState.
  14. Represents the most recent texture that was set with `setTile` or `setTexture`.
  15. Always null after state initialization or after `clear` call.
  16. **/
  17. public var currentTexture(get, never) : h3d.mat.Texture;
  18. /**
  19. A total amount of vertices added to the BatchDrawState.
  20. **/
  21. public var totalCount(default, null) : Int;
  22. var head : StateEntry;
  23. var tail : StateEntry;
  24. /**
  25. Create a new BatchDrawState instance.
  26. **/
  27. public function new() {
  28. this.head = this.tail = new StateEntry(null);
  29. this.totalCount = 0;
  30. }
  31. /**
  32. Switches currently active texture to one in the given `tile` if it differs and splits the render state.
  33. @param tile A Tile containing a texture that should be used for the next set of vertices. Does nothing if `null`.
  34. **/
  35. public inline function setTile( tile : h2d.Tile ) {
  36. if ( tile != null ) setTexture(tile.getTexture());
  37. }
  38. /**
  39. Switches currently active texture to the given `texture` if it differs and splits the render state.
  40. @param texture The texture that should be used for the next set of vertices. Does nothing if `null`.
  41. **/
  42. public function setTexture( texture : h3d.mat.Texture ) {
  43. if ( texture != null ) {
  44. if ( tail.texture == null ) tail.texture = texture;
  45. else if ( tail.texture != texture ) {
  46. var cur = tail;
  47. if ( cur.count == 0 ) cur.set(texture);
  48. else if ( cur.next == null ) cur.next = tail = new StateEntry(texture);
  49. else tail = cur.next.set(texture);
  50. }
  51. }
  52. }
  53. /**
  54. Add vertices to the state using currently active texture.
  55. Should be called when rendering buffers add more data in order to properly render the geometry.
  56. @param count The amount of vertices to add.
  57. **/
  58. public inline function add( count : Int ) {
  59. tail.count += count;
  60. totalCount += count;
  61. }
  62. /**
  63. Resets the BatchDrawState by removing all texture references and zeroing vertex counter.
  64. **/
  65. public function clear() {
  66. var state = head;
  67. do {
  68. state.texture = null;
  69. state = state.next;
  70. } while ( state != null );
  71. tail = head;
  72. tail.count = 0;
  73. totalCount = 0;
  74. }
  75. /**
  76. Renders given buffer as a set of quads. Buffer data should be in groups of 4 vertices per quad.
  77. @param ctx The render context which performs the rendering. Rendering object should call `h2d.RenderContext.beginDrawBatchState` before calling `drawQuads`.
  78. @param buffer The quad buffer used to render the state.
  79. @param offset An optional starting offset of the buffer to render in triangles (2 per quad).
  80. @param length An optional maximum limit of triangles to render.
  81. When `offset` and `length` are not provided or are default values, slightly faster rendering routine is used.
  82. **/
  83. public function drawQuads( ctx : RenderContext, buffer : Buffer, offset = 0, length = -1 ) {
  84. var state = head;
  85. var last = tail.next;
  86. var engine = ctx.engine;
  87. var stateLen : Int;
  88. inline function toQuads( count : Int ) return count >> 1;
  89. if ( offset == 0 && length == -1 ) {
  90. // Skip extra logic when not restraining rendering
  91. do {
  92. ctx.swapTexture(state.texture);
  93. stateLen = toQuads(state.count);
  94. engine.renderQuadBuffer(buffer, offset, stateLen);
  95. offset += stateLen;
  96. state = state.next;
  97. } while ( state != last );
  98. } else {
  99. if ( length == -1 ) length = toQuads(totalCount) - offset;
  100. var caret = 0;
  101. do {
  102. stateLen = toQuads(state.count);
  103. if ( caret + stateLen >= offset ) {
  104. var stateMin = offset >= caret ? offset : caret;
  105. var stateLen = length > stateLen ? stateLen : length;
  106. ctx.swapTexture(state.texture);
  107. engine.renderQuadBuffer(buffer, stateMin, stateLen);
  108. length -= stateLen;
  109. if ( length == 0 ) break;
  110. }
  111. caret += stateLen;
  112. state = state.next;
  113. } while ( state != last );
  114. }
  115. }
  116. /**
  117. Renders given indices as a set of triangles. Index data should be in groups of 3 vertices per quad.
  118. @param ctx The render context which performs the rendering. Rendering object should call `h2d.RenderContext.beginDrawBatchState` before calling `drawQuads`.
  119. @param buffer The vertex buffer used to render the state.
  120. @param indices Vertex indices used to render the state.
  121. @param offset An optional starting offset of the buffer to render in triangles.
  122. @param length An optional maximum limit of triangles to render.
  123. When `offset` and `length` are not provided or are default values, slightly faster rendering routine is used.
  124. **/
  125. public function drawIndexed( ctx : RenderContext, buffer : Buffer, indices : Indexes, offset : Int = 0, length : Int = -1 ) {
  126. var state = head;
  127. var last = tail.next;
  128. var engine = ctx.engine;
  129. var stateLen : Int;
  130. inline function toTris( count : Int ) return Std.int(count / 3);
  131. if ( offset == 0 && length == -1 ) {
  132. // Skip extra logic when not restraining rendering
  133. do {
  134. ctx.swapTexture(state.texture);
  135. stateLen = toTris(state.count);
  136. engine.renderIndexed(buffer, indices, offset, stateLen);
  137. offset += stateLen;
  138. state = state.next;
  139. } while ( state != last );
  140. } else {
  141. if ( length == -1 ) length = toTris(totalCount);
  142. var caret = 0;
  143. do {
  144. stateLen = toTris(state.count);
  145. if ( caret + stateLen >= offset ) {
  146. var stateMin = offset >= caret ? offset : caret;
  147. var stateLen = length > stateLen ? stateLen : length;
  148. ctx.swapTexture(state.texture);
  149. engine.renderIndexed(buffer, indices, stateMin, stateLen);
  150. length -= stateLen;
  151. if ( length == 0 ) break;
  152. }
  153. caret += stateLen;
  154. state = state.next;
  155. } while ( state != last );
  156. }
  157. }
  158. inline function get_currentTexture() return tail.texture;
  159. }
  160. private class StateEntry {
  161. /**
  162. Texture associated with draw state instance.
  163. **/
  164. public var texture : h3d.mat.Texture;
  165. /**
  166. A size of batch state.
  167. **/
  168. public var count : Int;
  169. public var next:StateEntry;
  170. public function new( texture : h3d.mat.Texture ) {
  171. this.texture = texture;
  172. this.count = 0;
  173. }
  174. public function set( texture : h3d.mat.Texture ) : StateEntry {
  175. this.texture = texture;
  176. this.count = 0;
  177. return this;
  178. }
  179. }