babylon.renderablePrim2d.ts 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011
  1. module BABYLON {
  2. export class InstanceClassInfo {
  3. constructor(base: InstanceClassInfo) {
  4. this._baseInfo = base;
  5. this._nextOffset = new StringDictionary<number>();
  6. this._attributes = new Array<InstancePropInfo>();
  7. }
  8. mapProperty(propInfo: InstancePropInfo, push: boolean) {
  9. let curOff = this._nextOffset.getOrAdd(InstanceClassInfo._CurCategories, 0);
  10. propInfo.instanceOffset.add(InstanceClassInfo._CurCategories, this._getBaseOffset(InstanceClassInfo._CurCategories) + curOff);
  11. //console.log(`[${InstanceClassInfo._CurCategories}] New PropInfo. Category: ${propInfo.category}, Name: ${propInfo.attributeName}, Offset: ${propInfo.instanceOffset.get(InstanceClassInfo._CurCategories)}, Size: ${propInfo.size / 4}`);
  12. this._nextOffset.set(InstanceClassInfo._CurCategories, curOff + (propInfo.size / 4));
  13. if (push) {
  14. this._attributes.push(propInfo);
  15. }
  16. }
  17. getInstancingAttributeInfos(effect: Effect, categories: string[]): InstancingAttributeInfo[] {
  18. let catInline = ";" + categories.join(";") + ";";
  19. let res = new Array<InstancingAttributeInfo>();
  20. let curInfo: InstanceClassInfo = this;
  21. while (curInfo) {
  22. for (let attrib of curInfo._attributes) {
  23. // Only map if there's no category assigned to the instance data or if there's a category and it's in the given list
  24. if (!attrib.category || categories.indexOf(attrib.category) !== -1) {
  25. let index = effect.getAttributeLocationByName(attrib.attributeName);
  26. if (index === - 1) {
  27. throw new Error(`Attribute ${attrib.attributeName} was not found in Effect: ${effect.name}. It's certainly no longer used in the Effect's Shaders`);
  28. }
  29. let iai = new InstancingAttributeInfo();
  30. iai.index = index;
  31. iai.attributeSize = attrib.size / 4; // attrib.size is in byte and we need to store in "component" (i.e float is 1, vec3 is 3)
  32. iai.offset = attrib.instanceOffset.get(catInline) * 4; // attrib.instanceOffset is in float, iai.offset must be in bytes
  33. iai.attributeName = attrib.attributeName;
  34. res.push(iai);
  35. }
  36. }
  37. curInfo = curInfo._baseInfo;
  38. }
  39. return res;
  40. }
  41. getShaderAttributes(categories: string[]): string[] {
  42. let res = new Array<string>();
  43. let curInfo: InstanceClassInfo = this;
  44. while (curInfo) {
  45. for (let attrib of curInfo._attributes) {
  46. // Only map if there's no category assigned to the instance data or if there's a category and it's in the given list
  47. if (!attrib.category || categories.indexOf(attrib.category) !== -1) {
  48. res.push(attrib.attributeName);
  49. }
  50. }
  51. curInfo = curInfo._baseInfo;
  52. }
  53. return res;
  54. }
  55. private _getBaseOffset(categories: string): number {
  56. let curOffset = 0;
  57. let curBase = this._baseInfo;
  58. while (curBase) {
  59. curOffset += curBase._nextOffset.getOrAdd(categories, 0);
  60. curBase = curBase._baseInfo;
  61. }
  62. return curOffset;
  63. }
  64. static _CurCategories: string;
  65. private _baseInfo: InstanceClassInfo;
  66. private _nextOffset: StringDictionary<number>;
  67. private _attributes: Array<InstancePropInfo>;
  68. }
  69. export class InstancePropInfo {
  70. attributeName: string;
  71. category: string;
  72. size: number;
  73. instanceOffset: StringDictionary<number>;
  74. dataType: ShaderDataType;
  75. curCategory: string;
  76. curCategoryOffset: number;
  77. //uniformLocation: WebGLUniformLocation;
  78. delimitedCategory: string;
  79. constructor() {
  80. this.attributeName = null;
  81. this.category = null;
  82. this.size = null;
  83. this.instanceOffset = new StringDictionary<number>();
  84. this.dataType = 0;
  85. this.curCategory = "";
  86. this.curCategoryOffset = 0;
  87. }
  88. setSize(val) {
  89. if (val instanceof Vector2) {
  90. this.size = 8;
  91. this.dataType = ShaderDataType.Vector2;
  92. return;
  93. }
  94. if (val instanceof Vector3) {
  95. this.size = 12;
  96. this.dataType = ShaderDataType.Vector3;
  97. return;
  98. }
  99. if (val instanceof Vector4) {
  100. this.size = 16;
  101. this.dataType = ShaderDataType.Vector4;
  102. return;
  103. }
  104. if (val instanceof Matrix2D) {
  105. throw new Error("Matrix2D type is not supported by WebGL Instance Buffer, you have to use four Vector4 properties instead");
  106. }
  107. if (typeof (val) === "number") {
  108. this.size = 4;
  109. this.dataType = ShaderDataType.float;
  110. return;
  111. }
  112. if (val instanceof Color3) {
  113. this.size = 12;
  114. this.dataType = ShaderDataType.Color3;
  115. return;
  116. }
  117. if (val instanceof Color4) {
  118. this.size = 16;
  119. this.dataType = ShaderDataType.Color4;
  120. return;
  121. }
  122. if (val instanceof Size) {
  123. this.size = 8;
  124. this.dataType = ShaderDataType.Size;
  125. return;
  126. } return;
  127. }
  128. writeData(array: Float32Array, offset: number, val) {
  129. switch (this.dataType) {
  130. case ShaderDataType.Vector2:
  131. {
  132. let v = <Vector2>val;
  133. array[offset + 0] = v.x;
  134. array[offset + 1] = v.y;
  135. break;
  136. }
  137. case ShaderDataType.Vector3:
  138. {
  139. let v = <Vector3>val;
  140. array[offset + 0] = v.x;
  141. array[offset + 1] = v.y;
  142. array[offset + 2] = v.z;
  143. break;
  144. }
  145. case ShaderDataType.Vector4:
  146. {
  147. let v = <Vector4>val;
  148. array[offset + 0] = v.x;
  149. array[offset + 1] = v.y;
  150. array[offset + 2] = v.z;
  151. array[offset + 3] = v.w;
  152. break;
  153. }
  154. case ShaderDataType.Color3:
  155. {
  156. let v = <Color3>val;
  157. array[offset + 0] = v.r;
  158. array[offset + 1] = v.g;
  159. array[offset + 2] = v.b;
  160. break;
  161. }
  162. case ShaderDataType.Color4:
  163. {
  164. let v = <Color4>val;
  165. array[offset + 0] = v.r;
  166. array[offset + 1] = v.g;
  167. array[offset + 2] = v.b;
  168. array[offset + 3] = v.a;
  169. break;
  170. }
  171. case ShaderDataType.float:
  172. {
  173. let v = <number>val;
  174. array[offset] = v;
  175. break;
  176. }
  177. case ShaderDataType.Size:
  178. {
  179. let s = <Size>val;
  180. array[offset + 0] = s.width;
  181. array[offset + 1] = s.height;
  182. break;
  183. }
  184. }
  185. }
  186. }
  187. export function instanceData<T>(category?: string, shaderAttributeName?: string): (target: Object, propName: string | symbol, descriptor: TypedPropertyDescriptor<T>) => void {
  188. return (target: Object, propName: string | symbol, descriptor: TypedPropertyDescriptor<T>) => {
  189. let dic = ClassTreeInfo.getOrRegister<InstanceClassInfo, InstancePropInfo>(target, (base) => new InstanceClassInfo(base));
  190. let node = dic.getLevelOf(target);
  191. let instanceDataName = <string>propName;
  192. shaderAttributeName = shaderAttributeName || instanceDataName;
  193. let info = node.levelContent.get(instanceDataName);
  194. if (info) {
  195. throw new Error(`The ID ${instanceDataName} is already taken by another instance data`);
  196. }
  197. info = new InstancePropInfo();
  198. info.attributeName = shaderAttributeName;
  199. info.category = category || null;
  200. if (info.category) {
  201. info.delimitedCategory = ";" + info.category + ";";
  202. }
  203. node.levelContent.add(instanceDataName, info);
  204. descriptor.get = function () {
  205. return null;
  206. }
  207. descriptor.set = function (val) {
  208. // Check that we're not trying to set a property that belongs to a category that is not allowed (current)
  209. // Quit if it's the case, otherwise we could overwrite data somewhere...
  210. if (info.category && InstanceClassInfo._CurCategories.indexOf(info.delimitedCategory) === -1) {
  211. return;
  212. }
  213. let catOffset: number;
  214. if (info.curCategory === InstanceClassInfo._CurCategories) {
  215. catOffset = info.curCategoryOffset;
  216. } else {
  217. if (!info.size) {
  218. info.setSize(val);
  219. node.classContent.mapProperty(info, true);
  220. } else if (!info.instanceOffset.contains(InstanceClassInfo._CurCategories)) {
  221. node.classContent.mapProperty(info, false);
  222. }
  223. catOffset = info.instanceOffset.get(InstanceClassInfo._CurCategories);
  224. info.curCategory = InstanceClassInfo._CurCategories;
  225. info.curCategoryOffset = catOffset;
  226. }
  227. let obj: InstanceDataBase = this;
  228. if (obj.dataBuffer && obj.dataElements) {
  229. let offset = obj.dataElements[obj.curElement].offset + catOffset;
  230. info.writeData(obj.dataBuffer.buffer, offset, val);
  231. }
  232. }
  233. }
  234. }
  235. export class InstanceDataBase {
  236. constructor(partId: number, dataElementCount: number) {
  237. this.id = partId;
  238. this.curElement = 0;
  239. this._dataElementCount = dataElementCount;
  240. this.renderMode = 0;
  241. this.arrayLengthChanged = false;
  242. }
  243. id: number;
  244. isVisible: boolean;
  245. @instanceData()
  246. get zBias(): Vector2 {
  247. return null;
  248. }
  249. set zBias(value: Vector2) {
  250. }
  251. @instanceData()
  252. get transformX(): Vector4 {
  253. return null;
  254. }
  255. set transformX(value: Vector4) {
  256. }
  257. @instanceData()
  258. get transformY(): Vector4 {
  259. return null;
  260. }
  261. set transformY(value: Vector4) {
  262. }
  263. // The vector3 is: rendering width, height and 1 if the primitive must be aligned to pixel or 0 otherwise
  264. @instanceData()
  265. get renderingInfo(): Vector3 {
  266. return null;
  267. }
  268. set renderingInfo(val: Vector3) {
  269. }
  270. @instanceData()
  271. get opacity(): number {
  272. return null;
  273. }
  274. set opacity(value: number) {
  275. }
  276. getClassTreeInfo(): ClassTreeInfo<InstanceClassInfo, InstancePropInfo> {
  277. if (!this.typeInfo) {
  278. this.typeInfo = ClassTreeInfo.get<InstanceClassInfo, InstancePropInfo>(Object.getPrototypeOf(this));
  279. }
  280. return this.typeInfo;
  281. }
  282. allocElements() {
  283. if (!this.dataBuffer || this.dataElements) {
  284. return;
  285. }
  286. let res = new Array<DynamicFloatArrayElementInfo>(this.dataElementCount);
  287. for (let i = 0; i < this.dataElementCount; i++) {
  288. res[i] = this.dataBuffer.allocElement();
  289. }
  290. this.dataElements = res;
  291. }
  292. freeElements() {
  293. if (!this.dataElements) {
  294. return;
  295. }
  296. for (let ei of this.dataElements) {
  297. this.dataBuffer.freeElement(ei);
  298. }
  299. this.dataElements = null;
  300. }
  301. get dataElementCount(): number {
  302. return this._dataElementCount;
  303. }
  304. set dataElementCount(value: number) {
  305. if (value === this._dataElementCount) {
  306. return;
  307. }
  308. this.arrayLengthChanged = true;
  309. this.freeElements();
  310. this._dataElementCount = value;
  311. this.allocElements();
  312. }
  313. groupInstanceInfo: GroupInstanceInfo;
  314. arrayLengthChanged: boolean;
  315. curElement: number;
  316. renderMode: number;
  317. dataElements: DynamicFloatArrayElementInfo[];
  318. dataBuffer: DynamicFloatArray;
  319. typeInfo: ClassTreeInfo<InstanceClassInfo, InstancePropInfo>;
  320. private _dataElementCount: number;
  321. }
  322. @className("RenderablePrim2D", "BABYLON")
  323. /**
  324. * The abstract class for primitive that render into the Canvas2D
  325. */
  326. export abstract class RenderablePrim2D extends Prim2DBase {
  327. static RENDERABLEPRIM2D_PROPCOUNT: number = Prim2DBase.PRIM2DBASE_PROPCOUNT + 5;
  328. public static isAlphaTestProperty: Prim2DPropInfo;
  329. public static isTransparentProperty: Prim2DPropInfo;
  330. @dynamicLevelProperty(Prim2DBase.PRIM2DBASE_PROPCOUNT + 0, pi => RenderablePrim2D.isAlphaTestProperty = pi)
  331. /**
  332. * Get/set if the Primitive is from the AlphaTest rendering category.
  333. * The AlphaTest category is the rendering pass with alpha blend, depth compare and write activated.
  334. * Primitives that render with an alpha mask should be from this category.
  335. * The setter should be used only by implementers of new primitive type.
  336. */
  337. public get isAlphaTest(): boolean {
  338. return this._useTextureAlpha() || this._isPrimAlphaTest();
  339. }
  340. @dynamicLevelProperty(Prim2DBase.PRIM2DBASE_PROPCOUNT + 1, pi => RenderablePrim2D.isTransparentProperty = pi)
  341. /**
  342. * Get/set if the Primitive is from the Transparent rendering category.
  343. * The setter should be used only by implementers of new primitive type.
  344. */
  345. public get isTransparent(): boolean {
  346. return (this.actualOpacity<1) || this._shouldUseAlphaFromTexture() || this._isPrimTransparent();
  347. }
  348. public get renderMode(): number {
  349. return this._renderMode;
  350. }
  351. constructor(settings?: {
  352. parent ?: Prim2DBase,
  353. id ?: string,
  354. origin ?: Vector2,
  355. isVisible ?: boolean,
  356. }) {
  357. super(settings);
  358. this._transparentPrimitiveInfo = null;
  359. }
  360. /**
  361. * Dispose the primitive and its resources, remove it from its parent
  362. */
  363. public dispose(): boolean {
  364. if (!super.dispose()) {
  365. return false;
  366. }
  367. if (this.renderGroup) {
  368. this.renderGroup._setCacheGroupDirty();
  369. }
  370. if (this._transparentPrimitiveInfo) {
  371. this.renderGroup._renderableData.removeTransparentPrimitiveInfo(this._transparentPrimitiveInfo);
  372. this._transparentPrimitiveInfo = null;
  373. }
  374. if (this._instanceDataParts) {
  375. this._cleanupInstanceDataParts();
  376. }
  377. if (this._modelRenderCache) {
  378. this._modelRenderCache.dispose();
  379. this._modelRenderCache = null;
  380. }
  381. if (this._instanceDataParts) {
  382. this._instanceDataParts.forEach(p => {
  383. p.freeElements();
  384. });
  385. this._instanceDataParts = null;
  386. }
  387. return true;
  388. }
  389. private _cleanupInstanceDataParts() {
  390. let gii: GroupInstanceInfo = null;
  391. for (let part of this._instanceDataParts) {
  392. part.freeElements();
  393. gii = part.groupInstanceInfo;
  394. part.groupInstanceInfo = null;
  395. }
  396. if (gii && !gii.isDisposed) {
  397. let usedCount = 0;
  398. if (gii.hasOpaqueData) {
  399. let od = gii.opaqueData[0];
  400. usedCount += od._partData.usedElementCount;
  401. gii.opaqueDirty = true;
  402. }
  403. if (gii.hasAlphaTestData) {
  404. let atd = gii.alphaTestData[0];
  405. usedCount += atd._partData.usedElementCount;
  406. gii.alphaTestDirty = true;
  407. }
  408. if (gii.hasTransparentData) {
  409. let td = gii.transparentData[0];
  410. usedCount += td._partData.usedElementCount;
  411. gii.transparentDirty = true;
  412. }
  413. if (usedCount === 0 && gii.modelRenderCache!=null) {
  414. this.renderGroup._renderableData._renderGroupInstancesInfo.remove(gii.modelRenderCache.modelKey);
  415. gii.dispose();
  416. }
  417. if (this._modelRenderCache) {
  418. this._modelRenderCache.dispose();
  419. this._modelRenderCache = null;
  420. }
  421. }
  422. this._instanceDataParts = null;
  423. }
  424. public _prepareRenderPre(context: PrepareRender2DContext) {
  425. super._prepareRenderPre(context);
  426. // If the model changed and we have already an instance, we must remove this instance from the obsolete model
  427. if (this._isFlagSet(SmartPropertyPrim.flagModelDirty) && this._instanceDataParts) {
  428. this._cleanupInstanceDataParts();
  429. }
  430. // Need to create the model?
  431. let setupModelRenderCache = false;
  432. if (!this._modelRenderCache || this._isFlagSet(SmartPropertyPrim.flagModelDirty)) {
  433. setupModelRenderCache = this._createModelRenderCache();
  434. }
  435. let gii: GroupInstanceInfo = null;
  436. let newInstance = false;
  437. // Need to create the instance data parts?
  438. if (!this._instanceDataParts) {
  439. // Yes, flag it for later, more processing will have to be done
  440. newInstance = true;
  441. gii = this._createModelDataParts();
  442. }
  443. // If the ModelRenderCache is brand new, now is the time to call the implementation's specific setup method to create the rendering resources
  444. if (setupModelRenderCache) {
  445. this.setupModelRenderCache(this._modelRenderCache);
  446. }
  447. if (this._isFlagSet(SmartPropertyPrim.flagModelUpdate)) {
  448. if (this._modelRenderCache.updateModelRenderCache(this)) {
  449. this._clearFlags(SmartPropertyPrim.flagModelUpdate);
  450. }
  451. }
  452. // At this stage we have everything correctly initialized, ModelRenderCache is setup, Model Instance data are good too, they have allocated elements in the Instanced DynamicFloatArray.
  453. // The last thing to do is check if the instanced related data must be updated because a InstanceLevel property had changed or the primitive visibility changed.
  454. if (this._areSomeFlagsSet(SmartPropertyPrim.flagVisibilityChanged | SmartPropertyPrim.flagNeedRefresh) || context.forceRefreshPrimitive || newInstance || (this._instanceDirtyFlags !== 0) || (this._globalTransformProcessStep !== this._globalTransformStep) || this._mustUpdateInstance()) {
  455. this._updateInstanceDataParts(gii);
  456. }
  457. }
  458. private _createModelRenderCache(): boolean {
  459. let setupModelRenderCache = false;
  460. if (this._modelRenderCache) {
  461. this._modelRenderCache.dispose();
  462. }
  463. this._modelRenderCache = this.owner._engineData.GetOrAddModelCache(this.modelKey, (key: string) => {
  464. let mrc = this.createModelRenderCache(key);
  465. setupModelRenderCache = true;
  466. return mrc;
  467. });
  468. this._clearFlags(SmartPropertyPrim.flagModelDirty);
  469. // if this is still false it means the MRC already exists, so we add a reference to it
  470. if (!setupModelRenderCache) {
  471. this._modelRenderCache.addRef();
  472. }
  473. return setupModelRenderCache;
  474. }
  475. private _createModelDataParts(): GroupInstanceInfo {
  476. // Create the instance data parts of the primitive and store them
  477. let parts = this.createInstanceDataParts();
  478. this._instanceDataParts = parts;
  479. // Check if the ModelRenderCache for this particular instance is also brand new, initialize it if it's the case
  480. if (!this._modelRenderCache._partData) {
  481. this._setupModelRenderCache(parts);
  482. }
  483. // The Rendering resources (Effect, VB, IB, Textures) are stored in the ModelRenderCache
  484. // But it's the RenderGroup that will store all the Instanced related data to render all the primitive it owns.
  485. // So for a given ModelKey we getOrAdd a GroupInstanceInfo that will store all these data
  486. let gii = this.renderGroup._renderableData._renderGroupInstancesInfo.getOrAddWithFactory(this.modelKey, k => {
  487. let res = new GroupInstanceInfo(this.renderGroup, this._modelRenderCache, this._modelRenderCache._partData.length);
  488. for (let j = 0; j < this._modelRenderCache._partData.length; j++) {
  489. let part = this._instanceDataParts[j];
  490. res.partIndexFromId.add(part.id.toString(), j);
  491. res.usedShaderCategories[j] = ";" + this.getUsedShaderCategories(part).join(";") + ";";
  492. res.strides[j] = this._modelRenderCache._partData[j]._partDataStride;
  493. }
  494. return res;
  495. });
  496. // Get the GroupInfoDataPart corresponding to the render category of the part
  497. let rm = 0;
  498. let gipd: GroupInfoPartData[] = null;
  499. if (this.isTransparent) {
  500. gipd = gii.transparentData;
  501. rm = Render2DContext.RenderModeTransparent;
  502. } else if (this.isAlphaTest) {
  503. gipd = gii.alphaTestData;
  504. rm = Render2DContext.RenderModeAlphaTest;
  505. } else {
  506. gipd = gii.opaqueData;
  507. rm = Render2DContext.RenderModeOpaque;
  508. }
  509. // For each instance data part of the primitive, allocate the instanced element it needs for render
  510. for (let i = 0; i < parts.length; i++) {
  511. let part = parts[i];
  512. part.dataBuffer = gipd[i]._partData;
  513. part.allocElements();
  514. part.renderMode = rm;
  515. part.groupInstanceInfo = gii;
  516. }
  517. return gii;
  518. }
  519. private _setupModelRenderCache(parts: InstanceDataBase[]) {
  520. let ctiArray = new Array<ClassTreeInfo<InstanceClassInfo, InstancePropInfo>>();
  521. this._modelRenderCache._partData = new Array<ModelRenderCachePartData>();
  522. for (let dataPart of parts) {
  523. var pd = new ModelRenderCachePartData();
  524. this._modelRenderCache._partData.push(pd)
  525. var cat = this.getUsedShaderCategories(dataPart);
  526. var cti = dataPart.getClassTreeInfo();
  527. // Make sure the instance is visible other the properties won't be set and their size/offset wont be computed
  528. let curVisible = this.isVisible;
  529. this.isVisible = true;
  530. // We manually trigger refreshInstanceData for the only sake of evaluating each instance property size and offset in the instance data, this can only be made at runtime. Once it's done we have all the information to create the instance data buffer.
  531. //console.log("Build Prop Layout for " + Tools.getClassName(this._instanceDataParts[0]));
  532. var joinCat = ";" + cat.join(";") + ";";
  533. pd._partJoinedUsedCategories = joinCat;
  534. InstanceClassInfo._CurCategories = joinCat;
  535. let obj = this.beforeRefreshForLayoutConstruction(dataPart);
  536. if (!this.refreshInstanceDataPart(dataPart)) {
  537. console.log(`Layout construction for ${Tools.getClassName(this._instanceDataParts[0])} failed because refresh returned false`);
  538. }
  539. this.afterRefreshForLayoutConstruction(dataPart, obj);
  540. this.isVisible = curVisible;
  541. var size = 0;
  542. cti.fullContent.forEach((k, v) => {
  543. if (!v.category || cat.indexOf(v.category) !== -1) {
  544. if (v.attributeName === "zBias") {
  545. pd._zBiasOffset = v.instanceOffset.get(joinCat);
  546. }
  547. if (!v.size) {
  548. console.log(`ERROR: Couldn't detect the size of the Property ${v.attributeName} from type ${Tools.getClassName(cti.type)}. Property is ignored.`);
  549. } else {
  550. size += v.size;
  551. }
  552. }
  553. });
  554. pd._partDataStride = size;
  555. pd._partUsedCategories = cat;
  556. pd._partId = dataPart.id;
  557. ctiArray.push(cti);
  558. }
  559. this._modelRenderCache._partsClassInfo = ctiArray;
  560. }
  561. protected onZOrderChanged() {
  562. if (this.isTransparent && this._transparentPrimitiveInfo) {
  563. this.renderGroup._renderableData.transparentPrimitiveZChanged(this._transparentPrimitiveInfo);
  564. let gii = this.renderGroup._renderableData._renderGroupInstancesInfo.get(this.modelKey);
  565. // Flag the transparentData dirty has will have to sort it again
  566. gii.transparentOrderDirty = true;
  567. }
  568. }
  569. protected _mustUpdateInstance(): boolean {
  570. return false;
  571. }
  572. protected _useTextureAlpha(): boolean {
  573. return false;
  574. }
  575. protected _shouldUseAlphaFromTexture(): boolean {
  576. return false;
  577. }
  578. protected _isPrimAlphaTest(): boolean {
  579. return false;
  580. }
  581. protected _isPrimTransparent(): boolean {
  582. return false;
  583. }
  584. private _updateInstanceDataParts(gii: GroupInstanceInfo) {
  585. // Fetch the GroupInstanceInfo if we don't already have it
  586. let rd = this.renderGroup._renderableData;
  587. if (!gii) {
  588. gii = rd._renderGroupInstancesInfo.get(this.modelKey);
  589. }
  590. if (gii.isDisposed) {
  591. return;
  592. }
  593. let isTransparent = this.isTransparent;
  594. let isAlphaTest = this.isAlphaTest;
  595. let wereTransparent = false;
  596. // Check a render mode change
  597. let rmChanged = false;
  598. if (this._instanceDataParts.length>0) {
  599. let firstPart = this._instanceDataParts[0];
  600. let partRM = firstPart.renderMode;
  601. let curRM = this.renderMode;
  602. if (partRM !== curRM) {
  603. wereTransparent = partRM === Render2DContext.RenderModeTransparent;
  604. rmChanged = true;
  605. let gipd: TransparentGroupInfoPartData[];
  606. switch (curRM) {
  607. case Render2DContext.RenderModeTransparent:
  608. gipd = gii.transparentData;
  609. break;
  610. case Render2DContext.RenderModeAlphaTest:
  611. gipd = gii.alphaTestData;
  612. break;
  613. default:
  614. gipd = gii.opaqueData;
  615. }
  616. for (let i = 0; i < this._instanceDataParts.length; i++) {
  617. let part = this._instanceDataParts[i];
  618. part.freeElements();
  619. part.dataBuffer = gipd[i]._partData;
  620. part.renderMode = curRM;
  621. }
  622. }
  623. }
  624. // Handle changes related to ZOffset
  625. let visChanged = this._isFlagSet(SmartPropertyPrim.flagVisibilityChanged);
  626. if (isTransparent || wereTransparent) {
  627. // Handle visibility change, which is also triggered when the primitive just got created
  628. if (visChanged || rmChanged) {
  629. if (this.isVisible && !wereTransparent) {
  630. if (!this._transparentPrimitiveInfo) {
  631. // Add the primitive to the list of transparent ones in the group that render is
  632. this._transparentPrimitiveInfo = rd.addNewTransparentPrimitiveInfo(this, gii);
  633. }
  634. } else {
  635. if (this._transparentPrimitiveInfo) {
  636. rd.removeTransparentPrimitiveInfo(this._transparentPrimitiveInfo);
  637. this._transparentPrimitiveInfo = null;
  638. }
  639. }
  640. gii.transparentOrderDirty = true;
  641. }
  642. }
  643. let rebuildTrans = false;
  644. // For each Instance Data part, refresh it to update the data in the DynamicFloatArray
  645. for (let part of this._instanceDataParts) {
  646. let justAllocated = false;
  647. // Check if we need to allocate data elements (hidden prim which becomes visible again)
  648. if (!part.dataElements && (visChanged || rmChanged || this.isVisible)) {
  649. part.allocElements();
  650. justAllocated = true;
  651. }
  652. InstanceClassInfo._CurCategories = gii.usedShaderCategories[gii.partIndexFromId.get(part.id.toString())];
  653. // Will return false if the instance should not be rendered (not visible or other any reasons)
  654. part.arrayLengthChanged = false;
  655. if (!this.refreshInstanceDataPart(part)) {
  656. // Free the data element
  657. if (part.dataElements) {
  658. part.freeElements();
  659. }
  660. // The refresh couldn't succeed, push the primitive to be dirty again for the next render
  661. if (this.isVisible) {
  662. rd._primNewDirtyList.push(this);
  663. }
  664. }
  665. rebuildTrans = rebuildTrans || part.arrayLengthChanged || justAllocated;
  666. }
  667. this._instanceDirtyFlags = 0;
  668. // Make the appropriate data dirty
  669. if (isTransparent) {
  670. gii.transparentDirty = true;
  671. if (rebuildTrans) {
  672. rd._transparentListChanged = true;
  673. }
  674. } else if (isAlphaTest) {
  675. gii.alphaTestDirty = true;
  676. } else {
  677. gii.opaqueDirty = true;
  678. }
  679. this._clearFlags(SmartPropertyPrim.flagVisibilityChanged); // Reset the flag as we've handled the case
  680. }
  681. _updateTransparentSegmentIndices(ts: TransparentSegment) {
  682. let minOff = Prim2DBase._bigInt;
  683. let maxOff = 0;
  684. for (let part of this._instanceDataParts) {
  685. if (part && part.dataElements) {
  686. part.dataBuffer.pack();
  687. for (let el of part.dataElements) {
  688. minOff = Math.min(minOff, el.offset);
  689. maxOff = Math.max(maxOff, el.offset);
  690. }
  691. ts.startDataIndex = Math.min(ts.startDataIndex, minOff / part.dataBuffer.stride);
  692. ts.endDataIndex = Math.max(ts.endDataIndex, (maxOff / part.dataBuffer.stride) + 1); // +1 for exclusive
  693. }
  694. }
  695. }
  696. // This internal method is mainly used for transparency processing
  697. public _getNextPrimZOrder(): number {
  698. let length = this._instanceDataParts.length;
  699. for (let i = 0; i < length; i++) {
  700. let part = this._instanceDataParts[i];
  701. if (part) {
  702. let stride = part.dataBuffer.stride;
  703. let lastElementOffset = part.dataElements[part.dataElements.length - 1].offset;
  704. // check if it's the last in the DFA
  705. if (part.dataBuffer.totalElementCount * stride <= lastElementOffset) {
  706. return null;
  707. }
  708. // Return the Z of the next primitive that lies in the DFA
  709. return part.dataBuffer[lastElementOffset + stride + this.modelRenderCache._partData[i]._zBiasOffset];
  710. }
  711. }
  712. return null;
  713. }
  714. // This internal method is mainly used for transparency processing
  715. public _getPrevPrimZOrder(): number {
  716. let length = this._instanceDataParts.length;
  717. for (let i = 0; i < length; i++) {
  718. let part = this._instanceDataParts[i];
  719. if (part) {
  720. let stride = part.dataBuffer.stride;
  721. let firstElementOffset = part.dataElements[0].offset;
  722. // check if it's the first in the DFA
  723. if (firstElementOffset === 0) {
  724. return null;
  725. }
  726. // Return the Z of the previous primitive that lies in the DFA
  727. return part.dataBuffer[firstElementOffset - stride + this.modelRenderCache._partData[i]._zBiasOffset];
  728. }
  729. }
  730. return null;
  731. }
  732. private static _toz = Size.Zero();
  733. /**
  734. * Get the info for a given effect based on the dataPart metadata
  735. * @param dataPartId partId in part list to get the info
  736. * @param vertexBufferAttributes vertex buffer attributes to manually add
  737. * @param uniforms uniforms to manually add
  738. * @param useInstanced specified if Instanced Array should be used, if null the engine caps will be used (so true if WebGL supports it, false otherwise), but you have the possibility to override the engine capability. However, if you manually set true but the engine does not support Instanced Array, this method will return null
  739. */
  740. protected getDataPartEffectInfo(dataPartId: number, vertexBufferAttributes: string[], uniforms: string[] = null, useInstanced: boolean = null): { attributes: string[], uniforms: string[], defines: string } {
  741. let dataPart = Tools.first(this._instanceDataParts, i => i.id === dataPartId);
  742. if (!dataPart) {
  743. return null;
  744. }
  745. let instancedArray = this.owner.supportInstancedArray;
  746. if (useInstanced != null) {
  747. // Check if the caller ask for Instanced Array and the engine does not support it, return null if it's the case
  748. if (useInstanced && instancedArray === false) {
  749. return null;
  750. }
  751. // Use the caller's setting
  752. instancedArray = useInstanced;
  753. }
  754. let cti = dataPart.getClassTreeInfo();
  755. let categories = this.getUsedShaderCategories(dataPart);
  756. let att = cti.classContent.getShaderAttributes(categories);
  757. let defines = "";
  758. categories.forEach(c => { defines += `#define ${c}\n` });
  759. if (instancedArray) {
  760. defines += "#define Instanced\n";
  761. }
  762. return {
  763. attributes: instancedArray ? vertexBufferAttributes.concat(att) : vertexBufferAttributes,
  764. uniforms: instancedArray ? (uniforms != null ? uniforms : []) : ((uniforms != null) ? att.concat(uniforms) : (att!=null ? att : [])),
  765. defines: defines
  766. };
  767. }
  768. protected get modelRenderCache(): ModelRenderCache {
  769. return this._modelRenderCache;
  770. }
  771. protected createModelRenderCache(modelKey: string): ModelRenderCache {
  772. return null;
  773. }
  774. protected setupModelRenderCache(modelRenderCache: ModelRenderCache) {
  775. }
  776. protected createInstanceDataParts(): InstanceDataBase[] {
  777. return null;
  778. }
  779. protected getUsedShaderCategories(dataPart: InstanceDataBase): string[] {
  780. return [];
  781. }
  782. protected beforeRefreshForLayoutConstruction(part: InstanceDataBase): any {
  783. }
  784. protected afterRefreshForLayoutConstruction(part: InstanceDataBase, obj: any) {
  785. }
  786. protected applyActualScaleOnTransform(): boolean {
  787. return true;
  788. }
  789. protected refreshInstanceDataPart(part: InstanceDataBase): boolean {
  790. if (!this.isVisible) {
  791. return false;
  792. }
  793. part.isVisible = this.isVisible;
  794. // Which means, if there's only one data element, we're update it from this method, otherwise it is the responsibility of the derived class to call updateInstanceDataPart as many times as needed, properly (look at Text2D's implementation for more information)
  795. if (part.dataElementCount === 1) {
  796. part.curElement = 0;
  797. this.updateInstanceDataPart(part);
  798. }
  799. return true;
  800. }
  801. private static _uV = new Vector2(1, 1);
  802. private static _s = Vector2.Zero();
  803. private static _r = Quaternion.Identity();
  804. private static _t = Vector2.Zero();
  805. private static _iV2 = new Vector2(1, 1); // Must stay identity vector3
  806. /**
  807. * Update the instanceDataBase level properties of a part
  808. * @param part the part to update
  809. * @param positionOffset to use in multi part per primitive (e.g. the Text2D has N parts for N letter to display), this give the offset to apply (e.g. the position of the letter from the bottom/left corner of the text).
  810. */
  811. protected updateInstanceDataPart(part: InstanceDataBase, positionOffset: Vector2 = null) {
  812. let t = this._globalTransform.multiply(this.renderGroup.invGlobalTransform); // Compute the transformation into the renderGroup's space
  813. let scl = RenderablePrim2D._s;
  814. let trn = RenderablePrim2D._t;
  815. let rot = t.decompose(scl, trn);
  816. let pas = this.actualScale;
  817. let canvasScale = this.owner._canvasLevelScale;
  818. scl.x = pas.x * canvasScale.x * this._postScale.x;
  819. scl.y = pas.y * canvasScale.y * this._postScale.y;
  820. t = Matrix2D.Compose(this.applyActualScaleOnTransform() ? scl : RenderablePrim2D._iV2, rot, trn);
  821. let size = (<Size>this.renderGroup.viewportSize);
  822. let zBias = this.actualZOffset;
  823. let offX = 0;
  824. let offY = 0;
  825. // If there's an offset, apply the global transformation matrix on it to get a global offset
  826. if (positionOffset) {
  827. offX = positionOffset.x * t.m[0] + positionOffset.y * t.m[2];
  828. offY = positionOffset.x * t.m[1] + positionOffset.y * t.m[3];
  829. }
  830. // Have to convert the coordinates to clip space which is ranged between [-1;1] on X and Y axis, with 0,0 being the left/bottom corner
  831. // Current coordinates are expressed in renderGroup coordinates ([0, renderGroup.actualSize.width|height]) with 0,0 being at the left/top corner
  832. // So for X:
  833. // - tx.x = value * 2 / width: is to switch from [0, renderGroup.width] to [0, 2]
  834. // - tx.w = (value * 2 / width) - 1: w stores the translation in renderGroup coordinates so (value * 2 / width) to switch to a clip space translation value. - 1 is to offset the overall [0;2] to [-1;1].
  835. // At last we don't forget to apply the actualScale of the Render Group to tx[0] and ty[1] to propagate scaling correctly
  836. let w = size.width;
  837. let h = size.height;
  838. let invZBias = 1 / zBias;
  839. let tx = new Vector4(t.m[0] * 2 / w, t.m[2] * 2 / w, 0, ((t.m[4] + offX) * 2 / w) - 1);
  840. let ty = new Vector4(t.m[1] * 2 / h, t.m[3] * 2 / h, 0, ((t.m[5] + offY) * 2 / h) - 1);
  841. part.renderingInfo = new Vector3(w, h, this.alignToPixel ? 1 : 0);
  842. part.transformX = tx;
  843. part.transformY = ty;
  844. part.opacity = this.actualOpacity;
  845. // Stores zBias and it's inverse value because that's needed to compute the clip space W coordinate (which is 1/Z, so 1/zBias)
  846. part.zBias = new Vector2(zBias, invZBias);
  847. }
  848. protected _updateRenderMode() {
  849. if (this.isTransparent) {
  850. this._renderMode = Render2DContext.RenderModeTransparent;
  851. } else if (this.isAlphaTest) {
  852. this._renderMode = Render2DContext.RenderModeAlphaTest;
  853. } else {
  854. this._renderMode = Render2DContext.RenderModeOpaque;
  855. }
  856. }
  857. private _modelRenderCache: ModelRenderCache;
  858. private _transparentPrimitiveInfo: TransparentPrimitiveInfo;
  859. protected _instanceDataParts: InstanceDataBase[];
  860. private _renderMode: number;
  861. }
  862. }